LearnNewsExamplesServices
Frontmatter
id17203
titleThe theme-staleness guard teaches the manual rebuild and never mentions watch-themes
stateClosed
labels
enhancementaibuild
assigneesneo-opus-grace
createdAtAug 15, 2026, 8:41 PM
updatedAtAug 25, 2026, 1:21 AM
githubUrlhttps://github.com/neomjs/neo/issues/17203
authorneo-opus-grace
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 25, 2026, 1:21 AM

The theme-staleness guard teaches the manual rebuild and never mentions watch-themes

Closed Backlog/active-chunk-16 enhancementaibuild
neo-opus-grace
neo-opus-grace commented on Aug 15, 2026, 8:41 PM

CORRECTED BY THE OPERATOR BEFORE ANY WORK STARTED — the original prescription was wrong.

"A new branch needs ONE theme build. Afterwards npm run watch-themes => done."

watch-themes exists (node ./buildScripts/helpers/watchThemes.mjs) and is the durable answer. So auto-rebuilding inside globalSetup is the wrong fix: it would duplicate a watcher that already solves this continuously, for every consumer — not just this harness.

I proposed automating something that is already automated, because I reached for a fix without checking whether the capability existed. That is the same assert-before-verify failure I spent the day cataloguing in code, applied to my own enablement proposal — and it landed one hour after I wrote a memory about friction awareness. Recorded rather than quietly rewritten.

SECOND CORRECTION — my cost figure was invented too. I wrote "~30s" for the theme build. Measured on this machine: 1.29s real. Off by more than 20×, and I had put it in this ticket and a Discussion comment as though it were measured.

That changes the reasoning, not just a digit. My argument against auto-rebuild was "it would bolt 30s onto every run" — at 1.3s that objection collapses. The conclusion survives on a different reason than the one I gave: the watcher already exists, is incremental and near-instant, and serves every consumer (dev server, browser, any suite) rather than one harness. A conclusion whose stated justification collapsed is one to re-derive, not to keep — so it is re-derived here, and the teaching-surface finding below is the part that never depended on the number.

The real defect is sharper than the one I filed. The guard's message names only the one-off command:

> Rebuild first:

> node ./buildScripts/build/themes.mjs -f -n -e dev > ``` > > It never mentions watch-themes. So every time it fires it teaches the manual workflow — and it fires precisely at the moment someone is least likely to go looking for a better one. I hit it twice today, typed the printed command twice, and did not learn the watcher existed until told. An error message is a teaching surface, and this one teaches the worse habit with perfect reliability. > > The ACs below are replaced by the corrected set at the bottom.

Refs #17201 Refs D#17085

<h2 class="neo-h2" data-record-id="2">Problem</h2>

A recurring shape across our tooling: a guard detects a condition precisely, knows the exact command that resolves it, prints that command, and then throws so a human or agent retypes it.

Instance found today, test/playwright/visual/globalSetup.mjs:js if (newestScss > newestCss) { throw new Error( 'visual harness: the built theme CSS is OLDER than the newest SCSS source — a baseline over ' + 'stale artifacts is a poisoned golden. Rebuild first:\n' + ' node ./buildScripts/build/themes.mjs -f -n -e dev' ) }


The detection is exactly right and the reasoning is correct — a baseline over stale artifacts *is* a poisoned golden. But the resolution is deterministic, idempotent, and already written down two lines below the throw. I hit this twice in one session, ran the printed command verbatim both times, and exercised no judgment either time.

`check-block-alignment` is the same shape with a real caveat and is filed separately as #17201; that one needs its fixer scoped before it can auto-apply. **This one has no such caveat** — a theme rebuild is idempotent and whole-tree by nature, so there is nothing to iron out.

<h2 class="neo-h2" data-record-id="3">Why this is the cheapest class of improvement we have</h2>

It removes cost while **keeping the guarantee completely intact**. The same condition is detected by the same code; only the disposition changes from *refuse* to *repair*. There is no risk calculus, no incident-currency question, no judgment about whether the rule still earns its place — the rule is untouched.

<h2 class="neo-h2" data-record-id="4">The Architectural Reality</h2>

- The throw exists to prevent a poisoned baseline. Rebuilding satisfies that intent **more** directly than refusing does: after a rebuild the artifacts are fresh, which is the state the guard wants.
- Cost is honest either way. Refusing costs a 30s rebuild **plus** a failed run, a round-trip, and the reader's attention. Auto-running costs the 30s.
- The failure mode of auto-running is bounded and visible: the build is idempotent, so the worst case is a no-op that logs.
- This is not "remove the guard". Anything that cannot deterministically compute its own fix keeps throwing.

<h2 class="neo-h2" data-record-id="5">Out of Scope</h2>

- `check-block-alignment` — same verb, real blocker, filed as #17201.
- Any guard whose fix requires a decision. The test is *deterministic and idempotent*, not merely *known*.
- Rewriting the guards' detection logic. Detection is what these get right.

<h2 class="neo-h2" data-record-id="6">Acceptance Criteria (SUPERSEDED — see the corrected set below)</h2>

- [ ] The visual harness rebuilds stale theme CSS itself and proceeds, rather than throwing with the command in the message. A run against deliberately stale artifacts completes green with no author action.
- [ ] The no-built-CSS-at-all branch behaves the same way — it prints the identical command today, so it is the identical case.
- [ ] Auto-repair is announced, not silent: the run logs that it rebuilt and why, so a slow first run is explained rather than mysterious.
- [ ] A repair that **fails** still fails the run, loudly, with the original guidance — this converts a refusal into an attempt, never into a suppression.
- [ ] A sweep records every other guard in `buildScripts/` and `test/` that prints a fix command in its failure text, with a keep/convert verdict per instance and the deterministic-and-idempotent test applied. The sweep output goes in the PR body so the population is a measurement, not an impression.

<h2 class="neo-h2" data-record-id="7">Evidence class</h2>

Read at source on `dev` 2026-08-15: `globalSetup.mjs` staleness and no-CSS branches, both printing `node ./buildScripts/build/themes.mjs -f -n -e dev`. Friction observed live twice in one session, resolved identically both times with no judgment exercised. Sibling instance and its scoping blocker diagnosed in #17201.

Live latest-open sweep at 2026-08-15T18:31:40Z plus the tickets filed since; nothing equivalent open. A2A sweep: no competing claim.

<h2 class="neo-h2" data-record-id="8">Decision Record impact</h2>

`none`.

🖖 Authored by Grace (Claude Opus 5, Claude Code). Session b17338dd-b474-494f-b08c-683044de2ddb. Filed after the operator's coffee-machine framing — the point being that I hit this twice today, typed the printed command both times, and did not once ask why I was the one typing it.


<h2 class="neo-h2" data-record-id="9">Acceptance Criteria (corrected)</h2>

- [ ] The staleness and no-CSS messages name the **durable** setup first — one build, then `npm run watch-themes` — and keep the one-off command as the fallback for a single run. The message teaches the workflow that stops it recurring, not the one that resolves this instance.
- [ ] The same correction lands wherever else the one-off build command is printed as guidance, found by sweeping for it rather than by memory.
- [ ] No auto-rebuild is added to `globalSetup` — but on the correct grounds: the watcher already covers it and serves every consumer, not because a rebuild is expensive (it is 1.29s here). If a belt-and-braces net is ever wanted, cost is not the argument against it.
- [ ] Where a new-branch first build is genuinely required, that sequencing is stated once, in the place a session actually starts, rather than discovered from a failed test run.
- [ ] The `check-block-alignment` convert in <a href="#/news/tickets/17201">#17201</a> is re-examined against this same question before implementation: is there an existing capability that makes the conversion unnecessary? The lesson generalises — **check for the tool before building the tool.**





</div>
                </div>
            </div>
                    <div id="timeline-17203-1" class="neo-timeline-item event" data-record-id="timeline-17203-1">
                        <div id="timeline-17203-1-target" class="neo-timeline-badge" ><i class="fa-solid fa-link"></i></div>
                        <div class="neo-timeline-body">
                            <a class="neo-timeline-user" href="https://github.com/tobiu" target="_blank">tobiu</a> referenced in commit <code><a href="https://github.com/neomjs/neo/commit/76701ee" target="_blank">76701ee</a></code> - &quot;fix(visual): the theme guard teaches the durable setup, not the one-off rebuild (#17203) (#17735) <span class="neo-timeline-date">on Aug 25, 2026, 1:21 AM</span>
                        </div>
                    </div>
                    <div id="timeline-17203-2" class="neo-timeline-item event" data-record-id="timeline-17203-2">
                        <div id="timeline-17203-2-target" class="neo-timeline-badge" style="color: #8250df"><i class="fa-solid fa-circle-check"></i></div>
                        <div class="neo-timeline-body">
                            <a class="neo-timeline-user" href="https://github.com/tobiu" target="_blank">tobiu</a> closed this issue <span class="neo-timeline-date">on Aug 25, 2026, 1:21 AM</span>
                        </div>
                    </div></div>