-
Notifications
You must be signed in to change notification settings - Fork 1
Docs site (MkDocs), web-installer split, per-module tests #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| export const meta = { | ||
| name: 'write-behaviour-tests', | ||
| description: 'Write behaviour-specific unit tests for the 24 untested effects/modifiers', | ||
| phases: [ | ||
| { title: 'Study + write', detail: 'one agent per module: read the .h, write a faithful behaviour test' }, | ||
| ], | ||
| } | ||
|
|
||
| // The 24 modules with no unit test (the broken effects.md/modifiers.md [Tests] links). | ||
| // Each entry: class name, header path, test-file destination, kind (effect|modifier). | ||
| const MODULES = [ | ||
| ['BlurzEffect', 'src/light/effects/BlurzEffect.h', 'effect'], | ||
| ['BouncingBallsEffect', 'src/light/effects/BouncingBallsEffect.h', 'effect'], | ||
| ['FixedRectangleEffect', 'src/light/effects/FixedRectangleEffect.h', 'effect'], | ||
| ['FreqMatrixEffect', 'src/light/effects/FreqMatrixEffect.h', 'effect'], | ||
| ['FreqSawsEffect', 'src/light/effects/FreqSawsEffect.h', 'effect'], | ||
| ['GEQ3DEffect', 'src/light/effects/GEQ3DEffect.h', 'effect'], | ||
| ['GEQEffect', 'src/light/effects/GEQEffect.h', 'effect'], | ||
| ['LissajousEffect', 'src/light/effects/LissajousEffect.h', 'effect'], | ||
| ['Noise2DEffect', 'src/light/effects/Noise2DEffect.h', 'effect'], | ||
| ['NoiseMeterEffect', 'src/light/effects/NoiseMeterEffect.h', 'effect'], | ||
| ['PaintBrushEffect', 'src/light/effects/PaintBrushEffect.h', 'effect'], | ||
| ['PraxisEffect', 'src/light/effects/PraxisEffect.h', 'effect'], | ||
| ['RandomEffect', 'src/light/effects/RandomEffect.h', 'effect'], | ||
| ['RubiksCubeEffect', 'src/light/effects/RubiksCubeEffect.h', 'effect'], | ||
| ['SolidEffect', 'src/light/effects/SolidEffect.h', 'effect'], | ||
| ['SphereMoveEffect', 'src/light/effects/SphereMoveEffect.h', 'effect'], | ||
| ['StarFieldEffect', 'src/light/effects/StarFieldEffect.h', 'effect'], | ||
| ['StarSkyEffect', 'src/light/effects/StarSkyEffect.h', 'effect'], | ||
| ['TetrixEffect', 'src/light/effects/TetrixEffect.h', 'effect'], | ||
| ['BlockModifier', 'src/light/modifiers/BlockModifier.h', 'modifier'], | ||
| ['CircleModifier', 'src/light/modifiers/CircleModifier.h', 'modifier'], | ||
| ['MirrorModifier', 'src/light/modifiers/MirrorModifier.h', 'modifier'], | ||
| ['RippleXZModifier', 'src/light/modifiers/RippleXZModifier.h', 'modifier'], | ||
| ['TransposeModifier', 'src/light/modifiers/TransposeModifier.h', 'modifier'], | ||
| ] | ||
|
|
||
| const RESULT = { | ||
| type: 'object', | ||
| properties: { | ||
| module: { type: 'string' }, | ||
| test_file: { type: 'string' }, | ||
| behaviours_pinned: { type: 'array', items: { type: 'string' }, | ||
| description: 'one line per TEST_CASE: what real behaviour it asserts' }, | ||
| wrote: { type: 'boolean', description: 'true if the test file was written' }, | ||
| notes: { type: 'string', description: 'anything the caller should know (e.g. audio-driven, needs a fed AudioFrame; or a behaviour that could not be pinned)' }, | ||
| }, | ||
| required: ['module', 'test_file', 'wrote'], | ||
| } | ||
|
|
||
| phase('Study + write') | ||
|
|
||
| const results = await parallel(MODULES.map(([cls, header, kind]) => () => | ||
| agent( | ||
| `Write a behaviour-specific doctest unit test for the projectMM module **${cls}** (${kind}). | ||
|
|
||
| Repo: the current workspace root (the projectMM checkout you're running in) — all paths below are relative to it. | ||
|
|
||
| ## 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). | ||
| 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). | ||
| Pick the one matching this module's kind (${kind}). | ||
|
|
||
| ## Write the test | ||
| - Destination: test/unit/light/unit_${cls}.cpp | ||
| - First line MUST be: \`// @module ${cls}\` (this is what the doc generator + MoonDeck read — the whole point is that this module becomes a documented, tested module). Add \`// @also X, Y\` only if the test genuinely also exercises another module. | ||
| - Each TEST_CASE gets a single \`//\` comment line ABOVE it describing the behaviour it pins (the generator turns that into the doc description). Write real, present-tense descriptions. | ||
| - Assert REAL BEHAVIOUR, not just "renders non-zero". Examples of the bar: | ||
| - SolidEffect → the whole buffer is ONE uniform colour (every light equals the configured colour). | ||
| - FixedRectangleEffect → only lights inside the configured rect are lit; outside is black; defaults (0,0,0)+(15,15,15) light the origin corner. | ||
| - MirrorModifier → a coord and its mirror map to the same logical position; modifyLogicalSize halves the mirrored axis (study the .h for which axis/percentage). | ||
| - TransposeModifier → swaps axes (x↔y etc. per the .h); modifyLogicalSize swaps the corresponding size fields. | ||
| - FreqMatrix/GEQ/FreqSaws/NoiseMeter are AUDIO effects → they read an AudioFrame. Study how an existing audio path is tested or how the effect gets its data (look for AudioModule / an audio-frame accessor). If the effect needs a fed audio frame to produce output, set it up; if you truly cannot feed audio in a unit test, pin what you CAN (runs at multiple grid sizes incl 0×0 without crashing — the "Effects must run at every grid size" hard rule — and any non-audio behaviour) and say so in notes. | ||
| - Respect the hard rule: include a case that runs the effect at a DEGENERATE grid (0×0×0 or 1×1) and asserts no crash, where sensible. | ||
| - Keep it to 2–4 focused TEST_CASEs. Match the exact include style and mm:: namespace usage of the template. | ||
| - Use doctest macros (TEST_CASE / CHECK / REQUIRE) exactly as the templates do. Do NOT add the file to any CMakeLists — the test build globs test/unit/**. | ||
|
|
||
| ## Do NOT | ||
| - Do NOT run the build or ctest (the caller compiles everything once at the end — per-agent builds would thrash). | ||
| - Do NOT edit any file other than creating test/unit/light/unit_${cls}.cpp. | ||
| - Do NOT invent controls or behaviour the .h doesn't have. | ||
|
|
||
| Return the structured result: the behaviours you pinned (one line each) and any notes (especially if audio-driven or a behaviour you couldn't pin).`, | ||
| { label: `test:${cls}`, phase: 'Study + write', schema: RESULT, agentType: 'general-purpose' } | ||
| ) | ||
| )) | ||
|
|
||
| const wrote = results.filter(r => r && r.wrote) | ||
| const failed = results.filter(r => !r || !r.wrote) | ||
| 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 })), | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,12 +26,15 @@ on: | |
| - 'CMakeLists.txt' | ||
| - 'library.json' | ||
| - '.github/workflows/release.yml' | ||
| # The web installer + landing page are served from Pages by the deploy-pages job | ||
| # 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/**' | ||
| - 'web-installer/**' | ||
| - 'mkdocs.yml' | ||
|
Comment on lines
+29
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.ymlRepository: 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 🤖 Prompt for AI Agents |
||
| workflow_dispatch: | ||
| inputs: | ||
| tag: | ||
|
|
@@ -78,7 +81,7 @@ jobs: | |
| TAG: ${{ inputs.tag || github.ref_name }} | ||
| run: uv run scripts/build/verify_version.py --tag "$TAG" | ||
|
|
||
| # The shipping firmware list, read from the generated docs/install/firmwares.json | ||
| # The shipping firmware list, read from the generated web-installer/firmwares.json | ||
| # (projected from build_esp32.py's FIRMWARES dict, drift-guarded by | ||
| # check_firmwares.py). Emitted as a JSON array so build-esp32's matrix can | ||
| # fromJSON() it — GitHub matrices can't read a file at parse time, so a job | ||
|
|
@@ -95,7 +98,7 @@ jobs: | |
| - id: gen | ||
| run: | | ||
| set -euo pipefail | ||
| echo "list=$(jq -c '[.firmwares[] | select(.ships) | .name]' docs/install/firmwares.json)" >> "$GITHUB_OUTPUT" | ||
| echo "list=$(jq -c '[.firmwares[] | select(.ships) | .name]' web-installer/firmwares.json)" >> "$GITHUB_OUTPUT" | ||
|
|
||
| build-esp32: | ||
| needs: [verify-version, firmwares] | ||
|
|
@@ -338,9 +341,9 @@ jobs: | |
| # — no CORS). The Pages-relative manifests are generated in the | ||
| # deploy-pages job, where the web installer (CORS-bound) consumes them. | ||
| BASE="https://github.com/${REPO}/releases/download/$TAG" | ||
| # The shipping firmware list — the same docs/install/firmwares.json the | ||
| # The shipping firmware list — the same web-installer/firmwares.json the | ||
| # build matrix reads, so manifests and builds can't drift. | ||
| for F in $(jq -r '.firmwares[] | select(.ships) | .name' docs/install/firmwares.json); do | ||
| for F in $(jq -r '.firmwares[] | select(.ships) | .name' web-installer/firmwares.json); do | ||
| uv run python scripts/build/generate_manifest.py \ | ||
| --firmware "$F" \ | ||
| --version "$V" \ | ||
|
|
@@ -514,7 +517,7 @@ jobs: | |
| # Install page + the shared install-picker module sit at the root. | ||
| # Each release's binaries + manifests live under releases/<tag>/. | ||
| mkdir -p pages/install | ||
| cp -r docs/install/. pages/install/ | ||
| cp -r web-installer/. pages/install/ | ||
| cp src/ui/install-picker.js pages/install/ | ||
| # The board-catalog / chip-detection half of the picker — web-installer | ||
| # only (not embedded in firmware), imported by index.html. Must ship to | ||
|
|
@@ -530,16 +533,27 @@ jobs: | |
| 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/...). | ||
| jq -r '.[].image // empty' docs/install/deviceModels.json | while read -r rel; do | ||
| jq -r '.[].image // empty' web-installer/deviceModels.json | while read -r rel; do | ||
| src="docs/$rel" | ||
| [ -f "$src" ] && cp "$src" "pages/install/$rel" \ | ||
| || echo "WARNING: deviceModels.json image not found: $src" | ||
| done | ||
| # Root landing page (moonmodules.org/projectMM/) → Flash button + repo | ||
| # links. Without it the bare root 404s; only /install/ would exist. | ||
| cp docs/landing/index.html pages/index.html | ||
| ls -la pages/ pages/install/ | ||
|
|
||
| - name: Build docs site into Pages root | ||
| run: | | ||
| set -euo pipefail | ||
| # Render the docs/ tree (Material for MkDocs) as the Pages ROOT | ||
| # (moonmodules.github.io/projectMM/) — the project's front door. | ||
| # Config: mkdocs.yml; deps declared inline in the build script (uv | ||
| # provisions them). Build to a temp dir, then copy INTO pages/ so the | ||
| # installer staged above under pages/install/ survives (a plain | ||
| # --site-dir pages would wipe it — mkdocs cleans its output dir). | ||
| # history/ and backlog/ are excluded in mkdocs.yml (internal docs). | ||
| uv run scripts/docs/build_docs.py --site-dir "$RUNNER_TEMP/docs-site" | ||
| cp -r "$RUNNER_TEMP/docs-site/." pages/ | ||
| ls -la pages/ | ||
|
|
||
| - uses: actions/upload-pages-artifact@v3 | ||
| with: | ||
| path: pages | ||
|
|
||
There was a problem hiding this comment.
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:
Repository: MoonModules/projectMM
Length of output: 427
🏁 Script executed:
Repository: MoonModules/projectMM
Length of output: 13910
Remove the top-level
returnhere. This file is parsed as JavaScript/ESM, soreturn { ... }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
Source: Linters/SAST tools