Commit Graph
100 Commits
Author SHA1 Message Date
Paul BakausandClaude Opus 4.7 4fa02bf573 docs: add live-mode E2E test instructions to CLAUDE.md and AGENTS.md
Documents `bun run test:live-e2e`, the IMPECCABLE_E2E_ONLY scope env var,
the IMPECCABLE_E2E_DEBUG diagnostic flag, the one-time
`npx playwright install chromium` setup, and why the suite is kept off
the default `bun run test` path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:44:14 -07:00
Paul BakausandClaude Opus 4.7 7baf77a457 chore: bump impeccable skill to v3.0.1
User-facing changes shipped in this patch:
- Live mode runs in strict-CSP apps (auto-patches meta CSP, reverts on stop)
- Live mode survives conditional-render content (modal/tab/collapsible)
- Live mode no longer breaks JSX projects (carbonize stash + accept rewrite)
- SvelteKit hydration race fixed
- Headless Chromium WebGL fallback fixed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:39:49 -07:00
Paul BakausandClaude Opus 4.7 c3e18fe664 fix(live): four bugs surfaced by E2E suite + CSP meta auto-patch
CSP meta-tag auto-patch (live-inject.mjs)
  When the user's HTML carries <meta http-equiv="Content-Security-Policy">,
  the cross-origin load of /live.js and the SSE/POST stream back to
  localhost:PORT are both blocked. Insert: append http://localhost:PORT to
  script-src and connect-src, plus blob: to img-src (the shader overlay),
  stash the original content value as a base64 data-impeccable-csp-original
  attribute. Remove: decode the marker and restore the original verbatim.
  Header-based CSP (Next/Nuxt/SvelteKit configs) intentionally untouched —
  those flow through the existing detect-csp.mjs reference path.

