Skip to content

Docs site (MkDocs), web-installer split, per-module tests#33

Open
ewowi wants to merge 3 commits into
mainfrom
next-iteration
Open

Docs site (MkDocs), web-installer split, per-module tests#33
ewowi wants to merge 3 commits into
mainfrom
next-iteration

Conversation

@ewowi

@ewowi ewowi commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

What this does

Stands up a rendered documentation site and de-overloads docs/, closes the test-coverage gaps the doc links exposed, and wires the pieces together.

Docs site (Phase 0 of the docs overhaul)

  • Material for MkDocs at the Pages root — top-down nav (user guide → developer reference), instant search, Material icons, use_directory_urls: false so relative image/source links resolve on both the rendered site and GitHub's raw view.
  • New docs/index.md landing page (replaces the single-file docs/landing/index.html); Flash button routes to /install/.
  • release.yml builds the site into the Pages root via a new scripts/docs/build_docs.py (uv wrapper, PEP-723 inline deps); history/ + backlog/ excluded (internal, transient).
  • Full design study: docs/backlog/docs-system-overhaul.md (phases 1–4 — nav split, generated-test folding, fact de-duplication, Doxide source drill-down — each need separate approval).

docs/install/ → top-level web-installer/

  • The web installer is an app, not documentation — moved out of docs/ (git mv, history preserved). Deployed URL unchanged at /install/ (release.yml maps web-installer/pages/install/), so QR codes, deployed-device OTA URLs, and existing links keep working.
  • ~104 references swept across scripts, both workflows, checks, tests, CLAUDE.md gate triggers, and docs.

