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>
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>
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>
- 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>
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>
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.
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>
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>
Pioneers a two-file convention for project context:
- PRODUCT.md (strategic): users, brand, principles — answers who/what/why
- DESIGN.md (visual): follows Google's Stitch DESIGN.md spec — answers how-it-looks
Both files live at the repo root. Filename matching is case-insensitive.
DESIGN.md wins on visual conflicts, PRODUCT.md wins on strategic/voice.
Legacy .impeccable.md is auto-migrated to PRODUCT.md on first read by the
new shared loader. This is silent and one-shot — the rename is permanent.
What changed:
- New scripts/load-context.mjs: shared context loader used by every command
that needs project context. Reads both files, handles legacy migration.
- New reference/document.md: /impeccable document command that generates
DESIGN.md by auto-extracting tokens (colors, typography, spacing, radii,
shadows, components) from CSS/Tailwind/theme files, then asking the user
to confirm descriptive language for atmosphere and color character.
Follows Google's Stitch DESIGN.md format for tool compatibility.
- SKILL.md Context Gathering Protocol updated to load both files and
nudge the user to run /impeccable document when DESIGN.md is missing.
- reference/teach.md rewritten to split discovery cleanly: strategic
questions go to PRODUCT.md, visual/design-system work is delegated to
/impeccable document (skipped on empty projects).
- reference/live.md consumes {product, design, productPath, designPath,
migrated} from the loader instead of a single context blob.
- scripts/live.mjs uses the shared loader instead of inline file reading.
- Command count updated 22 → 23 (new: document). Metadata, router table,
command menu, periodic table viz, and homepage data all updated.
- .gitignore adds PRODUCT.md + DESIGN.md (repo-local, not shared).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
os.tmpdir() returns /var/folders/.../T/ on macOS, not /tmp/. The skill
reference was telling the agent to cat /tmp/impeccable-live.json which
didn't exist. Moving the PID file to the project root makes it
predictable across platforms and project-scoped (multiple projects can
run independent live sessions).
Changed in: live-server.mjs, live-poll.mjs, live.md reference.
Added .impeccable-live.json to .gitignore.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The static "[command] [target]" hint didn't help users discover available
commands. The build now reads command-metadata.json and groups commands
by category (from SKILL_CATEGORIES) with middle-dot separators for
natural line-breaking in the prompt bar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New feature: /impeccable live starts an interactive visual iteration server.
Users select elements in the browser, pick a design action (bolder, quieter,
etc.), and the agent generates HTML+CSS variants written directly to source.
The dev server's HMR hot-swaps them in, and MutationObserver progressively
reveals each variant in a cycler UI as it arrives.
Architecture:
- src/live/server.mjs: HTTP + WebSocket server with session token auth,
long-poll /poll endpoint for the agent, WebSocket for the browser
- src/live/poll.mjs: CLI client (npx impeccable poll / poll --reply)
- src/live/browser.js: element picker with keyboard nav (arrows=siblings,
shift+arrows=parent/child), action panel (12 commands, freeform input,
variant count), variant cycler with progressive reveal via MutationObserver
- src/live/protocol.mjs: shared message types and event validation
- source/skills/impeccable/reference/live.md: agent loop instructions
(inject script, poll loop, generate variants, accept/discard, cleanup)
CLI changes:
- bin/cli.js: added "poll" top-level command
- src/detect-antipatterns.mjs: liveCli() now delegates to src/live/server.mjs
- package.json: added ws dependency
Registered /impeccable live as command #22 across all standard locations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-3.0, onboard was folded into /harden when we were trying to reduce
namespace pollution. In the single-skill model that tradeoff is gone,
so the weakest of the old merges is the first to undo.
Harden and onboard live in different mental modes. Harden is defensive
(edge cases, i18n, overflow, errors). Onboard is activation (first-run
flows, empty states as CTAs, progressive disclosure). A user thinking
"design the onboarding flow" was never going to type /impeccable harden.
Changes:
- New reference file at source/skills/impeccable/reference/onboard.md,
restored from the pre-merge version in git history rather than the
condensed 33-line summary that was in harden.md.
- Removed the "Onboarding & First-Run Experience" section from
source/skills/impeccable/reference/harden.md.
- Updated harden description/editorial/process-steps to drop onboarding
keywords; split commandProcessSteps so harden stays focused on
production resilience and onboard gets its own phases.
- Registered onboard in: SKILL.md description + command menu + router
table, command-metadata.json, IMPECCABLE_SUB_COMMANDS, pin.mjs
VALID_COMMANDS, SKILL_CATEGORIES, COMMAND_RELATIONSHIPS, data.js
commandCategories + commandProcessSteps + commandRelationships,
framework-viz commandSymbols + commandNumbers.
- Reused the existing content/site/skills/onboard.md editorial wrapper
(it was orphaned by the merge but never deleted), updating it to use
/impeccable onboard.
- Bumped all user-facing count references 20 -> 21: public/index.html,
CLAUDE.md, README.md, NOTICE.md, plugin.json, marketplace.json,
sitemap.xml, build-sub-pages.js.
- Harness dir audit.md and critique.md changes are the
{{available_commands}} placeholder expanding to include onboard.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Bump skills plugin version 2.1.1 -> 3.0.0 (plugin.json, marketplace.json,
harness SKILL.md files). CLI and Chrome extension unchanged.
- Remove prefixed universal zip bundle and all related code:
factory.js prefix/outputSuffix options, zip.js variant pass, utils.js
prefixSkillReferences, the "universal-prefixed" entry in
download-providers.js, and the matching test suite in utils.test.js.
- Redesign Get Started step 1 "Install the skill and CLI": two terminal
rows (npx skills + npm i -g impeccable) with paired notes, drop the
Recommended badge.
- Collapse "Other install methods" back into a <details> element so the
primary install path is the first thing users see.
- Simplify step 3 to "Add the Chrome extension": remove the CLI tool
block (now in step 1), use standard .btn .btn-primary for the CTA so
it matches other primary buttons (square corners, accent slide-up
hover), and lay out the preview screenshot next to the button instead
of stacked so the screenshot no longer dominates vertical space.
- CLAUDE.md: rewrite with v3.0 architecture, the "no em dash also means
no --" rule, the harness-dirs-are-tracked gotcha, the named-export
test-spy warning, and the evals inline-skill.ts sync note.
- AGENTS.md, DEVELOP.md: drop prefixed variant references.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Biggest change in a while. Users previously had 18 standalone skill
entries cluttering their /menu; now they have one entry (/impeccable)
that routes to 20 specialized commands via argument dispatch. The pin
mechanism (/impeccable pin audit) restores standalone shortcuts on
demand for commands users hit all the time.
## Architecture
- Single /impeccable skill with command router section in SKILL.md
- 20 commands served via reference files under source/skills/impeccable/reference/
- /impeccable pin <command> creates a lightweight redirect shim so users
who prefer /audit, /polish, etc. can still have them
- Context gathering (teach) auto-runs on first use
- command-metadata.json is the single source of truth for command
descriptions, argument hints, and relationships
## Site rewrite
- Docs URL: /skills renamed to /docs (with /skills permanent redirects)
- Homepage hero frames Impeccable as "one skill with 20 commands"
- "Get Started" split into 50/50 install + how-to-use with editorial
numbered steps, /impeccable shown as the home command with three modes
- New /docs overview: home command hero card + dense category rows
matching the old cheatsheet density, with leads-to/pairs-with/
combines-with relationship metadata served from a shared source
- Cheatsheet merged into /docs, /cheatsheet redirects
- Magazine spread and mobile cards show /impeccable as a stacked
namespace label above the command name at full display size
- Periodic table updated with craft/teach/extract as first-class cells
- Skill detail pages generate from reference files, with an editorial
wrapper per command for tagline + body
- Tutorials and anti-patterns pages updated to use /impeccable <cmd>
## Build system
- Dead code removed (scripts/lib/transformers/shared.js)
- Build log wording fixed ("1 skill" not "1 skills (1 user-invocable)")
- generateApiData fallback branch removed (throws loudly if metadata
missing instead of silently degrading)
- Commands API includes editorial tagline alongside the long description;
UI surfaces prefer tagline for human display, description for auto-
trigger keyword matching
## Gitignore
- Added .claude/scheduled_tasks.lock, .claude/settings.local.json to
ignore list (local Claude Code state that should not be tracked).
- Harness skill directories (.claude/skills/, .agents/skills/, etc.)
remain tracked by design: npx skills reads them from this repo at
install time and they enable clean submodule use.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
generateYamlFrontmatter only re-quoted values starting with `[` or `{`,
but parseFrontmatter strips surrounding quotes on input. Descriptions
containing `: ` (e.g. "Also handles: critique...") round-tripped into
unquoted plain scalars that YAML parsers reject. Added a yamlNeedsQuoting
check covering colon-space, space-hash, YAML indicator chars, reserved
keywords, and number-like strings, plus regression tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two detector bugs that produced false positives on sites like uselinkshot.com:
1. The bg-black regex matched Tailwind opacity modifiers (bg-black/3,
hover:bg-black/5) because / is a word boundary. Added negative lookahead.
2. resolveBackground ignored url() background-images, walking past them to
the body's white bg. White text on a dark hero image was flagged as
1.0:1 white-on-white. Now bails on url() images like it does for gradients.
Also: extension build auto-generates dist/extension.zip, version bumps for
CLI (2.1.7) and extension (1.0.1).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The gallery page had broken styling and missing images. The visual
mode page already has the same specimen gallery in a better layout.
- Removed public/gallery.html and its build entry point
- Updated homepage links to point to /visual-mode#try-it-live
- Added 301 redirect from /gallery to /visual-mode#try-it-live
- Added id="try-it-live" anchor to the visual-mode gallery section
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Build system now injects skills version (from plugin.json) into
every SKILL.md frontmatter as a version field
- CLI reads the version from the local impeccable SKILL.md and
displays it in check/update output
- Hash comparison normalizes the version field (so a version bump
alone doesn't trigger a full re-download)
- Removed misleading CLI version display from skills commands
CLI bumped to v2.1.5.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The refactored factory.js transformer dropped script file support that
existed in the old shared.js version. Scripts were read from source
but never written to dist/, so npx skills installed skills without the
cleanup-deprecated.mjs script, causing errors on first load.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The build still generates deprecated stubs in dist/ (so the cleanup
script can redirect users), but now removes them from the repo's own
harness directories so they don't clutter the local skill list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use h3 headings with custom IDs instead of inline spans so the browser
scrolls to the right position. Added {#id} syntax support to the
markdown heading renderer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Clarify that /impeccable craft runs /shape internally (not the other way around)
- Add three-mode documentation (freeform/craft/teach) to /impeccable page with anchor links
- Add sidebar sub-links for /impeccable craft and /impeccable teach
- Fix hallucinated npx impeccable live description in tutorial and visual-mode page
- Remove nonsensical "Do not skip the independent part" from critique tutorial
- Make Step 4 less prescriptive (users can fix all at once or one-by-one)
- Improve CLI and browser extension install copy with specific features and use cases
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extension is now live on the Chrome Web Store. Replace all
coming-soon placeholders with install links on the homepage,
visual-mode page, and overlay tutorial.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These self-contained HTML files and the detector script were served
by the dev server from public/ but never made it into the Cloudflare
Pages build directory, causing 404s on the deployed site.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cloudflare Pages merges headers from all matching rules rather than
overriding, so the path-specific SAMEORIGIN conflicted with the global
DENY. Switch the global rule to SAMEORIGIN since the site only frames
its own antipattern example pages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The global _headers rule set DENY for all paths, preventing the
/visual-mode/ page from embedding /antipattern-examples/ in an iframe.
Add a path-specific SAMEORIGIN override for /antipattern-examples/*.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Bun shipped with Cloudflare Pages doesn't dedupe shared CSS chunks
across HTML entrypoints — each entry tries to emit its own copy. With
chunk: '[name]-[hash].[ext]', three sub-pages all named index.html
(skills/, tutorials/, anti-patterns/) plus shared CSS content end up
producing chunks with identical name+hash and the build aborts on
'Multiple files share the same output path'.
Including [dir] in the chunk and asset templates scopes each chunk to
its entry's source directory, so the per-entry copies land in unique
paths even when dedupe is off. Local Bun (1.3.x) still emits a single
shared chunk because [dir] is only used when there are multiple chunk
candidates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cloudflare Pages ships an older Bun than the one used locally. That
version emits shared CSS chunks via the default 'chunk-[hash]' naming
template, but the [hash] token isn't always populated when the chunk
is shared across multiple HTML entrypoints — every sub-page that
imports sub-pages.css ends up wanting the same './chunk-' filename
and the build aborts with 'Multiple files share the same output path'.
Pin the chunk and asset naming explicitly so [hash] is always present
regardless of Bun version.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When Bun.build aggregates resolution failures, the thrown error keeps the
real causes on error.errors (an array). The previous handler only printed
error.message and error.stack, both of which are generic / undefined for
this kind of failure, so CI logs read as 'Bundle failed / undefined' with
no clue what was unresolved. Walk error.errors first so the actual file +
import that failed shows up in CI output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two unrelated breakages were stacking on the v2.0 PR:
1. The static site build crashed because the generated /visual-mode page
referenced images via root-absolute paths (/antipattern-images/*.png).
Bun's HTML loader resolves <img src> at build time relative to the
source HTML file and treats a leading slash as filesystem-absolute, so
it could not find the images. Use a relative path so Bun bundles and
hashes them the same way the homepage already does.
2. The Puppeteer-backed fixture tests crashed in GitHub Actions because
the Ubuntu runners block unprivileged user namespaces, so Chrome's
sandbox cannot initialize. Pass --no-sandbox / --disable-setuid-sandbox
only when process.env.CI is set, so local users keep the hardened
default launch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the brand-only card with a split layout: wordmark left,
floating Chrome extension detection panel right. Generator now counts
user-invocable, non-deprecated skills from source/skills/ (v2.0 unified
structure) instead of the removed source/commands/ directory.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The 'In the wild' section at the bottom of /anti-patterns was
mischaracterizing synthetic fixtures as real examples and was buried
deep in a taxonomy of detection rules. The specimens belong somewhere
that frames them as what they actually are: live pages you can click
into to experience Visual Mode. Split them off into a new top-level
page that also finally gives Visual Mode first-class treatment.
- New /visual-mode page, top-level nav item, single-column layout (no
sidebar). Structure:
1. Editorial header with an "Live detection overlay" eyebrow.
2. Live iframe embed of visual-mode-demo.html inside mac-window
chrome, same preview component the homepage uses.
3. "Three ways to run it" section with three method cards:
- /critique runs the overlay inside its browser pass
- `npx impeccable live` starts a standalone overlay server
- Chrome extension, marked coming soon, with a cream bg
4. "Try it live" gallery of the 11 synthetic specimens as
clickable cards. Each links to /antipattern-examples/{id}.html
where the detector script is already injected so the reader
lands on a live overlay.
- scripts/build-sub-pages.js: new renderVisualModeMain(); visualMode
added to outDirs; generator loop writes /visual-mode/index.html.
- server/index.js: new /visual-mode route serving the generated file.
- Top nav on every page gains 'Visual Mode' between Anti-Patterns
and the GitHub pill. Updated the partial + all 4 hand-authored
HTML pages.
- .gitignore adds public/visual-mode/.
- /anti-patterns: 'In the wild' section and its TOC entry removed.
Replaced with a one-line pointer at the end of the lede: "Want to
see them live on real pages? Try Visual Mode." GALLERY_ITEMS stays
in the catalog file (now used by /visual-mode only).
- public/css/sub-pages.css: new .visual-mode-page-body + .visual-mode-*
classes. Ports the mac-window chrome (dots + mono title) from
main.css, adds three-card method grid, and reuses the existing
.gallery-card styles for the specimen list.
Clean up a few em-dashes in the catalog (block comments + one visible
visual example) so the build-time validator stays clean.
Server restart required to pick up the new /visual-mode route.
Two fixes from the review.
1. Rule id chip hidden. The internal slugs (e.g. 'border-accent-on-rounded')
are not useful to readers, only to detector code. Drop the
.rule-card-id element from the card head entirely. The DOM id on
the article stays so rules can still be anchor-linked.
2. Merge /gallery into /anti-patterns and drop 'Gallery' from the nav.
'Gallery' in the top nav reads as 'things built with impeccable'
when it is actually a curated collection of AI-generated UI in the
wild — the complement to the rule catalog above.
- Add GALLERY_ITEMS to content/site/anti-patterns-catalog.js
(11 entries, same ids and copy as the old gallery.html)
- Render a new 'In the wild' section at the bottom of
/anti-patterns with a card grid of the 11 specimens, each linking
to its standalone live example under /antipattern-examples/{id}.html
- New .gallery-card CSS: square thumbnail, italic display title,
charcoal body, hover lifts the card and tints the title accent
- Add an 'In the wild' entry to the anti-patterns TOC sidebar so
readers can jump to it
- Drop the 'Gallery' link from the top-level nav in the shared
header partial and the 4 hand-authored HTML pages. The old
/gallery route still serves its page directly (for bookmarked
links), but the nav no longer advertises it and the gallery page
itself now marks Anti-Patterns as the active nav item.
Six fixes from the first-pass review.
1. Visuals for all 13 LLM-only rules. The catalog now ships a preview
snippet for every card: Syne-style display, monospace-as-technical,
dark-mode-default, everything-in-cards (nested), identical card
grids (literal 3x2), hero metric layout (big number + gradient +
supporting stats), glassmorphism (backdrop-filter on a gradient),
sparkline decoration, generic drop shadows (three rounded squares),
modal reflex (backdrop + centered dialog), every-button-primary,
redundant-headers, mobile-amputation. Every rule card now has the
same ~160px preview treatment.
2. Lede font normalized to match skill detail pages. .sub-page-lede
dropped from clamp(1.0625, 1.6vw, 1.25rem) to clamp(1, 1.4vw, 1.125rem)
so the paragraph under the anti-patterns title is the same size as
the tagline on every other /skills page.
3. "How to read this" legend collapsed into a <details> disclosure.
Summary is a single compact row with the title + chevron, padding
14px vertical. Body appears when opened, same content as before.
Chevron rotates on open.
4. Visual example height bumped 140px -> 160px for more breathing
room with the complex snippets.
5. Wider grid on the anti-patterns page. .anti-patterns-content no
longer has a 820px max-width; only the header (720px max) and
legend (720px max) are capped. The rule card grid fills the full
main column width on wide viewports, so 38 cards stop wasting
horizontal space.
6. Sidebar divider extends to the bottom of the viewport. Add
min-height: calc(100vh - var(--site-header-height)) to .skills-sidebar
so the sticky column fills the full viewport vertically regardless
of content height, and the border-right reaches the footer.
Three additions to the anti-patterns catalog page, all sourced from a
new content/site/anti-patterns-catalog.js file so the user's parallel
edits to src/detect-antipatterns.mjs don't conflict with display metadata.
1. Detection layer badge per rule. Three layers:
cli - static analysis or jsdom. Runs from `npx impeccable detect`
on files, no browser required. 23 of 25 current rules.
browser - needs real browser layout (getBoundingClientRect).
Runs via the browser extension or Puppeteer, not the
plain CLI. Only 2 rules: cramped-padding and line-length,
as documented in tests/detect-antipatterns-browser.test.mjs.
llm - no deterministic detector. Flagged by /critique's LLM
review pass. 13 rules live only in the skill's DON'T list.
Each card renders a mono pill with the layer label, color-coded per
layer (neutral mist for CLI, blue tint for browser, amber tint for LLM).
The How-to-read legend grows a dl explaining what each layer means.
2. Inline visual example per detected rule. All 25 detection rules get
a ~140px tall preview area at the top of the card showing the bad
pattern as live HTML (cream background, self-contained inline styles).
Visuals for side-tab, gradient-text, dark-glow, nested-cards, and the
rest let you see what the detector is actually flagging. LLM-only
rules ship without visuals for now; their card bodies take the full
card height.
3. LLM-only rules merged into the sections. Parsed out from
source/skills/impeccable/SKILL.md DON'T lines that the detector
doesn't cover: Syne, monospace-as-technical, dark-mode-default,
everything-in-cards, identical-card-grids, hero-metric-layout,
glassmorphism, sparkline-decoration, generic-drop-shadows,
modal-reflex, every-button-primary, redundant-headers,
mobile-amputation. Each renders like a detection rule card but
shows the 'LLM only' layer badge and has no rule id chip. They
slot into the same section groups as detected rules (Interaction
and Responsive sections added to the section order so these get
real headings).
- scripts/lib/sub-pages-data.js: imports the catalog, enriches
detected rules with { layer, visual }, appends LLM_ONLY_RULES with
layer: 'llm'. Re-exports LAYER_LABELS and LAYER_DESCRIPTIONS for
the generator.
- scripts/build-sub-pages.js: renderRuleCard adds the visual block
and the layer badge; LLM rules drop the rule id chip since their id
is just an internal slug. groupRulesBySection now extends the
primary order with whatever extra sections rules reference.
- public/css/sub-pages.css: .rule-card now has a .rule-card-visual
preview area on top with border-bottom, body section below. New
.rule-card-layer pill styling per layer. Layer legend dl using a
2-column grid for badge -> description.
Dev server serves 38 total cards (25 detected + 13 LLM) across 8
sections: Visual Details, Typography, Color & Contrast, Layout & Space,
Motion, Interaction, Responsive, General quality.
Three docs sidebar improvements.
1. Collapsible mobile menu. The sidebar on narrow viewports used to
dump 21 skill links and 2 tutorial links inline above the content,
forcing a long scroll past the nav. Add a toggle button at the top
of the sidebar that shows the current page label (e.g. "/overdrive"
or "Getting started") plus a chevron, and collapses the menu behind
it on mobile. Click the button to open/close. On desktop (>=920px)
the toggle is hidden and the menu shows unconditionally as before.
Pure aria-expanded state driven by a small delegated click handler
in render-page.js.
2. Active-state breathing room. The left-border accent on the current
sidebar item used to sit 2px from the text, which felt cramped. Pull
the border 14px to the left via margin-left and push the text 12px
to the right via padding-left. The net result: the accent bar sits
in the layout gutter, the text keeps its alignment with the brand
logo in the header, and there's now 12px of comfortable space
between the border and the text.
3. Active state visibility. The same change makes the accent bar more
visible on desktop, since it no longer hugs the text. 'aria-current'
was already being set correctly on /skills/* and /tutorials/* pages;
the bar just looked too subtle at 2px of clearance.
The demo's Before/After labels and the descriptive caption were on two
separate rows below the card. Merge them into one row: Before pinned
left, caption centered in the middle, After pinned right.
- Move the caption <p> inside .split-labels between the two label spans.
If a skill has no caption, emit an empty <span> placeholder so the
grid still has three cells and Before/After sit at the edges.
- Switch .split-labels from flex space-between to a 3-column grid
(auto minmax(0,1fr) auto) with baseline alignment. Before is
justify-self: start, After is justify-self: end, caption is
justify-self: center.
- Reset the caption's typography inside the grid (default body font,
not mono; text-transform: none; letter-spacing: 0) since it inherits
the label row's monospace caps by default.
The eyebrow ('Drag or hover to compare') was a sibling of
.split-comparison, sitting at the left edge of the outer .skill-demo
section. Because .split-comparison has 32px padding, the visible card
inside sat 32px to the right of the eyebrow, creating a visible
indentation mismatch. Move the eyebrow inside .split-comparison so it
inherits the same 32px offset and aligns with the card's left edge
(same as how .split-labels and .skill-demo-caption already align).
Note: the HTML order inside .split-comparison is now eyebrow -> container
-> labels -> caption, which matches the homepage's before/after demo
flow (card -> BEFORE/AFTER -> descriptive caption).
Recognize "DO NOT" / "DO" lines (with optional colon) inside <rules>
and <absolute_bans> blocks, and make skillGuideline substring matching
case-insensitive so the validator handles the new XML-structured
SKILL.md without rejecting the refactored prose.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two visual fixes to the skill detail demo block.
1. Remove the 24px padding from .split-content. This padding was the
'persistent indentation' visible in the screenshot: it created a
white band between the container border and demo content that had
its own card background. The overdrive demo (which fills the
container via absolute canvases and 100% divs) lost some of its
bleed to the padding; the polish-style demos (small centered cards)
don't need it because .split-content already uses flex centering.
2. Restructure the skill detail header into a .skill-detail-hero
wrapper that holds both the header text and the demo block.
- At >=1100px viewport: switch to a grid (minmax(0,1fr) auto),
text column on the left, demo on the right, align-items:center
so the eyebrow/title/tagline center with the demo vertically.
The demo floats as an editorial hero element alongside the title.
- Below 1100px: stack (demo under the header) with clamp-based
spacing between them. Same visual as before, just now inside the
hero wrapper.
- skill-detail-hero--has-demo class so skills without a demo
(/shape) keep the single-column layout with no grid quirks.
Caption was sitting with a ~56px gap below the labels (32px container
padding-bottom + 24px caption margin-top). Move the <p> inside
.split-comparison so the padding wraps the caption too, not separates
it from the labels. Drop caption margin-top from 24px to 12px and tone
the color down from charcoal to ash + 0.8125rem to match the rest of
the supporting-text rhythm on these pages.
Ported the homepage's padding-margin trick: .split-comparison now has
20px of padding around the visible box, with a matching negative
top/bottom margin so the padding does not affect layout flow. The
pointer event listeners move from .split-container to .split-comparison
so the hover tracking engages inside the buffer and only resets when
the mouse leaves the full padded area. Percentage math still reads
.split-container.getBoundingClientRect() so the divider position stays
aligned with the visible box.
This matches how the landing-page split demo feels: graze the edge
and the divider holds; leave the box entirely and it eases back.
Four issues reported on /skills/overdrive (and every other skill demo):
1. The demo block was centered inside the content column, looking odd
against the otherwise left-aligned page. Drop 'margin: 0 auto' from
both .split-comparison and .split-container, and remove the nested
max-width so the whole demo left-aligns at 500px max-width with no
centering.
2. The BEFORE and AFTER labels were stretching beyond the demo box
because .split-comparison (560px) was wider than .split-container
(500px) and .split-labels was using justify-content: space-between
across the wider parent. Collapse the two max-widths to a single
500px cap so the labels now sit flush with the container edges.
3. The label row was sitting way below the demo (16px margin-top plus
the height-stretched container). Tighten margin-top to 10px.
4. The inline split-compare handler only supported click-and-drag. The
homepage effect also tracks hover on devices with hover:hover, so
the mouse sweeps the divider and leaving the box eases it back to
the default. Port that behavior: matchMedia('(hover: hover)') to
detect, pointerenter/leave to toggle a hovering flag, pointerdown/up
for drag, and a tiny lerp on requestAnimationFrame so the return to
center feels smooth. Eyebrow text now reads 'Drag or hover to
compare' to signal both modes.
Also drop text-align: center on .skill-demo-eyebrow and .skill-demo-caption
for the same left-align consistency.
Two changes bundled:
1. Before/after split demos on every skill detail page.
- loadCommandDemos() in sub-pages-data.js: dynamically imports each
module in public/js/demos/commands (the same files the homepage
uses), returning a { skillId: { id, caption, before, after } } map.
Falls back to a warn-and-continue if a demo file can't be loaded
so one bad demo doesn't break the whole generator.
- buildSubPageData becomes async; caller in build-sub-pages.js
awaits it.
- Each skill object gets a .demo field (may be null for /shape).
- renderSkillDemo() produces the .split-comparison markup matching
the homepage: .split-container with .split-before + .split-after
+ .split-divider, plus Before/After labels and the caption. The
block sits between the detail header and the editorial wrapper
so readers see the visual before reading any prose.
- sub-pages.css ports the core .split-* layout from main.css (the
.slop-* and .impeccable-* helpers are homepage-specific and not
copied). Height is 360px to match the docs column.
- render-page.js grows a lightweight inline split-compare init
script (60 lines of vanilla JS) that handles drag and the skewed
clip-path without depending on the homepage's full lerp/ResizeObserver
module. Runs only on pages that actually have .split-container.
2. Sidebar reorder: Tutorials first, then skills.
Walk-throughs are the on-ramp; they belong at the top of the sidebar
where a new visitor will find them. Add <hr class="skills-sidebar-divider">
between the Tutorials group and the first skill category so the two
sections read as distinct.
Verified: /skills/polish, /skills/bolder, /skills/critique all render
the demo block. /skills/shape correctly has none. Sidebar on any /skills
or /tutorials page shows Tutorials first, then a thin mist divider,
then Create / Evaluate / Refine / Simplify / Harden / System skill
categories.
Two small-to-medium improvements bundled together.
1. Copy buttons on every rendered code block.
- render-markdown.js: wrap each fenced code block in a .code-block-wrap
container and emit a <button class="code-block-copy" data-copy="...">
alongside it. Button text is set via CSS ::before content so the
'Copy' / 'Copied' label is a single toggle class (.is-copied).
- render-page.js: 12-line inline script at the end of the body wires
a delegated click handler that calls navigator.clipboard.writeText
and flips .is-copied for 1.5s.
- sub-pages.css: button styles matching the dark terminal palette,
hidden until you hover the code block, accent-colored success state.
2. Merge Skills and Tutorials under a single Docs nav item.
- Rename the Skills nav link to 'Docs' in every header (partial +
4 hand-authored pages). Drop the separate Tutorials nav item; it
now lives inside Docs. Anti-patterns stays as its own top-level.
- scripts/build-sub-pages.js: replace renderSkillsSidebar and
renderTutorialsSidebar with a unified renderDocsSidebar that shows
every skill category followed by a Tutorials group. Takes a
current descriptor of shape { kind: 'skill'|'tutorial', id|slug }
so both skill detail and tutorial detail pages can mark the active
row. activeNav on every /skills/* and /tutorials/* page is now
'docs'; the shared site header's data-nav matches.
Verified: /skills/polish and /tutorials/getting-started both render
with the unified Docs sidebar (all 21 skills grouped by category +
both tutorials as a final group). The Docs nav item is aria-current
on both. Copy buttons appear on every fenced code block and toggle
to 'Copied' when clicked.
Feedback round from first review of the skill pages. Six concrete fixes:
1. Contain the auto-rendered SKILL.md in a distinct card.
The "skill itself" section was flowing straight into the editorial
wrapper above, making the two blocks read as one long mixed
document. Wrap the canonical body in .skill-source-card: white
paper background, mist border, rounded, with a small "SKILL.md"
badge header and an italic subtitle. Drop the old full-width
divider treatment; the card does the visual separation work.
2. Rewrite the /impeccable "do not fight the opinion" pitfall.
The old text said "fighting the opinion usually produces worse
output", which discouraged informed pushback. Replace with language
that explicitly encourages users with real reasons (brand guideline,
accessibility, user research) to push back; the skill raises the
floor, not overrules your judgment when you have evidence.
3. Move /onboard from create to refine.
Onboarding is refinement of empty states and first-run experiences,
not greenfield creation. Fixed in:
- scripts/lib/sub-pages-data.js SKILL_CATEGORIES
- public/js/data.js commandCategories
Both locations now list onboard under 'refine'.
4. De-dupe overdrive and animate.
- overdrive: "how it works" listed 7 techniques as bullets and then
"try it" listed 5 concrete examples using the same 7 techniques.
Collapse "how it works" into a tight paragraph and make "try it"
a specific scenario instead of a laundry list.
- animate: pitfalls repeated the "no layout properties" rule that
was already stated in "how it works". Drop the duplicate.
5. Remove outdated tutorial guidance.
getting-started.md said "Cursor needs Nightly channel plus Agent
Skills in Settings. Gemini CLI needs the preview version." Neither
is true anymore. Replace with a generic pointer to check the
harness's own skill docs.
6. Embed the live visual overlay in critique-with-overlay tutorial.
The tutorial now renders the same demo iframe the homepage uses
(/antipattern-examples/visual-mode-demo.html) inside step 2, with
a mac-window chrome frame that mirrors the homepage preview. New
.tutorial-embed CSS in sub-pages.css defines the header with
traffic-light dots + mono title, the iframe body (520px tall),
and an optional caption. The user now sees the overlay in action
before being asked to run it locally.