JSX-aware accept (live-accept.mjs)
  - Carbonize stash now emits style={{ display: 'contents' }} for JSX targets
    instead of style="display: contents" (HTML form). React 19 was throwing
    "Failed to set indexed property [0] on CSSStyleDeclaration" on the
    string form because it iterated chars onto the style object.
  - extractCss now matches </style> anywhere on a line, not just at line
    start. Previously a JSX template-literal close like `}</style> would
    leak the backtick + brace into the carbonize stash, breaking JSX.
  - Carbonize stash wraps the CSS body in {` … `} for JSX targets so curly
    braces in CSS rules don't get parsed as JSX expressions.

Conditional-render UX (live-browser.js)
  - Drop the 2s-then-window.location.reload() fallback in the SSE 'done'
    handler. That reload was masking a real failure mode: when the picked
    element lives inside conditional render (closed modal, hidden tab,
    other-route), Fast Refresh remounts the parent and state resets, so
    the variants land in source but never reach the DOM. Reload also reset
    state to default, leaving the user stuck.
  - Replace with a 6s contextual toast: "Variants ready. If the picked
    element isn't visible, retrace the path that revealed it — they'll
    appear automatically." The MutationObserver stays armed and
    auto-transitions to CYCLING once the variants finally mount.
  - Pick-time heads-up: when the picked element is inside [role="dialog"],
    [data-state="open"], a multi-tab tabpanel, or an aria-expanded
    collapsible, fire a brief upfront toast so the user knows what to
    expect if state resets during generation.

Hydration race (live-browser.js)
  - SvelteKit (and any framework that hydrates after HTML parse) was
    failing post-Vite-page-reload because init() ran resumeSession()
    before the variant wrapper hydrated into the DOM. The OLD reload
    fallback masked this by triggering a second reload whose hydration
    benefited from warm cache. Without that, fix it properly: install a
    scout MutationObserver in init() that retries resumeSession() once
    [data-impeccable-variants] lands in the DOM.

Shader overlay (live-browser.js)
  - WebGL fallback in showShaderOverlay used Object.assign(img.style,
    canvas.style, …), which throws on modern Chromium because
    CSSStyleDeclaration's indexed properties are not writable. Use
    cssText to copy positioning instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:37:45 -07:00
Paul BakausandClaude Opus 4.7 c8de59d81e test(live): add full-cycle E2E framework-fixture suite with pluggable agent
19 fixtures (11 styling/build variants + 4 conditional-render scenarios + 4
meta-frameworks) drive the entire user flow end-to-end: handshake, pick,
configure, Go, cycle, accept, carbonize cleanup. Each fixture installs real
deps, boots the framework dev server, and runs Playwright Chromium against a
deterministic fake agent that produces realistic variants (colocated style
with @scope rules, full data-impeccable-params manifests covering range +
steps + toggle, JSX/HTML/Svelte syntax-aware rendering).

The agent is pluggable via a one-method interface — generateVariants(event) —
so a future LLM-backed agent slots in by implementing the same shape. The
orchestrator handles wrap, file write, accept, and carbonize cleanup
deterministically regardless of which agent is plugged in.

Schema extensions (tests/framework-fixtures/README.md): runtime block adds
preActions / reloadProbe / pickSelector / scheme / ignoreHTTPSErrors so
fixtures can drive conditional UI (modal, tab, route) before pick and verify
the carbonized variant survives a reload.

Static fixture suite filtered to skip dirs without fixture.json so empty
scaffold dirs no longer break discovery. Total: 178 static checks, 19 E2E
full cycles, ~107s wall clock for the E2E suite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:37:04 -07:00
Paul Bakaus d29a690797 Fix Neon Mirai active navigation 2026-04-24 23:18:23 -07:00
Paul Bakaus d340f075e8 Improve Neon Mirai manifesto artwork 2026-04-24 23:10:34 -07:00
Paul Bakaus 7de610c620 Add Neon Mirai conference example 2026-04-24 17:22:59 -07:00
Paul BakausandClaude Opus 4.7 25353448e2 fix(site): restore docs-viz-caption top margin squashed by .prose p
The .prose p rule (specificity 0,1,1) was overriding .docs-viz-caption's
intended margin-top, leaving 0px between the caption and the cards/file/
report above it on every docs page that uses the hero block.

Bump specificity with .docs-viz-hero .docs-viz-caption and set
margin: 16px 0 0 so the caption sits 16px below the visual and lets the
hero's 24px padding alone control the bottom gap. Symmetric inside the
cream box.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:45:59 -07:00
Paul BakausandClaude Opus 4.7 346ce25952 docs(site): add image gen bullet and Live Mode alpha tag to v3.0 changelog
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 10:21:01 -07:00
Paul BakausandClaude Opus 4.7 f5e82162c1 fix(site): strip dev-only live.js inject tags from production HTML
public/index.html and public/privacy.html had stale
`<script src="http://localhost:8400/live.js">` scaffolding from a
local /impeccable live session. On impeccable.style (Cloudflare) this
fired Chrome's private-network-access prompt on every page load. The
inject is dev-only; normally stripped by live-server.mjs stop, but
these two slipped through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 18:21:26 -07:00
Paul BakausandGitHub 6816558d7a Merge pull request #109 from pbakaus/3.0
v3.0: single /impeccable skill, Live Mode, /designing orientation, visualize-first
2026-04-23 18:08:34 -07:00
Paul BakausandClaude Opus 4.7 0760cdf3e9 fix(skill): update stale SKILL.md font-tag reference in typography.md
typography.md pointed at SKILL.md's `<font_selection_procedure>` and
`<reflex_fonts_to_reject>` XML tags, which were removed in the v3
consolidation and moved into brand.md as the "Font selection procedure"
and "Reflex-reject list" sections. Agents loading typography.md via the
craft flow were chasing content that no longer existed. Now points at
brand.md with correct section names.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 18:03:57 -07:00
Paul BakausandClaude Opus 4.7 a42d21856c fix(skill): resolve cursor bot findings on colorize + critique
colorize.md: the brand-register paragraph claimed "a dominant color can own
the page" and "accent rate stays ≤10%" in the same breath. SKILL.md scopes
the ≤10% rule to Restrained only; Committed / Full palette / Drenched
exceed it on purpose, and brand.md explicitly encourages those strategies.
Rewritten to defer to the color-strategy ladder.

critique.md: two cross-references still pointed at "Step 4" / "Step 5"
after those headers were renamed to "Ask the User" / "Recommended Actions".
Swapped the references to the new names so the flow is self-consistent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 17:06:09 -07:00
Paul BakausandClaude Opus 4.7 5613891aa6 docs(typography): absorb tactical additions from typecraft-guide-skill
Merged ten tactical items from ehmo/typecraft-guide-skill into the typography
reference at the upstream author's request: dark-mode weight/tracking/leading
compensation, font-display: optional vs swap, preload-critical-weight-only,
variable fonts for 3+ weights, clamp() max-to-min ratio bound, container/
font-size coupling to preserve measure, text-wrap: balance / pretty,
font-optical-sizing: auto, quantified ALL-CAPS tracking (5-12%), and the
paragraph-rhythm rule (space OR indent, never both).

Skipped: platform-specific tables (iOS/Android/print), confidence markers,
severity-graded report format, academic sources, and the punctuation
subsection (em-dash prescription conflicts with the project copy rule).

Attribution lives in NOTICE.md, not inside the skill content.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 16:56:29 -07:00
Paul BakausandClaude Opus 4.7 e0ab3a73b7 feat(live + site): preserve variant attr on accept, designing-page redesigns
Runtime fix in live-browser.js: the 2s static-server fallback in handleAccept
now swaps the outer wrapper with the `[data-impeccable-variant="N"]` div itself
(+ display:contents), matching what live-accept.mjs writes to disk. Scope rules
anchored on the variant attribute keep matching on the non-HMR path, so the
accepted design no longer flashes unstyled until reload. Propagated to all
harness script copies.

/designing:
- §03 Polish redesigned as drenched magenta masthead: commands live in the band,
  three title/description columns beneath on cream.
- §04 Maintain redesigned as architectural poster diptych: extract + document
  vizzes become the hero element, caption below.
- §05 Interop section removed.
- §05 (was §06) "Pick a register" renamed to "Brand, or product." with a
  two-lane hairline-divided layout and an auto-selected framing in the sub.

Live mode status: BETA → ALPHA across the periodic table, magazine spread,
and docs callout, reflecting real-world-testing readiness.

Skill bootstrap: removed the `<post-update-cleanup>` block from source/SKILL.md
(the source repo is the origin; running cleanup-deprecated here would touch
legitimate source). CLAUDE.md and AGENTS.md now document the skip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 16:44:46 -07:00
Paul BakausandClaude Opus 4.7 37f79cd013 feat(site): foreground visualize-first workflow on /designing + Case carousel
/designing §01 is reframed around the words → pictures → code arc.
The phase sub covers teach, shape, and craft in one breath; the body
splits into two labeled micro-steps ("teach · in words" and
"shape + craft · in pictures") so the new image-gen spread reads as
step 2 of the same flow instead of a separate topic. Generated plates
from GPT Image 2 sit as an editorial two-up beside their brand and
hi-fi captions. Also fixes a long-standing font-size mismatch where
<code> inside .designing-phase-sub fell back to the browser's default
monospace.

Homepage: inserts a new "Visualize, then build" slot at position 02 of
The Case carousel, compact two-plate visual, shifts the remaining
eight slots to 03-09.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:32:32 -07:00
Paul Bakaus df72ca2a58 docs(impeccable): clarify live mode param expectations for freeform
Section 7 no longer reads as default-zero: composition-sized targets,
freeform bias toward 1–2 dials on non-tiny surfaces, hard cap of four.
Cross-link freeform to §7 in the action loader; sync all harness copies.

Made-with: Cursor
2026-04-23 13:28:35 -07:00
Paul BakausandClaude Opus 4.7 5316809139 feat(site): merge /anti-patterns and /visual-mode into /slop
Top nav was at six items and growing. The anti-patterns catalog and the
visual-mode overlay demo were always two views of the same subject (the
rule set and seeing it caught in place), so collapsing them into one page
reduces nav weight and puts the catalog + live demo next to each other.

Four sections in one scroll: See it (iframe demo), Try it live (specimen
gallery), The catalog (full rule list), Run it yourself (invocation
methods). Sidebar TOC nests the catalog sections under "The catalog" for
deep linking. 301s from the old URLs preserve external links.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:13:56 -07:00
Paul BakausandClaude Opus 4.7 c160ffc38d feat(live): v2 sidecar upgrade + preserve per-project config on build
Unify the design-system panel's data shape around DESIGN.md frontmatter
as the primary source of truth; the sidecar carries only what Stitch's
frontmatter schema can't (extensions + live component HTML + narrative).
Also fix a long-standing build bug that destroyed per-project config.

Shape changes:
- Server /design-system.json now returns { parsed, sidecar, hasMd,
  hasSidecar, mdNewerThanJson, parseError?, sidecarError? }. No more
  mode switching; both layers ship when present and the panel merges.
- Panel consolidates renderSidecarVisual + renderParsedMdVisual into a
  single renderDesignVisual that merges frontmatter primitives with
  sidecar extensions.colorMeta / typographyMeta. Helpers for color,
  typography, radii model-building. Parsed-md narrative synthesis
  survives as a fallback when no sidecar.
- DESIGN.json rewritten at schemaVersion 2: extensions.{colorMeta,
  typographyMeta, shadows, motion, breakpoints} + components (with
  refersTo pointing back to frontmatter component keys) + narrative.
  Token primitives no longer duplicated in the sidecar.

Build fix:
- scripts/build.js:634 wiped .claude/skills/ (and every other harness
  dir) on each rebuild, then recopied from dist. After commit b0feed0
  unbundled per-project config.json from dist, the sync destroyed the
  user's live-mode config on every build without replacing it.
- Added stashPerProjectArtifacts / restorePerProjectArtifacts in
  scripts/lib/utils.js. Hoisted PER_PROJECT_SCRIPT_ARTIFACTS to a
  module-level export so build.js and readSourceFiles share one
  source of truth. Build now preserves config.json across the sync.

Verified in browser: panel renders 10 colors, 9 typography roles, 3
shadows, 6 grouped components, 9 rules, 25 do/don't items, all merged
correctly from frontmatter + v2 sidecar with zero console errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 12:09:55 -07:00
Paul BakausandClaude Opus 4.7 a5cd7bf859 refine(skill): harden critique against skipping sub-agents and [Human] overlay
Two loopholes in the critique procedure let the model shortcut past its own rigor: "SHOULD delegate" with a broad "if sub-agents are not available" escape, and "Browser visualization (when available)" framing that made the [Human] detector-overlay tab read as optional color. Both get rationalized away under context pressure even though the isolation is what makes the combined score honest and the overlay is the user-facing deliverable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 11:02:39 -07:00
Paul BakausandClaude Opus 4.7 e1cfba0f25 feat(design): align DESIGN.md with Stitch's frontmatter spec
Adopt the Stitch google-labs-code/design.md format's two-layer shape:
YAML frontmatter carries machine-readable tokens, prose body covers the
six canonical sections. The sidecar DESIGN.json now extends frontmatter
rather than duplicating it.

- document.md: new frontmatter section, Step 2b staging, sidecar scoped
  to extensions, Do's/Don'ts now cite PRODUCT.md anti-references by name,
  OKLCH vs hex presented as project posture not mandate.
- design-parser.mjs: tiny YAML-subset reader, exposes model.frontmatter,
  schemaVersion bumped to 2, prose-scraping fallback intact.
- live-browser.js: "basic view" CTA copy reflects frontmatter-first model.
- DESIGN.md: add frontmatter with 10 colors, 9 type roles, 7 components;
  OKLCH values direct per The OKLCH-Only Rule.
- tests/design-parser.test.mjs: coverage for no-frontmatter, Stitch-shape,
  missing-terminator, comment handling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 09:30:00 -07:00
Paul BakausandClaude Opus 4.7 a95212aa87 feat(site): Designing orientation page with compass loop
New /designing page: the missing "how do I actually use this tool?" story,
organized around a four-phase core loop. Start, iterate, polish, maintain.
Text-dominant editorial sections, cards are rare, spacing pulls from
DESIGN.md's scale only.

Hero runs as a single editorial spread: title and lede on the left,
a compass loop on the right. The compass is a thin ink circle with
cardinal phase labels at N/E/S/W, clockwise arrow glyphs at 45°
transit points, a slow orbiting accent dot, and an italic impeccable
wordmark in the middle. Collapses to vertical stack below 880px.

Phase sections below the hero are full width. Each opens with a
numbered eyebrow, italic Cormorant title (spine), and a 60ch sub.
Appendix sections (register cross-link, DESIGN.md interop, what to
avoid, CTA) use a quieter title tier in roman to separate spine from
supporting material. All separators are 1px mist hairline, no 2px
black rules; rhythm comes from variable padding and margins.

Adds /live-mode hero-frame polish, new top-nav entry (Home · Designing
· Docs · Anti-Patterns · Live · Overlay), footer link, and the server
route for /designing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 09:13:15 -07:00
Paul BakausandClaude Opus 4.7 b0feed06c9 feat(live): config globs + drift-heal warning for multi-page projects
Config drift was a real tripwire for projects with static generators: new
HTML files get added, never make it into config.files, silently skip
injection. Two additions.

config.files entries now accept glob patterns (**, *, ?) expanded via
fs.globSync in live-inject. Multi-page projects can write
["public/**/*.html"] once and never maintain the list again. New optional
exclude field filters out matched files (email templates, demo fixtures).
HARD_EXCLUDES of node_modules and .git are enforced regardless of user
config so vendor trees can never receive a tracking script.

live.mjs now runs a drift scan after inject: walks common page-source
roots (public/, src/, app/, pages/) and reports HTML files not covered
by the resolved inject targets. Respects user excludes so intentional
omissions aren't flagged. Output JSON carries configDrift: { orphans,
orphanCount, hint } or null. live.md documents the agent flow for
surfacing drift to users without auto-mutating the config.

Unbundle config.json from the distributable skill: it's a per-project
artifact, not skill code. readSourceFiles now skips any PER_PROJECT_ARTIFACTS
during source scan so build output to .claude/ .cursor/ etc never ships
one project's inject targets to another's install. The per-harness
copies stay gitignored via the existing **/skills/impeccable/scripts/config.json
rule; each consuming project writes its own on first /impeccable live.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 09:12:56 -07:00
Paul BakausandClaude Opus 4.7 6846a135ce refine(live): skip screenshot upload when no annotations present
Without comments or strokes the screenshot is pure visual anchoring,
biasing the model toward the existing rendering and fighting the
three-distinct-directions brief. Local blob still drives the shader
overlay; upload and screenshotPath are gated on annotation presence.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:19:00 -07:00
Paul BakausandClaude Opus 4.7 d60de0fb97 refine(site): restructure "Get Started" around the skill first
Step 1 is now skill-only (the outdated "powers visual mode" claim
and the global CLI install are gone). Chrome extension stays at
step 3, the CLI moves to a new optional step 4 with a link to npm,
and "Stay updated" shifts to step 5. Step 2 collapses its three
numbered sub-steps into a compact command/caption table so the
"Use it" panel matches step 1's height instead of towering over it.

Also: keep code chips and the npm link on one line each (nowrap)
so the caption wraps only on natural prose.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 19:38:33 -07:00
Paul BakausandClaude Opus 4.7 2658baa461 refine(site): adapt "The Case" to mobile as horizontal pill strip
Vertical 8-tab list ate ~430px of viewport on mobile; panel content
sat far below the fold. At ≤900px, the tab list becomes a sticky
horizontal scrollable pill strip (47px tall) under the header, with
edge-fade mask, pill-shaped buttons, ink-filled active state, and
the active pill auto-centered as rotation advances or users tap.
Desktop layout unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 19:21:37 -07:00
Paul BakausandClaude Opus 4.7 3ffc485a8d refine(site): "The Case" crossfade + content cleanup, pattern-tabs scroll
- why-panel tab swap is a proper opacity+transform crossfade (display:grid
  stack area) instead of display:none jump; 650/800ms ease-out.
- tab progress indicator animates linearly (timer, not eased).
- Panel 01: dropped redundant "Every command reads this…" footer; moved the
  commands meta into the visual as a right-side sidebar aside PRODUCT.md.
- Panel 02: "Browse the full catalog →" moved under the Gallery of Shame;
  pattern category tabs are now always a single-row horizontal scroll with
  JS-tracked edge-fade mask and chevron affordances; click centers the
  selected tab inside the strip (never scrolls the page).
- Panel 04: "register" → "mode" in body, labels, meta for plain-language.
- Panel 05: removed redundant "Works in Claude Code…" meta.
- Panel 06: removed "Spec-compliant. Interoperable. Not a proprietary
  sidecar." meta.
- .language-content grid gap reduced from --spacing-lg to --spacing-sm so
  the commands palette sits closer to the section lead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:47:33 -07:00
Paul BakausandClaude Opus 4.7 2341fe3637 feat(live): tune popover, theme-aware bar/picker, action icons, params system
Adds a coarse-controls ("Tune") popover that slides out from behind the
contextual bar via clip-path, showing 2-4 per-variant knobs (range / steps /
toggle) driven by a `data-impeccable-params` manifest. Range/toggle drive
CSS custom properties on the variant wrapper; steps toggle a data
attribute. Values reset per variant; on accept, current values are passed
through live-poll to live-accept as an `impeccable-param-values` comment
for the carbonize cleanup step to bake in.

Other live-UI work in this change:
- Theme-aware palette (barPaletteForTheme) now drives the contextual bar,
  action picker, and tune popover. Dark sand on light pages, paper on
  dark. Detection has a localStorage dev override for QA.
- Action picker chips get inline SVG icons (wand / bars / funnel / sparkle /
  type ramp / circles / grid / devices / curve / star / bolt) stacked
  above the label; selection state recolors via currentColor.
- Accept button switched to saturated site magenta with paper text.
- Cycle dots reworked: solid accent for active, neutral for arrived,
  hairline ring for pending. No more magenta-on-gray noise.
- Tune chip sits in the cycling row with a count pill badge; open state
  uses accentSoft bg + accent text (no ad-hoc white border).
- Popover uses the bar's palette with a deeper surface (surfaceDeep),
  lives behind the bar via z-index so a 6px overlap reads as tucked under
  it, and animates with clip-path inset() for reliable slide behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:17:17 -07:00
Paul BakausandClaude Opus 4.7 59769e316f redesign(site): restructure "The Case" tabs, wrap panels in card
- Reorder and relabel all 8 tabs (Prevents AI slop, Brand + Product design, Ships code not mockups, Fluent in DESIGN.md, etc.)
- Flip every panel to title → body → visual → meta so the claim leads and the visual serves as proof
- Move "The Case" section header above the tab layout, matching the other sections
- Let panel visuals span the full right column; cap only the text at 60ch
- Wrap the right-side content in a paper card; strip .why-visual chrome to avoid nested cards
- Add a heading to panel 02 (Prevents AI slop) so it matches the others

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:59:47 -07:00
Paul Bakaus 96c6565cae Improve image-guided shape and craft flows 2026-04-22 15:57:18 -07:00
Paul Bakaus 1a9e357632 restructure(site): Antidote → 8th tab inside The Case
Remove section 03 (The Antidote) entirely as a standalone section.
Bring the original pattern list (patterns-tabbed via /api/patterns)
and gallery-stack-container back intact, and move them into a new
8th tab in The Case: "Anti-patterns, caught". The new tab panel
shows the list + gallery side-by-side on wide viewports, stacked on
narrow. Keeps the existing app.js wiring (renderPatternsWithTabs
and initGalleryStack both auto-discover via IDs).

Section numbers renumbered: Case 04→03, Live 05→04, Install 06→05,
Changelog 07→06, FAQ 08→07. Sticky nav updated to match.
2026-04-22 14:54:09 -07:00
Paul Bakaus 1ac4cf91e2 refine(site): Antidote pairs use compact inline visual examples, not page screenshots
Replaced the tall page-screenshot "before" images with the
self-contained inline visual examples from the /anti-patterns
catalog (VISUAL_EXAMPLES dict). Three before/after pairs:

1. Gradient Text — purple-blue "Build the Future" gradient text vs.
   a solid italic Cormorant "Build the future." with magenta emphasis.
2. Side-Tab Cards — the ban signature, thick colored left stripe on
   an otherwise neutral card vs. a hairline-bordered card with a mono
   kicker and italic title.
3. Hero-Metric Template — the SaaS gradient "10M+" with tiny stats
   vs. a single "+18.4%" italic insight with real explanation and
   "from checkout fixes" causal tail.

Panels are now ~140px tall (was 4:3 aspect of full-page screenshots),
so the whole section is roughly half as tall. No large images, no
risk of the "Before" screenshots reading as Impeccable output. The
After panels sit on warm cream (brand surface) while the Before
panels use neutral cool white to further distance them visually.
2026-04-22 14:43:40 -07:00
Paul Bakaus 598c43cc05 refine(site): Antidote wall → before/after pairs
The "wall of three screenshots" alone looked, at a glance, like
examples of pages made WITH Impeccable. Wrong signal. Replace each
card with a vertical before/after pair: anti-pattern screenshot on
top, hand-built refined alternative underneath. Pair labels use
colored pills (before = crimson, after = green) on the image corners
so the contrast is immediate. Three pairs shown:

1. Purple Gradients → warm editorial hero (cream bg, italic serif
   title with magenta emphasis word, squared ink CTA, no gradient)
2. Cardocalypse → flat data table (mono labels, display numbers,
   green delta, hairline rules between rows, no nested boxes)
3. Hero-Metric Template → one honest insight (single 52px italic
   number with percent accent, body explanation mentioning the
   actual reason, subtle sparkline)

Lead copy updated: "Before, and what Impeccable steers toward instead."

After mocks are pure inline HTML/CSS — no new image assets needed,
no font dependencies, fully respect brand (Cormorant italic display,
Instrument Sans body, magenta accent, warm cream bg, sharp CTAs).
2026-04-22 14:39:09 -07:00
Paul Bakaus e436ed53df fix(site): Antidote foot meta in sentence case; fold Suggest into foot row 2026-04-22 14:36:25 -07:00
Paul Bakaus 826c6cda05 refine(site): Antidote as a visual wall of three anti-pattern examples
Previous layout was a wall of text (three numbered stat rows plus a
side gallery). User feedback: "seeing is believing." Drop the stats
entirely, lead with three big anti-pattern screenshots in a 3-up grid
(Purple Gradients / Cardocalypse / Hero-Metric Template), tight
one-line lead above, footer row with "Browse all 25" link + a small
mono meta line that still carries the key claims (deterministic rules,
LLM layer, browser overlay) in a single sentence.

Images use 4:3 aspect, hover lifts them slightly with a magenta-soft
outline glow, labels are italic display face + mono accent number.
The whole section now reads at a glance: headline + three pictures +
one link.
2026-04-22 14:33:15 -07:00
Paul Bakaus bae622107a fix(site): Antidote row left-aligned (drop inherited justify-content: center) 2026-04-22 14:30:33 -07:00
Paul Bakaus 6e9a437920 refine(site): Antidote content + layout pass
Content: rewrite the stats to actually sell the differentiator.
- Row 1: "25 Named anti-patterns" — vs. the short prose lists in
  comparable skills (Anthropic's frontend-design, assorted "AI design"
  skills).
- Row 2: "2 Detection layers" — deterministic (source + DOM, no LLM)
  + LLM semantic pass via /impeccable critique.
- Row 3: "3 Surfaces" — CLI for PR gating, Chrome extension on any
  page, overlay inside /impeccable critique.

Lead copy amplified: "list a handful of don'ts in a document and hope
the model remembers" vs. "actively detects them."

Layout: grid-template-columns is now minmax(0, 1fr) minmax(320px,
420px), align-items: start. The row fills the section's full width,
left column sits left-aligned against the intro text, gallery stays
to the right in its natural 320-420px range. Stat numbers shifted
to magenta accent + bolder 3rem italic, rows separated by a dashed
hairline, with bold ink lead-words ("Named anti-patterns" etc.) in
the descriptions for scannability.
2026-04-22 14:27:17 -07:00
Paul Bakaus ec0bbda5e1 refine(site): bigger/bolder live-mode support column headers 2026-04-22 14:21:36 -07:00
Paul Bakaus e28ab98628 copy: live mode 'start' cell, drop verbose stop command 2026-04-22 14:13:44 -07:00
Paul Bakaus 32b81aebcd refine(site): live mode support list copy pass + drop phase caption + border
- Drop the black rule above the support list (it read as a hard hat
  over the column).
- Drop the phase-caption strip below the frame (the demo already
  carries its own state visually; the narrated loop underneath was
  noise).
- Rewrite the four support cells with stronger POV:
  - Start: the actual commands, /impeccable live and stop.
  - Your agent, your code: emphasises that it's the same AI you've
    been working with, reading your real project context, generating
    real edits.
  - Accept = write: canvas-like iteration, production output. The
    point almost no other AI design tool can claim.
  - Skill-native: no external tools or IDE extensions, works across
    every harness the skill targets.
- Drop CSP-patch bullet (edge case, not worth homepage real estate).
- Drop the "Accepts into ... via HMR" framing — accepts work because
  of the skill loop, not because of HMR per se.
2026-04-22 14:12:52 -07:00
Paul Bakaus ae6fa32318 refine(site): fold Language subtitle, split Antidote, Live Mode layout + variants
Four responses to feedback:

1. Language: fold "Pick any command to see it in action / Full command
   reference →" into the section-lead. The whole commands-header row
   is gone; the fisheye panel now sits much closer to the lead copy.

2. Antidote: full two-column editorial split. Left column carries the
   lead + a three-row stats block (25 / 6 / 3) and the "Browse full
   catalog" CTA as a squared editorial link. Right column is the
   Gallery of Shame card stack. No more empty right-of-gallery void.

3. The Case: tab labels switch from Cormorant italic serif to body
   sans. Tab numbers stay mono. Less aesthetic noise, cleaner reading.

4. Live Mode:
   - Layout split: the frame now sits in the left column with its
     caption; the support list ("Works on / Accepts into / CSP /
     Overlay") moves to a narrow right column, rule-delimited, with
     magenta-kicker-above-ink style per cell. Under the support list
     on desktop, stacks on mobile.
   - Skeleton page: nav bar (logo + 3 links + CTA) plus heading +
     body lines drawn in at 30% opacity behind the target card, so
     the iteration reads as "I clicked an element on an actual page."
   - Variant cards rewritten with distinct design language per card:
     V1 editorial column (rule-top, italic display, text-link button),
     V2 brutalist ink ticket (ink slab, magenta accent stripe, stamp
     glyph, sans bold), V3 playful dashed postcard (stickers, italic
     Cormorant, pill CTA). The demo now actually shows three visually
     different directions instead of three similar cards.
2026-04-22 14:03:32 -07:00
Paul Bakaus fe6186bb96 feat(site): shrink Antidote, faithful two-bar Live Mode demo
Antidote (task b): drop the duplicated pattern tabs (content lives on
/anti-patterns for the full list). Keep the Gallery of Shame as the
signature visual and add a "Browse the full catalog" link underneath.
Section is roughly 40% shorter vertically.

Live Mode demo (task faithful): rewrite to match the real picker flow
one-for-one. Now two separate bars:
- Global bar: dark pill, bottom of the frame, persistent for the
  whole session. / + Pick (active) + Detect + DESIGN.md + divider + ✕.
- Contextual bar: LIGHT paper-backed pill that floats above the
  picked element, mirroring buildConfigureRow() in live-browser.js.
  Dark command pill, transparent input, ×N count, magenta Go button.
  Morphs through configure → generating (spinner) → cycling (prev /
  counter / next / discard / Accept) → accepted (green ✓ written to
  source).

Contextual bar positions itself below the target when space allows,
else above. New JS positionCtx() mirrors the real positionBar()
logic. prefers-reduced-motion still freezes on a representative
"cycling, variant 3" still.

Timeline unchanged in steps but step captions updated to match the
new two-bar split ("Contextual bar appears." etc.).
2026-04-22 13:48:46 -07:00
Paul Bakaus 6743fe2e9d feat(site): Palette/Periodic toggle in Language, rename Why → The Case
Two homepage tweaks:

- Section 02 (The Language) gets a pill-style Palette / Periodic view
  toggle at the top-right of the intro row. Palette (fisheye command
  gallery) is default; Periodic (the periodic table) is behind a tab
  click. Collapses the section's vertical footprint substantially
  without losing the signature periodic-table visualization.
- Section 04 renamed "Why Impeccable" → "The Case". Matches the
  editorial voice of "The Language" / "The Antidote" / "The Editors"
  (changelog signature). Sticky nav label Case.
2026-04-22 13:41:34 -07:00
Paul Bakaus d72ac00226 feat(site): replace Visual Mode section with Live Mode + interactive demo
Section 05 is now "Live Mode" with a self-contained real-DOM animated
demo loop. Browser chrome + stage + picker bar + simulated cursor,
running through the full Live Mode flow:

  hover → outline → click → open command picker → pick "delight" →
  type "more playful" → draw stroke → Go → generating → three
  variants cycled → accept → write-to-source confirmation → reset

Three distinct card variants morph in place so the cycling reads as
real iteration, not a slideshow of the same card. Picker bar states
(idle / configuring / generating / cycling / accepted) are driven by
data-phase and matching CSS selectors. Simulated cursor animates
between targets on an expo-out curve; annotations draw a scribble
stroke and pop a comment tag. IntersectionObserver gates the timeline
so it only plays while visible, and prefers-reduced-motion freezes
on a cycling / variant 3 still.

Supporting row below the frame adds the three honest limitations:
frameworks supported (Vite/Next/SvelteKit/Astro/Nuxt), writes to real
source via HMR, CSP-strict apps get a one-time dev-only patch.

Top-nav rename "Visual Mode" → "Overlay" (points at /visual-mode
which is still the overlay-focused sub-page). Sticky section-nav
updated too.

New file: public/js/components/live-demo.js with the timeline driver.
2026-04-22 13:33:14 -07:00
Paul Bakaus bd25de9801 refine(site): group Why heading with tabs, cleaner borders, visible progress fill
Three polish items on the Why Impeccable section:

- Move the "04 / Why Impeccable" section header into the left column
  next to the tab list. The right-side panel now aligns vertically
  with the heading instead of sitting offset below it.
- Replace the outer border-top + per-tab border-bottom with an
  adjacent-sibling combinator (li + li). No more hairline above the
  first tab or below the last one.
- Rework the active-state progress bar. Previously the is-active
  border-left-color was fully magenta, which hid the countdown
  animation underneath it. Now the active state is driven entirely
  by a 2px progress column that either sits full-height (paused or
  user-selected) or fills from top to bottom over the cycle window
  on a gentle ease curve. The countdown is actually visible now.
2026-04-22 13:25:35 -07:00
Paul Bakaus d7f7df1b80 fix(live): subtle focus ring on global-bar icon buttons
Default browser focus-visible ring was a heavy blue outline that
clashed with the dark capsule. Inject a palette-aware inner box-shadow
ring (accentSoft + accent) scoped to the global bar buttons via a
one-time <style> tag. Keeps keyboard focus visible without the
visual noise.
2026-04-22 13:19:08 -07:00
Paul Bakaus 37c1a364a4 fix(live): Escape in PICKING also resets the global bar's Pick button
Previously Escape while picking just hid the highlight and set state
to IDLE, leaving the global bar's Pick button visually active. Next
pick attempt fell into a broken state where the button looked on but
no picker ran. Route through togglePick() so the flag, the button,
the UI, and the state all flip together.

Small copy fix on Why panel 04: Figma stamp now reads "last touched
Q3 2025" (was 2024; should reflect closer to the current calendar).
2026-04-22 13:16:41 -07:00
Paul Bakaus af50f22d57 chore(skill): slim per-event carbonize reminders
Previous hardening emitted a 7-line todo array and a 10-line ASCII-bar
stderr banner on every accept event, both printed to the agent's
transcript. Per-event overhead added up fast on multi-variant sessions.

Keep the three-layer defence but shrink the per-event noise:
- todo is now a single string: "REQUIRED before next poll: carbonize
  cleanup in FILE. See reference/live.md ..."
- stderr is one line with the same pointer.
- reference/live.md keeps the full five-step checklist (loaded once
  per session, so its verbosity is a fixed cost — no repetition tax).

Attention signal still triple-redundant: stdout todo, stderr line,
reference section.
2026-04-22 13:12:03 -07:00
Paul Bakaus f38a8d40ee chore(skill): harden post-accept carbonize cleanup so the agent can't skip it
After an LLM-triggered session where the carbonize cleanup got skipped
entirely (the instruction was buried as a single bullet among four
cases in live.md, and `_acceptResult.handled: true` felt like a
"done" signal), add three redundant reinforcements:

1. live-accept.mjs now emits a `todo` array on the event payload when
   carbonize is true, listing all five cleanup steps plus a pointer to
   reference/live.md. The agent reads this as part of the event JSON.

2. live-poll.mjs prints a loud multi-line stderr banner on every
   carbonize=true event. Even agents that parse only stdout JSON see
   stderr output and can't treat the event as handled without action.

3. reference/live.md pulls the carbonize branch out of the "Handle
   accept" bullet list into a dedicated "Required after accept
   (carbonize)" section with a numbered five-step checklist, a
   rationale paragraph for why skipping is bad, and an explicit
   "do not poll again until the file is clean" instruction.

The three layers are deliberately redundant: a future LLM that ignores
the reference text should still be caught by the stderr banner or the
in-event todo, and vice versa.
2026-04-22 12:42:10 -07:00
Paul Bakaus 239b75ddad chore(site): post-carbonize cleanup for Why panel 04
Per reference/live.md's carbonize instructions (which I should have
followed immediately after the accept event):
- Moved the inline @scope CSS into public/css/workflow.css, rewritten
  to use the real .why-visual--v2 class instead of
  [data-impeccable-variant="2"].
- Removed the <style data-impeccable-css="afb9794d"> block from index.html.
- Removed both impeccable-carbonize-start / -end marker comments.
- Unwrapped the <div data-impeccable-variant="2" style="display: contents">
  wrapper around the accepted variant content.
- Dropped the dead @scope rules for variants 1, 3, 4 entirely.

Only load-bearing element classes remain (why-visual--v2, v2-side,
v2-label, v2-canvas-*, v2-new-*) and they're all styled via the
central stylesheet now.
2026-04-22 12:38:56 -07:00
Paul Bakaus eeb35d42f4 refine(site): fix Why section alignment + rework panel 04
Alignment: .why-section had its own max-width + horizontal padding,
which made the whole section sit inside a narrower column than every
other section. Drop the extra wrapping; match the .antidote-section /
.visual-mode-section pattern (padding: 2xl 0, border-top).

Panel 04: scrap the "your IDE + AI agent" browser-chrome surface
(confusing, implied a code editor with a fake browser top bar, and
never mentioned what the point actually is). Replace with two side-by-
side tiles: dark "Your AI agent" tile + warm "Your browser / Live Mode"
tile, joined by a magenta "+" glyph. The line underneath lands the
point: two surfaces you already have open, zero handoff.
2026-04-22 12:25:18 -07:00
Paul Bakaus 023bf69882 refine(site): rework Why visuals for panels 01, 03, 04, 05
Addressing feedback that the first round didn't sell the differentiation:

01 Intentional design: scrap the "generic AI card vs warm card" comparison
(the slop card actually looked good, which undermined the point). Show
PRODUCT.md as a real file with fields filled in (Users, Register,
Brand voice, Anti-references) and a pulse-dot footer signaling it's
loaded on every command. The point is that Impeccable knows specific
things about the product before it designs anything.

03 Production codebases: expand the terminal from a single "reading
DESIGN.md" line to a full codebase walk. Tokens from tokens.css,
merged tailwind theme, 34 components listed with variants inferred,
DESIGN.md as one of many inputs. DESIGN.md is shallow; the real work
is traversing source.

04 Where you code: scrap the card grid. Replaced with a two-row
waterfall-vs-unified flow. Old way (greyed, strikethrough): Figma →
Handoff → Ticket → Engineer → QA drift. New way: a single surface
with Design / Code / Ship chips inside a fake IDE chrome. Pulls the
real argument instead of just showing logos.

05 DESIGN.md: picker-sidebar-style rich tiles. Accent color with
8-step tonal ramp, Aa specimens for display + body, component chips +
input preview. Stitch spec badge up top. Matches the visual vocabulary
of the live-mode DESIGN.md sidebar.

Panels 02, 06, 07 unchanged per feedback that they were working.
2026-04-22 12:18:44 -07:00
Paul Bakaus 03a1953ba7 feat(site): add visuals + auto-rotate to Why Impeccable
Per-panel storytelling visualizations, pure HTML/CSS, no image assets:

- 01 Intentional design: "Generic AI" dark/purple gradient card vs.
  warm editorial card with /impeccable vocabulary side by side.
- 02 Brand and product, both: tiny brand mock (italic display headline)
  vs. product mock (mono/stats rows).
- 03 Production codebases: dark terminal showing /impeccable polish
  reading DESIGN.md tokens and component APIs.
- 04 Where you code: prompt bar with blinking caret + 4×2 grid of
  harness logos (Claude, Cursor, Codex, Gemini, Copilot, Antigravity,
  Kiro, OpenCode).
- 05 DESIGN.md: a file-view of the six Stitch sections with a
  "Stitch spec" badge, plus an interop tagline.
- 06 CI/CD: terminal showing `impeccable detect` failing CI with
  three issues and exit 1.
- 07 Chrome extension: browser chrome + floating extension popup
  listing detections and two magenta outline boxes over "page content".

Auto-rotation: 7s per tab, pauses on hover, stops entirely on any
click/keyboard interaction (user-initiated navigation wins). Thin
magenta progress bar animates on the active tab's left accent as
the rotation progresses. IntersectionObserver gates the whole timer
so it only runs while the section is on screen. prefers-reduced-motion
disables the auto-rotation and the progress animation.

Dropped the "Seven reasons..." lead line.
2026-04-22 12:05:19 -07:00
Paul Bakaus e631074a65 feat(site): Why Impeccable section with tabbed feature loop
New section 04 with a vertical tab list on the left and a panel on the
right. Seven items covering the actual differentiators:

1. Intentional design — PRODUCT.md + DESIGN.md + shape as the opposite
   of one-shot-mock tools.
2. Brand and product, both — the register split, called out against
   frontend-design-style single-mode skills.
3. Production codebases — reads your design system, daily-driver.
4. Where you code — IDE-native, no canvas, no handoff.
5. DESIGN.md — Google Stitch spec-compliant, interoperable.
6. CI/CD-ready — CLI + JSON output for PR gates.
7. Chrome extension — 25 checks, one click, any page.

Section numbers bumped: Visual 04→05, Install 05→06, Changelog 06→07,
FAQ 07→08. Sticky nav updated to match.

Arrow-key tab navigation, aria-selected, hidden attr on inactive
panels, subtle fade on switch, respects prefers-reduced-motion.
Vertical layout on desktop, stacked on mobile.
2026-04-22 11:59:02 -07:00
Paul Bakaus d03dca1209 fix(site): restore Antidote section with curated anti-pattern list
The DO/DONT extractor in readPatterns stopped finding anything after
the skill went human-authored (new SKILL.md uses plain bullets, one-word
section headings, no DO:/DON'T: prefixes). Replace the extractor with
a hand-curated category list: six categories, three or four DOs and
DON'Ts each. Editorial tone, tight, deliberately a teaser — the full
catalog still lives on /anti-patterns.

The legacy SKILL.md parser is retained in the file as _legacyReadPatterns
in case we want to revive it later with a different format.

Small CSS fix: .faq-question was display:flex with justify-content:
space-between, so inline <code> in a summary got treated as its own
flex item and pushed apart. Switched to relative+absolute positioning
so the + icon sits in the right margin and text flows naturally.

Changelog font size reduced to 0.9375rem for the expanded v3.0 entry.
New FAQ entry on pinning standalone commands back. Pin section added
to /docs/impeccable editorial.
2026-04-22 11:52:40 -07:00
Paul Bakaus 549f92577c feat(site): v3.0 changelog expansion, pin FAQ, stale-ref cleanup
- v3.0 changelog now covers 6 items (was 2): Live Mode, PRODUCT.md,
  DESIGN.md (with Google Stitch spec compliance), brand/product
  registers, plus the existing consolidation + pin/unpin.
- New FAQ entry answering "how do I get /critique back" via pinning.
- Added Pin commands back as shortcuts section to /docs/impeccable
  editorial so it's findable outside the FAQ.
- Reduced changelog item font size (0.9375rem) to match length.
- Swept user-facing copy: .impeccable.md -> PRODUCT.md, removed three
  stale npx impeccable live references (the CLI subcommand no longer
  exists; live is /impeccable live inside the skill).
- Historical v1.5.0 changelog entry preserved as-is (period-accurate).
2026-04-22 11:28:19 -07:00
Paul Bakaus 7e473e48d0 fix(site): instant hash restore, retry on fonts.ready + load, drop smooth-scroll
Three related site scroll bugs:

1. initAnchorScroll and initHashTracking both called scrollTo with
   `behavior: 'auto'`, which defers to CSS `scroll-behavior`. Because
   sub-pages.css set `html { scroll-behavior: smooth }`, every anchor
   jump and reload-hash-restore animated — despite a code comment
   explicitly stating "Instant anchor scroll — no smooth scrolling".
   Switch to `behavior: 'instant'` so the JS wins.

2. The reload-hash restore used a fixed `setTimeout(100)` to compute
   target position. At 100ms, async Google Fonts (Cormorant Garamond
   italic) has not swapped in, so `getBoundingClientRect().top` is
   computed against fallback metrics and mislanded by hundreds of
   pixels. Retry on `document.fonts.ready` and on window `load`.

3. Remove `scroll-behavior: smooth` from sub-pages.css entirely —
   it was silently fighting the JS and made long-page anchor clicks
   feel sluggish.
2026-04-22 10:57:24 -07:00
Paul BakausandClaude Opus 4.7 bd86147d70 refactor(live): move scroll-restore out of injected inline back into live.js
The inline pre-restore wasn't actually fixing a timing issue — the
fix was the fonts.ready + load retries. Since live.js's own
top-level block runs before DOMContentLoaded and we can do the same
retries there, we don't need an inline script injected into every
user page. Simpler HTML, single source of truth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 10:55:32 -07:00
Paul BakausandClaude Opus 4.7 f0f2935547 fix(live): retry inline scroll-restore on fonts.ready and load
scrollTo(y) clamps to the current document.scrollHeight, which is
several hundred pixels short of the final value until async-loaded
fonts swap in (Cormorant Garamond italic grew consulting-section
layout by ~585px in the logs). The initial synchronous scroll was
clamping to ~6165 even though the Go-time target was 6749.5.

Retry on document.fonts.ready and on the window load event, both of
which fire once the document reaches its final height.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 10:48:13 -07:00
Paul BakausandClaude Opus 4.7 fb78ec4553 fix(live): inject inline pre-restore script so scrollY wins vs browser
external live.js is fetched, so by the time it runs the browser has
already queued its reload-scroll animation and history.scrollRestoration
='manual' has no effect. Inject a tiny inline synchronous <script> into
the same block live-inject writes, BEFORE the external live.js tag. It
sets scrollRestoration='manual' and does window.scrollTo(0, savedY)
during HTML parse — before the browser can animate anywhere.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 10:45:39 -07:00
Paul BakausandClaude Opus 4.7 5e04a9f25a fix(live): don't clear scroll key inside stopScrollLock
startScrollLock calls stopScrollLock at the top as a reset. I had
clearScrollY() inside stopScrollLock, so every Go sequence was:
writeScrollY(6749.5) → startScrollLock → stopScrollLock → clearScrollY
— the persisted value was wiped right after being written, so resume
after reload read null and locked to 0.

Move clearScrollY to the three genuine session-end sites (hideBar
error path, confirmed/accept, cleanup/discard). stopScrollLock no
longer touches persistent storage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 10:41:15 -07:00
Paul BakausandClaude Opus 4.7 868d8c4126 fix(live): separate scroll-key, pre-empt browser, snap on every scroll
Three concrete bugs from the diagnostic logs:

1. saveSession was writing scrollY alongside state, so every call during
   resumeSession clobbered the Go-time value with whatever the browser
   had left us at (typically 0). Move scrollY to its own localStorage
   key, touched only at Go and on user-scroll reanchor.

2. history.scrollRestoration='manual' was being set inside init() at
   DOMContentLoaded — by then the browser has already started animating
   its restore, especially with scroll-behavior: smooth on html. Apply
   it at script parse time, and apply the saved scrollY immediately
   there too, before the browser's animation starts.

3. Corrections only fired on MutationObserver. A programmatic smooth
   scroll (browser restore animation, or another script calling
   scrollIntoView) produces zero DOM mutations — so we never caught it
   walking scrollY from 0 up to 4800+ in the recorded session. Snap
   back on every scroll event, gated by a 250ms user-gesture window so
   we don't fight real user scrolls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 10:35:00 -07:00
Paul Bakaus a6aa98c616 chore(live): add diagnostic logging to scroll lock
Log target-Y at Go, every mutation that triggers a correction (with the
mutation type + added nodes), every correct-or-noop (with from/to/delta),
every reanchor, and every external scroll event >5px. Lets us see which
step is actually moving the page during wrap / variant insert.
2026-04-22 10:28:48 -07:00
Paul BakausandClaude Opus 4.7 565381a3e7 fix(live): pin window.scrollY instead of element viewport top
Element-based scroll tracking broke every time: Bun's HMR destroys the
target element, the browser's scroll anchoring picks a different nearby
element (e.g. the #downloads CTA) as its new anchor, and the page jumps
to wherever that surviving element is. My element-based correction then
computes against a replaced DOM node with stale / wrong geometry.

The primitive the user actually cares about is window.scrollY — they
want the page to stay where it is, regardless of which element survives
the patch. Pin scrollY directly: capture it at session start, restore it
on every mutation inside the wrapper, re-anchor on user scroll, store it
in saveSession for reload-resume.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 10:25:40 -07:00
Paul BakausandClaude Opus 4.7 1e533e535a fix(live): disable browser overflow-anchor during session, always correct
Two things were wrong. First, I capped large corrections — which was
backwards: a huge delta is exactly when we most need to restore (it
means the browser's own scroll anchoring drifted, which is what makes
the page 'jump to Get Started' when Bun's HMR destroys and re-inserts
our target). Remove the cap so any delta is corrected.

Second, the browser's built-in scroll anchoring was competing with us:
when Bun destroys our target element, the browser picks the nearest
surviving element (like a CTA anchor in another section) as its new
scroll anchor and scrolls to keep THAT stable. Disable overflow-anchor
on html and body for the duration of the session so we own scroll
entirely; restore the original values on stopScrollLock.

Kept the user-scroll grace window (400ms): wheel / touch / arrow keys
re-anchor and suppress corrections, so momentum scrolls don't get fought.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 10:20:51 -07:00
Paul BakausandClaude Opus 4.7 b99ab4db2c fix(live): scope scroll lock to session wrapper, let user scroll cancel corrections
Watching document.body caught every mutation on the page — shader
animations, Bun HMR indicators, tooltips, anything — and fired a
correction on each one, which fought the user when they tried to scroll
mid-session. Now the observer only responds to mutations inside the
session's wrapper. On user scroll intent (wheel / touchstart / touchmove
/ arrow & page keys), cancel any pending rAF correction and re-anchor
to the element's new position, so momentum scrolls don't get yanked
back by a stale correction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 10:15:23 -07:00
Paul BakausandClaude Opus 4.7 ad17880af1 fix(live): observer-based scroll lock holds element at viewport top during session
The reload-only fix missed the primary case: Bun's HTML loader hot-patches
the DOM in place rather than doing a full page load, so the resume
codepath never ran and the browser's scroll drifted wherever Bun's patch
left it. Likewise variant cycling (taller → shorter) and agent-driven
variant inserts both mutate layout without a reload.

Add a scroll lock: on Go (and on resume after a true reload), capture the
selected element's viewport-top and install a MutationObserver on body
that re-measures the target and corrects scroll on every batch of DOM
mutations. The target is re-resolved each pass via sessionId + visible
variant, so it survives DOM swaps that invalidate `selectedElement`.
Scroll intent events (wheel, touchstart, arrow/page keys) re-anchor to
the new position so we never fight a user who scrolls during a session.
Also set `scrollRestoration = 'manual'` at init so true reloads don't
land the user somewhere odd before our correction runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 10:08:00 -07:00
Paul BakausandClaude Opus 4.7 4f4df85250 fix(live): restore scroll to element's viewport-relative top after reload
When HMR misses and we fall back to window.location.reload(), the native
scroll restoration landed the page somewhere near the right region but
not on the selected element, because layout had shifted between the
save and the reload. Capture the element's getBoundingClientRect().top
into the session snapshot, disable native scroll restoration on resume,
and manually scroll the element back to that exact viewport-relative
position. Run a second correction pass after fonts and images settle to
absorb late layout shifts without animating the fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 09:58:30 -07:00
Paul BakausandClaude Opus 4.7 101dc50362 fix(live): resolve canvas background from ancestors when element is transparent
Screenshotting a transparent container rendered black because we were no
longer passing `backgroundColor` to modern-screenshot at all (to avoid
its `background-color !important` override on elements with their own
bg, like the teal card). That fix left elements without their own bg
rendering on a transparent canvas, which reads as black wherever the
PNG is previewed.

Now we resolve per-element: if the element has an opaque
background-color or a background-image, omit the option (element's own
bg renders, no override). If it's transparent, walk up ancestors to the
first opaque background (falling back to body/html) and pass that as
the canvas fill.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 09:47:08 -07:00
Paul BakausandClaude Opus 4.7 1ba75a820e fix(skill): 3 review-bot findings from EAC PR
1. cleanup-deprecated: strip `i-` prefix before fingerprint lookup so
   `i-harden` / `i-optimize` classify correctly (regression from the
   prefixed-naming migration).

2. build: substitute `{{scripts_path}}` in reference/*.md the same way
   it's substituted in SKILL.md. Previously the placeholder survived
   unresolved in built reference files, so any reference that told the
   agent to run a scripts path emitted a literal `{{scripts_path}}` to
   the shell.

3. live-poll: drop the `undici` import. Node's built-in fetch enforces a
   300s headers timeout that can't be lowered per-request, so we now cap
   each poll slice at 270s and loop internally until a real event or the
   caller's total timeout. Removes the hard `ERR_MODULE_NOT_FOUND`
   failure when undici isn't transitively hoisted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 09:32:31 -07:00
Paul BakausandClaude Opus 4.7 99494348bf fix(live): don't pass backgroundColor to domToBlob
modern-screenshot force-sets `background-color: X !important` on the root
clone's inline style when backgroundColor is passed, clobbering the
element's real background and rendering every captured card with the page
body color. Omit the option so the canvas stays transparent and the
element's own background renders into the foreignObject.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 02:05:22 -07:00
Paul BakausandClaude Opus 4.7 c7ee722472 fix(live): four HMR + React race bugs from Next 16 / Turbopack testing
Surfaced during hands-on testing against a real Next 16 + Turbopack app
(EACManagement). All four compound to produce unusable live iteration
for React users; fixed bottom-up because each one blocked testing the
next.

## 1. Picker bar snaps to (0,0) on first variant arrival

In startVariantObserver, `showVariantInDOM(sessionId, 1)` hides the
original via display:none but we never re-pointed selectedElement.
Next frame, getBoundingClientRect() on the hidden original returns a
zero rect and the bar positions at (0,0). Clicking Next masked the
bug because cycleVariant already calls updateSelectedElement.

Fix: after showVariantInDOM, re-point selectedElement via
pickVariantContent(wrapper, visibleVariant) — same call the no-HMR
fallback and updateSelectedElement already use.

## 2. React NotFoundError on accept/discard (Next 16 / Turbopack)

handleAccept and cleanup both called
`wrapper.parentElement.replaceChild(...)` eagerly, before the agent's
source rewrite had propagated through HMR. That yanks children out
from under React's reconciler; when React later tries to remove/replace
the wrapper, its fiber tree no longer matches the DOM and it throws.

Fix, both paths:
- cleanup (discard): `wrapper.style.display = 'none'` so variants
  disappear immediately, no structural DOM mutation.
- handleAccept: skip the eager replaceChild entirely. The accepted
  variant is already the only visible child of the wrapper thanks to
  the display: contents pattern; HMR cleans up the wrapper itself.
- Both paths schedule a 2s fallback replaceChild that runs only if
  HMR hasn't cleaned up — keeps static-server / no-HMR flows working.
- Capture sessionId + visibleVariant in closure variables before the
  1800ms cleanup timer zeros them, so the fallback still has context.

## 3. Server serves stale live.js forever

loadBrowserScripts() read live-browser.js once at startup into a
liveScript string. The /live.js handler served that cached string
with no cache headers. Every edit to the browser script was invisible
until a full server restart — silently broke the iteration loop on
fixes #1 and #2 for the user.

Fix:
- loadBrowserScripts returns { detectScript, livePath } — existence
  check only, no caching.
- /live.js handler re-reads livePath on every request and prepends
  __IMPECCABLE_TOKEN__ / __IMPECCABLE_PORT__ each time.
- Response headers: Cache-Control: no-store, no-cache, must-revalidate,
  max-age=0 + Pragma: no-cache.

detect.js stays cached — it rarely changes during a session.

## 4. Picker stuck in GENERATING when HMR doesn't fire

The only 'done' fallback fired when arrivedVariants === 0 and called
injectVariantsFromSource, which parses raw source via DOMParser. That
can't work for TSX/JSX/Vue/Svelte — JSX expressions aren't valid HTML.
If HMR flaked or was slow (500+ line inserts on Next 16 are prone to
this), state stayed in GENERATING and the spinner ran forever.

Fix: give HMR a 2s grace window, then `window.location.reload()`.
resumeSession already counts variants off the rendered DOM on load
and transitions straight to CYCLING — reload is the universal
recovery path that works for any framework, HTML, static server,
anything.

injectVariantsFromSource is now dead code on the 'done' path. Kept
for potential pure-HTML-no-HMR future use.

## Credit

Precise repro + root-cause diagnosis from the other agent in the
EACManagement session. #2 and #4 are the high-impact ones for Next 16
/ Turbopack; #3 is the meta-fix that made iterating on #1 and #2
possible at all.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 02:01:33 -07:00
Paul BakausandClaude Opus 4.7 89466e5d98 docs(product): add Register field (brand) to PRODUCT.md
Schema caught up after the editorial→brand rename. Without this,
the skill loader falls through to the "missing field — infer from
content" branch every session. Impeccable itself is firmly in the
brand register — distinctiveness is the bar, the voice lane is
editorial-magazine (serif + italic + long-form) chosen deliberately
from within that register.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 02:00:51 -07:00
Paul BakausandClaude Opus 4.7 bf6379a5d1 chore(skill): drop editorial→brand legacy alias
Pre-prod — no need to carry forward the backwards-compat line in
SKILL.md or the historical note in CLAUDE.md. Existing PRODUCT.md
files with `register: editorial` will hit the "missing field" branch
and get re-inferred from content, which is fine for the tiny number
of projects that touched it during iteration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 01:47:45 -07:00
Paul BakausandClaude Opus 4.7 562f7361c3 feat(skill): rename register from "editorial" to "brand"
"editorial" was doing semantic double duty — naming the strategic
distinction (design IS the product) AND a specific visual aesthetic
(editorial magazines, broadsheets, serif display, italic drop caps).
Models pattern-matched the aesthetic and defaulted to it on every
brand brief, producing magazine-shaped landing pages for hiking
brands, tech tools, restaurants.

The register name now describes the SURFACE KIND, not an aesthetic.
Brand covers every visual lane — tech-minimal, luxury, editorial-
magazine, consumer-warm, brutalist-grid, hand-drawn — each with
legitimate voice within the register.

## Changes

- `reference/editorial.md` → `reference/brand.md`. Content rewritten:
  broadened typography guidance (pairing shapes per brand genre,
  single-family commitment is valid), broadened color references
  (Stripe, Vercel, Liquid Death alongside Klim, Condé Nast), added
  a second slop test ("name your aesthetic lane") to prevent drift
  into editorial-magazine defaults, added brand ban against the
  drift itself.
- SKILL.md: register names brand/product; load brand.md.
- teach.md: register values brand/product; signals renamed; example
  principles no longer use "editorial over marketing" phrasing.
- Six sub-commands (animate/bolder/colorize/delight/layout/quieter):
  per-register subsections flipped Editorial: → Brand:.
- product.md: cross-references updated.
- live.md: register reference updated; density axis no longer uses
  "editorial" as a synonym for "dense".
- typeset.md: per-register paragraph generalised beyond serif+sans
  pairing.
- CLAUDE.md: architecture section rewritten; kept "editorial
  wrapper" content-authoring term as-is (different meaning).

## Legacy handling

- `editorial` is accepted as an alias for `brand` on PRODUCT.md's
  register field — agents treat it as `brand` without asking.
- Documented in SKILL.md setup section and CLAUDE.md.

## What's unchanged

- Register identification priority (task cue → surface → PRODUCT.md).
- Permission structure (brand can go big, product stays restrained).
- Shared design laws, absolute bans, color strategy vocabulary.
- Framework fixtures and tests.

Full build clean, test suite passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 01:44:41 -07:00
Paul BakausandClaude Opus 4.7 99cccc2f9b fix(live-inject): preserve indentation on remove, no orphan blank line
The insert path puts the block's opener line right after the anchor's
indent (e.g. six spaces plus </body>), which transfers the indent
onto the opener line and leaves the anchor unindented in the injected
file. The remove path consumed the block's trailing newline but left
the pre-block indent behind, producing two bugs in one:

  Before insert:  `      </body>`
  After remove:   `      \n</body>`  (orphan indent + unindented anchor)

Fix: capture `([ \t]*)` immediately before each marker and replace the
whole block (including its trailing newline and any trailing spaces on
the ender line) with just the captured indent. The indent now hands
itself back to the anchor line that follows — the file round-trips
byte-for-byte.

New tests/live-inject.test.mjs with four round-trip cases:
- HTML file with indented </body>
- JSX layout with indented </body> (EAC shape)
- Multi-file batch
- Column-0 </body> (no indent — already worked; regression baseline)

All four pass after the fix. Full suite clean via `bun run test`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 01:09:49 -07:00
Paul BakausandClaude Opus 4.7 e441e88cc1 feat(skill): strengthen editorial imagery guidance for weaker models
Gemini 3 Flash baseline showed the Unsplash bullet wasn't directive
enough — the model still dropped imagery entirely on italian-
restaurant and vintage-moto-forum niches when the brief clearly
implied photography.

Changes:
- Added a MUST-ship-imagery lead paragraph listing the niches that
  require photography (restaurant, hotel, magazine, etc.).
- Gave a literal Unsplash URL shape (`images.unsplash.com/photo-{id}?
  auto=format&fit=crop&w=1600&q=80`) with real photo-id examples, so
  weaker models have a concrete pattern to copy rather than inferring
  the URL format.
- Promoted "zero imagery on an imagery brief" into the Editorial bans
  list so it lands as a hard rule, not a nudge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 01:08:52 -07:00
Paul BakausandClaude Opus 4.7 cd8dbff014 fix(live-accept): handle JSX self-closing <style />, single-line variants, and same-line style blocks
Three related extraction bugs surfaced in the EAC session all rooted
in the line-based state machine:

1. `<style ... />` (JSX self-closing) had no separate `</style>` for
   the "skip until close" mode to exit on, so the state machine stuck
   and every `data-impeccable-variant` marker after it got missed.
   Accept reported `handled: false, error: "Variant N not found"`.
2. A variant whose entire `<div ...>...</div>` sits on one line had
   its body silently discarded — the marker line was `continue`d past,
   and the extractor started capturing from the next line, which
   usually belonged to a different variant or the wrapper close.
3. `extractCss` kept scanning for `</style>` after a self-closing
   opener, greedily swallowing every subsequent variant div as "CSS".
   Result: a mangled carbonize block stuffed with HTML and a duplicate
   variant rendered below.

## Fix

Replaced the line-based state machine with a string-based flow:

- `stripStyleAndJoin(lines, block)` returns the wrapper text with
  `<style>` elements fully removed. Handles self-closing, same-line
  open+close, and multi-line open/close. Markers inside CSS strings
  (e.g. `@scope ([data-impeccable-variant="1"])`) are gone by the
  time extraction runs — no false positives.
- `extractInnerByAttr(text, attrMatch)` is a balanced-tag matcher that
  walks the joined text finding `<TAG ...attrMatch...>…</TAG>` with
  proper depth tracking for nested same-tag elements. Handles
  single-line, multi-line, and deeply nested variants.
- `extractOriginal` and `extractVariant` are thin wrappers over the
  above.
- `extractCss` gets explicit same-line handling: returns null for
  self-closing (nothing to carbonize), extracts inner content via
  regex for same-line `<style>…</style>`, falls through to the
  existing multi-line path otherwise.

## Tests

New tests/live-accept.test.mjs with four cases — all failing before,
all passing after:

- Self-closing `<style />` with dangerouslySetInnerHTML
- Single-line `<style>…</style>`
- Multi-line `<style>...</style>` (regression baseline)
- Discard restores the original element after self-closing style

Wired into `bun run test`. Full suite passes.

Credit: precise repro + root-cause trace from the other agent in the
EAC session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 01:01:43 -07:00
Paul BakausandClaude Opus 4.7 a4832adf2f fix(live-wrap): JSX/TSX correctness — multi-line tags, className, tag narrowing
Five related bugs that surfaced in a real Next.js App Router project
(EAC) all rooted in live-wrap.mjs treating source as line-anchored HTML:

1. findElement matched on raw substring anywhere, so it landed on a
   className continuation line of a multi-line JSX tag whose class
   happened to collide with a later target. The wrong tag got wrapped
   (really, its attribute line got wrapped, producing broken JSX).
2. findClosingLine's opener regex required whitespace or `>` after the
   tag name, so a bare `<section\n  className="..."\n>` opener was
   unrecognised; it returned `start` silently, capturing only one line.
3. buildSearchQueries only emitted `class="..."`, missing React's
   `className="..."`. The full-combo query never fired in JSX, so
   search silently degraded to single-class substring matching.
4. Wrapper output used `style="display: contents"` unconditionally,
   which is invalid JSX (type error in strict setups, parser hazard
   in production transforms).
5. --tag was ignored during the primary class search. Ambiguous class
   hits inside the wrong element type weren't filtered out.

## Fixes

- New OPENER_RE `/<([A-Za-z][A-Za-z0-9]*)(?=[\s/>]|$)/` recognises
  tag openers at end-of-line too.
- New findOpenerLine(lines, matchLine, tag): walks up to 10 lines
  backward to the enclosing opener when the match lands on a
  continuation line. Aborts the walk if it hits a different tag.
- findElement now iterates all matches (not just the first), takes
  a tag parameter, and routes through findOpenerLine; wrapCli passes
  --tag through.
- buildSearchQueries emits both `class="..."` and `className="..."`
  for multi-class queries, and both `<tag class="..."` /
  `<tag className="..."` for tag+class combos.
- Wrapper builder emits `style={{ display: "contents" }}` when
  commentSyntax is JSX and `style="display: contents"` otherwise.
- findClosingLine uses the same OPENER_RE so its tag-name extraction
  works on multi-line openers too.

## Tests

Five new regression tests in tests/live-wrap.test.mjs, all failing
before the fix, all passing after:

- wraps the correct <section> when a class collides with a multi-line
  tag elsewhere
- emits JSX-safe style attribute ({{ }}) in .tsx files
- finds elements via className= (React) when the exact class combo is
  unique there
- respects --tag to reject matches inside the wrong element type
- findClosingLine recognises an opener line where the tag sits at
  end-of-line (multi-line JSX)

31/31 in tests/live-wrap.test.mjs and 54/54 in
tests/framework-fixtures.test.mjs pass.

Credit: precise bug report from the other agent in the EAC session
made diagnosis and test design straightforward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 00:19:32 -07:00
Paul BakausandClaude Opus 4.7 67e468f84c fix(cleanup): authoritative lock signal + fingerprint fallback for orphan dirs
The content heuristic for deciding whether a deprecated skill dir belongs
to us returned false for harden and optimize (their v2.x SKILL.md never
said "impeccable"), while lock-entry cleanup used the authoritative
source field. Result: lock entries purged, dirs orphaned.

Layer three signals now: lock source (authoritative), word heuristic,
then per-skill description fingerprints for the two stock v2.x skills
that predate the self-identification convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 23:54:29 -07:00
Paul BakausandClaude Opus 4.7 05b0ac3e1f feat(live): extend CSP detection to SvelteKit and Nuxt
Shape names renamed to describe the patch mechanism (what the agent does)
rather than the origin (where the CSP lives). One template now covers
multiple frameworks.

## Shape rename

- shared-helper   → append-arrays
- inline-headers  → append-string

append-arrays applies wherever CSP is a structured directive array.
append-string applies wherever CSP is a literal value string.

## New detection coverage

- SvelteKit kit.csp.directives in svelte.config.js → append-arrays
- Nuxt routeRules / nitro.routeRules CSP header → append-string
- Nuxt-security module's contentSecurityPolicy → append-arrays

## New fixtures

- sveltekit-csp/: SvelteKit config with kit.csp.directives. Includes
  expected-after-patch.js showing the array spread.
- nuxt-csp/: Nuxt 3 config with routeRules CSP. Includes
  expected-after-patch.ts showing the string splice.

## Skill docs

Single append-arrays template covers Next monorepo, SvelteKit, and
Nuxt-security. Single append-string template covers inline Next
headers() and Nuxt routeRules. Per-framework specifics listed as
sub-bullets under each shape.

54 tests across 9 fixtures, all passing. Clean fixtures (plain vite,
nextjs-app, astro, sveltekit, multipage-with-generator) still classify
as shape: null.

Astro and Vue (non-Nuxt) left unhandled by design: Astro has no
first-party CSP mechanism; Vue without Nuxt is covered by the existing
Vite fixture. Plain Svelte has no framework CSP primitive and inherits
from its bundler (Vite/Rollup).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 23:47:43 -07:00
Paul BakausandClaude Opus 4.7 d5480caee3 feat(live): CSP detection + consent-gated patch flow at first-time setup
Real-world tests (EAC Next turborepo) confirmed that CSP is the common
blocker for live mode. Adds setup-time detection with a one-time user
consent flow — the patch becomes a permanent, dev-guarded entry in the
user's own config, not a transient add/remove.

## Changes

- New detect-csp.mjs helper: grep-based classifier returning
  { shape, signals }. Shape is one of:
    - "shared-helper" (monorepo CSP helper with additional*Src arrays)
    - "inline-headers" (literal CSP string in headers())
    - "middleware" (response.headers.set in middleware.ts; detect-only v1)
    - "meta-tag" (<meta http-equiv>; detect-only v1)
    - null (no CSP)
  Max depth 6, skips node_modules / build / cache dirs, 64KB per file.

- cspChecked boolean on config.json. First-run setup runs detection;
  subsequent runs skip. Users re-trigger by deleting the flag.
  Validator accepts it.

- Skill live.md gains:
    - CSP detection step in first-time setup (gated by cspChecked)
    - Consent-prompt template (so every agent phrases it the same way)
    - Shape 1 patch template: append `...__impeccableLiveDev` to
      additionalScriptSrc/additionalConnectSrc in the app's config
    - Shape 2 patch template: two-point edit — declare a dev-only
      variable, interpolate into script-src and connect-src in the
      CSP literal string
    - Troubleshooting note for "said no but now live doesn't work"

## Fixtures

- nextjs-turborepo/: Turborepo shape (shared CSP helper with
  additionalScriptSrc options). Sanitized from a real monorepo so the
  patch mechanics get tested against realistic layering. Includes
  expected-after-patch.ts for human/agent review.

- nextjs-inline-csp/: app-level next.config.js with a literal CSP
  string. Includes expected-after-patch.js showing the Shape 2 edit.

## Tests

Framework-fixture harness extended with a detect-csp shape-classification
assertion per fixture. 42 tests across 7 fixtures pass. Clean fixtures
(vite-react, nextjs-app, astro, sveltekit, multipage-with-generator)
correctly return shape: null.

## Deliberately not doing

- No patches[] array, no marker-based rollback, no add/remove lifecycle.
  The patch is a permanent dev-guarded config line — the same kind of
  edit a user would make themselves.
- No base URL rewriting or proxy mechanism. Script tag still points at
  localhost:8400; CSP permits it once patched. No browser-side changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 23:41:11 -07:00
Paul BakausandClaude Opus 4.7 444f881295 fix(test): un-ignore fixture dist/ trees so they actually track
Top-level .gitignore excludes dist/ broadly, which silently dropped the
multipage-with-generator fixture's files/dist/*.html from the previous
commit. The fixture tests need those files on disk to copy into the
tmp repo and assert is-generated behavior — without them, the test
suite fails on a fresh clone.

Added a negation pattern that re-includes tests/framework-fixtures/**/dist/
paths. The real dist/ output directories elsewhere in the repo remain
ignored.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:43:35 -07:00
Paul BakausandClaude Opus 4.7 c9c152f0f0 test(live): framework fixture matrix for inject / wrap / is-generated
Five representative project shapes under tests/framework-fixtures/ that
stage into fresh tmp git repos and drive the live scripts against each:

- vite-react: tracked index.html shell + src/App.jsx
- nextjs-app: app/layout.tsx as JSX inject target
- astro: src/layouts/Layout.astro
- sveltekit: src/app.html shell + src/routes/+page.svelte
- multipage-with-generator: src/ tracked, dist/ gitignored (our own
  repo's shape); exercises the is-generated guard and
  element_not_in_source fallback

Each fixture declares its config, expected source/generated paths, and
wrap cases in fixture.json. The harness copies into tmpdir, applies
gitignore, commits, then asserts:

- inject --port lands the script tag at the correct anchor across all
  configured files
- inject --remove strips it cleanly
- is-generated classifies source vs generated paths correctly
- wrap routes to the expected source file or emits the expected
  fallback error

Plumbing + bug caught while building out the matrix:

- IMPECCABLE_LIVE_CONFIG env var so tests can point live-inject at a
  fixture-specific config.json without clobbering the harness copy.
  Backwards-compatible.
- live-wrap.mjs no longer hardcodes dist/build in its directory skip
  list. Only node_modules and .git remain universal skips; the
  isGeneratedFile check is now the sole guard for generated paths. This
  lets the includeGenerated second pass find elements in dist/ and
  report generatedMatch, which is what the multipage-with-generator
  fixture needs to exercise.

Wired into bun run test. 25 tests, 5 suites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:42:28 -07:00
Paul BakausandClaude Opus 4.7 37b8e8ba33 fix(live): multi-file inject, generated-file protection, and accept-flow correctness
Addresses every issue surfaced during hands-on live-mode testing.

## Injection across multi-page sites

- Config schema: `file` → `files: string[]` so multi-page static sites can
  opt into script-tag injection across every HTML entry the browser loads.
- `live-inject.mjs` loops the array, reports per-file results, and
  refuses silently with `config_invalid` if the schema is stale.
- `insertBefore` switched from first-match to last-match (lastIndexOf)
  so the anchor lands at the true close of `</body>`, not the first one
  embedded inside a `<pre><code>` documentation sample.

## Source-vs-generated detection

- New `is-generated.mjs` helper: gitignore check + generated-header
  markers. Edge-case `generatedFiles` config dropped — the two real
  signals cover every project shape we tested.
- `live-wrap.mjs` excludes generated files from auto-search and returns
  clear fallback errors: `file_is_generated`, `element_not_in_source`
  (with `generatedMatch` path), and `element_not_found`.
- `live-accept.mjs` refuses to persist into generated files; returns
  `mode: "fallback"` so the agent takes over via the Handle fallback
  flow.

## Accept correctness

- `extractVariant` / `extractOriginal` now skip `<style>` regions when
  matching markers. Previous regex substring match treated
  `@scope ([data-impeccable-variant="N"])` in CSS as the target HTML
  div, capturing garbage and producing orphan CSS that rendered as
  prose on the page.
- On accept, the chosen variant's content is wrapped in
  `<div data-impeccable-variant="N" style="display: contents">` so the
  carbonize block's `@scope` selectors keep matching. Users see the
  accepted design immediately; no pre-carbonize dead state.

## Browser-side UI

- `positionBar` gains a third case: when the selected element is taller
  than the viewport, pin the bar to a stable viewport anchor instead of
  teleporting between top and bottom as the user scrolls.
- No-HMR source-fetch path (`injectVariantsFromSource`) now calls
  `hideShaderOverlay()` on state transition to CYCLING. Previously the
  shader kept running after variants arrived via the fetch fallback.
- `pickVariantContent` helper replaces fragile `> :first-child`
  selection for outline positioning. Skips non-visual tags (style,
  script, link, meta, template) and falls back to the variant div
  itself when a variant contains multiple visual children.
- `resumeSession` re-captures and restarts the shader overlay when
  the page reloads mid-generation (Bun HTML HMR does a full reload
  and destroys the canvas).
- MutationObserver re-anchors `selectedElement` when the original
  element is detached by HMR, preventing zero-rect highlight drift.

## Skill docs

- `live.md` reframes `config.files` as "the HTML files the browser
  actually loads" and documents the regen-wipes-inject caveat for
  multi-page generator projects.
- New Handle fallback section covers the three wrap error shapes and
  how the agent should manually wrap for preview and commit to real
  source on accept.
- Handle accept documents the new `data-impeccable-variant` wrapper
  and the carbonize agent's duty to strip it.

## Prefetch feature (landed but disabled)

A `prefetch` event fires from the browser on first CONFIGURING per
route so the agent can pre-Read the source file before Go. Real latency
win in the linger-before-Go case but costs a harness round trip when
Go fires quickly. Disabled via a `PREFETCH_ENABLED = false` flag in
`live-browser.js`; server validator and skill dispatch stay so re-
enabling (with a browser-side debounce) is a one-line change.

## Harness guidance

Earlier skill rewrite compressed two load-bearing instructions:
- Restored prescriptive wording for "open the tab via Chrome MCP
  before the first poll" and the Claude Code background-poll policy.
- Flag-mapping for `live-wrap` rewritten as explicit bullets so models
  don't collapse `--element-id`/`--classes`/`--tag` into a single
  `--query` argument.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:30:27 -07:00
Paul BakausandClaude Opus 4.7 e2279ddab1 fix(live): restart shader and re-anchor selection on HMR page reload
Bun's HTML HMR does a full page reload when the live-wrap.mjs edit
lands, so the shader canvas is destroyed and in-memory capture blob is
lost. resumeSession rehydrated state from localStorage but never
restarted the overlay, so the wait went dead.

resumeSession now re-captures the original's content (still in the DOM
inside the variant wrapper) and restarts showShaderOverlay when we
reload mid-generation. Also swaps the two remaining :first-child
selectors in resumeSession for pickVariantContent so the earlier
loose-children robustness fix carries across reloads.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 15:45:46 -07:00
Paul BakausandClaude Opus 4.7 9ccb240dc7 fix(live): variant outline accuracy and shader re-anchor after wrap
Two regressions surfaced in smoke testing, both traceable to state
drift when live-wrap.mjs rewrites the source file and HMR swaps the
DOM.

1. Variant outline on the wrong element. The skill rewrite lost the
   explicit "each variant must be a complete element replacement"
   rule and dropped the "full element replacement" comments from
   variants 2 and 3. Models started producing variants with loose
   sibling children, so live-browser's :first-child selector framed
   only the first sibling. Restored the rule, made all three comments
   consistent, and replaced :first-child with pickVariantContent —
   which skips non-visual tags (style/script/link/meta/template) and
   falls back to the variant div itself when a model still ships
   multiple visual children.

2. Loading shader freezes after wrap. The MutationObserver only woke
   up when new non-original variants arrived, so when the wrapper
   first appeared via HMR with just the original inside, selectedElement
   was left dangling on the now-detached pre-wrap node. Scroll-tracking
   read a zero rect on every frame and collapsed the shader canvas to
   0x0. The observer now re-anchors selectedElement to the original's
   content the moment the wrapper shows up, keeping overlays positioned
   until real variants land.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 15:30:09 -07:00
Paul BakausandClaude Opus 4.7 62e5b2bb92 fix(live): restore prescriptive browser-open and background-poll guidance
The skill rewrite compressed two load-bearing instructions into ambient
context:

- "Navigate the browser to the URL" lost the signal that models with
  Chrome MCP should proactively open the tab before the first poll.
  Restored the forcing phrasing and the "before the first poll" anchor.
- "Claude Code can background the poll" read as permission rather than
  prescription. Models fell back to foreground blocking by default.
  Restructured harness guidance as a bulleted policy, prescriptive per
  harness, with the reason attached (harness notifies on completion so
  the conversation stays free).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 14:46:18 -07:00
Paul BakausandClaude Opus 4.7 4daabe5232 feat(skill): register split, color strategy, and pre-design intake
Splits the skill into two register references (editorial, product),
replaces category-based theme selection with a forced physical-scene
inference, and introduces a four-step color strategy axis (Restrained /
Committed / Full palette / Drenched) with editorial permission for the
bold three.

Adds a seed mode to /impeccable document for pre-implementation
projects, updates /impeccable teach Step 5 to offer the seed path, and
grows /impeccable shape with Design Direction + Scope intake
(fidelity, breadth, interactivity, time). Extends live-mode variant
distinctness to forbid three variants sharing theme and dominant hue.

Also drops the anti-pattern validator coupling, consolidates a11y into
audit.md, and updates CLAUDE.md with the register architecture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 14:13:29 -07:00
Paul BakausandClaude Opus 4.7 81f880d030 feat(live): annotation capture, comment pins, drawing, and halftone loading shader
Adds a full annotation pipeline to /impeccable live. On Go, the browser
captures the selected element as a PNG (with annotations composed in),
uploads it to the live helper, and sends the generate event with the
screenshot path so the agent reads user intent visually instead of from
HTML alone.

Annotation tools (while an element is picked):
- Click inside the outline to drop a magenta comment pin with a text input
- Drag to paint a magenta SVG stroke (5 px click-vs-drag threshold)
- Click a pin to edit; double-click to delete; drag a pin to reposition
- Click a stroke to delete it (wider invisible hit path)
- Clear chip top-right wipes everything; hidden when no annotations

Capture pipeline:
- modern-screenshot vendored as an IIFE (scripts/modern-screenshot.umd.js)
  and lazy-loaded from the live helper
- Font fix: cross-origin @font-face rules are fetched and fonts are inlined
  as base64 data URIs before being handed to modern-screenshot via
  font.cssText, since SVGs rasterized via canvas can't fetch external
  resources (fix for "Impeccable" rendering bold-serif and items wrapping
  wrong in the capture)
- Annotations are temporarily attached to the live element (not only the
  clone) so computed styles resolve during the embed pass
- Session screenshots live in .impeccable-live/annotations/session-*/ in
  the project root (gitignored) so the agent's Read tool doesn't trip a
  per-path permission prompt

Loading shader (activates during GENERATING):
- WebGL overlay rendering the captured PNG as a halftone — cells with
  luma-driven dot radius, rendered on paper-cream underneath a magenta
  roller that sweeps top-to-bottom with a 3.4s cycle and clean overshoot
- Fixed asymmetric bandAt() using one-sided smoothsteps (previous reversed
  smoothstep was undefined on d>0, giving "trail=1 everywhere below")
- Graceful <img> fallback when WebGL is unavailable; prefers-reduced-motion
  freezes the band at t=0

Server:
- POST /annotation endpoint (raw image/png body, token + eventId query),
  session-scoped tmpdir cleaned up on shutdown
- GET /modern-screenshot.js serves the vendored UMD with aggressive caching
- Optional screenshotPath / comments / strokes fields on generate events
- Fixed pre-existing /source crash on ENOENT (writeHead called twice)

Agent side:
- reference/live.md step 0 tells the agent to Read the screenshot first,
  with four rules for interpreting annotations: comments are position-
  anchored and scoped to the sub-element under their {x,y}; strokes are
  gestures (loop=focus, arrow=direction, cross=delete); comments and
  strokes are independent unless adjacent; don't silently guess on
  ambiguous strokes

Also:
- Generating bar no longer claims "Generating 1 of 3..." (variants arrive
  atomically) — now says "Generating N variants..."
- tests/live-server.test.mjs fixed to read the PID file from project root,
  matching the server; adds coverage for the new endpoints and validator
  fields
- .impeccable-live/ added to .gitignore

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 09:38:48 -07:00
Paul Bakaus 51d28cf1eb smooth detect outline transitions 2026-04-20 13:13:59 -07:00
Paul Bakaus 66630a0ead chore: sync skills, live tooling, and eval ignores
- Ship design-parser and refreshed live scripts/document refs across provider
  skill trees; align live.mjs and live-poll with source
- Update build/skills harness metadata, skills CLI test, and devDependencies
  (AI SDKs, zod)
- Gitignore .codex/ harness artifacts and tests/evals-v2/

Made-with: Cursor
2026-04-17 18:04:45 -07:00
Paul Bakaus 90cddb4f3c feat(live): remove injected script when stopping live server
live-server.mjs stop now runs live-inject.mjs --remove after the HTTP
server shuts down, so HTML entries do not keep loading a dead localhost
live.js URL. Add stop --keep-inject to stop only the helper.

Update reference/live.md cleanup steps and sync all provider skill copies.

Made-with: Cursor
2026-04-17 18:02:53 -07:00
Paul Bakaus 3d3cc156c5 Remove tracked .codex skill tree from repository
Made-with: Cursor
2026-04-17 17:32:37 -07:00
Paul Bakaus 0c90533055 docs(skill): clarify live-poll foreground vs background for Cursor and Claude Code
Document that Cursor Composer should run live-poll blocking in the same turn
(with a link to Cursor subagent foreground vs background docs). Claude Code may
use a background poll when the harness surfaces completion. Sync reference to
all provider skill bundles.

Made-with: Cursor
2026-04-17 17:08:11 -07:00
Paul BakausandClaude Opus 4.6 0b4bc377f2 Polish live-mode bar and DESIGN.md panel
Global bar is now a single compact unit: Pick → Detect → DESIGN.md
with the Impeccable brand mark as a full-height slab on the left.
Labels are icon-only at rest and expand as a group when the bar is
hovered, so moving the cursor across buttons no longer triggers
per-button layout thrashing. Button styling flips automatically based
on the page's ambient luminance — dark bar on light pages, light bar
on dark pages — so the bar doesn't fight with the host design.

The DESIGN.md panel chrome now matches the bar (same surface, hairline,
mono filename title) while the body canvas stays neutral so tile colors
and rendered component primitives look true. The separate floating
"Design" FAB is gone; the panel toggle lives in the bar. Panel always
starts closed; only the tab and collapsed-section preferences persist.

Other fixes: toggling pick off now clears the selection + hides the
contextual bar and action picker. PRODUCT.md presence is the signal
for "project context loaded" for variant generation (DESIGN.md lives
under its own empty-state in the panel). The live-poll client sets a
global undici dispatcher with no headers/body timeouts so one poll can
sit open indefinitely — fixes the silent "fetch failed" that killed
polls at the 5-minute mark.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:01:17 -07:00
Paul BakausandClaude Opus 4.6 fcb347b9b5 Add design system panel to /impeccable live
The live-mode float-bar now includes a "Design" toggle that slides a
panel in from the right. Tile-based layout (neutral canvas, one level
of hierarchy, no nested cards) with color swatches and tonal ramps,
typography specimens, corner radii, shadow previews, and *live
component primitives* rendered from the project's real tokens.
Collapsible Named Rules / Do's-and-Don'ts / Overview hold the
narrative context without crowding the tiles.

The /impeccable document command now writes a DESIGN.json sidecar
alongside DESIGN.md. The sidecar carries structured tokens plus
self-contained HTML+CSS snippets per component — this is what lets
the panel render each project's actual button/input/nav instead of
generic approximations. The document spec documents the translation
rules for Tailwind, CSS-in-JS, shadcn, and framework components.

The live server exposes /design-system.json and /design-system/raw.
If DESIGN.json is missing but DESIGN.md is present, the panel falls
back to a limited "basic view" parsed from the markdown and prompts
the user to run /impeccable document for the full visualization. A
stale hint appears when DESIGN.md has been edited after DESIGN.json.

Includes a hand-authored DESIGN.json for this project so the panel
has something to render against out of the box, and a deterministic
DESIGN.md parser (design-parser.mjs) as the fallback source.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 12:11:10 -07:00
Paul BakausandClaude Opus 4.6 50dfeef39f Tighten typography.md; remove reference/pin.md
typography.md had a parallel 4-step font-selection procedure and a smaller
banned-fonts list (5 fonts: Inter, Roboto, Open Sans, Lato, Montserrat)
that duplicated SKILL.md's authoritative <font_selection_procedure> with
its 23-font list. Removed the duplicate procedure and deferred to SKILL.md
for the banned list. Kept the unique material: anti-reflex corrections,
system-font note, pairing principles, web font loading, OpenType, fluid
type guidance, accessibility — and all of the scale/rhythm/measure
content that SKILL.md doesn't cover.

pin.md removed for the same reason as context.md: SKILL.md's inlined
pin section already covers what an agent needs (what pin does, usage,
valid commands, how to report back). No value in the indirection.

SKILL.md: 386 → 388 lines (slight growth from inlining pin details)
typography.md: 142 → 132 lines

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 10:56:07 -07:00
Paul BakausandClaude Opus 4.6 2c1d2a5a54 Remove reference/context.md; SKILL.md already covers the protocol
The main skill now has enough detail to stand alone: two files, load
command, no-truncate rule, never-infer warning, session cache, teach
fallback, DESIGN.md nudge, and the live-mode "already warmed"
exception. context.md was indirection without added value.

Inlined the two bits from context.md worth keeping:
- Content validity: treat empty / <200 chars / [TODO]-placeholder
  PRODUCT.md as missing
- live.mjs auto-warms, don't double-load with load-context.mjs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 10:53:00 -07:00
Paul BakausandClaude Opus 4.6 473dbd52ef Trim SKILL.md plumbing; extract context + pin protocols to references
Before: 422 lines. After: 386 lines. The cut is conservative because
evals/AGENT.md revealed that most of what looked like bloat is actually
load-bearing: the font_selection_procedure with its 23-font ban list
(lesson 6), the theme_selection with audience examples (lesson 9), and
the absolute_bans with literal CSS patterns (lesson 7) all drive
measurable eval improvements and must stay inline.

What moved out of SKILL.md:
- Context Gathering Protocol (52 → ~18 lines). The full protocol — cache
  semantics, dispatch tree, teach/document/live exceptions, why-it-matters
  — moved to reference/context.md. SKILL.md keeps only the compact hook:
  load command, "never infer from codebase" warning, and pointer.
- Pin/Unpin (14 → 6 lines). Details moved to reference/pin.md.
- Spatial principles: dropped 4pt-vs-8pt rationale, gap-vs-margins CSS
  technicality, and container-queries-vs-viewport explanation (not
  load-bearing in the main skill). Kept all load-bearing rules
  including the 80-char body-text line (detector-backed).

What did NOT move (load-bearing per evals):
- <font_selection_procedure> with the 23-font ban list
- <theme_selection> with the 8 audience examples
- <absolute_bans> with literal CSS patterns
- All XML tag structure (lesson 8: XML works better than markdown
  for reasoning models, especially OpenAI)

Also added:
- reference/context.md (new) — full context protocol
- reference/pin.md (new) — full pin/unpin docs
- "Never infer brand, audience, or tone from the codebase" warning
  restored to SKILL.md (was dropped in an earlier refactor)

Fixed:
- reference/colorize.md had "Accent borders: Add colored left/top
  borders to cards or sections" which directly contradicted
  SKILL.md's absolute_ban on border-left/right > 1px. Rewrote the
  accent-border advice to use hairline borders, surface tints, or
  leading glyphs instead, with an explicit reference to the ban.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 10:02:19 -07:00