Per-module tests (24 new)

  • The broken [Tests] links on effects/modifiers were a missing-coverage signal, not a doc bug. Added behaviour-specific unit tests for 19 effects + 5 modifiers (~90 cases): audio effects fed via AudioModule simulate + frozen clock with RAII mic-seat guards; degenerate-grid no-crash cases enforcing the every-grid-size rule. Regenerated docs/tests/*.md — all 25 broken test-anchor links now resolve.

Firmware ? links → rendered site

  • Module ? help links open moonmodules.org/projectMM/moonmodules/<path>.html (was GitHub blob URLs); all 85 registered docPaths verified to resolve to real pages + anchors. These 404 until the site's first Pages deploy (on merge to main); the installer stays live throughout.

MoonDeck + dev-server hygiene

  • New "Build Docs Site" card (PC → docs group).
  • build_docs.py --serve self-cleans a stray mkdocs serve; the three local dev servers use adjacent ports — MoonDeck 8420, installer preview 8421, docs preview 8422 — so all three coexist.

Verification (all green pre-commit)

Desktop build (0 warnings) · ctest 577 cases / 12,219 assertions · scenarios · ESP32 S3 build · platform boundary · KPI · device-catalog · firmware-list · JS host tests 31/31 · Python host tests 22/22 · spec-check · docs build (0 broken anchors).

Follow-ups (not in this PR)

  • scripts/moondeck/ rename — spec'd in docs/backlog/rename-scripts-to-moondeck.md, its own next commit.
  • Reflash boards after merge to pick up the new ? links.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new documentation site with a clearer homepage, navigation, search, and improved pages for getting started, architecture, testing, and module references.
    • Expanded the browser-based installer preview and docs-building workflow for easier local review.
  • Bug Fixes

    • Corrected many documentation links and references throughout the site.
    • Updated release and CI automation to use the new installer/docs locations.
  • Tests

    • Added broad new unit-test coverage for many light effects and modifiers, including edge cases and empty-grid handling.

Stand up a rendered documentation site (Material for MkDocs) as the project's front door and separate the three unlike things that lived under docs/: the doc-site source stays, the web installer moves out to a top-level web-installer/, and the transient internal notes stay excluded. Adds behaviour tests for the 24 effects/modifiers that shipped untested — the broken "Tests" doc links were a missing-coverage signal, now closed. Firmware module ? links point at the rendered site; MoonDeck gains a Build Docs card; the three local dev servers use adjacent ports.

KPI: 16384lights | PC:465KB | tick:367/229/1/357/48/6/795/176/49/62/515/347/57/3/1/105us(FPS:2724/4366/1000000/2801/20833/166666/1257/5681/20408/16129/1941/2881/17543/333333/1000000/9523) | ESP32:1369KB | tick:4832us(FPS:206) | heap:31523KB | src:166(30966) | test:115(16439) | lizard:113w

Core:
- I2cScanModule.h: updated the deviceModels.json path reference to web-installer/ (comment only).

UI:
- app.js: module ? help links now open the rendered docs site (moonmodules.org/projectMM/moonmodules/<path>.html, .md→.html with #anchor preserved) instead of GitHub blob URLs; all 85 registered docPaths verified to resolve to real pages + anchors.
- install-picker.js / install-picker-boards.js: updated docs/install/ path references to web-installer/ (comments only).

Light domain:
- Added behaviour-specific unit tests for 19 effects (Blurz, BouncingBalls, FixedRectangle, FreqMatrix, FreqSaws, GEQ, GEQ3D, Lissajous, Noise2D, NoiseMeter, PaintBrush, Praxis, Random, RubiksCube, Solid, SphereMove, StarField, StarSky, Tetrix) and 5 modifiers (Block, Circle, Mirror, RippleXZ, Transpose) — ~90 new cases pinning real behaviour, audio effects fed via AudioModule simulate + frozen clock with RAII mic-seat guards, plus degenerate-grid no-crash cases enforcing the every-grid-size rule.

Scripts / MoonDeck:
- New build_docs.py: uv wrapper around mkdocs build/serve (PEP-723 inline deps); --serve self-cleans a stray mkdocs serve and binds :8422 so it coexists with the installer preview.
- moondeck_config.json: new "Build Docs Site" card in the PC docs group.
- preview_installer.py: renumbered to :8421; moondeck.py / MoonDeck.md document the adjacent 8420 (MoonDeck) / 8421 (installer) / 8422 (docs) scheme.
- moondeck.py, moondeck_ui/app.js, check_devices.py, check_firmwares.py, generate_firmwares.py, improv_provision.py, improv_smoke_test.py: updated docs/install/ path references to web-installer/.

Tests:
- Regenerated docs/tests/unit-tests.md + scenario-tests.md from the new @module tags (resolves the 25 broken effect/modifier→test-doc anchor links).
- test/js and test/python: updated docs/install/ → web-installer/ paths (imports + readFileSync join-args).

Docs / CI:
- New docs site: mkdocs.yml (Material, instant search, pymdownx.emoji for icons, use_directory_urls:false so relative image/source links resolve on both the site and GitHub, pymdownx.snippets pre-enabled, history/backlog excluded), docs/index.md landing page (replaces docs/landing/index.html).
- Moved docs/install/ → web-installer/ (git mv, history preserved); deployed URL stays /install/ (release.yml maps web-installer/ → pages/install/); ~104 references swept across scripts, both workflows, checks, tests, and docs.
- release.yml: builds the MkDocs site into the Pages root via build_docs.py; docs/** + mkdocs.yml added to paths:; docs/install/ staging → web-installer/.
- test.yml: JS-test path filter docs/install/** → web-installer/**.
- CLAUDE.md: commit-gate triggers updated for web-installer/ (device-catalog, firmware-list, JS host-test gates).
- Fixed 8 stale cross-doc anchors (architecture/Layer/LcdLedDriver/Drivers headings) to match MkDocs' generated slugs.
- architecture.md, building.md, performance.md, testing.md, module specs, esp32-s31-coreboard.md: docs/install/ → web-installer/ link updates.
- Design record: docs/backlog/docs-system-overhaul.md (the phased plan), docs/backlog/rename-scripts-to-moondeck.md (deferred rename), docs/history/plans/Plan-20260702, .claude/workflows/write-behaviour-tests.js (the test-generation workflow).
- .gitignore: /site/ (MkDocs build output).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1f024d2c-dbbd-40cd-aff7-b2d69dfed405

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch next-iteration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/ui/app.js (1)

510-527: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fragile .md.html conversion.

docPath.replace(".md", ".html") replaces only the first literal match. It works today because docPath always contains exactly one .md (at the end, before an optional #anchor), but it's easy to regress silently if a future docPath segment ever contains .md elsewhere (e.g. a directory name). A suffix-anchored regex would make the intent explicit and be immune to that.

♻️ Proposed fix
-        help.href = "https://moonmodules.org/projectMM/moonmodules/" + docPath.replace(".md", ".html");
+        help.href = "https://moonmodules.org/projectMM/moonmodules/" + docPath.replace(/\.md(?=$|#)/, ".html");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ui/app.js` around lines 510 - 527, The help-link URL building in app.js
uses a fragile docPath.replace(".md", ".html") conversion that only replaces the
first literal match. Update the URL construction in the docPath/help anchor
logic to use a suffix-anchored replacement so only the trailing .md before any
`#anchor` is converted, keeping the intent explicit and avoiding accidental
changes if docPath ever contains .md elsewhere.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/workflows/write-behaviour-tests.js:
- Line 57: The generated prompt currently hardcodes a developer-specific
absolute repo path, which will break for other machines and CI. Update the
prompt निर्माण in write-behaviour-tests.js so it derives the repository location
dynamically from the current workspace/root instead of embedding a fixed
/Users/... value. Make the repo reference generic and reusable in the prompt
template so the same workflow works across contributors and runners.
- Around line 93-98: The module-level return in write-behaviour-tests.js is
invalid JavaScript/ESM and prevents the workflow from loading. Remove the
top-level return and either wrap this result-building block in a named function
or export the object directly from the workflow module, using the existing
wrote, failed, and results logic as the source for the returned data.

In @.github/workflows/release.yml:
- Around line 29-36: The release workflow path filter is missing the web
installer directory, so installer-only changes will not trigger the Pages
deploy. Update the path list in the release workflow to include web-installer/**
alongside the existing docs/** and mkdocs.yml entries, using the same trigger
block that already covers src/ui/install-picker*.js.

In `@docs/architecture.md`:
- Line 250: The rendered link text is stale and still says
install/deviceModels.json even though the target now points to
web-installer/deviceModels.json. Update the visible label in
docs/architecture.md to match the current destination, and keep the surrounding
reference to the catalog schema consistent with the existing
deviceModel/deviceModels.json wording.

In `@docs/backlog/docs-system-overhaul.md`:
- Around line 13-67: Add the missing blank lines around the tables and headings
in the docs/backlog/docs-system-overhaul.md content to satisfy markdownlint
MD058 and MD022. Update the affected Markdown sections in place so each table is
separated by a blank line before and after, and each heading has a blank line
below it; check the top fact table plus the later section headings like the ones
under “The core insight,” “The tools,” and “Phased transition” for consistency.

In `@docs/history/plans/Plan-20260702` - Docs site Phase 0 (MkDocs Material).md:
- Around line 13-14: Update the historical note in the plan so it matches what
was actually shipped: the docs build in CI does not use build_docs.py with
--strict, and link/anchor validation remains warn-level in mkdocs.yml. Adjust
the CI wiring description around deploy-pages and build_docs.py to state that CI
fails on nav/build issues, while broken links/anchors are only documented as a
local strict-mode check in scripts/MoonDeck.md.

In `@docs/index.md`:
- Line 5: The link in the docs index uses an absolute /install/ path, which will
break under the /projectMM/ subpath deployment. Update the link target in the
docs index to use the project-prefixed path consistently with the mkdocs
navigation fix, and ensure any other references in this page that point to
/install/ are updated to the same resolved subpath. Keep the change aligned with
the root-cause fix so the same path behavior is used throughout the site.

In `@docs/tests/unit-tests.md`:
- Line 835: The regression note in the unit test docs is written in past-tense,
change-history language instead of present tense. Update the text in the
affected documentation comment to describe the behavior in present tense only,
and remove phrases like “Before the fix” while keeping the same meaning; use the
surrounding regression note wording in docs/tests/unit-tests.md to locate it.

In `@mkdocs.yml`:
- Around line 101-107: The installer links are using a root-relative /install/
path, which skips the deployed /projectMM/ prefix. Update the Web installer
entry in mkdocs.yml and the homepage links in docs/index.md so they point to
/projectMM/install/ instead, keeping the existing installer CTA text and
placement unchanged.

In `@scripts/MoonDeck.md`:
- Around line 157-173: The new build_docs subsection is interrupting the
existing update_module_docs description, causing the unreferenced-screenshots
sentence to read like part of build_docs. Move the “Reports unreferenced
screenshots…” sentence back so it stays with the update_module_docs content and
the MoonDeck.md section boundaries remain correct; use the update_module_docs
and build_docs headings plus the “Reports unreferenced screenshots” sentence to
locate the affected text.

In `@test/unit/light/unit_BlockModifier.cpp`:
- Around line 67-77: The 1x1x1 expectation in BlockModifier survives degenerate
grids is inconsistent with the BlockModifier algorithm used elsewhere in
unit_BlockModifier.cpp. Update the assertions in the TEST_CASE to match the
actual fold/blockSize results for the {1,1,1} case, using the existing fold and
blockSize helpers as the reference points. Keep the degenerate 0x0x0 coverage,
but ensure the expected size values reflect the real logical size growth
produced by mm::BlockModifier.

In `@test/unit/light/unit_FreqMatrixEffect.cpp`:
- Around line 23-176: The new effect tests repeat the same
Layouts/GridLayout/Layer setup sequence in every TEST_CASE, so extract that
boilerplate into a shared helper such as makeGridLayer or buildEffectLayer.
Centralize the repeated steps in the helper using the existing Layouts,
GridLayout, Layer, and FreqMatrixEffect setup pattern, then update each test to
call it with the desired dimensions/channels and add the effect afterward if
needed.

In `@test/unit/light/unit_GEQ3DEffect.cpp`:
- Around line 44-91: Move the active-mic cleanup out of the manual tail call in
GEQ3DEffect test setup by using the same RAII pattern as AudioGuard from the
other unit tests. The issue is that mic.teardown() is only reached after
REQUIRE, so a failed assertion leaves AudioModule::active_ set; fix this by
introducing an automatic guard around mm::AudioModule in the test so teardown
runs in the destructor even on early abort, and remove the explicit teardown
call at the end.

In `@test/unit/light/unit_StarFieldEffect.cpp`:
- Around line 10-50: The test cases in unit_StarFieldEffect.cpp manually reset
the virtual clock with a trailing setTestNowMs(0), which can be skipped if a
REQUIRE fails and leave later tests polluted. Update each TEST_CASE that calls
setTestNowMs(...) to use the same RAII ClockGuard pattern used in
unit_BouncingBallsEffect.cpp, so the clock is restored automatically by scope
exit. Remove the manual end-of-test reset and keep the guard local to each test
body, especially around the StarFieldEffect checks and the other affected
TEST_CASEs in this file.

---

Outside diff comments:
In `@src/ui/app.js`:
- Around line 510-527: The help-link URL building in app.js uses a fragile
docPath.replace(".md", ".html") conversion that only replaces the first literal
match. Update the URL construction in the docPath/help anchor logic to use a
suffix-anchored replacement so only the trailing .md before any `#anchor` is
converted, keeping the intent explicit and avoiding accidental changes if
docPath ever contains .md elsewhere.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b2199448-d1d8-42d5-a99a-b9cb858dc4ee

📥 Commits

Reviewing files that changed from the base of the PR and between 766c7ed and 28e0854.

⛔ Files ignored due to path filters (5)
  • scripts/build/build_esp32.py is excluded by !**/build/**
  • scripts/build/generate_firmwares.py is excluded by !**/build/**
  • scripts/build/improv_provision.py is excluded by !**/build/**
  • scripts/build/improv_smoke_test.py is excluded by !**/build/**
  • web-installer/favicon.png is excluded by !**/*.png
