The containment gate re-canonicalized projectCwd for every target file
in the per-edit and Stop loops. The hook runs as a fresh process per
tool event, so a module-level memo makes it once-per-event work; the
size cap only matters to long-lived importers like the test runner.
Addresses Copilot review feedback on PR #471.
Written with AI assistance (Claude Code).
Co-Authored-By: Claude Code <noreply@anthropic.com>
hook-before-edit.mjs kept its own string-based isInsideProject; it now
uses the shared isScanTargetInsideProject so all three hook passes
apply one containment semantic, symlink canonicalization included.
Because the before-edit hook gates proposed Writes whose target does
not exist yet, canonicalPath now resolves the nearest existing
ancestor and re-appends the remainder instead of falling back to the
raw resolved path — a new file under a symlinked root compares equal
to its canonical project.
Written with AI assistance (Claude Code).
Co-Authored-By: Claude Code <noreply@anthropic.com>
The per-edit and Stop deep passes gated on sensitive paths, generated
paths, extension, config ignores, and size, but never on containment.
Any file the session touched outside the project (harness scratchpad
dirs under the system temp root, sibling checkouts) was scanned and
judged against THIS project's config and DESIGN.md palette, producing
design-system findings that are wrong by construction.
Both loops now check isScanTargetInsideProject() (audit reason:
outside-project), matching the gate hook-before-edit.mjs already had.
Paths are canonicalized so a symlinked root doesn't split the
comparison. The Stop pass re-checks containment itself because caches
written by older hook versions can still list out-of-project paths.
Umbrella-dir launches (issue #305) are unaffected: their projectCwd
resolves to the edited file's own project root, so containment holds.
Written with AI assistance (Claude Code).
Co-Authored-By: Claude Code <noreply@anthropic.com>
context.mjs now probes cwebp/sips/magick/ffmpeg once (which/where per
OS) and prints IMAGE_TOOLS, replacing macOS-specific prose; the
IMAGE_GEN_AVAILABLE directive leads with the harness-native tool so a
present OpenAI key stops reading as an instruction to bill it; and the
sandboxed board-start guidance sheds codex vocabulary.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Recognize both slash- and dollar-prefixed prescribed seed markers and exercise each variant in coverage tests.
AI assistance: Codex addressed Cursor and Copilot review feedback and reran validation under maintainer authorization.
Treat Components as optional only when DESIGN.md carries the prescribed seed marker, while retaining Colors and Typography checks.
AI assistance: Codex reproduced the issue, implemented the fix, and added regression coverage under maintainer authorization.
Join indented Markdown bullet continuations and keep them out of Overview philosophy text.
AI assistance: Codex reproduced the issue, implemented the fix, and added regression coverage under maintainer authorization.
Three of the six axes cannot be read from a world's prose, and widening their
keywords manufactures signal rather than finding it. Depth's probe matched
worlds that said "no cast shadow anywhere" and "without perspective or depth";
motion and colour strategy describe properties the system rules never state, so
they place 28% and 7%.
An optional axes object on the concept records the value instead. Absent means
inferred from the rules as before, so nothing needs backfilling. Validated
against the axes definition when the caller supplies it, because a typo would
read as "unrecorded" and fall back to a probe already known not to work, which
is the quietest way for this to fail.
This is what makes an assigned wave measurable. If a wave draws "drenched" and
"simulated physics" before designing anything, the world it produces has to
carry those values or the assignment is lost the moment it lands, and occupancy
goes back to guessing at prose.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codex's risk reviewer rejected the network-escalated roll rerun for
'contacting an unspecified external domain' and the assumed export of
project context, so the run degraded to no challengers. Both concerns
are answerable: the script's only network contact is one GET to
impeccable.style/api/roll carrying scope, mode, an eight-hex key, and a
re-roll counter, nothing project-derived. The degraded message now
states that verbatim and tells the model to put the URL and payload in
its approval request, so the reviewer judges the real action instead of
an unknown.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The composition pool (stagings) is not ready: too thin to help, and its
draws crowd the decision it rides along with. concept-seed.mjs stops
rendering the staging block by default; IMPECCABLE_COMPOSITIONS=1
re-enables it for catalog development, and the draw machinery,
rating-weighted selection, and mode scoping stay intact and tested for
its return. new-work.md drops the dress-the-staging-challengers
instruction and the FORM contract's staging clauses; the surface-scope
roll still assigns which of the model's own structures gets built.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A landscape frame was the silent default at every generation site,
which is a composition error before the build starts for native apps
and mobile-first surfaces. The sketch frame, the asset producer's
single-sketch contract, and the comp instruction now state it: portrait
at device viewport when the surface is a phone screen, landscape for
desktop web. The decision page adapts in kind: portrait art overrides
the 16/10 slot with its own exact ratio so nothing crops, and the deck
narrows so portrait cards line up side by side. The --schema guidance
tells the model the page handles either.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cards widen from 27vw to 34vw and the media slot matches the 16:10
sketch frame instead of cropping it to 16:9: at the old width the
imagery read as a thumbnail above a column of copy, and the copy won
the attention contest the sketch exists to win. The whole image is now
a zoom target with a zoom-in cursor, not just the expand chip; chip and
PIP handlers already stop propagation, so the art click is unambiguous.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The area taxonomy was wrong, and wrong in a way worth recording. It was
derived from Mobbin-style categories in the abstract rather than from what
the skill can be asked for, and measured against the catalog most of it
described problems that were not there: onboarding, settings, empty-state and
search each had zero entries.
Reframed against demand instead. A user asks for a docs site, an onboarding
flow, a landing page, or a data table, and those differ in how much of the
product is in play. Register already says what kind of work it is; grain says
how much: product, flow, view, region.
Named grain rather than scope because scope already means direction-or-surface
on every roll and 'surface' is already a register value, so a scope of
'surface' would have collided with both.
Platform is the second axis: web, ios, android. Unlike grain it is a hard
filter with no fallback, because a composition that leans on hover or a
pointer does not degrade on a phone into something slightly worse, it stops
working, and an empty deal is a visible gap where a broken one is not.
Both fields are optional and absence means eligible everywhere, so nothing
needs backfilling and no existing roll changes.
The third piece is the one a trace turned up. Asking for an onboarding flow
resolves to register=operate, grain=flow, and the catalog holds zero
flow-grain compositions, so the top-up would have dealt three plausible
single-screen compositions with no signal that none matched. The model would
have improvised the flow structure while believing it was handed one, which is
the same silent plausibility the axis exists to remove. Selection now returns
a match alongside the picks, and the rendered seed says when the structure is
borrowed and why.
Measured at the time of writing: 137 of 173 approved compositions are view
grain, product grain is empty, flow grain holds one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The roll API validates its `area` parameter against the surface's list, which
meant importing the taxonomy into a Pages Function. composition-catalog.mjs
reads the filesystem, so importing from there would have pulled node:fs into
the Worker bundle, the same trap WELL_TIERS hit. roll-selection.mjs has no
imports at all and is what both callers already load, so it owns the taxonomy
and composition-catalog re-exports it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>