fix: preview-truth CSS supersession + cascade ordering on Svelte accept

Field failure from a real Codex session: accepting a variant into
Pitch.svelte appended 23 selectors and removed none, so the source's old
.decisions grid rules re-attached through the kept root class and forced
the accepted board into a stale three-column layout; some appended base
rules also landed after the source's media block, weakening the mobile
cascade.

Two mechanical fixes:
- Preview truth: the scaffolder records the seeded selectors (the source
  rules that styled the replaced selection, which the isolated preview
  never applied). On accept, any seeded selector the variant does not
  re-declare is removed; the selector-loss postcondition treats those
  removals like compiler prunes. A regression test reproduces the exact
  Pitch shape end to end.
- Cascade order: reconciliation inserts new base rules BEFORE existing
  top-level media blocks instead of appending after them.

Init-latency reductions from the same transcript:
- live.mjs inlines the resolved surface brief (removes three
  surface-brief.mjs round-trips including a --help miss before first poll).
- The wrap/scaffold payload carries componentStubMarkup, and live.md
  instructs editing stubs in place (the session read the manifest + stub
  back and then deleted/recreated the files).
- live.md notes that a busy default port usually means the dev server is
  already running (the session spawned a duplicate).

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-27 18:45:07 -07:00
co-authored by Claude Code
parent 031e170d3e
commit 5b6b331785
6 changed files with 233 additions and 10 deletions
+18 -1
View File
@@ -24,6 +24,7 @@ import { fileURLToPath } from 'node:url';
import { resolveTargetSelection } from './context.mjs';
import { resolveFiles } from './live-inject.mjs';
import { readLiveServerInfo } from './lib/impeccable-paths.mjs';
import { resolveSurfaceBrief } from './lib/surface-briefs.mjs';
import { resolveLiveTarget } from './live-target.mjs';
import { resolveRoots, writeRootsManifest } from './live/roots.mjs';
@@ -161,7 +162,20 @@ The agent should then:
const resolvedFiles = resolveFiles(activeCwd, checkResult.config);
const drift = scanForDrift(activeCwd, resolvedFiles, checkResult.config);
// 5. Emit everything the agent needs
// 5. Emit everything the agent needs. The surface brief rides along so the
// agent does not spend three more tool calls (and a --help miss) on
// surface-brief.mjs before the first poll.
let surfaceBrief = null;
let surfaceBriefPath = null;
try {
const resolvedBrief = resolveSurfaceBrief(roots.appRoot, liveTarget.absoluteTargetPath || null);
if (resolvedBrief?.brief) {
surfaceBrief = resolvedBrief.brief.text ?? safeRead(resolvedBrief.brief.path);
surfaceBriefPath = resolvedBrief.brief.path
? path.relative(liveTarget.originalCwd, resolvedBrief.brief.path)
: null;
}
} catch { /* briefs are optional context */ }
console.log(JSON.stringify({
ok: true,
serverPort: serverInfo.port,
@@ -179,6 +193,9 @@ The agent should then:
hasDesign: !!design,
design,
designPath: relOrNull(liveTarget.originalCwd, roots.designPath),
hasSurfaceBrief: !!surfaceBrief,
surfaceBrief,
surfaceBriefPath,
}, null, 2));
}