📒 Files selected for processing (85)
  • .claude/workflows/write-behaviour-tests.js
  • .github/workflows/release.yml
  • .github/workflows/test.yml
  • .gitignore
  • CLAUDE.md
  • docs/architecture.md
  • docs/backlog/docs-system-overhaul.md
  • docs/backlog/rename-scripts-to-moondeck.md
  • docs/building.md
  • docs/history/plans/Plan-20260702 - Docs site Phase 0 (MkDocs Material).md
  • docs/index.md
  • docs/landing/index.html
  • docs/moonmodules/core/DevicesModule.md
  • docs/moonmodules/core/I2cScanModule.md
  • docs/moonmodules/core/NetworkModule.md
  • docs/moonmodules/core/SystemModule.md
  • docs/moonmodules/light/BlendMap.md
  • docs/moonmodules/light/Drivers.md
  • docs/moonmodules/light/Layers.md
  • docs/moonmodules/light/Layouts.md
  • docs/moonmodules/light/drivers/LcdLedDriver.md
  • docs/moonmodules/light/drivers/NetworkSendDriver.md
  • docs/moonmodules/light/drivers/ParlioLedDriver.md
  • docs/moonmodules/light/drivers/RmtLedDriver.md
  • docs/moonmodules/light/drivers/drivers.md
  • docs/performance.md
  • docs/reference/esp32-s31-coreboard.md
  • docs/testing.md
  • docs/tests/scenario-tests.md
  • docs/tests/unit-tests.md
  • mkdocs.yml
  • scripts/MoonDeck.md
  • scripts/check/check_devices.py
  • scripts/check/check_firmwares.py
  • scripts/docs/build_docs.py
  • scripts/moondeck.py
  • scripts/moondeck_config.json
  • scripts/moondeck_ui/app.js
  • scripts/run/preview_installer.py
  • src/core/I2cScanModule.h
  • src/ui/app.js
  • src/ui/install-picker-boards.js
  • src/ui/install-picker.js
  • test/js/config-ops.test.mjs
  • test/js/improv-frame.test.mjs
  • test/js/installer-eth-only.test.mjs
  • test/js/installer-s31-webflash.test.mjs
  • test/python/test_improv_frame.py
  • test/python/test_installer_manifests.py
  • test/scenarios/light/scenario_GridLayout_resize.json
  • test/scenarios/light/scenario_MultiplyModifier_pipeline.json
  • test/unit/light/unit_BlockModifier.cpp
  • test/unit/light/unit_BlurzEffect.cpp
  • test/unit/light/unit_BouncingBallsEffect.cpp
  • test/unit/light/unit_CircleModifier.cpp
  • test/unit/light/unit_FixedRectangleEffect.cpp
  • test/unit/light/unit_FreqMatrixEffect.cpp
  • test/unit/light/unit_FreqSawsEffect.cpp
  • test/unit/light/unit_GEQ3DEffect.cpp
  • test/unit/light/unit_GEQEffect.cpp
  • test/unit/light/unit_LissajousEffect.cpp
  • test/unit/light/unit_MirrorModifier.cpp
  • test/unit/light/unit_Noise2DEffect.cpp
  • test/unit/light/unit_NoiseMeterEffect.cpp
  • test/unit/light/unit_PaintBrushEffect.cpp
  • test/unit/light/unit_PraxisEffect.cpp
  • test/unit/light/unit_RandomEffect.cpp
  • test/unit/light/unit_RippleXZModifier.cpp
  • test/unit/light/unit_RubiksCubeEffect.cpp
  • test/unit/light/unit_SolidEffect.cpp
  • test/unit/light/unit_SphereMoveEffect.cpp
  • test/unit/light/unit_StarFieldEffect.cpp
  • test/unit/light/unit_StarSkyEffect.cpp
  • test/unit/light/unit_TetrixEffect.cpp
  • test/unit/light/unit_TransposeModifier.cpp
  • web-installer/README.md
  • web-installer/config-ops.js
  • web-installer/deviceModels.json
  • web-installer/devices.js
  • web-installer/firmwares.json
  • web-installer/improv-frame.js
  • web-installer/index.html
  • web-installer/install-orchestrator.js
  • web-installer/install.css
  • web-installer/install.js
