Commit Graph
57 Commits
Author SHA1 Message Date
Paul BakausandClaude Opus 4.7 fdb9e7c6f8 fix(live): screenshot overlay no longer flashes solid black during loading
Same alpha-string trap pattern as the recent detectPageTheme fix, on a
different code path. resolveCanvasBackground walks parents looking for
an opaque background; on a page that doesn't set its own bg the loop
runs out and fell through to:

  return getComputedStyle(document.body).backgroundColor
    || getComputedStyle(document.documentElement).backgroundColor
    || '#ffffff';

`getComputedStyle(body).backgroundColor` for a default-bg page returns
the literal string "rgba(0, 0, 0, 0)" — non-empty, truthy — so the `||`
chain short-circuits to transparent-black instead of falling through to
'#ffffff'. modern-screenshot then composites the capture onto a black
canvas; the WebGL shader overlay flashes solid black until the shader
finishes loading.

Fix: drop the buggy fallback. The while-loop already covered <body> and
<html>; if neither is opaque the only sensible answer is the browser's
default canvas color (white).

Test coverage:
- New tests/live-browser-regression.test.mjs pins the anti-pattern
  with a static-source check (live-browser.js is an IIFE with no module
  exports, so this is the cheapest reliable regression guard). Also
  pins the equivalent guard for detectPageTheme's readOpaque helper
  added in the prior commit.
- Wired the new test file into `bun run test`'s explicit list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 15:40:24 -07:00
Paul BakausandClaude Opus 4.7 9ec904302b fix(live): textContent disambiguation handles missing inter-element whitespace
While driving the new live loop end-to-end against the repeated-aside
fixture, --text disambiguation silently fell back to first-match instead
of landing on the picked card.

Root cause: `el.textContent` concatenates child text nodes without
inserting whitespace, so `<h1>Hero Two</h1><p>Second card body copy.</p>`
reads as "Hero TwoSecond card body copy." — but the source has whitespace
between </h1> and <p>. The single-space normalization on both sides
missed the join boundary; substring comparison failed; filterByText
returned [] and the caller fell through to first-match.

Fix: filterByText now compares both single-space AND no-whitespace
normalizations on each side, accepting the candidate if EITHER matches.
Bumped the minimum-target-length threshold from 6 to 8 to compensate
for the slightly looser comparison.

Plus two doc clarifications surfaced during the same session:

- live.md now warns that variant CSS using bare `:scope { ... }` styles
  the variant wrapper div, not the picked element. Always use a
  descendant combinator (`:scope > .card`, `:scope .hero-title`, etc.) —
  the fake test agent's CSS is the canonical template.
- live.md documents the agent-side abort path. Aborting an in-flight
  generate via `live-accept --discard` only mutates source — the browser
  bar stays in GENERATING forever. Use `live-poll --reply EVENT_ID error
  "msg"` instead so the browser receives the error SSE and resets.

Test coverage:
- New unit test in live-wrap.test.mjs covering the textContent-without-
  inter-element-whitespace shape (three identical <aside> branches each
  with <h1> + <p>, picks the second by --text).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 15:03:49 -07:00
Paul BakausandClaude Opus 4.7 54d9f05ea5 fix(live): land valid TSX through wrap → preview → accept → carbonize
Closes #114.

Three orthogonal bugs that surfaced together when live mode picked an
element inside a Vite React/TSX component with sibling branches:

1. JSX wrapper insertion produced invalid TSX
   - Replacing a single picked JSX child with [comment, <div>, comment]
     yields three adjacent siblings, which oxc rejects with "Adjacent
     JSX elements must be wrapped in an enclosing tag."
   - A Fragment `<></>` solves the adjacency case but breaks
     `cloneElement`-using parents (Radix `asChild`, Headless UI, etc.)
     with "Invalid prop supplied to React.Fragment."
   - Fix: keep the wrapper `<div data-impeccable-variants="ID">` as the
     single JSX-slot child and tuck both marker comments INSIDE it.
     accept/discard now expands its replacement range to include the
     wrapper's `<div>` open/close lines via div-depth tracking.

2. carbonize produced nested template literals in TSX `<style>`
   - extractCss captured `{` / `` `} `` lines from the agent's existing
     `<style>{`…`}</style>` template, then handleAccept re-wrapped with
     another pair, producing `<style>{`{`@scope…`}`}</style>` which oxc
     rejects with "Expected `}` but found `@`".
   - Fix: extractCss now strips a leading `{` and trailing `` `} ``
     wherever they appear in the captured content (own line OR attached
     to the first/last CSS line), so re-wrapping always yields exactly
     one `{` ` … ` `}` pair.

