Commit Graph
538 Commits
Author SHA1 Message Date
f240348cc5 Live: every active-session wrapper lookup goes through the resolver
Cursor Bugbot on #720: findVariantsWrapper alone was not enough.
resolveBarAnchor, the visible-variant element, mountedParameterCount,
readVisibleVariantFromDOM, showVariantInDOM, the source injection, and
the whole accept path still took the first [data-impeccable-variants]
match, so in the relocated-wrapper case Tune never bound and the bar kept
anchoring to the empty scaffold even after the resume reached CYCLING.

Thirteen call sites now resolve through findVariantsWrapper. The resolver
split in two so a missing id cannot silently widen the lookup to any
session: findVariantsWrapper(sessionId) returns null without an id, and
findAnyVariantsWrapper() is the entry point for the two resume paths that
have no id yet. Both share pickPopulatedVariantsWrapper, which is the old
querySelector whenever there are fewer than two matches.

Discard cleanup now hides every duplicate wrapper rather than the first,
since a target inside a `.map()` renders one per item and hiding one left
the rest of the discarded variants on screen.

What still takes a raw first match is deliberate: bare existence checks,
selector strings for stylesheets and observers (which want to cover every
match), querySelectorAll sweeps, the parsed source document, and the
Svelte component wrapper, which holds no variant children at all. The
source-shape test pins that exact set by name, so a new raw lookup fails
until it is either routed through the resolver or justified there.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
2026-09-04 14:31:25 +05:00
f7c92d9eb9 Live: the shader teardown can no longer race its own construction
The new cycling assertion caught a real defect on CI: vite8-react-insert
reached CYCLING with #impeccable-live-shader still painted over the page.

showShaderOverlay is async. It appends its canvas synchronously, then
awaits createImageBitmap and finishes the GL setup before it publishes
shaderState. hideShaderOverlay returned early on a null shaderState, so a
teardown that landed inside that window did nothing, and the construction
then published itself over a session that had already left GENERATING,
with no teardown left to run. The scroll tick kept repositioning it,
which is why the CI page.html shows the canvas sized from the capture
rect but styled to the cycling anchor.

Every teardown now bumps a shader epoch before it does anything else, and
a construction pins the epoch it owns and abandons its canvas (releasing
the GL context) at every point past an await and before any publish,
including both bitmap-fallback publishes. A teardown also drops a shader
node that no shaderState owns, so an already-orphaned canvas cannot
survive one.

Reproduced by widening the append-to-publish window: with a 400ms delay
after uiAppend, vite8-react-insert failed with the CI error and the probe
showed the teardown arriving at CYCLING with shaderState still null.
The same run passes with this change, as does a 1500ms window on insert
and plain. Locally that window is about 4ms, which is why it only showed
on a slower runner.

The four remaining setLiveState('CYCLING') sites that did not lower the
loader now do: the SSE done handler (the one route that can reach CYCLING
from GENERATING), the Svelte republish remount, and the two accept
failure recoveries.

