Commit Graph
216 Commits
Author SHA1 Message Date
Paul BakausandClaude Opus 5 a94331baf0 Add the mode and area axes to the roll
Two gaps, both reported from real use. Worlds were drawn with no mode
awareness at all: selectApprovedChallengers never received the mode, so a
build asking for an app UI could draw six worlds that only make sense on a
landing page. And surface alone is too coarse for compositions, because
"operate" spans onboarding, dashboards, editors and settings, so an
onboarding flow could legitimately be dealt a settings composition.

Worlds gain `allowedModes` on the review record, beside breadth and rating,
because it is a reviewer judgment rather than authored content. Absent means
eligible in every mode, so nothing needs backfilling and no existing roll
changes. Applied per tier and skipped where it would empty one, matching how
minRating and strength already degrade. It is a ceiling the reviewer lowers,
not a category they assign: a world is an identity, and identities transfer
across modes further than compositions do.

Compositions gain an optional `area`, one level below surface, with a
taxonomy per surface (COMPOSITION_AREAS). Area is a preference rather than a
filter: a request reorders the ranking to put area matches first and tops up
from the rest of the surface, because the per-area pools are small and
dealing one on-target composition would be worse than three good ones. A
stable partition of an already deterministic ranking stays deterministic.

`--area` on the CLI requires `--mode`, since areas are scoped to a surface,
and is validated against that surface's list so a wrong-surface area fails
loudly instead of silently matching nothing.

Also validated `breadth`, which selection has honoured for a while with
nothing checking it, so a typo read as "general" and quietly returned a
narrow world to the pool.

Four new tests: worlds excluded from a mode stay out, absent allowedModes
stays eligible everywhere, a tier whose every world excludes the mode falls
back instead of starving, and an area-scoped deal prefers its area, tops up
to three, and reproduces from its key.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 16:45:20 -07:00
Paul BakausandClaude Opus 5 a92ba5f2b0 Call them compositions; single-source WELL_TIERS
The data layer has said compositions since the catalog was split, while the
code, the model-facing text, and the UI still said stagings. The rename was
held back by the selection logic existing twice; it exists once now, so this
is one pass instead of two coordinated ones.

Renamed: selectApprovedStagings, selectApprovedStaging, renderStaging, and
the model-facing STAGING GRAMMAR / STAGING CHALLENGERS / FIRST-SURFACE
STAGING INPUTS headings. The block that introduces them now states what
they are for rather than only what they are not: what is the cleverest way
to present, organize, or make interactive the problem in front of you.

Three places keep the old word on purpose:

- The rank salt, `${scope}:${key}:staging`. It is hash input, so renaming
  it would re-deal every roll anyone has ever reproduced by key. Verified:
  240 seeder rolls and 252 API rolls reproduce exactly.
- `Staging/hierarchy:`, the first composition grammar prefix. Inside a
  composition, staging names one of its four aspects, which is a different
  word-sense from staging as the name for the whole artifact. It is also a
  schema constant that 317 catalog entries are validated against.
- The wire fields. The API keeps emitting `stagings` and `staging` beside
  `compositions`, because the wire is the one place a rename cannot be
  coordinated with already-installed skills. Clients prefer the new field
  and fall back through both old ones.

Separately, WELL_TIERS had two definitions after the extraction.
roll-selection.mjs owns it now and concept-catalog.mjs imports it, in that
direction because concept-catalog reads the filesystem and a Pages Function
must not pull node:fs into its bundle. Imported and re-exported rather than
re-exported alone: a bare `export { X } from` does not bind X locally, and
validateConceptCatalog needs it, which cost one round of red tests.

Dropped concept-catalog's synchronous deterministicRank. Nothing imports it
since selection moved out, and leaving a second ranking implementation
around is how the first drift started.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 16:26:46 -07:00
Paul BakausandClaude Opus 5 9b43e9f176 Extract roll selection into one module both callers drive
concept-seed.mjs and the service repo's functions/api/_worldroll-core.js
were two implementations of the same selection, and the API core's header
claimed they matched "exactly". They did not: it had no breadth gate on
either pool, no rating weighting for compositions, and dealt one
composition where the seeder dealt three. Since the catalog never ships
with the skill, every real user rolls through that API, so those gates
reached nobody. Two copies is the defect; this removes the second.

Written as generators rather than plain functions because the callers
cannot agree on a hash. Node has a synchronous one, Workers only have
async crypto.subtle, and renderConceptSeed's local path is deliberately
synchronous so prepared eval sessions and tests can call it without
awaiting. The selection yields batches of strings to hash and resumes
with their digests; runSyncSelection and runAsyncSelection are the only
runtime-specific code, eight lines each. Forcing the seeder async would
have broken the eval harness; forking the logic is what got us here.

No roll changes. Node's crypto.createHash('sha256') and Web Crypto's
SHA-256 return the same bytes, verified, and 240 seeder rolls plus 252
API rolls across both scopes, five modes, three reroll depths and the
rating gate reproduce their pre-refactor output exactly. The 23 existing
concept-seed tests pass unmodified, which is the point: the synchronous
contract survived.

