Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .claude/workflows/write-behaviour-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Repo: the current workspace root (the projectMM checkout you're running in) —

## Study first (do NOT guess behaviour)
1. Read the module header: ${header} — understand what it ACTUALLY does: its controls, its render/modify logic, what it writes to the buffer or how it transforms coordinates. Behaviour is the spec.
2. Read the module's spec entry if useful: docs/moonmodules/light/${kind === 'effect' ? 'effects/effects.md' : 'modifiers/modifiers.md'} (find the ${cls.replace(/Effect$|Modifier$/, '')} section).
2. Read the module's spec entry if useful: docs/moonmodules/light/${kind === 'effect' ? 'effects.md' : 'modifiers.md'} (find the ${cls.replace(/Effect$|Modifier$/, '')} section).
3. Read TWO existing tests as your pattern templates — match their idiom EXACTLY (includes, harness, naming, comment style):
- For an EFFECT: test/unit/light/unit_RainbowEffect.cpp (Layouts→GridLayout→Layer→addChild(effect)→onBuildState()→loop()→assert on layer.buffer()).
- For a MODIFIER: test/unit/light/unit_RegionModifier.cpp (call modifyLogical / modifyLogicalSize directly; assert coordinate folding / size).
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -525,14 +525,14 @@ jobs:
cp src/ui/install-picker-boards.js pages/install/
# library.json — install page reads the project version from it.
cp library.json pages/install/
# Board picker images live in docs/assets/boards/ (the project's asset
# Board picker images live in docs/assets/deviceModels/ (the project's asset
# home, also a library for boards not yet in the catalog). Stage ONLY the
# images a deviceModels.json entry actually references, under install/assets/,
# so an "image": "assets/boards/<slug>.jpg" resolves same-origin from
# so an "image": "assets/deviceModels/<slug>.jpg" resolves same-origin from
# /install/ without shipping the unused library to Pages.
mkdir -p pages/install/assets/boards
# rel is "assets/boards/<slug>.<ext>" (the path served from /install/);
# the source file lives in docs/<rel> (i.e. docs/assets/boards/...).
mkdir -p pages/install/assets/deviceModels
# rel is "assets/deviceModels/<slug>.<ext>" (the path served from /install/);
# the source file lives in docs/<rel> (i.e. docs/assets/deviceModels/...).
jq -r '.[].image // empty' web-installer/deviceModels.json | while read -r rel; do
src="docs/$rel"
[ -f "$src" ] && cp "$src" "pages/install/$rel" \
Expand Down
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ The design rationale for each rule below lives in [docs/architecture.md](docs/ar

Then check the recommendation against [§ Principles](#principles) (minimalism, data over objects, concrete first) and propose it as a question, not a fait accompli. The product owner picks; the agent implements only what was picked. If the picked option turns out to need a follow-up change (e.g. an updated naming convention to make the new layout consistent), surface that *before* starting the move so it's a single coherent refactor, not three round-trips.

**Plan before implementing.** Use `/plan` mode before every feature. Review plans for: unnecessary files, inheritance where structs suffice, modifications outside the relevant directory. Reject and regenerate bad plans. **Save every approved plan** to `docs/history/plans/` named `Plan-YYYYMMDD - <title>.md` (ISO-8601 date order so the directory sorts chronologically, e.g. `Plan-20260620 - Improv-as-REST.md` for 2026-06-20), as the first implementation step. The plan is the design record that complements `decisions.md` (the lesson record): the plan says what we set out to build and why; decisions.md captures what we learned doing it. **These saved plans are a reference archive for the product owner — agents WRITE a plan when creating one, but do NOT read the existing plan files for context unless the product owner explicitly points to one** (they're under the "Never automatically" rule below alongside the rest of `docs/history/`). Plans are **kept, not pruned** — they are the permanent design-intent record. When a plan's design ships (or doesn't), mark its outcome in the filename with a trailing parenthetical — `… (shipped).md` once it lands, `… (attempted, abandoned).md` if it was tried and dropped — so the directory shows at a glance what's done; an unmarked plan is still in flight.
**Plan before implementing.** Use `/plan` mode before every feature. Review plans for: unnecessary files, inheritance where structs suffice, modifications outside the relevant directory. Reject and regenerate bad plans. **Save every approved plan** to `docs/history/plans/` named `Plan-YYYYMMDD - <title>.md` (ISO-8601 date order so the directory sorts chronologically, e.g. `Plan-20260620 - Improv-as-REST.md` for 2026-06-20), as the first implementation step. The plan is the design record that complements `decisions.md` (the lesson record): the plan says what we set out to build and why; decisions.md captures what we learned doing it. **These saved plans are a reference archive for the product owner — agents WRITE a plan when creating one, but do NOT read the existing plan files for context unless the product owner explicitly points to one** (they're under the "Never automatically" rule below alongside the rest of `docs/history/`). Plans are **kept, not pruned** — they are the permanent design-intent record. When a plan's design ships (or doesn't), mark its outcome in the filename with a trailing parenthetical — `… (shipped).md` once it lands, `… (attempted, abandoned).md` if it was tried and dropped — so the directory shows at a glance what's done; an unmarked plan is still in flight. The **one exception** to "kept, not pruned": a multi-phase effort's per-phase plans may be **consolidated into a single `… (shipped).md` record** once the *whole* effort lands, provided that record preserves the design-intent arc (what each phase set out to do + the outcome, including any dead-ends). This isn't losing design intent — it's the same subtraction the rest of the process rewards, applied to a set of plans whose story is now one story. Consolidate only *shipped/settled* phases, never in-flight ones.

**Use `uv` for every Python invocation.** Never type `python` or `python3` directly; always go through `uv run` (e.g. `uv run moondeck/build/build_desktop.py`, `uv run python -c "…"`). This applies to shell commands, CMake `add_custom_command` / `execute_process`, documentation examples, and anything that shells out. In CMake, resolve `find_program(UV_EXECUTABLE NAMES uv REQUIRED HINTS "$ENV{USERPROFILE}/.local/bin" "$ENV{HOME}/.local/bin")` once and use `${UV_EXECUTABLE} run python …` thereafter. Reason: uv manages the project venv and is the project standard ([moondeck/MoonDeck.md](moondeck/MoonDeck.md)); bare `python3` isn't on PATH on Windows (and macOS Python Launcher pops a Store prompt). If you catch yourself about to type `python`, stop and prefix with `uv run`.

Expand Down Expand Up @@ -103,7 +103,7 @@ The narrow safety net: "this snapshot is internally consistent."

**Always run (cheap, applies to every commit):**

1. Spec check, `check_specs.py`, fast (<1s), catches `docs/moonmodules/*.md` ↔ control-name drift even on doc-only commits.
1. Spec check, `check_specs.py`, fast (<1s), catches `docs/moonmodules/*.md` ↔ control-name drift even on doc-only commits, and validates every `main.cpp` `registerType` docPath resolves to a real page + `#anchor` (so a docs rename can't silently 404 the in-UI help links).

**Conditional (run if trigger matches):**

Expand Down Expand Up @@ -212,7 +212,7 @@ docs/

Documentation describes the system as it is. Git commits are the history. Module specs are written before implementation. Doc pages are kept current with the code.

**Documentation model** — the full standard lives in [docs/coding-standards.md § Documentation model](docs/coding-standards.md#documentation-model); the working-memory summary: the four **catalog pages** (`effects/modifiers/layouts/drivers.md`, authored as prose `### ` blocks, rendered as tables by the build hook) are the end-user documentation. A per-module detail `.md` exists **only** for cross-file wiring / design rationale no single source file owns (and doesn't exist otherwise). Test inventories + catalog tables are **generated at build time, not committed**. Where a fact is the same in the `.h` and a doc (a control range, a source URL), `check_specs.py` **validates** they agree rather than duplicating; where a doc would hand-copy a struct/enum/wire format, embed the real source via a `--8<--` snippet. Never re-type a fact the `.h` already states.
**Documentation model** — the full standard lives in [docs/coding-standards.md § Documentation model](docs/coding-standards.md#documentation-model); the working-memory summary: every module has **two surfaces** — a hand-written **summary/catalog page** (one flat page per group under its domain: `light/{effects,modifiers,layouts,drivers,supporting}.md`, `core/{services,supporting,ui}.md`; authored as prose `### ` blocks, rendered as tables by the build hook) for the end user, and a **generated technical page** (`<domain>/moxygen/<Module>.md`, from the `.h`'s `///` comments) for the developer. Each summary card's Links column links to the technical page via a `Detail: [technical]` line (🧪 Tests · 📄 Technical, icon'd by the hook) — **not to the `.h`** (the technical page already links the `.h`). Rich rationale therefore goes in `///` (not `//` — `//` is invisible to moxygen), directly above the class; a `@card <file.png>` line adds the UI screenshot; relative `.md` links are stripped by Doxygen, so use full URLs. Shared rationale across sibling modules lives once on the base class's `///`. Test inventories + catalog tables are **generated at build time, not committed**. Where a fact is the same in the `.h` and a doc, `check_specs.py` **validates** they agree (control names, ranges, author URLs, and every `main.cpp` `registerType` docPath resolving to a real page + `#anchor`); embed a struct/enum/wire format via a `--8<--` snippet. Never re-type a fact the `.h` already states.

The `history/` folder is the distilled experience of years of building LED/light systems, from WLED, WLED-MM, StarLight, MoonLight, through projectMM. It contains proven patterns, memory tricks, control mechanisms, and hard-won lessons, studied under the [*Industry standards, our own code*](#principles) principle. Per-project credits live in the `history/` digests and the per-module "Prior art" sections.

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ If you like projectMM, give it a ⭐️, fork it, or open an issue or pull reque

🎵 **Audio-reactive**: an I²S microphone drives a 16-band FFT spectrum + sound level, consumed by audio-reactive effects — all built fresh from the mic datasheet and textbook DSP.

🏠 **Home-automation control**: a device joins Homebridge (and any MQTT hub) over a dependency-free MQTT 3.1.1 client — on/off, brightness, and a HomeKit colour wheel that picks the nearest palette. See [the MQTT module docs](docs/moonmodules/core/ui/ui.md#mqtt).
🏠 **Home-automation control**: a device joins Homebridge (and any MQTT hub) over a dependency-free MQTT 3.1.1 client — on/off, brightness, and a HomeKit colour wheel that picks the nearest palette. See [the MQTT module docs](docs/moonmodules/core/services.md#mqtt).

📁 **On-device File Manager**: browse and edit the device filesystem from the browser — a lazy folder tree with an inline editor, drag-drop upload, and create/delete, plus [firmware upload OTA](docs/moonmodules/core/ui/ui.md#firmware-update) (flash a `.bin` over the LAN, no USB). See [the File Manager docs](docs/moonmodules/core/ui/ui.md#file-manager).
📁 **On-device File Manager**: browse and edit the device filesystem from the browser — a lazy folder tree with an inline editor, drag-drop upload, and create/delete, plus [firmware upload OTA](docs/moonmodules/core/services.md#firmware-update) (flash a `.bin` over the LAN, no USB). See [the File Manager docs](docs/moonmodules/core/services.md#file-manager).

🛡️ **Robust to any input**: add, delete, replace, or reconfigure any module in any order, at any grid size, and the device keeps running — degraded or idle, never crashed. Every crash that's ever found becomes a regression test, so it stays fixed.

Expand Down
Loading
Loading