Submodule bump: 6129375 -> 1a984b0
Pulls in the firmware build fixes pushed to
github.com/electron-rare/ESP32_ZACUS branch
fix/build-arduino-esp32-3.20014 (PR pending review). The
firmware now compiles cleanly and survives boot on the
Freenove ESP32-S3 dev board (verified flash + 30 s
monitor: SD mounted, LittleFS ready, no panic, 90 k UI
frames).
Once the upstream PR is merged to main, the parent pointer
can be re-bumped to ESP32_ZACUS@main without changes.
CLAUDE.md refresh (init-deep follow-up):
- frontend-v3/apps/atelier/CLAUDE.md (new, 61 lines):
layout map, store responsibilities, lazy-chunk pattern,
dev-only window hooks, build with --base, anti-patterns.
- desktop/CLAUDE.md (+8 lines): Tests section + npm test
command + --ignore-scripts install hint.
- tests/CLAUDE.md (+1 line): list test_firmware_bundle.py
alongside test_runtime3_routes.py.
All nested CLAUDE.md remain within the 30-80 line target
(longest is atelier at 61).
Desktop had zero tests before. This commit adds vitest at
the desktop package level and 6 unit tests for the OTA
manager logic. The tests run with mocked electron / fs /
http so they need neither a real ESP32 nor the native
serialport / zacus-native rebuild.
Setup:
- vitest.config.ts: node env, scans src/**/__tests__/**/*.test.ts
- desktop/package.json: vitest@^2.1.8 devDep, "test" script
- npm install --ignore-scripts to avoid the
electron-builder install-app-deps postinstall + native
ABI rebuild (these matter for app launch, not unit tests)
Coverage (6 tests, all green in 251 ms):
- construction triggers ensureCacheDir() (mkdir -p
~/.zacus-studio/firmwares stubbed via os homedir mock)
- checkUpdate returns 'unknown' when device + manifest
unreachable (catch swallows the error)
- startUpdate rejects unknown OTA method
- startUpdate rate-limits on same device within 60 s
- importFirmware rejects a binary whose first byte is not
0xE9 (ESP32 image magic)
- importFirmware accepts 0xE9 and copies to cache dir
Documented gap: the activeOTAs concurrent-call guard is
unreachable from external callers because rate-limit
triggers first; left in source as belt-and-braces, not
covered by a test (would need bypass via reflection).
Out of scope (deferred):
- Playwright Electron mode (would require building the
desktop app + mocking serialport at runtime)
- HTTP-flow tests (otaViaWiFi, pollOTAStatus,
waitForReboot) — these need an HTTP server fixture
- BLE / USB delegation paths (executeJavaScript -> renderer)
Acceptance: pnpm test (or npm test) inside desktop/
prints "6 passed (6)" in <1 s.
Phase 4 of the V3 fusion plan. The atelier app now embeds
the real R3F 3D simulation in its right pane. P6 also
absorbed (apps/simulation removed, desktop bundle updated).
Migrated from apps/simulation/src (via git mv to preserve
history):
- scene/{Room, RoomScene, PuzzleStation, PuzzleStations,
RtcPhone, NpcBubble} (6 files)
- puzzles/{P1Sound, P5Morse, P6Symbols, P7Coffre}
- modes/{Sandbox, Demo, Test}Mode (Tailwind classes
replaced with inline styles since Tailwind was never
configured in V3)
- store/simStore.ts -> stores/simStore.ts (matches
atelier's plural convention)
Atelier wiring:
- StagePane: lazy(R3F Canvas + RoomScene) + lazy mode HUD
selected by simStore.mode. Stale badge still consumed
from runtimeStore.isStale (P5 will trigger).
- ConsolePane: switched mode binding from runtimeStore to
simStore (mode is a sim concern, not editor↔stage sync).
- runtimeStore: dropped `mode` field (now in simStore).
Kept currentIr/pendingIr/isStale/setPendingIr/
commitPendingIr for the live-diff flow (P5).
- atelier tsconfig: types: ["@react-three/fiber"] for JSX
intrinsics.
- @types/three added to atelier devDeps.
scenario-engine bug fixes (the 3 tests skipped in P0):
- yaml-parser now converts puzzles dict (YAML format) to
array (TS contract) at parse time. The YAML keeps puzzles
as a keyed dict for human readability; consumers expect
PuzzleConfig[]. This fixes 2 tests that called
scenario.puzzles.find/.reduce.
- getScore test realigned with engine implementation:
fast-completion bonus applies continuously when elapsed
is below 80% of target, so total at t=0 is base + bonus
= 1200, not just base = 1000. The test name + body now
documents this behaviour. (Whether engine should only
apply the bonus on game end is a separate question for
future engine refactor.)
Decommission (early P6 for simulation):
- git rm -r apps/simulation/
- desktop/scripts/build-frontends.sh: now iterates over
[atelier, dashboard] and uses pnpm instead of npm
- frontend-v3/CLAUDE.md: now describes 2 apps + 3 packages
- root CLAUDE.md: Authoring line lists atelier + dashboard
Acceptance:
- pnpm typecheck: 8/8 green
- pnpm test: 9/9 (atelier:0, dashboard:5, scenario-engine:9
ALL passing including the 3 previously skipped)
- pnpm build: 5/5 green
- Atelier production chunks: shell 76 kB, blockly 698 kB
(lazy), three 1.25 MB (lazy), R3F scene 10 kB, mode HUDs
~1 kB each (lazy)
Phase 6 scope reduced to: nothing left. The "decommission +
propagation" work was absorbed into P3 (editor) and P4
(simulation + desktop bundle + CLAUDE.md). Phase 5 (live-
diff wiring) is the only remaining phase.
V2 was the legacy single-app Vite playground. Per the
2026-05-01 atelier design brainstorm, V3 is the sole
authoring target going forward. MediaManager and
NetworkPanel features explicitly retired (no porting).
Removed:
- frontend-scratch-v2/ entirely (50 files, ~11k LOC)
Retargeted to frontend-v3 + pnpm:
- Makefile: FRONTEND_DIR, frontend-typecheck/test/build
(renamed from frontend-lint as eslint not yet wired in V3)
- tools/dev/zacus.sh: FRONTEND_ROOT, action commands
- tools/dev/zacus_tui.py: action_frontend_tests + build cwd
- .github/workflows/validate.yml: pnpm/action-setup,
cache: pnpm, --frozen-lockfile, typecheck step added
Documentation cleanup:
- Root CLAUDE.md: Architecture, Where to Look, Nested
Guidance lines purged of V2; pointer to atelier design
doc added
- frontend-v3/CLAUDE.md: V2 vs V3 section removed; editor
description points to fusion design doc
- desktop/CLAUDE.md: V2 anti-pattern removed
- specs/CLAUDE.md: cross-stack pair example updated to
packages/scenario-engine
Archive: tag archive/frontend-scratch-v2-final on the
commit immediately preceding deletion preserves the last
state where V2 still worked.
Acceptance:
- rg "frontend-scratch-v2" in code paths -> 0 hits
- make frontend-test -> 11/11 green (3 skipped)
- make frontend-build -> 6/6 green