The service repo's core keeps its own copy until this is on main, because
its deploy materializes skill/ from main and would fail to resolve an
import that is not there yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 15:57:20 -07:00
Paul Bakaus 99c4189788 Fix Google Fonts value suppression
AI-assisted change.
2026-07-29 14:28:15 -07:00
Paul BakausandGitHub 5f4b58d06d Merge pull request #433 from pbakaus/live-v2-rewrite
Live v2: root manifest, mount-ack protocol, AST scaffolder, mechanical accept
2026-07-29 13:12:23 -07:00
Paul BakausandClaude Code 6c7f7b5cc0 fix: scope each keys during restore and fail loudly on an unenterable app root
Two review findings:

- restoreSvelteMarkup visited an {#each} key with outer scopes only, so a
  contract prop sharing a loop binding name rewrote the key: with prop
  name -> user.name and loop context "name", the key (name.id) became
  (user.name.id) in the accepted route. The key evaluates per item, so it
  is now visited with the loop context and index bound. Regression test
  verified failing on the previous code.
- enterLiveRoot silently kept the ambient working directory when the
  resolved appRoot no longer existed or chdir failed, letting a helper
  derive server, session, and source paths from the wrong project. Both
  cases now exit with a clear error naming the app root and the --target
  escape hatch.

AI-assisted (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 18:18:05 -07:00
Paul BakausandClaude Code 0c18cbc9ef fix: stop treating the child combinator as a prelude boundary when pruning
removeSelectorAt walked backward to find the rule prelude and stopped at
any '>', added so the walk would not escape past the <style> open tag.
That same character is the CSS child combinator, so pruning one unused
selector from a list like '.wrap > .orphan, .orphan' cut the prelude
mid-list; when every remaining fragment equaled the flagged selector, the
whole-rule branch then deleted from the cut point and left a dangling
'.wrap >' in source. A '>' now bounds the walk only when it actually
closes a <style ...> tag; combinators are walked through.

Regression tests cover a mid-list combinator prune and the dangling-
fragment shape (verified failing on the previous code).

AI-assisted (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 18:05:41 -07:00
Paul BakausandClaude Code 20213a6817 fix: bound CSS seeding to real matches and ownership before supersession removal
Addresses two cursor findings on extractMatchingSourceCss plus an adjacent
hazard in the same removal machinery:

- Class matching is token-bounded, never substring: .btn no longer seeds
  .btn-primary and .stage no longer seeds .stages. A falsely seeded
  selector was an accept-time deletion of a hand-written rule, since any
  seeded selector the variant does not re-declare is removed as
  superseded.
- Tag rules that style the pick (h1, a, p) now seed the preview stub, so
  unclassed selections start from the real cascade. They are excluded
  from the supersedable set: tag rules style shared elements across the
  route and must never be removal candidates.
- Supersession removal is now bounded by ownership: a seeded class
  selector whose class is still used by markup OUTSIDE the replaced
  region survives the accept, because removing it would strip styling
  from markup the accept never touched.

Tests cover substring non-matches, tag seeding with a tag-free
supersedable set, and a shared-class accept where .card is used both
inside the pick and elsewhere.

AI-assisted (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 17:55:13 -07:00
Paul BakausandClaude Code a83d767cf9 fix: stop cross-project live session leakage and stale-adapter 401s
Field session on a nested SvelteKit app surfaced a self-reinforcing leak:
localStorage is per-origin, two projects reused 127.0.0.1:5174, and a
React project's leftover cycling session was resumed inside the Svelte
project. Its checkpoints then materialized a ghost session in the new
project's durable store that kept reattaching after every discard, and a
stale adapter module 401'd on live.js, hiding the picker.

Four fixes:

- Server: only session-creating events (generate, steer) may mint a
  journal. Progress events (checkpoints, mount acks, accept/discard) for
  unknown ids are refused with 404 unknown_session and never enqueued, so
  foreign browser state cannot create ghost sessions. Browser sends are
  gated so progress never overtakes its own creating POST (the Go-time
  checkpoint and generate are concurrent fetches; the first sweep caught
  the out-of-order arrival breaking every SvelteKit flow). Steer
  checkpoints now follow the steer event for the same reason.
- Browser: saved sessions carry the server's appRoot; a session stamped
  by another project is dropped at load time. Unstamped legacy state is
  caught by the unknown_session refusal, which clears local state and
  re-arms the picker with an explanatory toast.
- SvelteKit adapter: the layout import carries a token-derived revision
  query so a helper restart changes the module specifier and no Vite
  client/SSR cache can serve an adapter with a rotated-out token;
  live-inject --port reads the running helper's token from server.json
  instead of writing an unauthenticated live.js URL; script load failures
  log an actionable console error; and adapter removal is byte-exact
  (the old regex swallowed the next line's indentation).
- live.mjs resolves surface briefs from appRoot, then contextRoot, then
  repoRoot, matching context.mjs in nested-app repos.

Tests: server unknown-session rejection units, adapter revision/
byte-exact-removal units, and a foreign-session e2e scenario that seeds
another project's localStorage state and asserts it is cleared, no ghost
journal materializes, and picking still works.

AI-assisted (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 17:38:46 -07:00
Paul BakausandClaude Fable 5 6c4620bf53 A sandboxed wait cannot signal the server, and EPERM was read as death
A codex session declared the decision board dead while the user was
still reading it, then proceeded without their choice. The wait's
liveness probe was process.kill(pid, 0) with every error treated as
gone, but a sandboxed exec cannot signal a process outside its sandbox:
EPERM arrives for a living server. Liveness now leads with the page's
own heartbeat in the state file, falls back to the kill probe, and
reads EPERM specifically as exists-but-unsignalable. The exit-2 message
also stopped inviting the wrong recovery: it now states this is a
server failure, not a user decision, and orders a restart and reopen,
never an unattended proceed while the user's browser session is open.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 17:07:51 -07:00
Paul BakausandClaude Fable 5 a68b74e787 Weight staging draws by rating, with catalog validation for composition grades
Stagings now honour approval ratings exactly as world challengers do, a
3-star earning a second ticket and a 1-star marginal keep leaving the
pool, which matters more here because per-surface staging pools are
small enough that an unweighted shuffle repeats a weak staging often.
Each ticket carries its index into the deterministic ranking so the
id-dedupe cannot silently collapse the doubled odds into a no-op, and
an all-marginal pool still deals rather than starving. The composition
catalog validates the new grades: 1-3, approved entries only. Tests
cover the weighting, the dedupe subtlety, and the fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 16:26:38 -07:00
Paul BakausandClaude Code 69456364b2 fix: self-discard orphaned variant sessions instead of freezing the picker
Fixes #439. When a cycling session is abandoned and the wrapped region is
then edited or regenerated out of the source file, the resumed page used
to sit in GENERATING forever with the picker disarmed; the only recovery
was a manual live-complete --discarded. Now a resumed CYCLING session
whose wrapper cannot be found in source retries the read a few times
(HMR or an agent write may be mid-flight), then discards itself, clears
local state, and re-arms the picker with a toast. GENERATING restores
are exempt: deferred-wrapper flows legitimately have no wrapper in
source until the agent's write lands.

The browser tags the discard event orphaned:true; the server terminalizes
that session directly (phase discarded) and keeps the event out of the
agent poll queue, since there is no source cleanup left to perform and
the normal discard flow would just fail against the missing scaffolding.

New e2e scenario on vite8-react-plain drives the full repro: cycle,
revert source externally, reload, assert self-discard, terminal durable
phase, and a working picker afterward.

AI-assisted (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 15:57:33 -07:00
Paul BakausandClaude Code b9c1d86d68 fix: reject a valueless --target instead of falling back to implicit selection
A trailing --target, an empty --target=, or --target followed by another
flag used to degrade into implicit root selection, letting a mutating
helper (poll, accept, complete) act on the most recent live app instead
of the one the caller tried to name. consumeTargetArg now throws on those
shapes and enterLiveRoot exits with a clear error before any session
state can be touched. Unit tests cover the malformed shapes and a
subprocess test proves the helper body never runs.

AI-assisted (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 15:57:33 -07:00
Paul BakausandClaude Code 39f233ac24 fix: hydrate attribute-bound each values and guard style directives
Addresses two cursor review findings:

- {#each} bodies whose bound values appear in attributes (href={link.href},
  src={item.img}) now record attr slots; the browser hydrates them from the
  rendered attribute so component previews no longer mount with empty links.
  Single-expression attributes hydrate exactly; mixed values stay unhydrated
  as before. A new slot classifier also refuses shapes that would crash a
  shallow hydration item (deep paths, method calls, bare item renders) and
  routes them to source-preview mode instead.
- Style directives now run the mixed loop/outer identifier check before the
  free-identifier param check, so style:width={base + r.pct} falls back
  instead of minting a broken param.

Tests: attr-slot analysis units, crashy/lossy fallback units, an attribute-
bound anchor in the stateful SvelteKit fixture asserted through accept, and
a mountedDomProbe e2e hook that reads the hydrated href off the mounted
variant DOM (verified to fail when hydration is disabled).

AI-assisted (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 15:33:16 -07:00
Paul BakausandClaude Code 6997e4bdb5 fix: no unauthenticated path in live-server liveness
greptile-apps[bot]: the legacy fallback (server.json without port or
token) accepted a pid-only record on Windows without identity. Every
server.json this codebase has ever written records port and token, so a
record without them is malformed or foreign; it now classifies as not
live and resolution falls to the durable-session tier, the correct
recovery path for a crashed helper. The ps-based identity heuristic is
gone with it: authentication or nothing.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 15:03:10 -07:00
Paul BakausandClaude Code 16a84bc390 fix: authenticate the live-server liveness probe
greptile-apps[bot] escalated the identity ladder to a pid AND port both
coincidentally reused by different processes. The definitive terminator
was available all along: the helper serves an authenticated endpoint and
server.json records the token, so the probe now requires a 200 from
/status?token=... over HTTP. Nothing but our helper can answer that,
which closes the entire misidentification class rather than the next
rung. The regression test hosts its responder in a child process (the
probe is execFileSync, so a same-process responder can never accept
while the parent's event loop is blocked; production helpers are always
separate processes).

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 14:50:28 -07:00
Paul BakausandClaude Code 9a3f5aa34b fix: portable port probe for live-server liveness
greptile-apps[bot]: the win32 branch skipped the port probe entirely
(bash /dev/tcp is not portable), so a reused pid on Windows still
classified as a running helper. The probe is now a spawned node
one-liner that behaves identically on every platform, which also drops
the bash dependency for minimal Linux environments; the ps identity
check remains only for legacy server.json records without a port.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 14:33:11 -07:00
Paul BakausandClaude Code 24d69675e0 fix: mixed loop/outer expressions fall back; globals are neither free nor bound
cursor[bot]: an expression mixing loop bindings with outer free names
(fmt(r.label) where fmt lives in the route script) was left verbatim, so
the detached preview referenced an undeclared identifier and failed at
mount, past the compile gate, because globals make it legal to the
compiler. Such expressions now mark the analysis unsupported and the
session takes source-preview mode. A globals allowlist makes Math/JSON
and friends count as neither free nor bound, which also fixes a latent
bug where a pure-global expression minted a nonsense prop.

Won't-fix on the same pass: the live-setup.md filename cross-reference
matches the repo's established reference-link convention.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 14:22:57 -07:00
Paul BakausandClaude Code dc5420b64f fix: compile-check svelte variants at publish time
Field failure (Codex session, 2026-07-28): the agent kept the seeded
stub style block and appended its own second top-level style element in
all three variants. Svelte forbids that, so the user saw a red Vite
compile overlay; the mount-ack loop then self-healed (failure event,
repair, republish, clean accept), but the overlay window is exactly the
kind of thing the user should never see.

The publish gate closes the class: a done reply for a component session
now compile-checks every variant with the app's own compiler BEFORE the
revision bump and the browser broadcast. Failures bounce as a 422 with
file, line, and message plus _instructions; live-poll surfaces the
details in the thrown reply error. The browser never imports a variant
that cannot compile.

Also: the stub guard comments warn that all CSS belongs in the single
existing style block, worded to never contain the literal "<style"
sequence (a mention inside a CSS comment truncates the string surgery
agents use to find the block; the fake test agent caught exactly that).
The JIT svelte instructions carry the same warning.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 14:13:47 -07:00
Paul BakausandClaude Fable 5 47b875a7e3 One prompt carrier across every harness: embed-prompt.mjs
The prompt behind a generated image was recorded three different ways,
a sidecar in the eval harness, nothing in the skill's API tool, nothing
for native tools, so intent survived or vanished depending on where you
ran. One dependency-free script now embeds the prompt inside the image
itself, PNG tEXt or JPEG COM with a sidecar fallback for other formats,
idempotent, and reads it back from any impeccable-generated file. The
API tool embeds automatically; the prose directs every native-tool
generation through it; copies between machines and harnesses keep their
intent. Comps meanwhile are declared the build thread's own work, never
delegated, and the comp-skeleton guidance now asks for the surface's
actual regions instead of prescribing navs onto pages that have none.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 13:39:48 -07:00
Paul BakausandClaude Fable 5 39532a65a2 Comps are pages not vignettes, and the prompt travels with the asset
Two findings from the first human-validated probe. The comps rendered
as scene vignettes because the generation prompts led with the world's
atmosphere; the model painted the fish market instead of the fish
market's website. The comp guidance now demands the page's literal
skeleton in the prompt, nav and its items, headline block, sections in
order, footer, with a self-check: a render that could hang as a poster
is not a comp. And generation context is part of the asset: the thread
that wrote a prompt knows what the image contains and why, so build-
critical imagery prefers the build thread, and subagent-produced assets
must carry their prompts, via the tool's new sidecar or the manifest,
read by the builder before composing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 13:27:03 -07:00
Paul BakausandClaude Code da68678e7e fix: app discovery uses the same criterion as the upward walk
cursor[bot]: discoverAppCandidates only matched dev-config markers while
the upward walk also honors an existing .impeccable/live/config.json,
so booting from a repo root without --target missed a nested
live-configured static site and fell through to the wrong root. Both
paths now share isAppRoot; regression test covers the static-site shape.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 19:38:56 -07:00
Paul BakausandClaude Code 7fa25da98e fix: probe the recorded port for live-server liveness
greptile-apps[bot] re-raised the residual with a repro: a stale
server.json pid reused by an unrelated node process passed the
command-name check. The decisive signal is the recorded PORT: a real
helper is listening on it, a pid squatter is not. hasLiveServer now
probes 127.0.0.1:<port> (bash /dev/tcp, sync, ~ms, win32-guarded with
the previous behavior); the multi-app preference test runs a real
listener instead of faking liveness with a bare pid.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 19:29:33 -07:00
Paul BakausandClaude Code 26f54d15c2 feat: just-in-time event instructions + frontier default for the LLM e2e agent
Field feedback from two more Codex sessions drove both changes.

JIT instructions (live/instructions.mjs): every event live-poll prints
now carries _instructions, the authoritative next step for that exact
situation with real ids, paths, and line numbers substituted, and only
the active path's rules (a svelte-component session never sees JSX
guidance). The boot payload carries loop instructions the same way.
Instructions are versioned with the scripts, so they cannot drift from
behavior, and live.md's plumbing can keep shrinking toward contract plus
craft guidance. The Codex poll-discipline failure observed in the field
("the long poll was started, but I yielded the task instead of actively
servicing its result") gets a named anti-pattern in both the harness
policy and the boot instructions.

LLM e2e agent: default provider/model moves from Claude Haiku 4.5 to
OpenAI gpt-5.6-terra at medium reasoning effort via an Anthropic-shaped
shim over the ai SDK (the three call sites stay provider-agnostic;
Anthropic and DeepSeek remain selectable). The harness should exercise
the model tier that actually drives live sessions. Both the react and
sveltekit fixtures pass end to end with terra driving the trimmed
live.md and the new _instructions.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 19:20:02 -07:00
Paul BakausandClaude Fable 5 ce4dcf9a93 Split breadth from rating in the challenger and staging pools
Rating grades quality, breadth says whether a world can serve an
arbitrary build at all; while they shared one field, the only way to
hold a narrow world back was calling it marginal, which made excellent
but narrow unrecordable and corrupted the ratings as a calibration
signal for the next authoring round. Both axes now exclude
independently, either kind of hold keeps its approval for direct
briefs, an all-niche tier falls back rather than starving, and
stagings honour the same gate with the same fallback. Tests cover the
niche exclusion at strength, the fallback parity with marginal-only
tiers, and the staging gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 19:17:09 -07:00
Paul BakausandClaude Code 5b6b331785 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>
2026-07-27 18:45:07 -07:00
Paul BakausandClaude Fable 5 4329f757f5 Only the visible card face is interactive
A hidden backface still hit-tests in Chrome, so after flipping a card
the front's picture-in-picture sat invisibly over the back's chips,
showing its zoom cursor and eating the flip-back click. Pointer events
now follow visibility: the back is inert until the card flips, and the
front goes inert while it is flipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 18:13:04 -07:00
Paul BakausandClaude Fable 5 ca88ea008b Patience while sketches land, honesty when standing in
Field data: the first image of a real batch took ninety seconds and the
page's 150-second fallback then silently promoted catalog art to full
bleed, unlabeled, which is exactly the this-is-your-design misread the
picture-in-picture treatment exists to prevent. The policy is now
patience while there is progress: a slot shows its inspiration only
after waiting four minutes with nothing landing anywhere on the page
for four minutes, the stand-in is dimmed and labeled 'inspiration ·
sketch pending', and polling continues so the real sketch still swaps
in whenever it arrives. Slots with no inspiration keep the honest
elapsed shimmer instead of folding. The parent's reclaim rule matches:
files landing steadily is health at any pace, and only total silence,
no first file in three minutes, takes the batch back inline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 18:10:56 -07:00
Paul BakausandClaude Fable 5 17bc2701f3 Put the full read on the card's back; the front is for choosing
Field feedback: with every fact stacked under the media the cards ran
past a screen tall. The front now carries only what the choice needs,
sketch, lineage, title, thesis, identity, and the honest risk clamped
to two lines, while first viewport and the case read on the back behind
a Details chip, sharing the face with the board when the world has one.
Risk stays on the front because the counterweights are pointless if the
downside hides behind a flip, and once the sketch lands the first
viewport is a picture anyway. The schema notes now ask for one-sentence
facts, since a long fact should cost the reader a flip, not the page
its scanability.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 18:05:41 -07:00
Paul BakausandClaude Fable 5 0eb443d29b Bound the hand, greek the copy, and treat waiting as supervision
A codex field run dealt six challengers into an eight-sketch batch
behind an opaque subagent, and the user stared at a page of shimmer
asking whether anything was happening at all. Four fixes from that run.
A hand now holds at most three challengers, the rest banked for
re-rolls, so fairness within the hand stops multiplying into a queue.
Sketches greek everything but the product's real name and one real
headline, because an invented spec, price, or ship date in a sketch is
a claim PRODUCT.md never made, and comps have solved this for a century.
Sketch production follows the user's reading order with the first file
doubling as the producer's heartbeat, and the parent's --wait loop
checks the sketch directory each pass, reclaiming the batch inline when
two minutes pass with nothing landed. And a failed --start now captures
the daemon's stderr to a per-key log and names the sandbox as the usual
suspect, instead of reporting only that failure occurred. The shimmer
counts its elapsed seconds, and gives up at 150 instead of 300.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:49:21 -07:00
Paul BakausandClaude Fable 5 58a2d3dccd Bleed the deck to the viewport, fade the fuller side, let the glance take over
Three field notes from a live review. The deck now escapes the content
column and runs edge to edge, so a cut-off card sits at the screen edge
where it reads as more cards instead of at an invisible container edge
where it reads as a bug; the first card still aligns with the column
via scroll padding. Whichever side hides more content wears a fade, and
a hard edge means the end. The vertical pager grows from a bare chevron
into labeled Back and More pills, because in a column deck it is the
primary way forward. And hovering the inspiration thumb now takes over
the whole media region instead of a timid zoom; the sketch is the
promise, the inspiration is a glance, and the glance must cost nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:14:59 -07:00
Paul BakausandClaude Fable 5 e6612ea8ef Page the deck on its long axis, and never let decoration hide the cards
Field-checked in a real browser, which surfaced three defects the DOM
tests could not: the generic .media img display rule defeated [hidden]
and floated an empty block over the shimmer and its sketching note; the
deal animation left every card at opacity zero in an unfocused tab,
because rAF throttling is real and decoration must never gate content;
and the sketch poll's cache-busting query missed the anchored /img
route, so a landed sketch kept shimmering forever.

The grid is now a snap-scrolling deck: one row in a wide viewport, one
column in a tall one, with edge arrows that appear only on overflow and
page one card at a time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:10:39 -07:00
Paul BakausandClaude Code 031e170d3e fix: harden live-server liveness against pid reuse
greptile-apps[bot] repro: a helper that died without removing
server.json leaves a pid the OS can hand to an unrelated process, which
kill(pid, 0) classifies as a running server and routes repo-root helpers
onto the stale app. The liveness check now also requires the pid's
command line to look like a node process (ps-based, platform-guarded),
removing reuse by arbitrary processes; the residual node-reuse case is
covered by the multi-app warning and the --target escape hatch.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 17:07:25 -07:00
Paul BakausandClaude Fable 5 d89ee5f87c Deal every card the same hand: anatomy, sketches, and the standing door
The decision page compared unlike things: the grounded direction was a
wall of text beside curated catalog art, the catalog art read as a
promise of the build, the weighing silently shrank the challenger set,
and the standing exit hid in the footer under the cards it must not
soften. Every card now shares one anatomy (thesis, palette chips,
material tags, first viewport, case, risk), every dealt challenger is
presented with the weighing written on it rather than applied to it,
the catalog image rides picture-in-picture as labeled inspiration with
the lightbox a click away, and canonCard renders the category standard
as one honest, subordinate card.

When image generation exists, each card declares a sketch slot the page
polls: serve first, generate after, through one shared deliberately
unfinished frame, so the comparison stays about direction instead of
rendering luck. The asset producer takes the batch when subagents
exist; the chosen sketch returns in ANSWER to seed at most one comp
probe, and the comp round still renders its full set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:03:05 -07:00
Paul BakausandClaude Code baed04a52b fix: helpers honor --target for multi-app disambiguation
greptile-apps[bot] repro: the multi-app warning recommended --target,
but the helper CLIs never parsed it, so live-poll --target appB still
re-anchored onto the pointer's first choice. enterLiveRoot now consumes
a --target argument (removing it from argv so downstream flag parsers
never see it) and resolves roots against it, making the documented
escape hatch real on every helper. Regression test drives a two-live-app
repo through a child process and asserts both the chdir target and the
argv scrubbing.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 16:52:01 -07:00
Paul BakausandClaude Code f1d450e6ab fix: sixth review round (verify precision, base-path @fs fallback)
cursor[bot]:
- verifyAcceptedSource anchors its param patterns to the exact shapes
  live mode writes (data-p-x= / [data-p-x] attributes, var(--p-x, ...)
  references) instead of bare prefixes, shrinking the false-positive
  class near the completion gate. Note: the reported examples (data-page,
  var(--primary)) did not actually match the previous hyphenated
  substrings; the tightening removes the residual class (e.g. a user's
  own data-p-* attribute) regardless.
- With a non-root Vite base, the /@fs/ fallback is tried both under the
  base and at the server root, covering Vite versions that serve @fs at
  either location.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 16:40:08 -07:00
Paul BakausandClaude Code e5f6d27a9c fix: fifth review round (durable mount failures, {#key} hydration slots)
cursor[bot]:
- variant_mount_failed now sets the session's pendingEvent (without
  clobbering a still-pending generate), so a helper restart replays it
  onto /poll and a repair --reply resolves instead of returning
  unknown_poll_reply_id. live-resume's next action names the real event
  id instead of a literal EVENT_ID placeholder.
- Contract v2 text hydration strips {#key} DELIMITERS from the zip
  source (content stays; it always renders), so key blocks can no longer
  shift expression slots against the live DOM.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 16:33:34 -07:00
Paul BakausandClaude Code 39df25ee5a fix: fourth review round (mount-failure truth, toggle baking, root ambiguity)
cursor[bot]:
- enqueueEvent dedupes variant_mount_failed per variant, so a second
  broken variant is no longer swallowed while the first is queued.
- Every component (re)injection resets the mount-failure dedupe, so a
  republish that is still broken at the same URL reports again instead
  of silently convincing the agent the repair landed.
- Toggle baking now mirrors preview truth exactly: the runtime sets
  data-p-<id>="on" or removes the attribute, so presence and "on" forms
  survive only while on, and any other valued branch (never matched at
  preview) is dropped in either state.

greptile-apps[bot] (both P1 repros):
- When several apps qualify at the same resolution tier (two live
  servers, or two stopped apps with interrupted sessions), the choice
  stays deterministic but is now loud: a stderr warning names the chosen
  app, the alternatives, and how to target a specific app. Silent
  wrong-app routing was the failure in both repro harnesses.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 16:22:09 -07:00
Paul BakausandClaude Code 40b2a80653 fix: restrict server-session adoption to comparison phases
The CI-only astro accept hang: the carbonize source edit triggers a
framework reload, and on a slow runner the reloaded page rehydrated the
still-non-terminal carbonize_required session back into GENERATING,
stranding the bar over a decided comparison. Adoption now uses a
positive allowlist of comparison phases (generate_requested,
variants_ready, generating, cycling); accept/carbonize/steer/manual
phases are agent-side work and never adoptable. Regression guard pins
the allowlist.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 16:11:52 -07:00
Paul BakausandClaude Code f27bea5bc0 fix: third review round + unmask and fix the astro-vite7 e2e failure
cursor[bot]:
- variant_mount_failed joins EVENT_TYPES_NEEDING_AGENT_REPLY so stream
  mode waits for the repair reply instead of moving on mid-lease.
- The fake agent's mount-failure repair no longer forces
  sourceEventType generate; the server maps the done reply onto the
  pending failure event, which acknowledges it instead of leaving it to
  be redelivered on every poll.

greptile-apps[bot]:
- With every helper server stopped, repo-root resolution now prefers the
  app whose durable store holds a non-terminal session (the interrupted
  session the user is recovering) over the most recent boot.

astro-vite7 (pre-existing CI failure, root-caused): Astro 7 auto-detects
AI-agent environments and daemonizes `astro dev`; the detached server
holds a lock, outlives the harness, squats dev ports across runs, and
makes the parent exit 0, which the harness read as a crash. The fixture
now sets ASTRO_DEV_BACKGROUND=1 (disables the agent detection) plus
--ignore-lock, and the harness supports per-fixture runtime.env. The
core cycle now passes for the first time; the missed-done recovery
scenario fails identically at origin/main with the daemon bypassed, so
it is marked as a per-scenario known limitation with that rationale.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 15:55:03 -07:00
Paul BakausandClaude Code a6f965e8bf fix: address second round of PR review bot findings
cursor[bot]:
- style: directives with dynamic values now fall back to source-preview
  instead of being scaffolded as boolean condition props that falsified
  the style in the detached preview.
- class: directives carry a className probe, so v2 hydration answers the
  condition from the live DOM instead of always defaulting to false.
- The existing-wrapper remount path now checks the mount result; a failed
  remount keeps the error card instead of advancing to a CYCLING bar over
  a page where nothing rendered.

greptile-apps[bot]:
- The repo-root live pointer records every booted app (most recent
  first) and resolution prefers the app whose helper server is alive, so
  a helper run from the repo root of a two-app monorepo can no longer be
  redirected onto the wrong app's session store by the last boot. Legacy
  single-value pointers still read.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 15:34:57 -07:00
Paul BakausandClaude Code 4ac54bebee fix: address PR review bot findings
cursor[bot] findings on #433:
- Nightly schedule no longer enables the paid opt-in suites: a schedule
  event has no diff base, so the change-detection fallback flagged every
  file-triggered suite, which would have billed the skill-behavior,
  accept-cleanup, and deepseek LLM suites nightly. The plan now pins the
  schedule event to deterministic suites plus the full live-e2e matrix,
  with a regression test.
- Dismissing the mount-error card no longer strands the session: while
  the bar is hidden in GENERATING the card is the only recovery surface,
  so dismiss now returns the state machine to PICKING (session and
  server truth survive for a later republish).

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 15:19:58 -07:00
Paul BakausandClaude Code 17dabf4b7e Live v2: root manifest, mount-ack protocol, AST scaffolder, mechanical accept
A ground-up hardening of live mode, driven by a production session in a
nested-app monorepo that hit six distinct failure classes. Full design
rationale in docs/LIVE-REWRITE-PLAN.md; every Codex-reported failure now
has a mechanical fix and a regression test.

Roots: live/roots.mjs resolves appRoot/repoRoot/contextRoot once at boot
(keyed on dev-server configs, not monorepo brand markers), persists a
manifest, and every live CLI re-anchors onto it at startup, so a helper
run from the wrong directory can no longer fork session state. Context
files are discovered upward to the git root.

Render truth: variant_mounted / variant_mount_failed events give the
journal per-variant mount state; failures reach the agent's poll queue,
raise a persistent error card with Retry (no more localStorage wipe), and
an attach probe names root/dev-server mismatches explicitly. The browser
rehydrates from the server when localStorage is gone.

Svelte: the scaffolder now parses with the app's own svelte 5 compiler.
Control flow survives (an each collection crosses the contract as one
structured prop), keyed each blocks hydrate synthetic keys, and anything
a detached preview cannot support falls back to source-preview instead of
shipping a wrong scaffold. Preview modules live in per-publish revision
directories, defeating stale transform caches.

Accept: CSS is reconciled, not appended. Matching selectors are replaced,
params bake from params.json kinds, the compiler's unused-selector pass
prunes superseded rules (pre-existing dead rules protected), a selector-
loss postcondition refuses any write that would drop hand-written rules,
and live-complete refuses to finish while live plumbing remains in source.

Also: framework registry (live/frameworks/) with a crash-safe injection
journal, session-store snapshot caching with read-only reads, protocol
enum consolidation, steer Send button, honest DESIGN-panel empty states.

Testing: new unit suites (roots, AST scaffolder, accept CSS, accept
pipeline, framework conformance); e2e now fails on preview-tree 404s,
proves computed-style mount for every variant, drives the Tune panel
through baked params, and injects failures (broken mounts, republish,
storage loss). New runtime fixtures: monorepo-nested-vite (repo root !=
app root) and vite8-sveltekit-stateful (each blocks + state). Nightly
full-matrix cron. An independent adversarial review pass preceded this
commit; its blocker and major findings are fixed and regression-tested.

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 15:09:40 -07:00
Paul BakausandGitHub 9b613ef931 Merge pull request #419 from pbakaus/diff-base-detection
Detect the diff base in context-signals instead of assuming main/master
2026-07-27 10:06:48 -07:00
Paul BakausandClaude Code 01d5d357c5 The develop candidate leads with an advertised develop default rev
Round eight closes the stale-local class completely: the develop
candidate sits before the remote-default entries, so when origin/HEAD
itself points at develop, its name claim let a stale local develop win
over the fresher origin/develop. The candidate now leads with any
remote-advertised develop rev, exactly as the remote-default and
upstream candidates already lead with theirs. main/master were already
covered since their remote-default entries come first in the order.
Failing-first test forces local develop two commits behind.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-26 19:04:48 -07:00
Paul BakausandClaude Code e2c1c43ee7 Remote defaults lead with their own rev, like upstreams already do
Round seven: a remote-advertised default candidate tried the local
branch first, so a stale local main outranked the fresher origin/main
the symref points at and refilled changedFiles with the divergence.
The candidate now leads with the advertised remote rev, mirroring the
upstream candidate's reasoning. Failing-first test: local main forced
two commits behind the remote default, feature delta stays clean.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-26 18:56:33 -07:00
Paul BakausandClaude Code a470fc777a Read the upstream as a full symbolic ref instead of guessing at prefixes
Round six, and the upstream-parsing ambiguity dies at the root: @{u} is
now resolved via rev-parse --symbolic-full-name, where refs/heads/...
IS a local upstream and refs/remotes/<r>/... IS remote-tracking. The
previous remote-membership heuristic still misread a local feature/foo
upstream when a remote literally named "feature" existed. The
adversarial test now configures exactly that remote and passes.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-26 18:48:14 -07:00
Paul BakausandClaude Code f89b6c10b1 Only strip a remote prefix that names a configured remote
Round-five bot findings, one real root cause: splitRemoteRef treated the
first slash in any ref as a remote separator. A local upstream named
release/2.0 was truncated to "2.0", and feature/foo tracking from branch
foo collapsed to the current branch's own name and was self-skipped,
discarding a valid base both times.

The split now happens only when the prefix names a configured remote;
otherwise the whole ref is one local branch name. The per-remote HEAD
symref loop strips its own queried prefix directly (that remote may be
fabricated in tests or partial clones without appearing in git remote).
The reported pruned-upstream shape already resolves via the multi-remote
rev lists from the previous round; its test now guards that.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-26 18:39:20 -07:00
Paul BakausandClaude Code 386d3e7051 Cover every remote in each candidate's rev list
Cursor and Greptile converged on one root cause from the previous round:
candidate revs stopped at origin (develop tried only develop and
origin/develop; a remote-default entry carried only its own rev), so the
name-level dedup discarded a same-name base living on another remote. A
fork-parent layout with develop only as upstream/develop, or a pruned
origin/main beside a live upstream/main, lost its base entirely.

revsFor(name) now expands to the local branch plus <remote>/<name> for
every remote (origin first), and all named candidates use it, which is
exactly what makes the dedup safe. Two failing-first tests cover the
upstream-only develop and the pruned-origin/live-upstream main shapes.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-26 18:23:56 -07:00
Paul BakausandClaude Code 46f29ca8b3 Guard detached HEADs and non-origin remote defaults
Two more real gaps from the post-rebase review round: a detached
checkout reads its branch as the literal HEAD, so the integration guard
never fired and candidate selection could diff a detached tip on main
against develop; and the remote-default check only consulted origin, so
a fork-parent layout whose only remote is upstream lost the guard on
its default branch entirely.

The guard now treats a detached HEAD as no-diff-base, and default-branch
symrefs are collected from every remote (origin first), feeding both the
guard and the candidate list. Two failing-first tests cover a detached
tip beside a diverged develop and an upstream-only trunk default.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-26 18:14:34 -07:00