💤 Files with no reviewable changes (1)
  • docs/landing/index.html

agent(
`Write a behaviour-specific doctest unit test for the projectMM module **${cls}** (${kind}).

Repo: /Users/ewoud/Developer/GitHub/MoonModules/projectMM

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Hardcoded developer-specific absolute path in generated prompt.

Repo: /Users/ewoud/Developer/GitHub/MoonModules/projectMM bakes one developer's local machine path into the agent prompt. This workflow will produce broken/misleading prompts for any other contributor or CI runner that isn't at that exact path.

🔧 Proposed fix
-Repo: /Users/ewoud/Developer/GitHub/MoonModules/projectMM
+Repo: ${process.cwd()}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Repo: /Users/ewoud/Developer/GitHub/MoonModules/projectMM
Repo: ${process.cwd()}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/workflows/write-behaviour-tests.js at line 57, The generated prompt
currently hardcodes a developer-specific absolute repo path, which will break
for other machines and CI. Update the prompt निर्माण in write-behaviour-tests.js
so it derives the repository location dynamically from the current
workspace/root instead of embedding a fixed /Users/... value. Make the repo
reference generic and reusable in the prompt template so the same workflow works
across contributors and runners.

Comment on lines +93 to +98
return {
written: wrote.length,
failed: failed.map(r => r ? r.module : 'unknown'),
audio_or_caveats: results.filter(r => r && r.notes).map(r => ({ module: r.module, notes: r.notes })),
summary: results.filter(Boolean).map(r => ({ module: r.module, file: r.test_file, behaviours: r.behaviours_pinned })),
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for other workflow files using the same top-level phase()/agent()/parallel()/return pattern,
# and find the runner that loads/executes .claude/workflows/*.js to see if it wraps the module body.
fd . .claude/workflows --type f
rg -n 'return \{' .claude/workflows -A2
rg -nP '\bnew Function\b|vm\.(Script|createContext|runInContext)|\bimport\(' .claude -g '*.js' -C3

Repository: MoonModules/projectMM

Length of output: 427


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- outline .claude/workflows/write-behaviour-tests.js ---'
wc -l .claude/workflows/write-behaviour-tests.js
sed -n '1,170p' .claude/workflows/write-behaviour-tests.js

echo
echo '--- search for workflow runner / execution wrapper ---'
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' \
  'phase\(|agent\(|parallel\(|write-behaviour-tests|\.claude/workflows|new Function|vm\.(Script|createContext|runInContext)|Function\(' \
  . -C 2

Repository: MoonModules/projectMM

Length of output: 13910


Remove the top-level return here. This file is parsed as JavaScript/ESM, so return { ... } at module scope is a syntax error and will stop the workflow from loading. Wrap the workflow body in a function or export the result instead.

🧰 Tools
🪛 Biome (2.5.1)

[error] 93-98: Illegal return statement outside of a function

(parse)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/workflows/write-behaviour-tests.js around lines 93 - 98, The
module-level return in write-behaviour-tests.js is invalid JavaScript/ESM and
prevents the workflow from loading. Remove the top-level return and either wrap
this result-building block in a named function or export the object directly
from the workflow module, using the existing wrote, failed, and results logic as
the source for the returned data.

Source: Linters/SAST tools

Comment on lines +29 to +36
# The web installer + docs site are served from Pages by the deploy-pages job
# below; a change to them must trigger a deploy or it never reaches the live site
# (the eth-only-provisioning fix shipped a commit that didn't auto-deploy because
# docs/install was missing here). src/ui/install-picker*.js is already covered by src/**.
- 'docs/install/**'
- 'docs/landing/**'
# web-installer was missing here). src/ui/install-picker*.js is already covered by src/**.
# docs/** covers every page rendered into the Pages root by MkDocs; mkdocs.yml is
# the site config (nav/theme) — a nav change with no doc change must still redeploy.
- 'docs/**'
- 'mkdocs.yml'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -B3 -A3 "paths:" .github/workflows/release.yml | rg -n -A15 "on:" 
rg -n "web-installer" .github/workflows/release.yml

Repository: MoonModules/projectMM

Length of output: 1369


🏁 Script executed:

#!/bin/bash
cat -n .github/workflows/release.yml | sed -n '1,70p'

Repository: MoonModules/projectMM

Length of output: 3530


🏁 Script executed:

#!/bin/bash
git ls-files | rg '^web-installer(/|$)|/web-installer(/|$)'

Repository: MoonModules/projectMM

Length of output: 467


Add web-installer/** to the release workflow paths .github/workflows/release.yml still triggers on docs/** and mkdocs.yml, but not on web-installer/**; installer-only changes will skip the Pages deploy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 29 - 36, The release workflow
path filter is missing the web installer directory, so installer-only changes
will not trigger the Pages deploy. Update the path list in the release workflow
to include web-installer/** alongside the existing docs/** and mkdocs.yml
entries, using the same trigger block that already covers
src/ui/install-picker*.js.

Comment thread docs/architecture.md Outdated
Comment thread docs/backlog/docs-system-overhaul.md Outdated
Comment on lines +13 to +67
| Fact | Lives in | Sync today |
|---|---|---|
| Control name (`"sparking"`) | `.h` (definition) + `.md` spec + test code | `check_specs.py` checks *presence* in `.md`, not accuracy |
| Control range/default (8000/16000/22050/44100) | `.h` array + `.md` prose | none — hand-copied |
| Author/attribution | `.h` `// Author:` + `.md` `Origin:` (40+ effects, two formats) | none |
| Module name (`FireEffect`) | class + `registerType(...)` string + test `@module` + `.md` anchor + `deviceModels.json` | `check_specs`/`check_devices` partial |
| Architectural fact (buffer persists; AudioModule respects `enabled`) | `.h` comment + module `.md` + `architecture.md` | none |
- **No structured doc-comments in source.** `.h`/`.cpp` carry rich *inline `//` rationale* but no Doxygen/`///` API blocks. So "move technical detail into the code" is a real move, not a relabel.

## The core insight

Two different problems wear the same "docs too big" coat, and they have **opposite** fixes:

1. **Navigation / readability** (end users). Fix = a rendered site with a top-down nav and search. Additive: nothing is deleted, the raw `.md` gets a front door. **Cheap, immediate, low-risk.**
2. **Duplication** (developers). Fix = single-source each fact and *generate* the copies (or include them). Subtractive and invasive: it changes where facts live and how they're authored. **Do incrementally, proven per fact-type before rollout.**

Phase them in that order: the site is the quick win that makes everything else visible; de-duplication is the slow structural win. Do **not** bundle them — a big-bang "new site + moved all facts into code" is the kind of change *Refactor for simplicity* exists to stop.

## The tools (all GitHub-Pages-native)

| Tool | Role | Why it fits here |
|---|---|---|
| **Material for MkDocs** | The site: nav tree, instant search, versioning. | PO-named default; the recognised standard (WLED-adjacent projects, FastLED-ecosystem, thousands of firmware projects use it). Ships to Pages via one CI job. `nav:` in `mkdocs.yml` *is* the top-down structure. |
| **`pymdownx.snippets`** (`--8<--`) | De-dup mechanism #1: pull real source lines into a doc. | Built into Material. A spec can embed the actual `controls_.addX(...)` block or an author comment *from the `.h`* — one source of truth, rendered in two places. No new tool. |
| **`mkdocs-gen-files` / `mkdocs-macros`** | De-dup mechanism #2: generate whole doc pages from data at build time. | Standard MkDocs plugins. Lets `generate_test_docs.py`-style generation run *inside* the site build instead of committing generated `.md`. Kills the "forgot to regenerate" drift. |
| **Doxide** | Developer drill-down: parse `.h`/`.cpp` with Tree-sitter → **Markdown** → rendered *in the same Material site*. | This is the ".h/.cpp viewer layered on top" the PO asked for. Unlike classic Doxygen (1998-era HTML, a separate ugly site), Doxide emits Markdown that lives in *our* nav, *our* search, *our* theme — developers drill from a module's user page straight into its annotated source, one site. |

Rejected: **classic Doxygen HTML** (separate site, dated UI, not integrated), **Sphinx/Breathe** (Python-doc-shaped, heavier, C++ via Breathe is awkward), **Docusaurus/VitePress** (Node toolchain, no C++ story), **mdBook** (great but no C++ integration, weaker search). Material+Doxide is the least bespoke combination that hits all four goals on Pages.

## Phased transition

### Phase 0 — Stand up the site (no content changes) — *now, ~half a day*
- Add `mkdocs.yml` with Material, `pymdownx.snippets`, instant search. Author a `nav:` tree that imposes the **top-down end-user order** (see below) over the *existing* files — no file moves, no rewrites.
- Add a `build-docs` CI job that `mkdocs build`s and publishes to Pages **alongside** the installer (installer already owns `/install/`; docs take `/` or `/docs/`).
- Add a **landing page** (`docs/index.md`) — the front door end users currently lack: "what is this → install → first light → effects → drill down."
- **Outcome:** every existing word is now navigable + searchable, zero duplication introduced, zero risk. This alone solves the *"impossible for end users to read"* complaint.

### Phase 1 — Restructure nav for the two audiences — *small*
- Split the nav (not the files, yet) into **User guide** (README intro, getting-started, effect catalog, per-board install) and **Developer/Reference** (architecture, coding-standards, module specs, generated tests, source-drill). `history/` + `backlog/` stay out of the published nav (internal).
- Move the *most* end-user-hostile prose (deep architecture) below a "Developer" fold so a user's top-down path never hits it unless they drill.
- **Outcome:** the "top-to-down, easy navigate" structure, still additive.

### Phase 2 — Fold generated tests into the site + surface them to users — *small, high-value*
- Run the existing test-doc generation *at site-build time* via `mkdocs-gen-files` (stop committing `docs/tests/*.md` — the 25K generated words leave the repo, generated fresh each build). Kills the "forgot to regenerate" drift class entirely.
- On **each effect/module user page**, auto-embed "Tests proving this works: …" from the same test metadata — so an end user reading about *Fire* sees the tests that pin it. This is the PO's *"github issues will be solved adding a new test to proof it, this should be visible to end users."* The link from issue → test → visible-on-the-module-page becomes the norm.
- **Outcome:** tests visible to users; 25K words of committed generation deleted (subtraction).

### Phase 3 — De-duplicate facts, one fact-type at a time — *incremental, the slow win*
Prove each on **one module**, then sweep. Order by leverage:
1. **Author/attribution** → single source in the `.h`, `--8<--` snippet-include into the `.md`. Deletes 40+ hand-maintained `Origin:` copies. (Lowest risk: it's a comment.)
2. **Control names + ranges/defaults** → the `.h` `controls_.addX(...)` block is already the source of truth; either snippet-include it, or extend `check_specs.py` into a *generator* that emits the control table into the spec. Deletes the hand-copied range prose; upgrades `check_specs` from "checks presence" to "owns the table."
3. **Cross-doc architectural facts** → pick the one true home (usually `architecture.md` or the `.h`), replace the other copies with a link/anchor per *Document a thing once, reference it generically* (already a principle — this enforces it mechanically).
- **Outcome:** "change a small thing, many files change" shrinks to "change the source, the copies regenerate."

### Phase 4 — Developer drill-down into source — *medium, do last*

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Markdownlint: missing blank lines around tables/headings.

Static analysis flags MD058 (tables not surrounded by blank lines) at lines 13 and 19, and MD022 (headings need a blank line below) at lines 44, 50, 55, 60, and 67.

🧹 Example fix pattern (apply to each flagged table/heading)
 - **Duplication hotspots** (each fact lives in N places, changing one forces the others):
+
   | Fact | Lives in | Sync today |
   |---|---|---|
@@
   | Architectural fact (buffer persists; AudioModule respects `enabled`) | `.h` comment + module `.md` + `architecture.md` | none |
+
 - **No structured doc-comments in source.**
@@
 ### Phase 0 — Stand up the site (no content changes) — *now, ~half a day*
+
 - Add `mkdocs.yml` with Material, `pymdownx.snippets`, instant search.
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 13-13: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


[warning] 19-19: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


[warning] 44-44: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 50-50: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 55-55: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 60-60: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 67-67: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/backlog/docs-system-overhaul.md` around lines 13 - 67, Add the missing
blank lines around the tables and headings in the
docs/backlog/docs-system-overhaul.md content to satisfy markdownlint MD058 and
MD022. Update the affected Markdown sections in place so each table is separated
by a blank line before and after, and each heading has a blank line below it;
check the top fact table plus the later section headings like the ones under
“The core insight,” “The tools,” and “Phased transition” for consistency.

Source: Linters/SAST tools

Comment thread scripts/MoonDeck.md Outdated
Comment on lines +67 to +77
TEST_CASE("BlockModifier survives degenerate grids") {
mm::BlockModifier b;
// 1x1x1: single light is the centre -> distance 0; logical box {1,1,1}.
CHECK(fold(b, 0, 0, 0, {1, 1, 1}) == mm::Coord3D{0, 0, 0});
CHECK(blockSize(b, {1, 1, 1}) == mm::Coord3D{1, 1, 1});
// 0x0x0: no crash; the fold and size are still finite (each axis grows by one).
CHECK_NOTHROW(fold(b, 0, 0, 0, {0, 0, 0}));
CHECK(blockSize(b, {0, 0, 0}).x >= 1);
CHECK(blockSize(b, {0, 0, 0}).y >= 1);
CHECK(blockSize(b, {0, 0, 0}).z >= 1);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Wrong expected value for the 1×1×1 degenerate case.

Applying BlockModifier's actual formula (validated by the other three TEST_CASEs in this file) to box={1,1,1}: centerX = (1+1)/2-1 = 0, dx = |1-0| = 1, dy = 1, distance = 1, so the logical size becomes {0,1,0} then each axis grows by one → {1,2,1}, not {1,1,1}. This assertion will fail against the real implementation.

🐛 Proposed fix
-    // 1x1x1: single light is the centre -> distance 0; logical box {1,1,1}.
+    // 1x1x1: centre offset (1-0)=1 on each axis -> distance 1; logical box {1,2,1}.
     CHECK(fold(b, 0, 0, 0, {1, 1, 1}) == mm::Coord3D{0, 0, 0});
-    CHECK(blockSize(b, {1, 1, 1}) == mm::Coord3D{1, 1, 1});
+    CHECK(blockSize(b, {1, 1, 1}) == mm::Coord3D{1, 2, 1});

As per path instructions, "Verify tests cover edge cases and match the specifications in docs/moonmodules/" — this edge case doesn't match the modifier's own algorithm.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
TEST_CASE("BlockModifier survives degenerate grids") {
mm::BlockModifier b;
// 1x1x1: single light is the centre -> distance 0; logical box {1,1,1}.
CHECK(fold(b, 0, 0, 0, {1, 1, 1}) == mm::Coord3D{0, 0, 0});
CHECK(blockSize(b, {1, 1, 1}) == mm::Coord3D{1, 1, 1});
// 0x0x0: no crash; the fold and size are still finite (each axis grows by one).
CHECK_NOTHROW(fold(b, 0, 0, 0, {0, 0, 0}));
CHECK(blockSize(b, {0, 0, 0}).x >= 1);
CHECK(blockSize(b, {0, 0, 0}).y >= 1);
CHECK(blockSize(b, {0, 0, 0}).z >= 1);
}
TEST_CASE("BlockModifier survives degenerate grids") {
mm::BlockModifier b;
// 1x1x1: centre offset (1-0)=1 on each axis -> distance 1; logical box {1,2,1}.
CHECK(fold(b, 0, 0, 0, {1, 1, 1}) == mm::Coord3D{0, 0, 0});
CHECK(blockSize(b, {1, 1, 1}) == mm::Coord3D{1, 2, 1});
// 0x0x0: no crash; the fold and size are still finite (each axis grows by one).
CHECK_NOTHROW(fold(b, 0, 0, 0, {0, 0, 0}));
CHECK(blockSize(b, {0, 0, 0}).x >= 1);
CHECK(blockSize(b, {0, 0, 0}).y >= 1);
CHECK(blockSize(b, {0, 0, 0}).z >= 1);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/unit/light/unit_BlockModifier.cpp` around lines 67 - 77, The 1x1x1
expectation in BlockModifier survives degenerate grids is inconsistent with the
BlockModifier algorithm used elsewhere in unit_BlockModifier.cpp. Update the
assertions in the TEST_CASE to match the actual fold/blockSize results for the
{1,1,1} case, using the existing fold and blockSize helpers as the reference
points. Keep the degenerate 0x0x0 coverage, but ensure the expected size values
reflect the real logical size growth produced by mm::BlockModifier.

Source: Path instructions

Comment thread test/unit/light/unit_FreqMatrixEffect.cpp
Comment thread test/unit/light/unit_GEQ3DEffect.cpp
Comment thread test/unit/light/unit_StarFieldEffect.cpp
Generate the test-inventory pages at MkDocs build time (no longer committed) and render the effect/modifier/layout/driver catalogs as MoonLight-style 4-column tables, both from a build hook — the source .md stays authored as readable prose. Consolidates the three LED-output drivers into one card, moves the firmware ? help links onto the rendered docs site, and processes the CodeRabbit batch. Net subtraction: the ~25K-word committed test docs leave the repo (rebuilt from source every build).

KPI: 16384lights | PC:465KB | tick:351/247/1/349/43/6/788/173/50/57/484/337/48/3/1/99us(FPS:2849/4048/1000000/2865/23255/166666/1269/5780/20000/17543/2066/2967/20833/333333/1000000/10101) | ESP32:1369KB | tick:4897us(FPS:204) | heap:31526KB | src:166(30966) | test:115(16430) | lizard:113w

UI:
- app.js: module ? help links now open the rendered docs site (moonmodules.org/projectMM/), with a suffix-anchored .md→.html conversion so a trailing #anchor is preserved (🐇 CodeRabbit).

Scripts / MoonDeck:
- New mkdocs_hooks.py: the docs build hook — generates tests/{unit,scenario}-tests.md into the virtual tree from the test files (never committed → can't drift), renders each catalog page's prose ### blocks as a 4-column table (name+description / preview / parameters / tests+source+detail links), and rewrites out-of-docs source links (../src/*.h, ../CLAUDE.md) to GitHub URLs so they resolve on the served/deployed site. Multi-anchor support (merged cards keep every old anchor), Detail:-line routing to the Links column, and a `⌄ details` link to per-module overflow sections.
- build_docs.py: --serve self-cleans a stray mkdocs serve, binds :8422 (adjacent to MoonDeck :8420 / installer :8421), prints a closing URL summary, and stages the installer into the site on serve so the local preview mirrors production single-origin.
- moondeck.py / moondeck_config.json: "Preview Docs Site" card (long-running serve); a card can now declare fixed `args`.
- check_specs.py: catalog pages validate their per-block `source [..]` links instead of requiring a `## Source` section (removed as redundant with the table's Links column).

Tests:
- unit_StarFieldEffect / unit_GEQ3DEffect: RAII guards (ClockGuard / AudioGuard) so a failed REQUIRE can't leak the frozen clock or the active-mic seat into later tests (🐇). unit_FreqMatrixEffect: extracted the repeated Layouts/Grid/Layer setup into a GridLayer helper (🐇). unit_PinwheelModifier: dropped a past-tense "before the fix" note (🐇).
- Deleted the committed docs/tests/{unit,scenario}-tests.md — now build-generated.

Docs / CI:
- mkdocs.yml: moon-man favicon + logo, extra.css for the catalog tables (table-layout:fixed column widths, column-filling preview images, purple names / grey descriptions), build hook, history/ + backlog/ built into the site but off-nav so internal doc links resolve.
- New docs/assets/{extra.css,favicon.png}.
- drivers.md: rewrote as compact cards — merged the 3 LED-output drivers (RMT/LCD/Parlio) into one card + a comparison-table details section (all three original anchors preserved), moved UI screenshots to the Preview column, trimmed descriptions, routed Detail: links to the Links column.
- effects/modifiers/layouts.md: removed the redundant trailing `## Source` list (each row links its source).
- index.md / mkdocs.yml: /install/ links use the /projectMM/ subpath (🐇). architecture.md: fixed a stale install/ link label (🐇). release.yml: web-installer/** added to the Pages-deploy paths (🐇). Plan-20260702 Phase-0: corrected the CI --strict note to match what shipped (🐇).
- New docs/history/plans/Plan-20260702 - Docs Phase 1+2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MoonModules

Copy link
Copy Markdown
Owner

@coderabbitai, I asked for review but it is not scheduled in, so again: please review 49c16cb and prosent your findings in a copy pastable prompt for agents

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Oops, something went wrong! Please try again later. 🐰 💔

@MoonModules

Copy link
Copy Markdown
Owner

@coderabbitai, I asked for review but it is not scheduled in, so again: please review 49c16cb and prosent your findings in a copy pastable prompt for agents

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Oops, something went wrong! Please try again later. 🐰 💔

@MoonModules

Copy link
Copy Markdown
Owner

@coderabbitai, I asked for review but it is not scheduled in, so again: please review 49c16cb and prosent your findings in a copy pastable prompt for agents

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Oops, something went wrong! Please try again later. 🐰 💔

Add spec-drift validation (control ranges + author URLs checked .h↔.md), embed real source into docs via pymdownx.snippets where the doc would otherwise hand-copy it, minimize the module docs to "only what the code can't say", and write the documentation model down in coding-standards.md. The docs-overhaul goal — the code is the doc basis, human docs carry only cross-file knowledge — is now a stated standard, not just a practice.

KPI: 16384lights | PC:465KB | tick:346/233/1/332/43/6/783/182/49/59/503/339/48/3/1/95us(FPS:2890/4291/1000000/3012/23255/166666/1277/5494/20408/16949/1988/2949/20833/333333/1000000/10526) | ESP32:1369KB | tick:6134us(FPS:163) | heap:31527KB | src:166(30970) | test:115(16430) | lizard:113w
(ESP32 tick/FPS is an idle-monitor capture, not this change — the only src edits are comment-only --8<-- markers; no hot-path code touched.)

Core:
- ImprovFrame.h / PreviewDriver.h: `// --8<-- [start/end:...]` marker comments (compiler-inert) delimiting the frame-constants enum and the preview wire-format, so the docs embed the real source instead of hand-copying it.

Scripts / MoonDeck:
- check_specs.py: two new drift guards inside the spec-check gate — a control's numeric range from the .h validated against the doc's prose (block-scoped on catalog pages so a shared name like fps/fadeRate matches only its own module; tolerant of 1–8 / 1-8 / 1 to 8 spellings), and the .h `// Author:` URL validated present in the doc. Plus a card-backed-detail-page exemption for `## Source` (the driver detail pages link back to their catalog card, which carries the source).

Tests:
- New test/python/test_check_specs_drift.py (8 cases) pins both drift guards: catches a conflicting range / missing URL, stays silent on the common no-range-in-prose and hyphen/en-dash/"to" cases.

Docs / CI:
- coding-standards.md § Documentation model (new): the settled doc standard — two module kinds (catalog module types: effects/modifiers/layouts/drivers, a library of interchangeable implementations documented as catalog cards; vs services & infrastructure: core *Modules + light infrastructure, standalone pages), two reader surfaces, generated-not-committed, drift-validation, source-as-spec via snippets, external-prior-art-only. Autodoc/Doxide marked TBD (Phase 4b). CLAUDE.md § Documentation updated to point at it (replacing the stale per-module-spec text).
- Phase 4a snippets: ImprovProvisioningModule.md embeds ImprovFrame.h:frame-constants; PreviewDriver.md embeds PreviewDriver.h:wire-format.
- Docs minimization: PreviewDriver.md 64→31 lines (cross-file-only); the 5 other driver detail pages shrunk to the same template (prior-art/source/tests moved to their drivers.md cards, which gained Origin + Tests + .h + detail links); em-dash→colon in the 6 driver opening lines (coding-standards ban).
- Removed all self-referential projectMM v1/v2 prior-art across 13 module docs (superseded prototypes of this repo, not lineage); external prior art (FastLED/WLED/MoonLight) kept.
- drivers.md: Links column compacted (per-driver Tests · .h · detail); testing.md documents the new drift checks; backlog + Phase-3/4a plans record the pivots (snippets can't bridge code↔prose → validate instead; Doxide can't use uv → 4a snippets now, 4b next).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants