Context
PR #15584 (OPEN, draft, head 4372d3b4e6 — not yet merged) gives the test pipeline a theme-freshness guard: a shared inspector (buildScripts/util/developmentThemeAssets.mjs) derives the development CSS census from current SCSS, validates theme maps, rejects borrowed symlink outputs, and invokes the canonical build when outputs are stale. This ticket depends on #15584 landing — current dev@1c2c293424 has no such module; harness/prepareAssets.mjs and test/playwright/visual/globalSetup.mjs still own separate newestMtime implementations.
The interactive dev server has no such guard. A long-running npm run server-start session serves dist/ CSS exactly as built at server-start; after merges land (e.g., a component anatomy change), the human's browser gets new DOM against stale CSS and the failure looks like a product bug. 2026-07-21 produced two concrete instances in one day: an operator screenshot of garbled AgentCards (the pre-merge anatomy), and an agent's own probe runs on a bisect checkout serving a 2-day-old tree. Both cost real diagnosis time before "rebuild themes" was identified as the entire fix.
Live latest-open sweep: checked latest 20 open issues at 2026-07-21 ~22:05Z; no equivalent found. A2A in-flight claim sweep (last 60 min): no competing claim. Intake re-run by Euclid at 22:31Z (issuecomment-5039687186): needs-relinking + needs-contract-alignment — repaired by this body revision.
The Problem
No runtime signal tells the operator (or an agent) that the served CSS trails the SCSS sources. The browser shows a confidently wrong render and every first hypothesis points at product code. The motivating case is the long-running server: SCSS changes and merges arrive after server start, so a start-only check leaves the root symptom outside the contract.
The Boundary with #15585
#15585 owns the dev-loop watcher lifecycle and its startup precondition. This ticket adds no watcher: the guard fires at server start and on served-CSS requests (below), and when #15585 lands its watcher may become a third consumer of the same inspector. Until then the request-time guard alone covers the post-start merge/change case.
The Fix
Reuse #15584's inspector (SSOT — do not re-derive a second newestMtime): compare newest SCSS source mtime against the built theme outputs and surface a loud, unmissable server-log warning naming the recovery command via #15584's exported DEVELOPMENT_THEME_BUILD_COMMAND constant — never a second hardcoded literal.
Detection cadence (chosen, cycle-2 narrowed per Euclid's exact-head falsifier, issuecomment-5040112885): startup inspection + bounded/coalesced request-triggered revalidation with a minimum recheck interval. His exact-head measurement: the uncached inspector costs a 17.75ms median synchronous pass per call (378 SCSS / 640 outputs), and a naive memoize-per-stale-state has no invalidation path back to fresh. Contract: (a) one startup inspection; (b) any CSS request after the interval elapsed triggers ONE inspection; many requests inside the interval coalesce to the last result; a later request after the interval can observe a rebuild and the fresh transition. A 5s default interval bounds worst-case overhead to ~18ms per 5s of active CSS serving (0.4% of one event-loop thread). (c) Warning de-duplication is SEPARATE from inspection caching: warn once per fresh→stale transition; silent while fresh; warn again on the next transition after a rebuild (fresh → stale → stale(de-duped) → fresh → stale(warns again)). Fail-soft: warn, never block the server or the response. (d) The warning text consumes #15584's exported DEVELOPMENT_THEME_BUILD_COMMAND — no second literal.
Browser signal: descoped. No universal dev-server-owned in-app surface exists short of response-body injection; the contracted human surface for this ticket is the server log. A dismissible in-app banner may follow as its own ticket once a universal seam (dev-only injection point vs app-owned component) is chosen deliberately.
Contract Ledger
| Target surface |
Source of authority |
Proposed behavior |
Fallback / edge case |
Evidence |
| shared inspector |
PR #15584 post-merge |
named inspector result consumed, not re-derived |
dependency absent → this ticket stays blocked; inspector error → fail-soft, no warning spam |
focused unit witness (stale → warn, fresh → silent) |
| dev-server warning cadence |
this ticket (boundary: #15585) |
startup + bounded request-triggered revalidation (minimum recheck interval, default 5s; many in-interval requests coalesce) |
inspection caching ≠ warning de-dupe; interval/interval-elapsed request observes rebuild; never blocks boot or response |
interval coalescing + fresh→stale→stale(de-duped)→fresh→stale(warns again) witnesses |
| recovery command text |
#15584's exported DEVELOPMENT_THEME_BUILD_COMMAND |
single SSOT literal consumed by the warning |
no second hardcoded command |
grep shows one literal |
| browser signal |
follow-up ticket (not this one) |
none contracted here |
no production artifact; no app-boot block; no response-body injection |
n/a (descoped) |
Acceptance Criteria
Out of Scope
- The in-app browser banner (needs its own ticket once a universal seam is chosen)
- The dev-loop watcher lifecycle + startup precondition (owned by
#15585)
- The test-pipeline guard itself (owned by
#15584)
Related
#15584 (blocking dependency — the inspector to consume; OPEN draft at time of writing)
#15585 (adjacent boundary — watcher lifecycle; not this ticket)
#15651 (the Accounts selector rot — unrelated class of failure, kept distinct in triage)
Origin Session ID: d8a51237-4fcc-4171-8071-a391da0be361
Retrieval Hint: "dev server stale CSS banner theme build freshness warning"
Context
PR
#15584(OPEN, draft, head4372d3b4e6— not yet merged) gives the test pipeline a theme-freshness guard: a shared inspector (buildScripts/util/developmentThemeAssets.mjs) derives the development CSS census from current SCSS, validates theme maps, rejects borrowed symlink outputs, and invokes the canonical build when outputs are stale. This ticket depends on#15584landing — currentdev@1c2c293424has no such module;harness/prepareAssets.mjsandtest/playwright/visual/globalSetup.mjsstill own separatenewestMtimeimplementations.The interactive dev server has no such guard. A long-running
npm run server-startsession servesdist/CSS exactly as built at server-start; after merges land (e.g., a component anatomy change), the human's browser gets new DOM against stale CSS and the failure looks like a product bug. 2026-07-21 produced two concrete instances in one day: an operator screenshot of garbled AgentCards (the pre-merge anatomy), and an agent's own probe runs on a bisect checkout serving a 2-day-old tree. Both cost real diagnosis time before "rebuild themes" was identified as the entire fix.Live latest-open sweep: checked latest 20 open issues at 2026-07-21 ~22:05Z; no equivalent found. A2A in-flight claim sweep (last 60 min): no competing claim. Intake re-run by Euclid at 22:31Z (
issuecomment-5039687186): needs-relinking + needs-contract-alignment — repaired by this body revision.The Problem
No runtime signal tells the operator (or an agent) that the served CSS trails the SCSS sources. The browser shows a confidently wrong render and every first hypothesis points at product code. The motivating case is the long-running server: SCSS changes and merges arrive after server start, so a start-only check leaves the root symptom outside the contract.
The Boundary with
#15585#15585owns the dev-loop watcher lifecycle and its startup precondition. This ticket adds no watcher: the guard fires at server start and on served-CSS requests (below), and when#15585lands its watcher may become a third consumer of the same inspector. Until then the request-time guard alone covers the post-start merge/change case.The Fix
Reuse
#15584's inspector (SSOT — do not re-derive a secondnewestMtime): compare newest SCSS source mtime against the built theme outputs and surface a loud, unmissable server-log warning naming the recovery command via#15584's exportedDEVELOPMENT_THEME_BUILD_COMMANDconstant — never a second hardcoded literal.Detection cadence (chosen, cycle-2 narrowed per Euclid's exact-head falsifier, issuecomment-5040112885): startup inspection + bounded/coalesced request-triggered revalidation with a minimum recheck interval. His exact-head measurement: the uncached inspector costs a 17.75ms median synchronous pass per call (378 SCSS / 640 outputs), and a naive memoize-per-stale-state has no invalidation path back to fresh. Contract: (a) one startup inspection; (b) any CSS request after the interval elapsed triggers ONE inspection; many requests inside the interval coalesce to the last result; a later request after the interval can observe a rebuild and the fresh transition. A 5s default interval bounds worst-case overhead to ~18ms per 5s of active CSS serving (0.4% of one event-loop thread). (c) Warning de-duplication is SEPARATE from inspection caching: warn once per fresh→stale transition; silent while fresh; warn again on the next transition after a rebuild (
fresh → stale → stale(de-duped) → fresh → stale(warns again)). Fail-soft: warn, never block the server or the response. (d) The warning text consumes#15584's exportedDEVELOPMENT_THEME_BUILD_COMMAND— no second literal.Browser signal: descoped. No universal dev-server-owned in-app surface exists short of response-body injection; the contracted human surface for this ticket is the server log. A dismissible in-app banner may follow as its own ticket once a universal seam (dev-only injection point vs app-owned component) is chosen deliberately.
Contract Ledger
#15584post-merge#15585)#15584's exportedDEVELOPMENT_THEME_BUILD_COMMANDAcceptance Criteria
#15584census/validation module after its merge (one implementation, two consumers); the ticket remains blocked until#15584landsOut of Scope
#15585)#15584)Related
#15584(blocking dependency — the inspector to consume; OPEN draft at time of writing)#15585(adjacent boundary — watcher lifecycle; not this ticket)#15651(the Accounts selector rot — unrelated class of failure, kept distinct in triage)Origin Session ID: d8a51237-4fcc-4171-8071-a391da0be361
Retrieval Hint: "dev server stale CSS banner theme build freshness warning"