The e2e assertion already waits up to 5s for the shader to clear, so it
was never racing a legitimate teardown; it is left as it is.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
2026-09-04 14:31:25 +05:00
6d5f78eebf Live: the loader now hands off when the resume is the arrival
The overlay could sit in its generating shader over a DOM that already
held all three variants, and only a page refresh cleared it (#719).

The server's generation preflight runs live-wrap with
--defer-source-write, so the wrapper and every variant reach the DOM in a
single HMR batch. The deferred-wrapper scout is constructed at init and
the variant MutationObserver at Go; observer callbacks run in
construction order, so on that batch the scout resumes first and
resumeSession, not the observer, is the transition into CYCLING. It set
the state and the bar but never called hideShaderOverlay(), so the frozen
capture of the original stayed painted over the variants. It also
reported browser_resumed, which does not count as publication progress,
and then disconnected and re-created the observer, dropping the records
that observer had already queued for the same batch, so variants_ready
never fired at all.

resumeSession now finishes the same transition the observer does (shader
down, inline edit off, insert session finalized, params panel rebuilt)
and reports variants_ready when it already holds every variant. The
deferred scout names itself in the journal as
browser_resumed_deferred_wrapper, so the two resume paths are no longer
indistinguishable.

Wrapper resolution goes through findVariantsWrapper, which prefers a
wrapper that actually holds non-original variants. A target inside a
.map() renders one wrapper per item, and an agent that relocates the
wrapper out of the shared primitive live-wrap scaffolded leaves an empty
one behind; first match could pin either and strand the session at 0/N.
With zero or one match this is the querySelector it replaces.

Tests: waitForCycling now asserts the generating shader is gone once the
bar cycles, across every runtime fixture (it failed on vite8-react-plain
before this change and passes after), marked no-retry so the reload
recovery cannot hide it. Source-shape tests pin the transition, the
variants_ready report, and the wrapper preference.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
2026-09-04 14:31:25 +05:00
3f815865ab Self-discard orphaned JSX live sessions again (#716)
* Self-discard orphaned JSX live sessions again (#715)

#694 stopped the source fallback from fetching and DOMParser-injecting raw JSX, which was painting {expressions} and comment markers into the page. The JSX gate it put in front of the fetch decided everything from the live DOM alone, and an unmounted wrapper looks exactly like a wrapper that was deleted from the file, so it treated both as "wait for mount": the orphan branch counted down its retry budget and then fell out of the function with no terminal action. A resumed CYCLING session whose region had been edited out of source therefore never reached discardOrphanedSession, the durable snapshot stayed out of the discarded phase, and the picker stayed frozen, which is the #439 regression the live-e2e scenario pins. The fix restores the decision without restoring the parse: probeJsxWrapperForOrphan reads the file as plain text and matches the session marker, so no DOM is ever built from JSX. Marker present means the component is simply not mounted and the observer keeps waiting; marker absent after the same retry budget the HTML path uses means the file moved on, and the session self-discards.

AI assistance: prepared by Claude Code under pbakaus's direction.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY

* Orphan probe: a source read that fails also retries, then discards

Review on #716 (Greptile, Copilot): the probe's empty catch swallowed a
failed /source read, so a session whose file had been renamed or deleted
(404), or that hit a transient fetch failure, neither retried nor reached a
terminal action, which is the frozen-picker failure the probe exists to end.
A read that cannot answer now shares the retry budget with a read that
answers without the marker, and after the budget the session is discarded
with a reason that names the failure. Unit test pins that the probe has no
empty catch and that the failure path discards.

AI assistance: prepared by Claude Code under pbakaus's direction.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY

* Orphan probe: only evidence that the wrapper is gone may discard

Review on #716 (Greptile, second pass): after the previous change a
transient /source failure that outlasted the 3.6 s retry budget discarded a
valid session, and a discard is durable. Now a read that answers without the
marker, or a 404 (the file renamed or deleted), retries on the budget and
then discards; any other failure retries on the budget and then keeps the
session, warns, and tells the user it is checked again on the next event.
The unit test pins both halves.

AI assistance: prepared by Claude Code under pbakaus's direction.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 16:53:47 -07:00
8b326fc81e Fix: resolve unique --target names in monorepos (#700) (#706)
* Fix: resolve unique --target names in monorepos (#700)

Bare child names such as Cantaro.Web now match a unique workspace candidate instead of being reported missing.

AI assistance: Cursor Grok 4.6 implemented this change.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: resolve --target once in the context CLI

Reuse the resolved path for loadContext so a bare name does not walk workspace candidates twice.

AI assistance: Cursor Grok 4.6 implemented this change.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: match unique --target names after cwd absolutizing

Live and other helpers resolve --target against cwd before context.mjs sees it. Treat a missing single-segment path the same as a bare workspace name so those callers still select the unique child.

AI assistance: Cursor Grok 4.6 implemented this change.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Paul Bakaus <paul.bakaus@gmail.com>
2026-09-02 21:29:10 -04:00
Paul BakausandGitHub fa44839f72 Fix detector URL scans and advisory handling (#709)
* Fix detector URL and advisory handling

Recover joined URL arguments without splitting local paths, derive advisory behavior from registry severity across consumers, inspect readable linked CSS in URL scans, and report only the dominant primary font.

AI assistance disclosure: Implemented and verified with Codex under maintainer direction.

* Filter linked CSS to rendered selectors

Flatten linked stylesheet grouping rules and collect only selector rules that target the live DOM, preventing unused grouped and selector-less patterns from leaking into URL findings.

AI assistance disclosure: Implemented and verified with Codex under maintainer direction.

* Fix detector review edge cases

AI assistance disclosure: Codex implemented and verified these fixes under maintainer direction.

* Preserve unresolved linked CSS selectors

AI assistance disclosure: Codex implemented and verified this fix under maintainer direction.

* Fix linked CSS selector filtering

Resolve pseudo-element selectors to live hosts, reject unresolvable linked CSS findings, and make the regression assertions independent. Also ignore comment delimiters when recovering CSS rule selectors.

AI assistance disclosure: This commit was prepared with Codex under maintainer direction.

* Skip unresolved container query CSS

Exclude linked container-query groups when their current applicability cannot be resolved, with a browser regression proving inactive styles do not leak.

AI assistance disclosure: This commit was prepared with Codex under maintainer direction.

* Detect active container query CSS

Use a temporary custom-property probe so the browser decides whether a nested style rule actually applies in the current container layout.

AI assistance disclosure: Codex helped implement and test this fix under maintainer direction.

* Filter inactive linked CSS states

Keep valid empty pseudo-class matches authoritative and omit selector-less linked at-rules that cannot be tied to rendered nodes.

AI assistance disclosure: Codex helped implement and test this fix under maintainer direction.

* Parse pseudo-elements without rewriting literals

Preserve quoted attribute values and escaped identifiers while resolving real pseudo-elements to live hosts.

AI assistance disclosure: Codex helped implement and test this fix under maintainer direction.

* Restore live linked keyframes

AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction.

* Handle grouped linked keyframes

AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction.

* Respect keyframe definition order

AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction.

* Resolve effective linked keyframes

AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction.

* Fix keyframe easing detection

Serialize effective per-keyframe easing back into the linked stylesheet corpus so overshoot motion is detected. Add a browser regression with a neutral animation name.\n\nAI assistance disclosure: Codex helped implement and test this fix under maintainer direction.
2026-09-02 15:45:34 -04:00
Paul BakausandGitHub 672ca29642 Fix Next.js 16 CSP and parent hook discovery (#710)
* Fix CSP and hook ancestor discovery

Recognize Next.js 16 proxy files when detecting runtime CSP and mirror harness ancestor lookup when locating active hook manifests for nested projects.

AI assistance disclosure: Implemented and verified with Codex under maintainer direction.

* Tighten hook and proxy discovery

AI assistance disclosure: Codex implemented and verified these fixes under maintainer direction.

* Honor ancestor hook disable config

AI assistance disclosure: Codex implemented and verified this fix under maintainer direction.

* Keep hook discovery within target repository

Stop manifest discovery at the target repository boundary instead of re-adding an outer workspace root, with regression coverage for nested Git targets.

AI assistance disclosure: This commit was prepared with Codex under maintainer direction.

* Detect proxy CSP in nested Next apps

Recognize proxy files at root or src placement relative to nested Next project markers while continuing to ignore unrelated proxy helpers.

AI assistance disclosure: This commit was prepared with Codex under maintainer direction.

* Resolve external targets from their own repository

Scope explicit sibling targets to their own Git root so caller context and hook manifests cannot suppress required detector guidance.

AI assistance disclosure: This commit was prepared with Codex under maintainer direction.

* Isolate explicit targets at Git boundaries

Keep nested repositories and external targets out of caller and home-level context or hook discovery.

AI assistance disclosure: Codex helped implement and test this fix under maintainer direction.
2026-09-02 14:10:46 -04:00
Paul BakausandGitHub 482368511a Fix Codex skill version metadata (#703)
Move Codex and .agents skill versions under metadata while keeping all version readers compatible with legacy top-level frontmatter.\n\nAI assistance: prepared with Codex under maintainer direction.
2026-09-01 20:21:01 -04:00
4nibhalandGitHub 9736a9f6e9 Fix OpenCode slash command bridge (#483)
Add a first-class OpenCode command bridge across builds, installs, updates, linked installs, and pinned shortcuts. Preserve current provider behavior while backfilling missing or drifted command files.\n\nAI assistance: contributor and maintainer work used AI tools as disclosed in the PR discussion and commits.
2026-09-01 19:05:37 -04:00
38e102f0b2 Fix: never inject raw JSX in live-mode fallback (#454) (#694)
* Fix: never inject raw JSX in live-mode fallback (#454)

On React/JSX targets, missed HMR used to fetch source and DOMParser-inject it, painting {expressions} and comment markers as page text. Adopt a live wrapper that already has variants, otherwise leave HMR alone.

AI assistance: Cursor Grok 4.6 implemented this change.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: wait for unmounted JSX variants instead of tearing down (#454)

A missing live wrapper on React is often a closed modal or other route, not a failed generation. Leave the observer armed so mount can still reach CYCLING.

AI assistance: Cursor Grok 4.6 implemented this change.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: recover empty JSX replace wraps after fallback retries (#454)

Insert scaffolds still wait for HMR. A replace wrapper with no variants after retries is a failed generation and should leave GENERATING.

AI assistance: Cursor Grok 4.6 implemented this change.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: align live-reference setup assertions with current SKILL.src.md

#689 shortened Setup step 2, but the live-reference tests still expected the old playbook sentence, which kept CI red on main and this branch.

AI assistance: Cursor Grok 4.6 implemented this change.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 15:01:46 -07:00
Paul BakausandGitHub 6bc4f242c7 Fix live cleanup races with framework HMR (#695)
Guard delayed accept and discard DOM fallbacks when framework/HMR ownership is present, while preserving static-page cleanup. Add unit/source regressions for both paths and refresh stale Setup wording assertions from #689.

AI-assisted: prepared with Codex under @pbakaus direction.
2026-09-01 17:09:25 -04:00
Paul BakausandGitHub 6fe900dbb4 Improve incumbent evidence and direction fusion (#689)
Prefer committed visual goldens when the app cannot run and make assigned-system translation explicit when a pinned register conflicts with literal materials.\n\nAI-assisted: prepared with Codex under @pbakaus direction.
2026-09-01 00:22:49 -04:00
Paul BakausandGitHub 632912b5ae Fix live script response encoding (#690)
Declare UTF-8 on the generated live and detector JavaScript responses and cover both endpoints with integration assertions.\n\nAI-assisted: prepared with Codex under @pbakaus direction.
2026-09-01 00:00:51 -04:00
Paul BakausandGitHub 187790826d Fix concept seed under symlinked installs (#686)
Resolve the CLI entry path through realpath and cover linked skill directories on Unix and Windows junctions.

AI-assisted change: implemented with Codex under @pbakaus direction.
2026-08-31 23:48:43 -04:00
Abdul WahabandGitHub 5211bdf4b1 Fix: critique snapshot close path (#660)
Preserve critique trend history while closing the exact processed snapshot. Track target identity and content freshness for local files and URLs, isolate colliding streams, and prevent legacy snapshots from resurfacing after a modern close.

Validated with 50 focused tests, a clean 17-provider build, 834 core tests, passing GitHub CI and Cursor Bugbot, Greptile 5/5, and zero unresolved review threads.

AI-assisted maintainer repair: implemented and reviewed with Codex under @pbakaus direction.
2026-08-31 22:42:44 -04:00
Abdul WahabandGitHub 4fb66a15e7 Fix prompt embedding for install paths with spaces (#676)
Resolves the embed helper with a filesystem path, reports fallback accurately, and adds regression coverage while leaving generated harness output to the post-merge sync.\n\nAI-assisted maintainer repair, review, and validation by Codex under maintainer direction.
2026-08-31 20:10:41 -04:00
Paul BakausandGitHub 8c59bc7fb0 Centralize image prompt parsing (#641)
Centralize raster prompt lookup and PNG chunk parsing while preserving read, scan, replacement, and sidecar behavior.

AI-assisted merge: reviewed and executed by Codex under explicit interactive maintainer direction.
2026-08-31 19:34:07 -04:00
jesusdizvalls-cellandGitHub ac64a2fe12 Fix grammar in skill introduction (#679)
Correct the article before award-winning in the source skill introduction. Generated provider copies will refresh through the post-merge sync.

AI-assisted merge: reviewed and executed by Codex under maintainer direction.
2026-08-31 19:21:34 -04:00
MorganandGitHub 1130d00ebc Fix: keep direction contracts out of production output (#673)
Store direction contracts in development-only surface briefs and guard against browser-delivered leakage with regression coverage.

AI-assisted merge: reviewed and executed by Codex under maintainer direction.
2026-08-31 19:07:44 -04:00
Abdul WahabandGitHub fc442be223 Fix: make hooks reset remove installed manifest entries (#668)
Fixes #512 and resolves the verified reproduction in #667.\n\nAI-assisted change: reviewed and merged by Codex under maintainer direction.
2026-08-31 18:21:34 -04:00
Paul Bakaus f1e8d4e70e Simplify named rule extraction
AI-assisted by OpenAI Codex under maintainer pbakaus's scheduled-refactor authorization.
2026-08-31 12:59:53 -07:00
Paul Bakaus 9434dde9af Merge main: skipScan visual-contrast coverage, live overlay waivers, generated output sync
The generated browser bundle is rebuilt from the merged engine sources in the next commit's build step (both branches had regenerated it).

AI-assisted (Claude Code).
2026-08-28 16:01:35 -07:00
Paul Bakaus 10f7c7b6f8 The unreferenced-plate refusal names its own escape: --artifact <page> when the scan cannot see the reference
Closes the residual page-inference edge (no recorded artifact, no index.html, several root HTML files) by making the conservative refusal self-correcting instead of adding more inference; the gate never falsely passes in that configuration, only asks for the page.

AI-assisted (Claude Code).
2026-08-28 15:53:52 -07:00
Paul Bakaus af109a85ae gateHero resolves the page before the unreferenced-plates check
Greptile's fourth finding on the seam: the no-artifact path was depth-limited. The page default (index.html, or the one .html at the root) now applies before unreferencedPlates, so the link-following path, which is exact and unbounded, handles every build that has a page; the bounded walk is only the no-page fallback.

AI-assisted (Claude Code).
2026-08-28 15:43:53 -07:00
Paul Bakaus 0d2df39339 Root-relative stylesheet hrefs resolve against the project, not the drive root
Bugbot on #599: path.resolve treated /assets/hero.css as filesystem-absolute. Both the working directory and the artifact's directory are tried; unreadable candidates skip.

AI-assisted (Claude Code).
2026-08-28 15:32:22 -07:00
Paul Bakaus 64001fe213 unreferencedPlates follows the artifact's linked stylesheets by name
Greptile's third P1 on the same seam: a stylesheet linked from the artifact but outside the bounded walk's root, depth, or file limit was still invisible. The hrefs the artifact itself declares are resolved against its directory and joined to the corpus, which closes every variant.

AI-assisted (Claude Code).
2026-08-28 15:26:28 -07:00
Paul Bakaus 18e8c287b5 unreferencedPlates: an explicit artifact joins the source corpus instead of replacing it
Greptile's follow-up P1 on #599: with --artifact set, only that HTML file was read, so a plate referenced exclusively from a linked stylesheet still read as unused. The bounded source walk now runs either way.

AI-assisted (Claude Code).
2026-08-28 15:17:09 -07:00
Paul Bakaus 09ddc1758e sourceFiles walks assets/: a stylesheet there may be the one reference to a plate
Greptile P1 on #599: unreferencedPlates read a plate referenced only from assets/hero.css as unused and the hero gate refused a valid build. The extension filter already keeps binaries out of the walk.

AI-assisted (Claude Code).
2026-08-28 15:04:57 -07:00
Paul Bakaus 3818a5655b Address the Bugbot and Copilot findings on #599
- keyChroma re-encodes with the PNG's tEXt chunks intact (the embedded prompt survived generation but not keying)
- organic-clip-path counts relative curve commands too (path data letters are only commands, so the match is case-insensitive)
- buried-raster normalizes percentage alphas (parseFloat('80%') read as 80) and reads 4- and 8-digit hex alpha instead of treating #rrggbbaa as opaque
- the extension-injected-node skip in checkQuality runs before any finding is pushed (a low-opacity injected raster was recorded, then returned by the skip)
- fake-mode plates carry impeccable:fake tEXt and the plates gate's crop-identity refusal skips them (fake mode IS the crop by design; the refusal is for models shipping the comp's pixels as artwork)

Findings by cursor[bot] and Copilot on PR #599; detector engines rebuilt (build:browser, build:extension).

AI-assisted (Claude Code).
2026-08-28 14:54:04 -07:00
Paul Bakaus 7edc5a43da font-match: a browser module without its binary is the same as no browser
CI resolves playwright but has no downloaded chromium; launch threw instead of falling back to the catalog ranking, and every spec gate downstream failed. Launch failures now return the no-browser path (and the browser test skips instead of asserting).

AI-assisted (Claude Code).
2026-08-28 14:47:21 -07:00
Paul Bakaus c75f9f1086 Above the bar, hero readings advise instead of block; spec escape hatches persist and announce; font-match tolerates an unwritable /tmp
Paul's decision on the tenth sweep's design question: hard vetoes (missing region, contradicted plate or text, SVG illustration, clipped plate, invented ink) stay unconditional; at overall >= HERO_MIN the numeric readings (ink colour, letter-spacing, line pitch, strip heights, box positions) print as advisories with the pass and belong to the polish pass. Every sweep-10 sample closes its hero under this condition, which settles 07 without another paid round.

Ninth-sweep defects: codeDrawn / container / bleed now persist into spec.json with WARN lines (an overridden refusal used to vanish from the record); font-match probes os.tmpdir() and points TMPDIR at .impeccable/tmp when the sandbox /tmp is unwritable (every ninth-sweep rank silently fell back to the catalog).

AI-assisted (Claude Code).
2026-08-28 14:41:47 -07:00
46f13989eb Fix: write the build-path flip before answering the POST
serve-question answered POST /build-path with 200 and only then wrote the
flip file. The caller is a separate process, so the response could reach it
while the server was still preempted before the write landed: a poller that
trusted the 200 could look for the flip file and miss it.

Measured on a loaded machine, the old order lost that race 29 times out of
40; writing first and answering after loses it 0 times out of 40. This is
what made tests/serve-question.test.mjs fail intermittently in CI on the
Node 22 job while passing on Node 24.

AI-assisted change: diagnosed and implemented with Claude Code under
maintainer direction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 18:38:36 +05:00
152d6940b0 Fix: harden live overlay detector waivers (#639 follow-up)
Read waiver config from every live root (appRoot, contextRoot, repoRoot),
so monorepo projects whose config lives at the repo root reach the
overlay; serialize served roots and page identities repo-relative there.

Resolve each page URL to its actual serving file via the inject config's
resolved page list before applying file-scoped waivers; ambiguous URLs
keep the conservative common-ancestor fallback (PR #645 review
discussion r3840011436).

Honour detector.ignoreFiles: a wholly waived page now scans to zero
findings in the overlay, matching the CLI and the edit hook.

Guard the resolver call so a throwing resolver degrades to an unfiltered
scan instead of breaking the detect toggle.

Match design-system-color waivers by color value across hex and rgb()
spellings, and stop extracting font values for bounce-easing findings,
mirroring extractFindingIgnoreValue. Regenerate the browser bundle.

AI-assisted change: reviewed, planned, and implemented with Claude Code
under maintainer direction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 18:38:36 +05:00
cbd7870159 Address review: collision-resistant slugs, os.homedir() tilde expansion
- The per-project state dir key is now the readable separator-mapped
  slug plus an 8-hex sha256 of the resolved project path. The readable
  part alone is lossy (/x/my.app and /x/my-app both mapped to -x-my-app
  and shared hook state); the digest keeps distinct projects' cache and
  pending state apart while the dir name stays human-scannable.
- Tilde roots now expand via os.homedir() instead of HOME/USERPROFILE
  with a '.' fallback. When no home dir can be determined, expansion is
  rejected and state falls back to the project-local default rather
  than anchoring under the hook process's working directory.
- Tests updated to the digest-suffixed slug via a mirrored slugFor()
  helper, plus two new cases: colliding readable slugs get distinct
  state dirs, and the tilde form resolves identically to the explicit
  homedir-joined form.

Prepared with AI assistance (Claude Code) under direction of
0xDarkMatter, per the maintainer-approved issue #422.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 15:14:49 +05:00
30b3628f5b Expand a leading ~ in IMPECCABLE_CACHE_ROOT against the home dir
Env files and settings JSON hand '~/caches' to Node unexpanded; without
this it would resolve to a literal '~' directory under the process cwd.
Mirrors the exact treatment IMPECCABLE_HOOK_LOG already gets in
writeAuditLog (HOME || USERPROFILE fallback), plus the Windows '~\'
spelling.

Prepared with AI assistance (Claude Code) under direction of
0xDarkMatter, per the maintainer-approved issue #422.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 15:14:49 +05:00
5c82d58b7e Harden IMPECCABLE_CACHE_ROOT edges: normalization, opt-in gate, failure path
- hookStateDir now trims the env value (stray whitespace in env files)
  and path.resolve()s both the root and the cwd, so trailing separators
  and relative segments slug to the same per-project dir.
- The #344/#305 persist gate also treats an existing (possibly
  redirected) cache file as the opted-in marker. Without this, once
  state relocated, clean-edit editCount bumps stopped persisting because
  the project-local .impeccable/ dir never appears. No-op under stock
  paths, where the cache file lives inside .impeccable/.
- New tests: slug normalization equivalences, whitespace trim, graceful
  persistCache failure on an unusable root, and three runHook
  end-to-end cases (findings persist + dedup through the redirect,
  clean-edit editCount persistence, and the no-footprint no-op gate
  holding under redirect).

Prepared with AI assistance (Claude Code) under direction of
0xDarkMatter, per the maintainer-approved issue #422.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 15:14:49 +05:00
77a2eae861 Add IMPECCABLE_CACHE_ROOT to relocate hook state out of project roots (#422)
Honor an optional IMPECCABLE_CACHE_ROOT env var in getCachePath() /
getPendingPath(): when set, hook.cache.json and hook.pending.json land
under $IMPECCABLE_CACHE_ROOT/<project-slug>/ (slug = project path with
[:\/.] mapped to hyphens, mirroring Claude Code's ~/.claude/projects/
convention). Unset or blank env keeps stock project-local behavior.
User-authored config (config.json, config.local.json, design.json)
deliberately stays project-local - only disposable state relocates.

Also clears ambient IMPECCABLE_CACHE_ROOT at the top of hook.test.mjs so
a developer running the suite with the redirect active still gets
deterministic stock-path assertions; the new suite sets and restores the
var explicitly.

Prepared with AI assistance (Claude Code) under direction of
0xDarkMatter, per the maintainer-approved issue #422.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 15:14:49 +05:00
Paul BakausandAbdul Wahab 08b03e8763 Centralize live path glob matching
AI-assisted change prepared by Codex under scheduled architecture-simplification authorization from maintainer pbakaus.
2026-08-28 15:01:21 +05:00
45943c3b1f Fix: assert only the common ancestor of the glob roots as a URL prefix
Cursor's review caught the previous commit over-correcting. One tree listed
at two depths (prototype/*.html plus prototype/library/**/*.html) derived
two roots, and requiring a waiver to match under both stopped a normal
project-relative waiver like prototype/index.html from applying anywhere.

The rule both reviews were circling is simpler: one live session is served
by one server, so a single document root must sit at or above every
configured page. The only prefix the resolver can safely assert is the
deepest common ancestor of the glob roots. Nested roots collapse to their
shared tree, so normal waivers keep applying. Disjoint roots (src/ and
public/) share nothing, so no prefix is asserted and only the URL path
itself matches, which keeps the earlier fix intact: a src/foo.html waiver
still cannot hide a finding on a page served from public/foo.html.

This also deletes the match-under-every-root machinery from the previous
commit; with a single asserted prefix, plain matching is enough.

Also switches the new test file to derive the repo root from
import.meta.url rather than process.cwd(), per review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 07:13:43 +05:00
ce1c9f8dad Fix: a waiver scoped to one served root must not hide findings on another
Greptile's review found a real bug in the new resolver. When the live config
lists pages under more than one folder (src/**/*.html and public/**/*.html),
the overlay treated a URL like /foo.html as src/foo.html and public/foo.html
at the same time. A waiver written only for src/foo.html could then hide a
finding on the page actually served from public/foo.html. That fails in the
worst direction: a real finding disappears and nothing says so.

The overlay can never look up the right file. The live server does not serve
the pages; the project's own dev or static server does, and its URL-to-file
mapping is invisible from here. So the fix stops guessing: a file-scoped
waiver now applies only when it matches the URL path itself, which is true
whichever folder serves the page, or when it matches under every configured
folder, so no possible reading disagrees. Anything ambiguous shows the
finding, which is also what the CLI reports for the file really being served.

With a single configured root, the common case, nothing changes: the new
rule reduces to the old behaviour exactly. Multi-root projects keep three
ways to write a waiver that still applies: name the file under each folder,
use the bare path, or use **/.

Two new unit tests pin the ambiguous case and the safe spellings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 07:13:43 +05:00
5330fa358e Fix: honour .impeccable detector ignores in the live overlay (#639)
The live overlay's detect scan ran unfiltered: requestDetectScan() posted
only { scanId }, so detector.ignoreRules and detector.ignoreValues in
.impeccable/config.json reached impeccable detect and the edit hook but
never the surface a designer actually watches.

The server now serializes the project's detector waivers into the /live.js
prelude (window.__IMPECCABLE_PROJECT_IGNORES__), read per request through
hook-lib's readConfig so config.local.json wins and edits land on the next
tab reload. A new script part, live-browser-ignores.js, resolves that
config against the page URL when a scan starts: ignoreRules suppress
outright, wildcard ignoreValues suppress their rule in the files their
globs name, and the remaining entries ride along as disabledValues for the
detector to match on each finding's own value. The detector bundle applies
those where the findings are assembled, since the overlay draws its own
markers from the collected findings.

Scope resolution mirrors cli/lib/impeccable-config.mjs deliberately: the
same glob dialect (globToRegex, including {a,b} alternation), the same
path-suffix matching as findingMatchesScopedIgnoreFile, and the same
refusal to apply an unscoped wildcard entry. The served-root prefixes that
bridge project-relative globs and site-relative URLs come from the inject
config's own files globs, never from the ignore globs; deriving them from
the ignore globs lets one entry scoped to prototype/library/** lend its
prefix to every page and suppress site-wide, which looks like success
because the numbers go down.

Known gaps, recorded in the detector comment: the motion value extractor
is not mirrored, so a value-scoped bounce-easing waiver only matches when
the finding carries ignoreValue directly, and design-system-color matches
on the normalized string without the CLI's color-equality fallback.

Tests: unit tests for the resolver part (stale globals, string ignoreRules,
malformed entries, directory URLs, percent-escapes, glob metacharacters,
the roots trap), an extension-mode puppeteer test that disabledValues
suppress exactly the waived findings, and the live-browser regression pin
now asserts the new scan config shape instead of { scanId }.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 07:13:43 +05:00
Paul BakausandAbdul Wahab 2896e28613 Hero code scans read index.html when start recorded no artifact; painted-note regex learns 'geometry', 'leader lines', 'thumbnail'
A sol build named its two carburetor drawings 'countable ... geometry' chrome regions, drew them in inline SVG, and the SVG ban never ran because state.artifact was null.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab bef185360f build-phase.mjs scaffold: the measured layout as CSS custom properties and a reference page
A reference, not the page: --r-<id>-x/y/w/h in % of the comp (plus cap height, font-size, family, weight where measured) to bind to any markup, and hero-reference.html with every region at its box and every plate placed with object-fit: contain, as a check on positions. Attacks the most common execution failure of weaker builders (badly positioned, overflowing, pushed below the fold) without dictating structure to strong ones; overlapping boxes are overlapping boxes and the gate reads pixels regardless.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab 2debb7078f Hero refuses inline SVG illustrations; finish cannot record ship over an open phase; the comp-led path names its model tier
From the human review's most repeated pin ('terrible svg instead of asset', on every model) and from sessions that wrote 'ship' with the hero open. Icons, arrows, chevrons, and runtime data charts stay code; diagrams, notation, and leader lines are plates.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab 38170564c6 Plates gate refuses a comp crop shipped as a plate; text readings include letter-spacing
From the final review batch: 'bad asset crop (crops are never allowed)' twice, 'letter spacing way too wide'. A crop resampled to the region scores 99.8% structure against the raw region; a produced plate scores 30-60.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab 77acd73cd3 Hero readings: sibling regions fold into one line; ink colour only on type at cap 16+
A build reached hero 81% and stalled on eight staff rows read one at a time (and both ways on ink colour). The session asked the user and was told to build as written; the force was legitimate and recorded.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab 7cea970c6c comp-spec snaps text and control regions to the largest ink mass in their grid span
A session's own note said its hero sat at 67 because the 10x10 grid boxes straddled two elements each, and it was right: every downstream measurement (cap height, line count, structure) inherited the slop. Text and control regions now snap to the dominant connected ink inside the span (page-ground threshold, dilated cells, masses touching the span's sides lose to inside masses), keep the span on the record for coverage, and can opt out with snap: false.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab 36e6864d56 Controls are held like text at the hero: a contradicted control vetoes, a far drift is named; the icon concession covers glyphs only
Per pbakaus: close-enough icons are fine, arrows and dropdown chrome are not.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab f80846ad85 Side-by-side shows the capture, not the shift-padded copy; colour reading on unmeasurable text; line pitch on 3+ lines
The shifted copy's padding read as a white 'letterbox' on the build in every human review. A vertical spine came back white on red where the comp had black in five builds; its ink colour is now compared even though its type cannot be measured.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab 07e3fad3b6 Spec refuses a plate box that cuts its artwork; hero counts strong small invented inserts; painted-note regex no longer matches a label
From the second review batch: the best build of the fifth sweep passed the hero at 87% with the cover arch cut flat on the left (object-fit: cover on a box narrower than the shape), and legends, badges, and extra controls one or two cells wide slipped under the invented-ink floor. comp-spec measures the artwork's contiguous contact with each box edge against the page ground; the spec gate refuses such a box unless bleed is set. Human pass line landed at comp-diff 72-73; HERO_MIN stays 0.72.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00