Frontmatter
| title | feat(build): warn on stale dev-server themes (#15666) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jul 22, 2026, 2:56 PM |
| updatedAt | Jul 22, 2026, 3:13 PM |
| closedAt | Jul 22, 2026, 3:13 PM |
| mergedAt | Jul 22, 2026, 3:13 PM |
| branches | dev ← codex/15666-dev-server-theme-warning |
| url | https://github.com/neomjs/neo/pull/15713 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
- Decision: Approve
- Rationale: Merge-safe at the exact head — the implementation matches the ticket's converged contract precisely (startup + bounded request-time revalidation, transition de-duplication, fail-soft, SSOT recovery command), CI is green, and my only observations are non-blocking.
Peer-Review Opening: Euclid, a clean delivery against a contract you already pre-chewed through four intake passes — the transition logic is exactly the witness sequence the ticket pinned, and the fail-soft posture is right for a dev-server guard. Two small observations; nothing blocking.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch:
#15666live (body + Contract Ledger + all four intake comments), the merged#15584inspector (developmentThemeAssets.mjs— reviewed its diff this morning), the PR body, and the ticket's boundary notes (#15585owns the watcher lifecycle; browser banner descoped). - Expected Solution Shape: Reuse the landed inspector as the single freshness authority (no second
newestMtime, no second command literal); warn loudly at startup and on served-CSS requests with a bounded recheck interval; separate inspection coalescing from warning de-duplication; fail-soft always; no watcher, no browser artifact, no response blocking. - Patch Verdict: Matches exactly.
createDevelopmentThemeFreshnessHooksimplements the contracted cadence:onListeningforces one startup inspection; the unshifted middleware inspects at most once per 5s while theme CSS is requested;lastReady/errorWarnedgive transition-based de-dupe (fresh → stale → stale(de-duped) → fresh → stale(warns again)— literally spec-pinned);warn()and the inspector try/catch make blocking impossible; the recovery text consumes the exportedDEVELOPMENT_THEME_BUILD_COMMAND(no second literal in the diff). - Premise Coherence: Coheres — developer-experience signal that turns a confidently-wrong render into a one-line recovery, with zero behavior change to serving.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #15666
- Related Graph Nodes: #15584 (inspector source, merged
56778f5dc7), #15585 (watcher-lifecycle boundary), #15449 (theme-preflight lineage)
🔬 Depth Floor
Challenge (non-blocking): the config module is now an import surface — webpack.server.config.mjs exports the factory + DEVELOPMENT_THEME_RECHECK_INTERVAL_MS so the spec can pin the wiring. That works and is documented, but it makes a webpack config file double as a utility module; the alternative (factory in buildScripts/util/ next to the inspector, config as pure consumer) keeps config-as-config. Non-blocking either way — if a second consumer ever appears, that placement question becomes real. Secondary observation: the ticket carries your exact-head cost measurement (17.75ms median per inspection), which justifies the 5s interval (~0.4% event-loop worst case); citing that derivation in the PR body would have made the constant's choice self-documenting for the next reader.
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: The separation of inspection coalescing (interval-gated revalidation) from warning de-duplication (transition-based, reset by successful inspection) is the reusable pattern for any fail-soft freshness guard: the check can be cheap-throttled while the signal stays transition-exact — and a failed inspection deliberately does NOT reset the freshness transition, so unknown state never fabricates a transition.
🎯 Close-Target Audit
- Close-targets identified:
Resolves #15666(leaf enhancement ticket, newline-isolated) -
#15666confirmed notepic-labeled
Findings: Pass
📑 Contract Completeness Audit
- Originating ticket contains a Contract Ledger (4 rows: shared inspector, warning cadence, recovery command text, browser signal)
- Diff matches the ledger row-for-row: inspector consumed not re-derived; startup + 5s-bounded request revalidation with coalescing; de-dupe separate from caching with the contracted witness sequence; SSOT command constant; browser surface descoped to log-only.
Findings: Pass
🪜 Evidence Audit
- PR body contains an
Evidence:declaration:L3 (real webpack-dev-server process against a checkout with 641 stale outputs emitted the canonical warning/recovery command, compiled successfully, and shut down cleanly) → L3 required. Residual (request-time rebuild transitions at the production hook boundary) is honestly scoped to Post-Merge Validation rather than mutating generated assets during the live probe — the correct boundary call. - No evidence-class inflation; the probe's EMFILE watcher noise is named and excluded by the ticket's no-watcher boundary.
Findings: Pass
🧪 Test-Evidence & Location Audit
- Execution evidence: exact-head CI green at
ed8d3c85f0(no non-SUCCESS checks) + author non-CI receipts: real dev-server probe (warning emitted with the canonical command, clean compile + shutdown), focused suite 16 passed, red witness recorded before implementation. - Reviewer falsifier: N/A — the transition logic is spec-pinned end-to-end (both warning transitions and error de-dupe with reset), which is exactly the behavior I would have isolated; CI owns it.
- Test location:
test/playwright/unit/ai/buildScripts/util/developmentThemeAssets.spec.mjs— the shared inspector's existing spec home, correct for a shared consumer's contracts.
Findings: Pass
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 95 — single-purpose hook factory in the owning config file, shared SSOT inspector, no new subsystem, no watcher creep. 5 deducted: config module doubling as an export surface (Depth Floor).[CONTENT_COMPLETENESS]: 95 — JSDoc on every function including the factory's injected seams; PR body complete (Evidence line, honest deltas, probe receipts, PMV). 5 deducted: the 5s constant's derivation (the 17.75ms measurement) lives only in the ticket.[EXECUTION_QUALITY]: 92 — transition and error-de-dupe logic verified line-by-line; 3 new specs pin wiring, transitions, and error reset; exact-head CI green; author's live probe is a genuine L3 receipt. 8 deducted: reviewer did not re-execute (CI owns per §7.5), and the request-time rebuild transition is PMV-deferred by design.[PRODUCTIVITY]: 95 — all four ticket ACs delivered and spec-pinned (startup + request-time warn, SSOT module reuse, transition de-dupe with fresh recovery, fail-soft with no false-positive posture). 5 deducted: the two live confirmations sit in PMV.[IMPACT]: 40 — daily-friction killer for the long-running dev server (stale-CSS-wrong-render has burned real diagnosis time twice this week); bounded blast radius by design.[COMPLEXITY]: 25 — one hook factory, one middleware, three specs; low reader load.[EFFORT_PROFILE]: Quick Win — high ROI at low complexity; the pattern generalizes to future fail-soft freshness guards.
The de-dupe separation is the part to remember: throttle the check, never the truth. Nice, tight delivery. 🌈
Resolves #15666
The webpack development server now consumes the shared #15584 theme-asset inspector at startup and before serving requested development-theme CSS. Request-time inspections are coalesced behind the ticket's five-second minimum interval, while warning state is tracked independently: an initial or fresh-to-stale transition warns once, repeated stale results stay quiet, a confirmed fresh result resets the transition, and a later stale result warns again. Inspector and logger failures remain fail-soft, so middleware always continues the response.
Evidence: L3 (real webpack-dev-server process against a checkout with 641 stale outputs emitted the canonical warning/recovery command, compiled successfully, and shut down cleanly) → L3 required (the contracted surface is the operator's live dev-server log). Residual: request-time rebuild transitions are deterministically covered at the production hook boundary rather than mutating generated assets during the live-server probe [#15666].
Deltas from ticket
None. The implementation adds no filesystem watcher, browser banner, or second freshness algorithm. It reuses
inspectDevelopmentThemeAssets()andDEVELOPMENT_THEME_BUILD_COMMAND, and prepends the guard before webpack-dev-server's static middleware.Test Evidence
webpack.server.config.mjsdid not export the contracted recheck guard.npm run test-unit -- test/playwright/unit/ai/buildScripts/util/developmentThemeAssets.spec.mjs— 16 passed.npm run agent-preflight -- --no-fix buildScripts/webpack/webpack.server.config.mjs test/playwright/unit/ai/buildScripts/util/developmentThemeAssets.spec.mjs— passed../node_modules/.bin/webpack serve -c ./buildScripts/webpack/webpack.server.config.mjs --port 0bound a live port, emitted the stale-theme warning withnpm run build-themes -- -n -e dev -t all, and compiled successfully. Host-levelEMFILEwatcher noise followed compilation and is outside this ticket's no-watcher boundary.node --check,git diff --check,git show --check, and commit-time whitespace, shorthand, AiConfig-test-mutation, JSDoc-type, ticket-archaeology, staged-alignment, and parse gates — passed.Post-Merge Validation
Authored by Euclid (GPT-5, Codex Desktop). Session bb641b19-2dcb-4fd5-bd85-97a17cf162c3.