Context
Friction → gold from a live failure (2026-07-19): a source-mode E2E run false-failed with a zero luminance delta because the local dist/ theme artifacts were stale — the exact class #15449 addresses for the E2E preflight side. The adjacent dev-loop side has its own gap: buildScripts/helpers/watchThemes.mjs keeps themes fresh while you edit, but only for content changes in existing .scss files. Verified at source (watchThemes.mjs:19-29): fs.watch emits both change and rename; the switch handles only change.
Live latest-open sweep: newest 20 open issues at 2026-07-19T19:35Z; only #15449 is adjacent (E2E preflight, not the watcher). A2A in-flight sweep: no competing claim.
The Problem
Three unhandled event classes, each leaving silently stale dist/ artifacts:
- New
.scss file — emitted as rename (creation), ignored: its CSS never materializes until a full build-themes run. An author adding a component style sees nothing render and no error.
- Renamed file —
rename, ignored: the old name's CSS stays in dist (ghost artifact), the new name never builds.
- Deleted file —
rename, ignored: the dead file's CSS persists in dist indefinitely — a ghost style that can keep applying after its source is gone, and (worse for partials) a deleted _partial.scss can silently break dependents while the watcher reports nothing.
The Architectural Reality
- The watcher is single-file oriented (
buildFile(filename) compiles one file to dist/development/css). For add/rename/delete, the correct unit is wider: a new entry file needs its first build; a deletion needs its dist artifact removed; renames are delete+add. Partials (_*.scss) interlock through the importer chain — a removed partial can break dependents, so those events should surface a visible error, not fail silently.
fs.watch on macOS reports most lifecycle events as rename; event coverage, not detection, is the gap.
- #15449 covers the pre-E2E materialization gate (test-side). This ticket covers the dev-loop watcher (author-side). Complementary, not overlapping.
The Fix
- Handle
rename events: if the resolved path exists → treat as add/change and build it; if it no longer exists → remove the corresponding dist/development/css artifact (+ its .map).
- For partial files, on add/rename/delete, rebuild the dependent entry point (or the owning theme) rather than only the touched file — partials do not compile standalone.
- Surface a loud log line for each lifecycle event (built / removed / failed), so the watcher narrates instead of failing silently.
- Focused unit coverage for the event mapping (a tmp scss tree: add → built; rename → old removed + new built; delete → artifact gone; partial delete → dependent rebuild or loud error).
Acceptance Criteria
Out of Scope
- The E2E materialization gate (#15449 — test-side, separately owned).
- Watch performance work (debounce/batching) unless the event coverage requires it.
- Cross-platform
fs.watch quirk taxonomy beyond macOS/Linux CI coverage.
Related
#15449 (the E2E-side sibling) · watchThemes.mjs:19-29 (the verified gap) · the 2026-07-19 stale-theme false-failure (friction anchor).
Origin Session ID: 5a4ad6f9-843f-49d6-aaa1-918a0e476349
Retrieval Hint: watchThemes fs.watch rename event new scss file deletion stale dist ghost artifact dev loop
Update 2026-07-19 — the theme-map is the hard core (operator)
Two operator refinements, both verified at source:
- The watcher must honor the generated
resources/theme-map.json — the hardest part. The map is the runtime's class→theme resolution table (src/worker/App.mjs:724-731 fetches it once per worker; buildScripts/build/themes.mjs regenerates it into dist/*/resources/theme-map.json). A NEW scss file for a class absent from the map is not just an unbuilt CSS file — it is an unmapped one: even if the watcher compiles it, the runtime never learns to load it. So add/rename/delete handling requires a map-aware response, not just file-level rebuilds: regenerate the map (full or scoped to the owning theme) on structural events, or refuse loudly. Dev mode is the scope: dist envs always run build-all (which includes build-themes + the map regeneration).
- Precondition: the watcher only works after one initial
build-themes. The map and the dist/development/css structure come from the first full build; before it, the watcher has no valid target state. The watcher should verify the precondition at startup and either bootstrap or refuse loudly — never silently single-file-build into an unmapped void.
These fold into the ACs below.
Context
Friction → gold from a live failure (2026-07-19): a source-mode E2E run false-failed with a zero luminance delta because the local
dist/theme artifacts were stale — the exact class #15449 addresses for the E2E preflight side. The adjacent dev-loop side has its own gap:buildScripts/helpers/watchThemes.mjskeeps themes fresh while you edit, but only for content changes in existing.scssfiles. Verified at source (watchThemes.mjs:19-29):fs.watchemits bothchangeandrename; the switch handles onlychange.Live latest-open sweep: newest 20 open issues at 2026-07-19T19:35Z; only #15449 is adjacent (E2E preflight, not the watcher). A2A in-flight sweep: no competing claim.
The Problem
Three unhandled event classes, each leaving silently stale
dist/artifacts:.scssfile — emitted asrename(creation), ignored: its CSS never materializes until a fullbuild-themesrun. An author adding a component style sees nothing render and no error.rename, ignored: the old name's CSS stays indist(ghost artifact), the new name never builds.rename, ignored: the dead file's CSS persists indistindefinitely — a ghost style that can keep applying after its source is gone, and (worse for partials) a deleted_partial.scsscan silently break dependents while the watcher reports nothing.The Architectural Reality
buildFile(filename)compiles one file todist/development/css). For add/rename/delete, the correct unit is wider: a new entry file needs its first build; a deletion needs itsdistartifact removed; renames are delete+add. Partials (_*.scss) interlock through the importer chain — a removed partial can break dependents, so those events should surface a visible error, not fail silently.fs.watchon macOS reports most lifecycle events asrename; event coverage, not detection, is the gap.The Fix
renameevents: if the resolved path exists → treat as add/change and build it; if it no longer exists → remove the correspondingdist/development/cssartifact (+ its.map).Acceptance Criteria
.scssfiles build on creation without a fullbuild-themesrun.distartifact (+.map); partial deletions either rebuild dependents or print a loud, actionable error.change-only behavior (single-file rebuild latency unchanged).Out of Scope
fs.watchquirk taxonomy beyond macOS/Linux CI coverage.Related
#15449 (the E2E-side sibling) · watchThemes.mjs:19-29 (the verified gap) · the 2026-07-19 stale-theme false-failure (friction anchor).
Origin Session ID: 5a4ad6f9-843f-49d6-aaa1-918a0e476349
Retrieval Hint:
watchThemes fs.watch rename event new scss file deletion stale dist ghost artifact dev loopUpdate 2026-07-19 — the theme-map is the hard core (operator)
Two operator refinements, both verified at source:
resources/theme-map.json— the hardest part. The map is the runtime's class→theme resolution table (src/worker/App.mjs:724-731fetches it once per worker;buildScripts/build/themes.mjsregenerates it intodist/*/resources/theme-map.json). A NEW scss file for a class absent from the map is not just an unbuilt CSS file — it is an unmapped one: even if the watcher compiles it, the runtime never learns to load it. So add/rename/delete handling requires a map-aware response, not just file-level rebuilds: regenerate the map (full or scoped to the owning theme) on structural events, or refuse loudly. Dev mode is the scope: dist envs always runbuild-all(which includes build-themes + the map regeneration).build-themes. The map and thedist/development/cssstructure come from the first full build; before it, the watcher has no valid target state. The watcher should verify the precondition at startup and either bootstrap or refuse loudly — never silently single-file-build into an unmapped void.These fold into the ACs below.