3. Ambiguous source matching for repeated JSX branches
   - `findElement` returned the first substring match. Multiple
     `<aside className="card">` siblings all matched the same query, so
     wrap silently landed on the first regardless of which one the user
     picked.
   - Fix: live-wrap accepts `--text TEXT` (the picked element's
     textContent), collects ALL candidates via `findAllElements`, and
     narrows by a tag-stripped, JSX-expression-stripped substring match.
     Returns `element_ambiguous + candidates[]` when multiple branches
     match equally; falls back to first-match when source uses dynamic
     content (`<h1>{title}</h1>`) so existing flows aren't broken.
   - The fake e2e agent now forwards `event.element.textContent` to
     wrap, and live.md tells the agent to do the same.

Test coverage:
- New `vite8-react-tsx-repeated-aside` e2e fixture: three identical
  `<aside>` branches, picks the second card's <h1>, runs the full
  wrap → Go → cycle → accept → carbonize cycle on a real Vite + TSX
  dev server, asserts that Hero One and Hero Three survive untouched
  (proving wrap landed on the correct branch).
- Six new unit tests across live-wrap.test.mjs and live-accept.test.mjs
  covering the Fragment-replacement design, both leading/trailing
  template-literal placements, --text disambiguation, the dynamic-
  content fallback, and the element_ambiguous error shape.
- New `runtime.assertSourceContains` fixture hook so other regression
  fixtures can assert sibling-branch survivability cheaply.

All 186 unit + static-fixture tests pass; all 21 live e2e fixtures
(20 prior + new TSX) pass with no console errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 13:23:20 -07:00
Paul Bakaus 5f5e2b013d Release impeccable skill v3.0.4 2026-04-28 00:16:30 -07:00
Paul Bakaus 54f6ccf6f0 codex in auto-review became way too autonomous - significantly harden craft/shape flows 2026-04-27 23:41:23 -07:00
Paul BakausandClaude Opus 4.7 630e586b01 feat(live): make picker chrome modal-host friendly (Radix, Headless UI, vaul)
Closes #113.

Picker chrome could become unclickable inside Radix Dialog portals, and
clicking it dismissed the host dialog. Three orthogonal issues surfaced
during manual verification:

1. Modal-aware chrome
   - Add `defangOutsideHandlers` and apply it to bar, picker, params
     panel, annotation overlay, global bar, and design panel host.
   - Sets `pointer-events: auto !important` on interactive chrome so
     Radix's `body { pointer-events: none }` modal scroll-lock can't
     silence our UI.
   - Stops `pointerdown` / `mousedown` / `focusin` propagation at the
     chrome boundary so DismissableLayer / FocusScope outside-handlers
     never fire for clicks that land on us.

2. detectPageTheme: misread transparent body as black
   - `getComputedStyle(body).backgroundColor` returns `rgba(0,0,0,0)`
     when no bg is set; the prior regex captured (0,0,0) and ignored
     alpha, calling every default-bg page "dark."
   - Honor alpha, walk body → html, fall back to
     `prefers-color-scheme` only when both are transparent.

3. Exit X invisible on host pages with `button { padding: ... }`
   - Every other chrome button sets padding inline; exitBtn didn't.
     Host resets like `button { padding: 0.5rem 1rem }` (in the new
     fixture, common in the wild) inflated the 24x24 button into 56x40
     and pushed the SVG into a non-rendering region — DevTools showed
     the right styles, the X just didn't paint.
   - Pin `padding: 0` + `box-sizing: border-box`, match the toggle
     icon spec (14 / stroke 1.5 / textDim → text on hover).

4. Toast no longer obscures the global bar
   - Position the toast above globalBarEl's actual rect instead of a
     fixed bottom: 16px that overlapped the bar's bottom: 14px.

Test coverage: new `vite8-react-radix-dialog` fixture exercises the
full pick → Go → cycle → Accept loop with `@radix-ui/react-dialog`
+ `Portal` + `Overlay` + `Content`. Without the fix, clicking Go
dismisses the dialog and unmounts the picked element. All 20 live
e2e fixtures pass; all 180 unit tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 16:22:00 -07:00
70a9246401 fix(plugin): slim Claude Code install (291MB→770KB) + fix skills path
- Marketplace source moved from "./" to "./plugin", a thin generated
  subtree containing only the plugin manifest and the impeccable skill.
  Per-version plugin cache shrinks ~378× (~770 KB instead of ~291 MB),
  and the lockfile is no longer included in the source path so the
  cache extraction never runs bun install. (#107)
- skills field in plugin.json now ends with a trailing slash to match
  the documented schema (code.claude.com/docs/en/plugins-reference,
  every directory example uses ./path/). Three reporters converged on
  this fix because Claude Code's plugin loader skips command
  registration on some setups when the slash is missing. (#86)
- Anti-patterns maintenance agent moved out of .claude/agents/ into
  CLAUDE.md / AGENTS.md as concise inline guidance, since it is
  repo-internal dev workflow, not user-facing. The plugin was also
  the only place this agent was exposed to install users.
- Skills version bumped to 3.0.2 so existing users pick up the new
  install path on next /plugin update.
- Top-level harness directories (.claude/skills/, .cursor/skills/, ...)
  intentionally stay where they are; npx skills add reads them
  directly from the GitHub repo and that path is unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:59:06 -07:00