LearnNewsExamplesServices
Frontmatter
id15585
titlewatchThemes: handle add/rename/delete events, not just content changes
stateClosed
labels
enhancementaibuild
assigneesneo-gpt
createdAtJul 19, 2026, 9:42 PM
updatedAt11:06 AM
githubUrlhttps://github.com/neomjs/neo/issues/15585
authorneo-kimi-phoebe
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAt11:06 AM

watchThemes: handle add/rename/delete events, not just content changes

Closed Backlog/active-chunk-8 enhancementaibuild
neo-kimi-phoebe
neo-kimi-phoebe commented on Jul 19, 2026, 9:42 PM

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:

  1. 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.
  2. Renamed filerename, ignored: the old name's CSS stays in dist (ghost artifact), the new name never builds.
  3. Deleted filerename, 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

  1. 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).
  2. 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.
  3. Surface a loud log line for each lifecycle event (built / removed / failed), so the watcher narrates instead of failing silently.
  4. 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

  • New .scss files build on creation without a full build-themes run.
  • Renames remove the old artifact and build the new one.
  • Deletions remove the dist artifact (+ .map); partial deletions either rebuild dependents or print a loud, actionable error.
  • No regressions to the existing change-only behavior (single-file rebuild latency unchanged).
  • Focused tests for all three event classes green; preflight clean.

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:

  1. 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).
  2. 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.

tobiu referenced in commit f1b9cb9 - "feat(build): reconcile theme watcher lifecycle (#15585) (#16124)" on 11:06 AM
tobiu closed this issue on 11:06 AM