Standards and Guidelines
Development Principles
| Principle | Detail |
|---|---|
| One firmware per board | Everything in a single binary β keeps management simple. Target β€ 3 MB flash. |
| Docs with every PR | Functionality and documentation go in the same Pull Request. |
| Minimal upstream changes | Stay in sync with ESP32-sveltekit. Mark any upstream change with // π (MoonBase) or // π« (MoonLight). |
| Main branch is protected | No direct code commits to main. Branch β PR β merge. Doc-only changes may go directly to main (docs folder = website source). |
| Compilable PRs | Every PR must compile and not crash the system. Must support boards without PSRAM (e.g. ESP32-D0). Work-in-progress code is fine if it compiles. |
Where code lives
src/ MoonBase + MoonLight nodes and modules (backend)
lib/ Upstream SvelteKit (do not modify)
interface/ Frontend β SvelteKit + generic module/node UI
The UI for modules and nodes is generated automatically β no module-specific Svelte code is needed.
Artificial Intelligence
MoonLight supports AI tooling. Opinions on AI vary β we use it to stay informed, document what we learn, and adjust as we go.
Most important principle: MoonLight must never depend on AI.
Every workflow must remain fully functional without any AI tool.
AI Principles
Five principles govern all AI use: 4EP Β· Unit tests Β· Reversible Β· Attribution Β· Documentation
4 Eyes Principle (4EP)
AI-generated code must always be reviewed by a human before it lands in the repo:
- Developer reviews Claude Code output π before committing to a feature branch
- CodeRabbit automatically reviews each commit π in the PR
- Developer processes and resolves CodeRabbit's findings
Unit Tests
Unit tests are required for both AI-generated and human-written code.
| Layer | Tool | Where |
|---|---|---|
| Backend β static analysis | cppcheck | platformio.ini (compile) Β· lint.yml (CI) |
| Backend β unit tests | doctest | test/test_native/ Β· pio test -e native |
| Frontend β unit tests | Vitest | *.test.ts co-located with source Β· npm run test |
| Frontend β style | ESLint + Prettier | lint.yml CI |
| Experimental | IWYU | lint.yml (continue-on-error: true) |
All checks run in CI: lint.yml
Reversible
- Development must never depend on any AI tool
- Committing without AI assistance must always be possible
- Any AI tool can be replaced or removed at any time
Attribution
AI models are trained on third-party code and research. When that knowledge surfaces in MoonLight, document the source β link to the GitHub repo, algorithm paper, or website the AI drew from.
Documentation
AI-generated code must be documented to the same standard as human-written code.
Tooling: with or without AI
You're free to contribute with or without AI tools. Both approaches are fully supported.
Without AI: You can write, review, and commit code entirely on your own using your favorite editor and tools. This is fast for small changes and gives you complete control.
With AI: Tools like Claude Code or Mistral can help with planning, writing code, and addressing review feedback. This is useful for complex tasks, exploring unfamiliar areas, or getting a second opinion.
Most developers use both β writing some code manually, using AI for other parts, and letting CodeRabbit guide the process. Pick what works for you.
Contributing with AI
If you use AI tools, remember that you're ultimately responsible for the code. Take time to understand and review what the AI produces:
- Understand it β don't just accept output because it "seems to work"
- Review changes to existing code carefully β AI can silently drop comments or break subtle logic
- Verify technical accuracy β AI is good with language, but technical terms and concepts can sometimes be wrong
Mark larger AI-generated sections with a comment:
Using CodeRabbit's AI prompts
When CodeRabbit reviews your PR, it may provide Prompt for AI agents β ready-to-use prompts for Claude Code or Mistral. If you'd like help addressing feedback, you can copy these prompts directly into your AI tool. They can help with:
- Fixing code style issues
- Improving documentation
- Resolving failing tests
- Addressing other feedback
Of course, you're also welcome to address feedback manually β just do what works best for you.
AI Context for contributions
If you use AI tools to contribute, it helps to give them context about MoonLight:
-
Claude Code: Reference CLAUDE.md β it has an overview of the architecture, build process, coding rules, and project structure. Share this file with Claude Code when asking for help with significant contributions.
-
Mistral Vibe: Mistral doesn't have a dedicated context file. Instead, you can share relevant excerpts from this document, the AI Models section, or other architecture docs in your prompt as needed.
AI Models
Status: April 2026. AI models evolve rapidly β re-evaluate model choices every few months.
MoonLight uses two AI coding tools: Claude Code (Anthropic CLI) and Mistral (vibe coding via Le Chat / API).
Claude Code
Claude Code is a CLI tool (claude) that integrates with your terminal and editor.
| Model | ID | Speed | Use when |
|---|---|---|---|
| Opus 4.6 | claude-opus-4-6 |
Slower | Hardest tasks: architecture, cross-cutting refactors, complex debugging |
| Sonnet 4.6 | claude-sonnet-4-6 |
Fast | Default workhorse β 90% of tasks |
| Haiku 4.5 | claude-haiku-4-5-20251001 |
Fastest | Quick lookups, single-line fixes, bulk repetitive edits |
Switch model with /model <model-id> inside a Claude Code session. Enable Fast mode with /fast (same Opus model, faster output).
Mistral
Mistral's code generation is available via Le Chat or the Mistral API.
| Model | Use when |
|---|---|
| devstral-2 | General-purpose coding tasks, debugging, and documentation β solid alternative to Claude Code |
Task β model guide
Add a new node (e.g. a new effect E_MyEffect.h)
Complexity: medium β mostly self-contained
Model: Sonnet 4.6 (Claude) Β· devstral-2 (Mistral)
Create a new effect node E_MyEffect in src/MoonLight/Nodes/Effects/.
Base it on an existing simple effect like E_Sinelon.h.
The effect should [describe behaviour].
Add controls for [list controls].
Register it in ModuleEffects.
Add a new module (e.g. ModuleFoo)
Complexity: high β touches backend state, HTTP/WS endpoints, and the generic UI
Model: Opus 4.6 (Claude) Β· devstral-2 (Mistral)
Create a new MoonBase module ModuleFoo following the pattern in src/MoonBase/.
State fields: [list fields and types].
Add loop20ms() that [describe periodic work].
Register it in main.cpp alongside existing modules.
Show how the generic UI will render it.
Change a node implementation (tweak an existing effect/layout/driver)
Complexity: lowβmedium β scoped to one file
Model: Sonnet 4.6 (Claude) Β· devstral-2 (Mistral)
In src/MoonLight/Nodes/Effects/E_Plasma.h, change the speed control
from a float [0β1] range to an int [1β255] range.
Update the loop() calculation accordingly.
Do not change anything outside this file.
Change a module implementation (tweak state, controls, or loop logic)
Complexity: medium β may affect UI rendering and persistence
Model: Sonnet 4.6 (Claude) Β· devstral-2 for cross-module impact
In ModuleDrivers, add a new boolean field `autoRestart` to the state struct.
Default to false. When true, restart the driver node if loop() hasn't produced
a frame in 5 seconds. Persist it via FSPersistence.
Change the UI (SvelteKit frontend)
Complexity: medium β must not touch upstream files
Model: Sonnet 4.6 (Claude) Β· devstral-2 (Mistral)
Key constraint: only edit files undersrc/routes/moonbase/,src/lib/components/moonbase/,moonbase_utilities.ts,moonbase_models.ts
In interface/src/lib/components/moonbase/FieldRenderer.svelte,
add rendering support for control type "color" (currently unhandled).
Render it as an <input type="color"> bound to the field value.
Follow the Svelte 5 runes pattern used in the rest of the file.
Do not reformat any surrounding code.
Debugging a crash / hard-to-reproduce bug
Complexity: high β needs full context
Model: Opus 4.6 (Claude) Β· devstral-2 (Mistral) with image if you have a screenshot of the stack trace
The ESP32 crashes with the following stack trace (decoded):
[paste decoded trace]
The crash happens after [describe steps].
Relevant files: [list files].
Identify the root cause and suggest a minimal fix.
Writing or extending unit tests
Model: Sonnet 4.6 (Claude) Β· devstral-2 (Mistral)
Add doctest unit tests for the function fastDiv255() in
src/MoonBase/utilities/PureFunctions.h.
Tests go in test/test_native/test_utilities.cpp.
Cover: zero, one, mid-range, max (255), and a known rounding boundary.
Do not copy the function β include the header.
Code Style
Formatting
| Layer | Tool | When to run |
|---|---|---|
| C/C++ | Clang-format (see .clang-format) |
Right-click β Format Document before every commit |
| Frontend (MoonLight files only) | Prettier | npm run format from interface/ |
Do not run Prettier on upstream frontend files β they are excluded in .prettierignore to avoid merge conflicts.
Comments
Every function must have a comment describing what it does and what its arguments mean. Comments are formatted by Clang-format / Prettier along with the code.
// Single-line comment.
/* Inline CSS comment */
/*
* Multi-line comment β used for file headers and function explanations.
* Wrap at the width of your editor window.
*/
Inline comments are fine when they describe only that line and are not too wide.
(Adapted from WLED-MM β π @softhack007)