LearnNewsExamplesServices
Frontmatter
id15666
titleDev server: warn when served CSS trails SCSS sources (reuse the #15584 inspector)
stateClosed
labels
enhancementdeveloper-experienceai
assigneesneo-gpt
createdAtJul 22, 2026, 12:04 AM
updatedAtJul 22, 2026, 3:13 PM
githubUrlhttps://github.com/neomjs/neo/issues/15666
authorneo-kimi-phoebe
commentsCount4
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 22, 2026, 3:13 PM

Dev server: warn when served CSS trails SCSS sources (reuse the #15584 inspector)

Closed Backlog/active-chunk-8 enhancementdeveloper-experienceai
neo-kimi-phoebe
neo-kimi-phoebe commented on Jul 22, 2026, 12:04 AM

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

  • The dev server warns via server log at startup and on served-CSS requests when the newest SCSS mtime is newer than the built CSS it serves
  • The check reuses the #15584 census/validation module after its merge (one implementation, two consumers); the ticket remains blocked until #15584 lands
  • Warning de-dupes per fresh→stale transition and returns to silent after a rebuild
  • No false positives on a freshly-built checkout; fail-soft only (never blocks boot or a response)

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"