The loading hand now has the true proportions: 16:9 shimmer media, a
tier line, a title line, three detail lines, and a button-shaped block
pinned to the card bottom, with each skeleton inheriting the measured
height of the card it replaces.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live-fire bug from the demo: collecting a re-roll answer deleted the
state file, so --update could not find the still-running server and the
next hand had nowhere to land. Cleanup is now terminal-only: a re-roll
consumes just the answer file and leaves the server state for --update;
any other choice cleans up fully as before.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two upgrades from the live session. Re-roll no longer ends the page: in
detached mode the server stays alive, the client gathers the cards back
into the center stack, deals skeleton cards with the site's shimmer,
and polls /next-status; the new --update mode delivers the next hand
and the page reloads into the fresh deal. Choices other than re-roll
still resolve and exit as before.
And the headline bug had a root cause: the fonts link never loaded the
weights in use, so the browser synthesized a fake 300 that read
off-brand and muddy. The link now loads Alumni 100 and 400 exactly, and
the h1 wears the homepage hero display role: weight 100 at
clamp(2.6rem, 5vw, 4.2rem), champagne.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#lightbox { display: flex } outspecified the UA's [hidden] rule, so an
invisible full-viewport layer sat over the page and blocked all hover
and click. #lightbox[hidden] { display: none } restores reality.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Craft-gate forensics on matched hands: both models obeyed the same
assigned indices, but Opus rendered every kids cell as cream paper,
lamplight, and serif, and sample 1 chose Fraunces off the reflex list
with a bookshop-signage rationalization, while Sol rendered the same
positions as indigo bookcloth, coral thread, tomato, and marigold. The
dice work; the rendition prior escaped through two hatches, now closed:
naming a reflex face requires a reason no other face satisfies and a
subject association is never that reason; and bookish or child-facing
subjects do not soften the calibration, because cream paper is the
smallest corner of the book world.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul's motion and exploration pass. The reveal is now a real deal: the
cards begin piled at the grid center, blurred and slightly rotated, and
travel to their seats with a 110ms stagger on the brand ease (JS
measures each card's seat, so the pile works at any grid shape;
reduced motion skips it). Hovering a card bleeds its hero into the page
ground behind a lacquer scrim. An expand chip beside Board opens
whichever face is showing in a zoom-out lightbox with Escape to close.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
No cropping and no pillarboxing: the board spans the card width at its
native 16:9 exactly like the hero on the front, with the deep-lacquer
ground below and the label-and-CTA bar pinned to the card's bottom edge.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The design-system board is an information sheet; the back face now
contains it fully on a deep-lacquer ground rather than cover-cropping
its top and bottom.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul's read: flipping only the image inside a static frame looked
unconvincing. The card is now the object: front face carries hero,
lineage, title, and CTA; the back face is the design-system board at
full card height with a slim bar keeping the label and Build-this
reachable. The outer card keeps fan, deal, and hover; each face carries
its own lacquer chrome and the rolled card's gold ring rides both
faces. 700ms preserve-3d turn, instant under reduced motion.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Third polish pass with Paul live. The details collapsible is gone: the
card media is now a 3D flip, hero on the front and the design-system
board on the back, toggled by a small mono Board/Hero chip with a 600ms
preserve-3d turn that reduced-motion collapses to an instant swap. The
headline and question sit directly above the dealt hand inside the
centered stage while the logo holds the top-left corner. Re-roll
stretches to the steer input's height and says just Re-roll beside the
five-pip die.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul's second polish pass. The h1 uses DESIGN.md's headline role
(Alumni 300 at clamp(2rem, 4vw, 3.4rem), tracking 0) instead of a bold
weight the brand no longer uses; card headings use the title role
(Albert 500, 1.125rem) which also holds at small sizes; the rolled
card's border and ring use actual kinpaku gold, not the deep variant;
and the dealt hand centers vertically in the viewport with header and
footer framing it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul's review against DESIGN.md and the design-system page: the real
24px logo mark with the uppercase tracked Alumni 400 wordmark (was a
bootleg lowercase 600); the five-pip stroke die from the homepage
worlds-roll as the headline accent and re-roll icon (the tilted numeral
cube was off-brand); the re-roll button is the worlds-reroll pattern
verbatim (mono 0.72rem uppercase tracked, rule border); Build-this uses
the DESIGN.md button-primary spec (title typography, 38px padding,
kinpaku-pale hover); THE ROLL kicker is the worlds-played-chip pattern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The decision page now mirrors impeccable.style's Neo kinpaku system:
the split logo mark and Alumni Sans wordmark in kinpaku gold, lacquer
ground with raised-panel cards, the gold die beside the headline
(count of dealt options, rotated like the research page dice), THE ROLL
badge as a mini die, worlds-roll card treatment (rule borders, fan
rotation, deal-in stagger honoring reduced motion, hover lift), mono
tracked lineage lines, champagne display type, gold CTA with dark ink,
and a die-glyph re-roll button. Tokens mirrored from kinpaku-tokens.css.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul's call: start mode never auto-opens; the agent is alive and opens
the printed URL itself, in-app browser first, then the system opener,
then showing the URL (--open forces the system browser from the script).
The prose now leads with the start/open/wait flow and keeps the blocking
auto-open path for harnesses that can background a shell.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul's two concerns with the blocking design. --schema prints the exact
payload example so the model never guesses the shape (new-work.md points
at it). And harnesses that cannot leave a shell blocked (or cannot open
a browser while blocked) get a two-phase path: --start daemonizes the
server and returns the URL plus a key immediately, --wait polls for the
answer with exit 3 meaning ask again, exit 2 meaning the server died,
and --stop for cleanup. The browser open happens from the detached
server process, so it works even when the agent thread is short-lived.
State lives under .impeccable/questions/.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul's design, three pieces:
serve-question.mjs: the world decision presented as a themed page instead
of a text prompt. The script serves an impeccable-styled option board
(assigned direction leading with THE ROLL badge, dealt challengers as
alternates carrying their QUALITY BAR cards, re-roll and steer built in),
prints the URL, opens the browser, and blocks until the user chooses;
the answer lands on stdout as ANSWER JSON, so the shell call itself is
the wait and no harness machinery is needed. Local images are served by
the ephemeral server; nothing leaves the machine.
generate-image.mjs + context.mjs IMAGE_GEN_AVAILABLE: when an OpenAI key
is in the environment, context reports that image generation works even
without a harness-native tool (gpt-image-2, billed to the user's key,
stated before first use; Google skipped by decision). Harness-native
tools always win when present.
new-work.md: visualize-before-build is now the default whenever any
image generation exists, not a codex.md special case; the attended
presentation prefers the visual decision page and falls back to the
structured question tool. Evals keep the unattended path untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul confirmed the craft-bar experiment: builds that saw the dealt
worlds' hero cards produced visibly stronger execution than the
no-image control. One clause makes the mechanism reachable for
harnesses that read only local images: download the card, then view it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul's directive: the rendered board and hero for each dealt world ride
along with the challengers, framed as a quality bar (the finish and
commitment level the build is expected to reach), never as a mockup to
copy. The seed prints QUALITY BAR urls per challenger, preferring
API-provided cardBoard/cardHero fields and deriving from the concept id
otherwise; new-work.md instructs image-capable harnesses to view them
for the world being built. Server side, the roll API now returns
cardBoard/cardHero per challenger (impeccable-site).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Discussion outcome with Paul: a mediocre material world loses to
excellent abstract craft, but an unanchored "just be beautiful" escape
hatch would hand selection straight back to the model's priors. The
resolution: abstraction enters as named systems with their own grammar.
The derivation now states that the audience's graphic and screen
traditions (notation, publications, identity programs, data graphics,
interfaces) are as concrete a candidate as any physical artifact. The
catalog side of the same decision is a 12-entry abstract-graphic
authoring round in impeccable-site, pending review.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Smoke findings (Paul's review): the kids-reading derivation produced
seven candidates from one material family despite the divergence line,
and the brief-pinned bookshop world was rendered as the generic AI
bookshop (cream, serif italic, soft glow). The list must now span at
least three material families, and a pinned world licenses its full
material range, never just its softest rendition.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Craft-smoke finding (recovery lektor build): the model read ASSIGNED
INDEX 4 as challenger 4 and built from the challenger list. Challengers
enter only through fusion-and-weigh; say so at the assignment site.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul's probe review (recovery-ab): the no-invention rule was blocking
bold greenfield directions whose demonstration data does not exist yet;
kids-reading amplified the product's "quiet support" adjectives into a
whole-page aesthetic; and nothing guaranteed a Persuade surface still
sells once the form commits (a prior generation shipped zero nav and
zero CTA in the first viewport).
- Truth split in two: commercial and factual claims stay uninventable;
illustrative material is authored at full fidelity, labeled synthetic,
with a replace-with-real list for the user. Mirrored in the build
section so execution-only sessions get it too.
- Persuade floor restored from a22: conversion lives inside the form's
own vocabulary (one-line hook, visible primary action, legible reading
order); a committed form that hides the offer has not finished
translating. The contract's FIRST VIEWPORT block now names where the
primary action sits, and the finishing review verifies the mode did
its job.
- Calibration: negative constraints rule out devices, not exuberance;
product-behavior adjectives do not dictate surface energy.
- Web leverage: when the chosen world names a technique (canvas, WebGL,
view transitions), build the technique, not a static imitation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Probe finding (recovery-ab, obs sample 1): "treat both structures as the
rut, not the range" let the model rank the observability dashboard grid
at position 7, and the dice landed on it; the challenger fusion rescued
that draw, but a die face spent on the category's own page is a wasted
roll. The a26 wording excluded both structures outright; restore that
with the reason attached.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ship40 concept pipeline had reversed the proven a-series mechanisms:
the seed's roll decayed into a shortlist nomination that taste functions
(model ranking, candidate floor, simulated user) then argmaxed into the
safest card; the costume check returned as the Translation veto and
carrier-removal test; and the 07-15 rewrite deleted the calibration,
reflex-font lanes, color strategies, and commit-every-atom language that
had held off the cream-editorial default since the alpha era. Five of six
frozen craft directions converged on the same warm-paper family and both
builders obeyed them.
This lands the repair on top of the in-progress simplification:
- new-work.md: the script assigns the build index again on both scopes;
catalog challengers are fused (challenger supplies form and grammar,
product supplies every fact, clarity wins conflicts) and weighed on the
two proven axes only; attended runs present one fully committed
direction with re-roll and an optional steer instead of a ranked
lineup; the color-strategy picker, reflex-face list, saturated-look
calibration, first-viewport thesis and memory test, commit-every-atom,
scroll pacing, and prove-don't-claim return; the direction contract
returns as five lean blocks audited by the separate-agent finish.
- concept-seed.mjs: PROMOTED INDEX becomes ASSIGNED INDEX with
build-assignment semantics; self re-roll only on named factual grounds.
- craft-floor.md: hook-active sessions act on findings instead of
re-auditing; the Refuse list is framed as category defaults the brief
can earn; a closing commitment line keeps a ban list from being the
last word before code.
- codex.md / shape.md: contract references restored for flow coherence.
Adopts the concurrent session's ceremony cuts, softened challenger
instruction, seed SOURCE IDs and --candidate-count, detector-ownership
fix, and the removal of the hook-side contract audit (the audit now
belongs to the separate reviewer at finish).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deleting interaction-design.md took the last copy of
skill-interaction-dropdown-clipping with it. harden.md's `overflow:
hidden` hits are code samples, not the rule.
It lands in operate.md's Components list rather than the craft floor:
dropdowns and overlays are dense-product-UI components, and the floor
just lost 25% of its length for being a place where specifics accumulate.
The detector's clipped-overflow-container rule catches this after the
fact, but only in sessions with a hook.
Co-Authored-By: Claude <noreply@anthropic.com>
Nothing has loaded it since bbed6eef (Jul 15) rewrote shape.md, which
held its only referrer, a parenthetical example in a list of files that
might be useful. It was never in the Commands table or command-metadata,
so no route reached it either, yet all 189 lines shipped into every one
of the 14 provider bundles.
The eight interactive states and focus rings it covered live in
craft-floor.md's States check and, in more depth, in audit.md, polish.md,
and layout.md. Its CSS anchor positioning, Popover API, and roving
tabindex material has no home elsewhere; recover it from git if a command
turns out to want it.
Co-Authored-By: Claude <noreply@anthropic.com>
live.md's insert branch told the agent to load `brand.md` or `product.md`.
Both files are gone on this branch; the register system became SKILL.md's
modes plus operate.md. Net-new markup in live mode now decides the mode
from the surface and loads craft-floor.md, which is where the bans live.
The freeform generate path gets the same pointer, since live never runs
Setup step 3 and so never picks the floor up on its own.
operate.md still located the craft floor inside SKILL.md.
Co-Authored-By: Claude <noreply@anthropic.com>
910 words to 682, same 35 rule markers, no guidance dropped.
- Two sections instead of three. "Absolute bans" and "detector-blind
reflexes" split the same list by whether our scanner happens to catch
it, which is a fact about our tooling and tells the model nothing about
the design. Merged into one Refuse list, grouped as page scaffolds and
surface habits, which is a distinction the model can act on.
- Folded three duplicates: text-overflow was already in the Type check,
the uniform section reveal was the other half of the Motion check, and
card-everything was already inside the card-grid ban.
- Cut explanation the model does not need. It knows what gradient text
is and what group-hover does; it needs the refusal, not the mechanism.
The gemini block goes from four sentences to three short ones, and the
motion palette line drops the CSS tutorial for "reach past transform
and opacity."
- The authority note moves to the header so no item has to hedge.
Co-Authored-By: Claude <noreply@anthropic.com>
The detector-blind slop review existed because the AI-tell rules had been
stripped out of SKILL.md and nothing carried them. The floor is a better
home: it loads after concept ideation and immediately before editing UI,
which is the placement that made stripping them necessary in the first
place. Models tread lightly when a ban list is present during ideation;
by the time the floor loads, the direction is already committed.
- Rename build-floor.md to craft-floor.md and restore the absolute bans
(side-stripes, gradient text, glassmorphism, hero-metric, identical card
grids, eyebrow-on-every-section, numbered markers, text overflow), the
codex and gemini defect lists, and the reflexes no scanner catches.
Rule ids match the ones the ablation catalog already knows.
- Delete lib/slop-review.mjs and both injections. The Stop hook is now
purely a mechanical pass and stays silent with nothing to report.
- context.mjs replaces AI_SLOP_REVIEW_REQUIRED with the narrower
MANUAL_DETECTOR_REQUIRED, emitted only when a session has no hook at
all. A per-edit hook already covers the mechanical gap, and the floor
covers the judgment one either way.
Co-Authored-By: Claude <noreply@anthropic.com>
main still carries the site, so every `site/` path resolves to deleted.
`tests/docs-integrity.test.js` goes with it (it imports the site's demo
renderer), and `package.json` keeps main's `@anthropic-ai/sdk` bump while
dropping `@google/genai` and `@paper-design/shaders`, which nothing in the
product layer imports.
Real code merges:
- hook-lib: main's #391 cache fix (sync the remembered set to the live
scan so fixed findings stop being named and a reintroduced one fires
again) now runs on the immediate tier rather than the whole filtered
set. Remembering a deferred finding the per-edit pass never reported
would let the Stop deep pass dedupe it away. main's `maxFileBytes`
ceiling, `cleanAcked` once-per-file ack, and template-extensions
re-export all land alongside the tiering work.
- live-browser: main's `hasParams` gate on the Tune badge, keeping this
branch's `C.ink` badge text so it stays legible on kinpaku gold.
- detect-text: both the block-level codex-grid-background scan and main's
inset-stripe CSS check.
- test-suites: union of both trigger sets and file lists, minus the
site-only entries (`shiki-theme`, `docs-integrity`).
- Two hook tests moved off deferred-tier rules (`overused-font`,
`side-tab`) onto immediate-tier ones. They assert cache bookkeeping,
which the per-edit pass only reaches for the immediate tier.
Also drops the site waivers from `.impeccable/config.json` and stops
`build:browser` recreating a stray `site/` tree just to write a bundle
the other repo builds itself.
Co-Authored-By: Claude <noreply@anthropic.com>
Replace the Consequence floor with Signature: one authored move that
makes the experience unmistakable and shapes implementation, named in
terms of what the visitor experiences. Translation now demands the
source's aesthetic and compositional laws survive alongside product
structure, so function-without-character reads as safe flattening and
character-without-structure as costume.
Add an expand-then-contract step before the direction contract: decide
spatial, motion, interaction, narrative, and system questions as one
studio plan that causes itself, then compress into the contract. Staging
guidance follows the seed's move to several inputs.
Co-Authored-By: Claude <noreply@anthropic.com>
A single staging input was too weak a counterweight to the model's
habitual page skeleton: beside six identity challengers it read as one
optional flourish rather than a real search over composition. Roll three
from distinct staging families so a roll tests materially different
hierarchy, sequence, and interaction laws.
selectApprovedStagings replaces the single-pick selector; the old
selectApprovedStaging stays as a count-1 wrapper for smoke tests. Re-rolls
exclude every earlier set, and an absent mode still returns nothing rather
than falling back across modes.
Co-Authored-By: Claude <noreply@anthropic.com>
The public repo keeps the OSS promise surface: skill, CLI, extension,
tests, and the provider build. The site, labs, concept/composition
catalogs, image pipeline, Cloudflare functions, and authoring guide move
to pbakaus/impeccable-site.
concept-seed tests run against a synthetic fixture catalog; the plugin
icon and skill categories moved in-repo; build validation narrows to
README prose and non-site counts; release notes read from a sibling
impeccable-site checkout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
/api/roll deals deterministic challenger rolls server-side (same salts and
sha256 ranking as the local seed, verified bit-for-bit); the request log is
the impression record. /api/chosen takes the anonymous choice ping. Events
land in Workers Analytics Engine.
concept-seed.mjs resolves data in order: local catalog dir, roll API,
degraded promotion-only seed. --chosen sends the choice ping; DO_NOT_TRACK
and IMPECCABLE_NO_TELEMETRY disable it. API-dealt seeds carry the telemetry
instruction inline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Allow context roots to be declared in .impeccable/config.json
Monorepo detection previously read workspace roots only from package
managers (package.json workspaces, pnpm-workspace.yaml, lerna.json),
coupling "where design context lives" to the dependency graph. Add a
`contextRoots` glob list to .impeccable/config.json / config.local.json
so non-JS repos -- and design-context boundaries that don't match
packages -- can declare nested PRODUCT.md/DESIGN.md roots directly.
The new source is folded into readWorkspacePatterns(), so detection,
project resolution, and the app picker pick it up unchanged. Negation
and config.local.json extension work for free.
* Define projectRoots composition with package workspaces
Address review feedback on #307:
- Rename the config key contextRoots -> projectRoots: the globs establish
project boundaries and app-picker targets, not just where context files
live.
- Make cross-source precedence explicit: a path matched by any impeccable
pattern, positive or negated, is governed by the impeccable group alone;
package-manager patterns fill in the paths it does not match, and `!`
negations apply only within their own source. readWorkspacePatterns()
becomes readProjectPatternGroups() / readProjectPatterns(), with package
workspaces as one discovery source.
- Drop app-picker candidates that would resolve elsewhere: a package
workspace subsumed by a broader impeccable boundary is no longer listed,
since choosing it would silently resolve to that boundary.
- Add five composition tests and document the key in the config and
context reference pages (path relativity, glob and negation syntax,
shared/local merge, precedence).
The "Repository Structure" section still described the pre-v3.0
multi-skill layout (source/skills/audit/SKILL.md, polish/SKILL.md, ...).
Updated it to match the current single skill/ directory and top-level
layout (cli/, site/, extension/, functions/, plugin/, etc.).
* feat: add Mistral Vibe harness support
Mistral Vibe is Mistral AI's open-source CLI coding assistant that ships an
Agent Skills system at .vibe/skills/{name}/SKILL.md with slash-command
invocation, mapping cleanly onto the existing transformer pipeline. Adds
Vibe as a 14th first-class harness:
- PROVIDER_PLACEHOLDERS entry in scripts/lib/utils.js (model, config_file
= AGENTS.md, ask_instruction, command_prefix) mirroring the Qoder shape.
- PROVIDERS entry in scripts/lib/transformers/providers.js with configDir
.vibe and frontmatterFields user-invocable, license, compatibility,
metadata, allowed-tools (Vibe docs do not document argument-hint).
- transformVibe named export in scripts/lib/transformers/index.js for
test-spy parity.
- vibe added to FILE_DOWNLOAD_PROVIDER_CONFIG_DIRS so the download
endpoint accepts /api/download/skill/vibe/* and resolves to
dist/vibe/.vibe/.
- .vibe added to PROVIDER_DIRS, PROVIDER_ALIASES, PROVIDER_DISPLAY,
PROVIDER_INPUT_ORDER, GLOBAL_HARNESS_HINTS, and the normalizeForHash
provider regex in cli/bin/commands/skills.mjs so the CLI detects
existing Vibe installs.
- docs/HARNESSES.md updated: official docs row, frontmatter support
column, directory structure row, and Last verified date bumped.
- docs/DEVELOP.md, README.md (install instructions, providers list,
Supported Tools), .github issue/PR templates, sync-generated-output
workflow, and AGENTS.md extended with Vibe.
The dynamic providers.test.js loop picks up Vibe automatically; all 160
provider tests pass including the 14 new Mistral Vibe cases. The build
regenerates dist/vibe/.vibe/skills/impeccable/ with correct frontmatter
and .vibe-substituted script paths. Generated harness output is left
unstaged per the source-first policy; the sync-generated-output workflow
(now listing .vibe) will commit it back to main after merge.
Co-Authored-By: Mistral Vibe <noreply@mistral.ai>
* Address review on Vibe harness support
Verified the Vibe claims against the docs and the mistralai/mistral-vibe
source, then tightened what the tables say.
- model placeholder: 'Mistral', not 'the model'. Vibe is Mistral's own
first-party CLI, so it belongs with gemini -> Gemini and codex -> GPT
rather than with the provider-agnostic harnesses. SKILL.src.md's one
use of {{model}} now renders 'Mistral is capable of extraordinary
work.' instead of a lowercase 'the model'.
- Docs links point at the skills page, not the product overview, matching
every other row in both tables.
- disable-model-invocation is No, not TBD. The field appears nowhere in
Vibe's source; unknown frontmatter keys are silently ignored.
- Split the directory row into project and global scopes the way the Pi
row already does. Vibe reads .vibe/skills/ and .agents/skills/ at the
project level and ~/.vibe/skills/ and ~/.agents/skills/ globally; the
global .agents dir was missing, and project .agents/skills/ was sitting
in the global column. Sources: vibe/core/paths/_local_config_files.py
and vibe/core/config/harness_files/_paths.py.
- Restored 'Last verified' to 2026-04-28 and dated the Vibe row on its
own. Only that row was checked, and this file warns against trusting
stale claims, so a blanket re-date made ten other rows look fresher
than they are.
AI assistance: written with Claude Code.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Mistral Vibe <noreply@mistral.ai>
Co-authored-by: Paul Bakaus <paul.bakaus@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
* Fix Live accept for Elixir templates in lib/
Wrap and accept search the repo for impeccable variant markers. That
search skipped .ex files and the lib/ tree, so Phoenix LiveView markup
inside ~H""" blocks never matched and browser Accept returned
"Session markers not found".
Extend the same EXTENSIONS and searchDirs in live-accept.mjs and
live-wrap.mjs. Add a regression test that accepts from
lib/my_app_web/components/layouts.ex.
* Live: give the source search one owner for template extensions
The #374 fix had to patch the same hardcoded EXTENSIONS array in two
files because live-wrap.mjs and live-accept.mjs each carried their own
copy of the project source walk. The copies had already drifted: same
extension list twice, same searchDirs twice, and one realpathSync
guarded by try/catch while the other was not.
Meanwhile hook-lib.mjs had solved this properly for the design hook in
#316/#347 with a configurable `detector.extensions` and suffix matching
that handles .blade.php and .html.erb. Live never read it, so a project
that taught the hook about .heex still got 'Session markers not found'
on Accept.
- lib/template-extensions.mjs is the single owner. It holds Live's
built-in markup list, the suffix matcher, and the detector.extensions
config reader. hook-lib.mjs now imports its normalize/merge/match
helpers from here instead of duplicating them, and re-exports
matchConfiguredExtension for its existing callers.
- Live resolves built-ins PLUS detector.extensions, so teaching the hook
about a server template teaches wrap and accept at the same time.
- live/source-search.mjs holds the walk both scripts share. Callers pass
the one thing that actually differs (skipDirs, fileFilter). Unifying
gives live-wrap the guarded realpathSync, so a dangling symlink in the
tree no longer throws out of the whole wrap, and makes it skip
.impeccable artifacts the way accept already did.
- Extensions are matched on filename suffix rather than path.extname, so
root.html.heex and show.html.erb resolve.
- Drop .exs. Those are Elixir scripts (mix.exs, config/*.exs), never
markup, and including them only lets a wrap query match build config.
- Fill the Elixir gap in the manual-edit paths, which kept their own
allowlists and would have left Live half-working for Phoenix:
live-commit-manual-edits.mjs and live-manual-edit-evidence.mjs.
Verified the round trip by hand against a Phoenix layout: wrap injects
markers into a ~H""" block in lib/**/*.ex, accept carbonizes the chosen
variant back out.
AI assistance: written with Claude Code.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Nils Kanevad <heliumbrain@users.noreply.github.com>
Co-authored-by: Paul Bakaus <paul.bakaus@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Upgrade actions/setup-node to v7 across CI, sheriff, and generated-output sync workflows.
The action migrated to ESM and updated its cache internals; existing inputs required no migration. The PR's full exercised CI matrix passed on v7, and all changed workflows passed local YAML validation.
AI assistance: Codex performed dependency review and validation under maintainer automation instructions.
Update the coordinated AI SDK stack plus Anthropic, Google GenAI, Astro, and Wrangler minor/patch releases.
Validated with a frozen Bun install, full build, full test suite, and green GitHub checks.
AI assistance: Codex performed dependency review and validation under maintainer automation instructions.
* Stop the design hook lying about findings it already reported
Three fixes, all aimed at the hook being trustworthy enough that an agent
keeps reading it.
1. The session cache was append-only, so the hook lied and then went blind.
`rememberFindings` unioned new keys into the remembered set and nothing ever
removed them, and the pending ack took its count from that set rather than
from the live scan. Fixing two of three findings produced:
Still has 3 finding(s) flagged earlier this session
(overused-font:1:inter, overused-font:2:roboto, overused-font:3:geist)
with roboto and geist already gone. Worse, a finding that was fixed and then
reintroduced was deduped against the stale memory and never re-reported, so
the hook was permanently blind to that regression for the rest of the session.
The cache now syncs to the complete current scan on every scan, so the count
shrinks as work lands and a reintroduced finding reads as fresh. Dedup within
a session still works, because it compares against the previous scan rather
than against all history. A detector failure leaves the remembered set alone
instead of recording an empty scan as truth.
2. The size ceiling, for generated files that do not live under dist/.
`GENERATED_PATH` covered dist, build, out, .next, .cache, coverage and
.min., but repos commit browser bundles and vendored detector copies next to
source. The hook was reading and scanning a 215KB generated bundle, and
reporting findings in it. Added `generated` as a path segment, matched with
separators on both sides so authored names such as generated-utils.ts and
CodeGenerator.tsx still get scanned, plus a `limits.maxFileBytes` ceiling
defaulting to 128KB. In this codebase authored files top out at 86KB while
the bundles start at 215KB, so the gap is comfortable.
3. The clean ack repeated on every clean edit.
It carries no finding, only the standing steer that a silent hook is not a
verdict on the design. That steer is worth saying, but not dozens of times
per session. It now fires once per file per session and reports
`clean-ack-deduped` in the audit log so suppressed noise stays visible. The
pending ack is deliberately untouched: it names real unresolved work, and the
comment explaining why it must repeat still holds.
Verified end-to-end against the built hook: three findings, fix two and the
count drops to one naming only the survivor, fix the last and it goes clean,
edit again and it stays silent, reintroduce and it fires as fresh.
Generated provider output is deliberately left out; the sync workflow owns it.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
* Address review: three clean-ack and audit bugs in the dedupe change
All three were introduced by this PR and all three are fair catches.
Quiet mode spent the ack (bugbot). A clean scan marked cleanAcked and
persisted it even when quiet suppressed all output, so a later non-quiet run
in the same session never got the steer. The quiet decision is now hoisted
above the scan loop and quiet leaves the ack unspent.
Multi-file events lost the ack (copilot). The first clean target became
cleanWinner unconditionally; if that file was already acked, cleanAckDeduped
went true and the `!cleanWinner` guard meant a later target that had never
been acked could never win. A raw apply_patch touching two files would drop
the second file's ack entirely. The loop now keeps looking for a target that
is actually owed an ack.
audit.bytes leaked across targets (copilot). It was set when a file was
skipped as too-large and never cleared, so in a multi-file event a later
emitted result carried the skipped file's byte count. Cleared per iteration.
The tests use a raw apply_patch payload rather than MultiEdit, because
MultiEdit in this harness is single-file ({ file_path, edits: [] }) and would
not have exercised the multi-target paths at all. Verified the three tests
fail against the pre-fix code and pass after, so they are not passing for the
wrong reason.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
* Address review: font-size waivers silently did nothing
Two more review findings, both real.
Specific-value font-size waivers were dead config (greptile). The rule emits an
ignoreValue, and the hook's own directive footer tells the agent to waive
value-specific findings with `hooks ignore-value <rule> <value>`, but
`design-system-font-size` was missing from the direct-value rule set in
`extractFindingIgnoreValue`. The extracted value came back empty, so any
waiver naming an actual size was compared against nothing and silently
dropped. Only the `*` wildcard worked, which is why the framework-viz waiver
earlier in this branch appeared to function.
Reproduced against the built hook: with a `0.82rem` waiver the finding still
fired; it now goes clean, while a waiver naming a different size correctly
still fires, so this is not over-matching.
Wrong audit skip reason (bugbot). In a mixed multi-target run, an earlier UI
file whose ack was already spent set `cleanAckDeduped`, and a later non-UI
clean file became the winner. The tail then reported `clean-ack-deduped` when
the honest reason was `non-ui-ack`. Audit-label only, no behavior change.
Reordered so the winner is described first and dedupe is reported only when it
is genuinely why nothing was emitted.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
* Mirror the font-size waiver fix into the CLI's config reader
Bugbot caught that the previous commit only fixed one of two copies.
`extractFindingIgnoreValue` exists twice, in skill/scripts/hook-lib.mjs and in
cli/lib/impeccable-config.mjs, and the direct-value rule list is duplicated in
both. Adding design-system-font-size to the hook alone meant the same
.impeccable/config.json filtered differently depending on the entry point: a
size waiver was honored by the hook and ignored by `npx impeccable detect`.
The two functions are otherwise byte-identical, so this restores parity rather
than changing CLI behavior independently. The new test notes the duplication so
the next person knows the pair has drifted once already.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix the audit byte-count leak properly, not just one scan order
My earlier fix cleared audit.bytes at the top of each iteration, which was
wrong twice over, and bugbot caught both.
The clear sat below the sensitive, generated, extension, ignore-file and
file-missing continues, so a later target exiting through any of those never
reached it and kept the oversized file's size while audit.file pointed
somewhere else. It also only handled the bundle-scanned-first order; when the
oversized file came last, the byte count was set after the emitting file had
already been decided and rode along on its audit entry regardless.
The root problem was keeping per-file state on the shared audit object. The
size is now held in a local and attached only when the oversized skip is the
run's actual outcome, so it cannot describe a file other than the one being
reported. Tests cover both scan orders, an early-continue target after the
skip, and the single-oversized-file case where the count should still appear.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Give DESIGN.md a real type ramp so the design hook stops crying wolf
The design hook fired on nearly every CSS file we touched. The cause was
DESIGN.md's typography block: it declared seven named roles rather than a
scale, and two of those roles used clamp(), which the extractor skipped
outright. That left an allowlist of five sizes standing against the 86
distinct font sizes actually in use, so design-system-font-size flagged
roughly 500 declarations. Editing any .astro page made it worse, because
the companion-stylesheet scan re-reported the whole backlog.
Extractor (cli/engine/design-system.mjs):
- Read a typography.scale map as the enumerated ramp.
- Read both clamp() endpoints as allowed sizes. These stay additive on
purpose: clamp endpoints alone cannot switch the rule on, because a fully
fluid system enumerates no discrete ramp and inferring one from its
endpoints would flag every intermediate size. The existing abstention
test still passes, and three new tests cover the added behavior.
DESIGN.md:
- Document a 19-step ramp, 8px through 72px at a 16px root.
- Snap the five discrete role sizes onto ramp steps.
This also fixes real drift. DESIGN.md claims to mirror kinpaku-tokens.css
verbatim, but wordmark was 1.15rem in the CSS against 1.3rem documented,
with tracking at 0.42em against 0.15em. Both are re-synced.
Standardization, 64 declarations:
- Six near-identical steps between 13.7px and 15.4px collapse onto 14 and 15.
- .foundation-card-label, .designing-lane-mock-title and
.designing-iterate-name each existed at two different sizes in two files.
Now unified.
- The wordmark rendered at four sizes (20.8, 18.4, 17, 16.8px). Now 18px,
plus one deliberate smaller nav variant.
Exemptions, for designs that are foreign on purpose: the antipattern-example
fixtures, the neo-mirai case-study build, the periodic-table cell
annotations in framework-viz.js (5 to 7px diagram geometry sitting at 2 to
3px offsets), and the .why-slop-* before-state card's Inter and gradient
text.
Verified by computed style across ten rendered pages: every element lands on
a ramp step except clamp() values mid-interpolation, which is what fluid
means. Full test suite and build validators pass.
Generated provider output is deliberately left out; the sync workflow owns it.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
* Validate clamp() endpoints in usage, not just when reading DESIGN.md
Reading clamp endpoints as documented steps without also checking them in
source left an asymmetry: `isAllowedFontSizeRaw` returned true for anything
failing the px/rem literal test, so `clamp(99rem, 1vw, 200rem)` passed. That
is how `.ptable-symbol` at `clamp(1.45rem, 1.8vw, 1.8rem)` stayed invisible
until someone measured computed styles, which is not a check the hook can run.
Fluid values are now judged on their min and max. The viewport term
interpolates between them and is never a fixed step, so it is left alone.
Endpoints that cannot be resolved, such as var() or calc() or em, abstain
rather than guess. Findings name the offending endpoint and use it as the
ignore-value, because the whole clamp string is not actionable on its own.
Turning the check on surfaced 22 fluid declarations that had never been
looked at. Three used hero sizes above the ramp's 72px cap (80, 83.2 and
88px) alongside the display role's documented 89.6px max, so the top of the
ramp was genuinely incomplete. Added the 80 and 88 steps, which gives the
display end consistent 8px increments instead of 48/56/64/72 plus an orphan
at 89.6, and fixes two declarations outright.
The other 20 are snapped by a stated rule: nearest step, ties toward the
smaller step, endpoints already matching a documented fluid role left as-is,
and where nearest-step would make a breakpoint override meet or exceed its
base, the next smaller step so the override still reduces. That last case
applies once, to .designing-page-title.
Also narrows the framework-viz.js waiver. The periodic-table cell
annotations now carry two `impeccable-disable-line` comments naming the
reason, instead of a config entry wildcarding the whole file for the rule.
Inline waivers travel with the code and cannot silence future drift
elsewhere in that file.
Verified at 420px, 900px and 1600px across seven pages. The pinned ends are
fully on-ramp; the only off-ramp values at 900px are the vw term
mid-interpolation, which is what fluid means.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
* Address review: wordmark tracking picked the wrong side, stale ramp count
Two review findings, both fair.
Wordmark tracking (greptile, bugbot). This PR moved DESIGN.md's wordmark
letterSpacing from 0.15em to 0.42em on the grounds that DESIGN.md claims to
mirror kinpaku-tokens.css and the token read 0.42em. That was the wrong side
to trust. `--ks-type-wordmark-track` has exactly one consumer,
design-system.css:570, which is the specimen page. Every production lockup
(.ks-wordmark, .kinpaku-chrome .site-header-brand-name, .footer-logo)
hardcodes 0.15em, so 0.15em is what every visitor actually sees and what
DESIGN.md already documented correctly before this PR touched it.
Reverted the doc to 0.15em and moved the token to 0.15em as well, so the
specimen now renders the same lockup as production instead of a wider one
nothing else uses. Verified by computed style: header and specimen both
report 18px with 2.7px tracking. No production visual change.
Stale ramp count (copilot). The sidecar described an "18-step ramp, 8px
through 72px". It went stale twice inside this PR, once when the 8 step was
added and again when 80 and 88 were added for the hero display sizes. It is
21 steps, 8px through 88px.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
* Strip !important from the font-size ignore value
Follow-on from the waiver wiring in the hook branch. The ignoreValue is what a
`hooks ignore-value` waiver has to match, and `font-size: 1.4rem !important`
emitted `1.4rem !important` while a plain declaration emitted `1.4rem`. Once
font-size is a direct-value rule, that means the same size needs two different
waivers depending on whether it carries a priority marker.
font-family already strips the marker before matching, and there is a test for
that. font-size now does the same. The snippet still shows the declaration as
authored.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
* Have the wordmark rules consume their tokens instead of copying the values
Follow-up to the tracking fix, and the residual half of what the reviewers
were pointing at. `.ks-wordmark` and the kinpaku chrome lockup each repeated
`1.125rem` and `0.15em` literally rather than reading
`--ks-type-wordmark-size` and `--ks-type-wordmark-track`. That duplication is
exactly how the token drifted to 0.42em while every production lockup stayed
at 0.15em and nobody noticed, which is the confusion that started this thread.
The values already agree, so this is a no-op visually and is verified as such:
computed styles across the home, design-system, docs and changelog pages all
still report 18px with 2.7px tracking. What changes is that there is now one
place to edit, so the next tracking change cannot silently apply to the
specimen page alone.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>