Two things:
1. clone --fidelity: after generating the Next.js starter, rebuild its
components from the extracted tokens, pixel-diff vs the live site, and write
FIDELITY.md + fidelity.json + fidelity-card.svg + a ranked correction plan
into the clone. Reuses the existing extraction (no second crawl) and points
the user at `designlang fidelity` for the full visual+motion score of the
running clone.
2. resolveOut(): the root command greedily owns -o/--out, and a sub-level
*default* masked any override (opts.out was always truthy). clone/verify/
visual-diff dropped their masking defaults and now resolve via resolveOut,
which prefers an explicit sub value, else the root value only when actually
passed on the CLI (getOptionValueSource === 'cli'), else the fallback.
Verified end-to-end: cloned stripe.com builds clean (next build, 4/4 pages),
served, and `fidelity` scored it 63/100 (motion 52 — the gap competitors miss).
-o now writes where asked. Adds a clone --fidelity CLI smoke test.
README quick-start, a dedicated 'Measured clone fidelity' section, the plugin
command table, and the full CLI reference now cover /fidelity and /gallery.
CHANGELOG 12.24.0 entry. Version bumped in lockstep across package.json,
plugin.json, marketplace.json (manifests describe the two new commands); the
version-sync test now guards fidelity + gallery too.
designlang gallery [dir] scans a directory for fidelity.json reports, builds the
gallery model, and writes a deployable static site (index + permalink pages +
OG cards) to --out. load.js does a safe recursive scan (skips node_modules and
malformed files). Adds command doc + loader tests + 2 CLI smoke tests. Verified
end-to-end against real reports.
formatGalleryIndex renders a dark card grid (best-first) with summary stats;
formatGalleryItemPage renders a permalink page that embeds the SVG score card,
OG/Twitter meta (so a shared link unfurls the number), a motion table, and the
ranked correction plan. renderGallerySite emits the full [{path,content}] tree
(index + per-clone page + per-clone card.svg). 4 unit tests.
The distribution surface for the fidelity loop (feature C). buildGallery() turns
a set of fidelity reports into a deduped, best-first item list with summary
stats (count, avg, best/worst). slugify() + toGalleryItem() are tolerant of
partial reports. Pure data shaping; 6 unit tests.
Renders a combined fidelity result three ways: machine JSON, a Markdown report
(headline score, motion breakdown table, ranked correction plan), and a
self-contained SVG share card ('88% · B · stripe.com'). The card is the gallery
share hook — a measured number no competitor can post. 6 unit tests.
The wedge neither competitor ships: don't just measure fidelity — prescribe the
next edit and loop on it.
- correction-plan.js: turns verify's per-token-family attribution and the motion
aspect scores into ranked, actionable directives (hardest-hitting first), each
with an expectedGain heuristic so the loop spends its budget well.
- index.js: combineFidelity() blends visual (verify) + motion into one score and
letter grade; runFidelityLoop() drives measure→plan→rebuild→re-measure until a
threshold or round budget, with measure/rebuild injected for offline testing.
10 unit tests covering ranking, threshold gating, blend math, convergence, the
stall path, and the no-directives early exit.
verify/ scores static visual fidelity but deliberately ignores motion. This
fills that gap: a pure function over two extractMotion() outputs that blends
seven aspects (feel, durations, easings, springs, keyframe kinds, scroll-linked,
choreography/stagger) into a 0-100 motion fidelity score with per-aspect gaps.
Missing-on-clone motion is the dominant penalty; over-animation is penalised
gently. No browser, no I/O — fully unit-tested (7 cases).
13 tests: warm/cool axis polarity, archetype selection from the twelve,
adjective/positioning/evidence shape, dangling-dash guard, empty/null
robustness, string-form inputs, and Essence chapter in html + md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Asserts no literal [object Object], semantic/easing/z-index values render,
and component class names keep their last character.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Synthesis reduced swatches to {hex,count}, dropping rgb/contexts and
crashing formatMarkdown (reads c.contexts.join and c.rgb) when the
canonical pack was emitted. Keep the whole swatch (spread) so every
emitter works. Caught by an end-to-end run on example.com; covered by a
regression test asserting canonical swatches retain rgb + contexts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scripts/check-plugin-version.mjs fails loud when plugin.json or
marketplace.json drift from package.json; wired as 'npm run check-plugin'
and enforced by tests/plugin-version.test.js (also asserts every new
command has a backing file and is described). Prevents the drift recurring.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New Motion tab in the local studio renders extracted easing as SVG curves
(springs styled distinctly), duration chips, and runtime choreography +
scroll recipes. Click a curve to preview its timing; play a choreography
sequence to see the real captured stagger. Degrades to a --motion-runtime
prompt when no motion tokens are present.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
motion-tokens.json gains choreography + scroll-recipe + observed-duration
blocks. Framer Motion stagger and the new GSAP stagger() helper now use the
real runtime-observed staggerMs instead of a heuristic guess when
--motion-runtime captured one. Degrades cleanly with no runtime data.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pure processing layer for Motion Lang v3: processRuntimeMotion() turns raw
document.getAnimations() observations into a structured per-trigger model with
real durations; detectChoreography() finds evenly-staggered sequences;
detectScrollRecipes() classifies scroll motion into parallax/reveal/pin.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The preview felt random. This rebuilds it as a deliberate design-system
specimen instead of a scatter of placeholder components.
- Composed preview: masthead, a centered hero using the site's own strongest
heading + top CTA verbs, labeled Color and Type foundations, then Buttons /
Inputs / Status / In-context cards / Metrics — each under a clear section
eyebrow, on a consistent vertical rhythm. The rebuilt-Page tab reuses the
same crafted components. Honest product copy, no lorem.
- Harmonised deriveTokens: guarantee a legible surface/text pair, filter greys
out of accent selection (no neutral promoted to brand), deepen a near-
invisible accent to read on the surface, hold secondary text above a
legibility floor. Keep white-on-warm-accent convention for accent foreground.
- Calmer chrome: quieter hairlines, consistent 30px rounded controls, pill
tabs, segmented light/dark — the frame recedes so the system is the focus.
Shared studio engine, so the CLI and website /studio get it identically.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a Light/Dark toggle that generates a dark variant from the extracted
light tokens: surface → near-black tinted toward the brand hue, text →
near-white, muted/border/card recomputed; the brand accent is preserved
(lightened only if it would vanish on dark). Type/shape/spacing/motion carry
over. Contrast grading recomputes live, inspector edits layer on top of
either theme, and the theme is encoded in the shareable URL.
Pure shared deriveDark() in src/studio-tokens.js keeps the CLI and website
studios in lock-step. New unit tests cover the dark derivation and control.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Turn `designlang studio` from a read-only viewer into an interactive
playground, and ship the same experience on the website.
CLI (`designlang studio`):
- Inspector (color/type/shape/spacing/motion) writes a small `--p-*` CSS
variable set; a tabbed live preview — component wall + a page rebuilt from
the extraction's reading order — restyles instantly off those variables.
- Live WCAG contrast grading per color, paper/white/dark backdrops, an edit
counter, and smooth token-driven transitions.
- Export edited system as DTCG tokens / CSS variables / Tailwind theme
(zero-dep client downloads); share via URL (edits encoded as hash deltas).
Website (`/studio`):
- Paste any URL → extract → embed the identical editable studio in a framed
viewport. Shareable via `?url=`; shares the free 2/day demo budget.
- New POST /api/studio reuses the same `studioHtml` engine server-side.
Internals:
- Token derivation extracted to a pure, dep-free module (src/studio-tokens.js)
shared by the CLI and the website route, keeping both surfaces in lock-step.
- New unit tests: derivation, WCAG contrast math, rendered studio document.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`designlang verify <url>` closes the extract→prove loop. For each detected
component (button+card in v1) it rebuilds a clone styled ONLY by the extracted
tokens (each visual property snapped to nearest token, colours in CIE-Lab ΔE),
renders it, and pixel-diffs against the live component → 0-100 fidelity score +
per-token-family attribution + a verify.html triptych (original | rebuilt |
loss heatmap) + verify.json. `--min` gates CI.
Honest by construction: missing component → n/a (excluded, no silent 100);
property with no token → unmapped (counted as loss, surfaced in attribution);
size mismatch letterboxed, never stretched.
New isolated modules under src/verify/ (tokens/restyle/render/diff/index) +
formatters/verify.js + verify CLI command. Adds pixelmatch+pngjs. 13 unit tests,
full suite green (438). Live smoke vs github.com: button 89/100.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Two new framework targets for the extracted motion language:
- motion-gsap.js: registers extracted cubic-bezier curves as named GSAP
CustomEases, emits gsap.from() reveal helpers + a ScrollTrigger batch
reveal when the page uses scroll/view-timeline.
- motion-waapi.js: zero-dependency Web Animations API preset. WAAPI takes
cubic-bezier easing strings verbatim, so curves reproduce exactly (no
spring approximation); helpers honour prefers-reduced-motion.
Wired into the CLI emit list (`<host>-motion.gsap.js`, `-motion.waapi.js`),
the /api/motion exports, and the motionlang analyzer export cards. Adds 8
emitter tests (425 -> all green). Also ignores .npmrc.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
motionlang:
- New formatter src/formatters/motion-css.js — <host>-motion.css with
--duration-*/--ease-* custom properties, reusable + reconstructed
@keyframes, .mo-* utility classes, and a prefers-reduced-motion guard.
- New formatter src/formatters/motion-tailwind.js — <host>-motion.tailwind.js
require-able theme.extend (transitionDuration, transitionTimingFunction
incl. a spring curve from detected overshoot beziers, keyframes, animation).
- Wired both through api.js (RENDERERS + ext map) and the bin files array as
the motion-css and motion-tailwind renderer ids.
- 13 new tests; full suite 414 pass.
website PDF fix ("downloaded PDF won't open"):
- extract route awaits the Blob cache write before the stream closes, so the
serverless instance can't freeze mid-write and leave /api/pdf/<hash> with
no cached design (was a fire-and-forget write returning 404 JSON).
- ShareExtractionButton fetches + validates content-type is application/pdf
before saving, surfacing an error instead of writing a broken file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- New formatter src/formatters/motion-one.js — emits <host>-motion.one.js
with animate() helpers (fadeIn, slideUp, scaleIn, pop), springs derived
from detected overshoot beziers, reconstructed keyframe maps, and
inView/scroll helpers when the source uses scroll-/view-timeline.
- Framer Motion: springs now derived from detected overshoot beziers
(was hard-coded), added pop variant on a spring, keyframe-derived
variants reconstructed from @keyframes, and a whileInView inView
block when the source uses scroll motion.
- Wire motion-one through api.js (RENDERERS + ext map) and the
bin/design-extract.js files array so it ships alongside the existing
Framer Motion preset.
- Tests: 7 new cases covering spring derivation, scroll-conditional
emit, keyframe reconstruction, and minimal-design degradation. All
pass; 154 existing formatter tests still green.
Shared springFromBezier heuristic keeps Motion One and Framer Motion
outputs feel-consistent for the same site.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds native PDF brand-guide output to the brand command:
--pdf, --paper a4|letter|tabloid, --landscape,
--no-print-background, --attach-tokens
PDF features: per-chapter page breaks, running footer with page
numbers, selectable text, embedded fonts, optional DTCG-tokens
JSON attached as a file inside the PDF (paperclip in Acrobat).
One new dep: pdf-lib (lazy-imported, only used when --attach-tokens
is passed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two tight quality-of-life additions on top of the v12.9 extraction pass.
stats command
$ npx designlang stats stripe.com
Grade B · 87/100
Primary #533afd ×899 59% conf
Fonts sohne-var
Type scale 14 sizes
Spacing base 2px · 13 steps
Shape 5 radii · 6 shadows
Colours 31 tokens
WCAG 79%
Stack next · unknown
Material skeuomorphic
Tone neutral
Intent landing
One-screen summary, no files written. Use -j / --as-json for
machine-readable output (CI, scripting).
(--json is already a global program flag for the main extract
command's raw-JSON-to-stdout mode, so the subcommand flag is
--as-json to avoid the clash. Both --as-json and -j shortcuts work.)
Low-confidence note in grade.html
v12.9 added primary.confidence (0-1) on the colours payload but no
surface used it. v12.10 renders an amber callout above the
dimensions grid in the grade report card when confidence < 0.5:
"Primary detection was low-confidence (32%). The brand colour
may be a soft pick — review the palette below."
Soft inline note, not an error. Stays hidden on the common high-
confidence path. Dark-mode aware.
Tests
- 2 new tests cover the present + absent paths of the confidence
note.
- 398/398 total.
No breaking changes, no new dependencies.
Eight features rode on top of the same extractor. This release fixes
four real defects in that extractor — visible across every downstream
output (grade, battle, remix, pack, theme-swap, brand, pair).
Fixed
1. clusterColors() representative bug. Before, the first-encountered
colour seeded each cluster, so a sparsely-used pale shade could
become the canonical hex of a cluster that mostly held a vivid
brand colour. The brand-book primary slot was reading lavender
for Stripe instead of #533afd. Now: representative is the
most-counted member.
2. detectScale() missed common production base units. Before, only
[2, 4, 6, 8] were tried, so Bootstrap-style base-5 and base-7/10/12
sites returned base:null. Now: [2, 4, 5, 6, 7, 8, 10, 12, 16] with
a small bonus for 4 and 8 to keep results stable for production
defaults.
3. Typography noise. Generic CSS stacks (sans-serif, monospace,
system-ui, inherit, -apple-system, ...) and icon fonts (Material
Icons, Font Awesome, Lucide, Tabler, ...) polluted the families
list. Brand books had icon fonts documented as the body family.
Now: an explicit filter at the source.
Added
4. primary.confidence (0–1) on design.colors.primary. Computed from
the brand-score gap between rank 1 and rank 2. Runaway leader →
1.0; near-tie → 0.3. Downstream consumers can surface uncertainty
warnings on low-confidence extractions.
5. asList(v) helper exported from src/utils.js. Coerces anything-
shaped input (array / object / comma-string / scalar) into a clean
string array. Consolidates ad-hoc per-formatter defenses.
Verified live on stripe.com
Before: primary slot showed a washed-out lavender; multiple icon
fonts polluted families; spacing base often null.
After: primary #533afd (count 899, confidence 0.59), families
['sohne-var'], spacing base correctly detected.
No new dependencies, no schema breaks, no public-API changes.
396/396 tests pass (6 new).
`designlang pair <urlA> <urlB>` extracts both sites in parallel, then
mixes their design systems along seven configurable axes (colours,
typography, spacing, shape, motion, voice, components). Default split
is "visuals from A, voice + type + components from B" — i.e. the most
distinctive crossover.
$ npx designlang pair stripe.com linear.app
stripe.com × linear.app
A Colours · stripe.com
B Typography · linear.app
A Spacing · stripe.com
A Shape · stripe.com
A Motion · stripe.com
B Voice · linear.app
B Components · linear.app
✓ stripe-com-x-linear-app.pair.html
✓ stripe-com-x-linear-app.pair.md
✓ stripe-com-x-linear-app.pair.json
How it works
- src/fuse.js — fuseDesigns(a, b, opts) deep-clones both inputs,
picks each axis from the requested source, synthesises a
pair-specific meta URL ("pair://<a>-x-<b>"), and strips score /
cssHealth (those measurements belong to the source extractions,
not the fusion).
- Default axes calibrated for the most interesting crossover:
colour/spacing/shape/motion from A, typography/voice/components
from B. Override any axis with --<axis>-from a|b.
- Layout follows colour (mixing primary from one site with neutrals
from another tends to muddy the brand). Material language and
imagery style track the colour pick. Variables follow colour.
- Components carry their detected library label (componentLibrary)
along with the anatomy.
Pair card formatter
- src/formatters/pair.js — editorial three-card hero (A · B · Fused),
per-axis source matrix, and a fused specimen using the *real*
headline from whichever site contributed the voice axis.
- --brand flag also emits a full brand-guidelines book of the fused
identity (re-uses formatBrandBook on the fused design).
Plugin
- /pair is the 8th slash command (commands/pair.md).
- .claude-plugin/plugin.json + marketplace.json bumped to 12.8.0
with an eight-command description.
Tests (12 new, 390/390 total)
- Default split, per-axis overrides, score-stripping, meta synthesis
- Source-design immutability, missing-input errors
- HTML rendering, voice carry-through to specimen, XSS escaping
- Markdown axis source table
Verified live: Stripe × Linear fuses Stripe's purple palette with
Linear's Inter Variable typography and Linear's actual top heading
("The product development system for teams and agents") as the
specimen quote.
Why
battle (v12.2) answers "which is better"; pair answers "what would
the intersection look like". Same parallel extraction, opposite
operation. Pure logic, no LLM, no new dependencies.
The v12.7.0 brand book had a real-data deficit:
- Cover used a generic grade-coloured accent (not the brand's actual primary).
- Every section opened with a philosophical lede ("the felt pace of an
interface", "form follows feeling", "quiet authority of restraint").
- Components chapter was a metadata table, not a real mock.
- Type specimen reused the same recycled aphorisms across files.
This pass replaces all of that with the extracted values themselves.
Cover
Full-bleed band of the brand's actual primary (auto-detected via the
most-saturated brand-classified colour; falls back through secondary,
accent, most-used). The hex prints inside the band. Host name lives
below the band in massive serif.
Section ledes
Replaced philosophy with one-line data summaries.
"1 primary · 1 secondary · 1 accent · 27 neutrals · 88 total" beats
"Brand colours carry meaning. Neutrals carry structure."
Type specimen
Now uses real headlines extracted from design.voice.sampleHeadings.
Falls back to a single neutral pangram only when those are absent.
Recycled aphorisms removed entirely.
Colour chapter
Primary at full-width hero (aspect 2.6:1), secondary + accent
half-width pair, neutrals flush horizontal strip, full palette grid.
Hex labels render *inside* the swatch in auto-contrast text.
Components chapter
Renders an actual primary + secondary button using extracted brand
colour + radius, plus a card mock using extracted surface + text
+ accent for the link. Metadata moved below the mock.
Accessibility chapter
Failing pairs render as actual stacked colour blocks ("Aa" in
foreground on the failing background, ratio inline). Big WCAG score
on top.
Tokens chapter
Code blocks now have a header bar with language + target filename.
"How to use"
Trimmed from 6 rules of thumb to 4 punchier ones. Dropped the
"rule of thumb" framing.
Layout
Section padding moved into the wrap (no gutter around the hero
band). TOC sits on tinted sub-paper background. Chapter headers
thinner with a bottom rule.
Same 13 chapters, same public API, no breaking changes.
378/378 tests pass (one assertion updated for the lowercase
"Brand guidelines" cover label).
Replaces the (now-closed) PR #76 with a tighter version of the same idea.
Before: a Playwright crash, DNS miss, or ad-walled URL would surface as
an unhandled exception inside `runCi()` — the CI artifact directory ended
up empty, and downstream jobs (artifact upload, status comments,
dashboards) had nothing to point at.
Now:
1. Wrap only the actual throw site (`extractDesignLanguage(url)`).
2. On failure, write a partial `ci-report.md` with the message + full
stack as a fenced block, plus a `ci-summary.json` keyed identically
to the success path (`{url, score, grade, driftVerdict, timestamp}`)
with two extra failure fields (`extractionFailed: true`, `error`).
3. Surface the original error via `cause` on the returned object so
callers can re-raise with a real stack — none of this string-prepending
wrapping that loses the trace (which was the main issue with #76).
4. Append the partial report to `GITHUB_STEP_SUMMARY` if present so
GitHub Actions runs still get a step summary on the failure.
Tests (tests/ci.test.js, 4 new):
- returns shouldFail=true without throwing
- writes ci-report.md + ci-summary.json on failure
- summary preserves the success-path schema + adds extractionFailed flag
- failure is a structured return, never an exception
Test exercises the path with an unreachable host (DNS resolves fast).
371/371 tests pass.
Closes#76 conceptually (PR closed per code review at
https://github.com/Manavarya09/design-extract/pull/76#issuecomment-4406699448).
Closes#59.
`designlang pack <url>` runs the full extraction once and writes every
emitter output into a single, signed, layered directory. One artifact a
designer or dev can clone, drop into a project, or zip up and send to a
client.
Layout:
<host>-design-system/
├── README.md bespoke, references source + grade + at-a-glance
├── LICENSE.txt provenance + usage guidance
├── tokens/ DTCG + Tailwind + CSS vars + Figma vars + motion + theme.js
├── components/ typed React stubs (anatomy.tsx)
├── storybook/ runnable Storybook project
├── starter/ minimal HTML starter wired to tokens/variables.css
├── prompts/ v0 / Lovable / Cursor / Claude Artifacts + named recipes/*.md
└── extras/ voice.json + prompt-pack.md rollup
Pure composition — zero new deps, zero new analysis. Reuses formatDtcg,
formatTokens, formatTailwind, formatCssVars, formatFigma, formatMotionTokens,
formatReactTheme, formatAnatomyStubs, formatStorybook, buildPromptPack,
generateClone (opt-in via --with-clone).
Internals:
- `toText()` normaliser handles the inconsistent return shapes across
formatters (some return objects, some return JSON strings already).
- Recipes now write as `<name>.md` files, not array indices.
- 7 new tests cover directory shape, valid JSON token outputs, README
content, starter wiring, recipe filenames, and a regression test for
the double-stringify bug.
357/357 tests pass.
Verified live on stripe.com: 40 files, 232KB, all token JSON parses
cleanly.
A genuinely new product surface. Take an extracted page-shape (sections,
voice, page-intent, anatomy) and re-render it under one of six opinionated
design vocabularies. Same content, different language.
$ designlang remix stripe.com --as cyberpunk
$ designlang remix stripe.com --all # all 6 vocabularies, one extract
Six vocabularies:
brutalist — hard edges, mono type, single screaming accent
swiss — Helvetica, grids, restraint (post-Bauhaus default)
art-deco — gold on ink, geometric ornament, vertical type
cyberpunk — neon on midnight, scanlines, chromatic-aberration headings
soft-ui — cushioned shapes, low contrast, Vision-OS-adjacent
editorial — broadsheet serifs, generous whitespace, ink on paper
Each vocabulary is a self-contained tokens + font-stack + signature CSS
file in src/vocabularies/. The formatter (src/formatters/remix.js) maps
every section role (hero, feature-grid, pricing-table, stats, testimonial,
faq, logo-wall, steps, cta) to vocabulary-styled markup, using the host's
own copy (headings, ledes, CTA verbs from voice).
Hero deduplication: real-world section walkers — especially on SPA
marketing pages — often emit a hero wrapper + an inner hero with the
same h1. Remix dedupes by heading and excludes section-claimed headings
from the voice pool, so heading-less sections (cta bands, logo walls)
don't re-render an already-claimed heading.
14 new tests (350 total, all passing). Cover registry shape, per-vocab
token validity, dedup semantics, no-cross-claim heading pool, XSS
escaping, missing-input errors.
Why: Grade (v12.1) is the audit. Battle (v12.2) is the comparison.
Remix is the transformation. No competitor (Dembrandt, Superposition,
html.to.design, Builder Visual Copilot) ships site-shape-preserving
vocabulary swap.
Website: bumps v12.2 → v12.3 across home/features/gallery/spec/permalink,
adds /features#remix section, swaps Battle CTA chip to Remix.
Closes 8 CodeQL js/unused-local-variable alerts. All deletions are pure
import-list trims — no runtime behavior changes:
src/history.js -- existsSync (#72)
src/sync.js -- readFileSync (#73)
src/visual-diff.js -- basename (#62)
src/index.js -- formatAnatomyStubs (#57; still re-exported)
src/index.js -- formatMotionTokens (#58; still re-exported)
src/formatters/tailwind.js -- rgbToHex (#54)
src/formatters/vue-theme.js -- rgbToHsl (#55)
src/formatters/css-vars.js -- pxToRem (#52)
tests/wide-gamut.test.js -- oklchToSrgb, oklabToSrgb (#64)
Confirmed via grep that none of the removed names had call sites in
their source file. The two src/index.js entries still appear as
`export { … } from '…'` re-exports on lines 201–202 — those are
self-contained re-exports, not affected by the removed imports.
Skipped intentionally:
- Unused functions (#66–#69, #61, #76–#77) — may be retained as
internal API surface; need per-call review.
- File-system race conditions (#70, #71) in sync.js / studio.js — real
bugs, but need a dedicated atomic-write fix, not in scope.
336/336 tests pass; all touched modules load cleanly.
Three CodeQL alerts (#78, #80, #81 — js/incomplete-url-substring-sanitization)
flagged `html.includes('example.com')` patterns in tests as potential URL
sanitization gaps. They are *test* assertions, not security checks, but the
same patterns are dangerous in real validators — replacing with regex
anchored inside rendered HTML markup ('>host<') makes the intent explicit
and silences the rule.
Also removes the unused `diffDarkMode` import in bin/design-extract.js
(alert #79 — js/unused-local-variable). The CLI uses `diffDesigns` from
src/diff.js, not the dark-mode-only `diffDarkMode` helper.
No behavior change. 336/336 tests still pass.
Two ships on top of the v12.1 Grade flywheel.
`designlang battle <urlA> <urlB>` — head-to-head graded card, parallel
extraction, dimension-by-dimension bar table with verdict + win/tie/loss
tally. Self-contained HTML, also emits .battle.md and .battle.json.
Design-score SVG badges:
- `designlang grade --badge` writes *-badge.svg locally
- New formatter src/formatters/badge.js — shields.io style, color by
letter grade, accessible (aria-label, role=img), Verdana-width math so
it renders identically across GitHub / npm / arbitrary markdown
- Live endpoint at website/app/api/badge/[host]/route.js — reuses the
same blob cache /api/extract writes to, edge-cached 6h with 24h SWR
for friendly behavior under GitHub's image proxy
- Friendly URL rewrites: /badge/<host> and /badge/<host>.svg → /api/badge/<host>
Why both: Grade is the artifact, Battle is the viral content layer
("Stripe vs Vercel — guess who lost"), Badge is the permanent
distribution layer (every README adoption is a backlink to a live
grade page).
13 new tests (336 total, all passing). Cover battle markup, score
comparison thresholds, SVG escaping, grade-color mapping, missing-score
handling.
Website: bumps v12.1 → v12.2 across home/gallery/spec/features/permalink,
adds /features#battle section, swaps Grade CTA chip to Battle.
Adds `designlang grade <url>` — a self-contained, editorial-style HTML
audit page (plus JSON + Markdown variants). Big serif letter grade,
eight scored dimensions with arc gauges, evidence pulled from the
audited site itself (palette swatches, type specimen, spacing rhythm),
and a strengths / what-to-fix ledger. Paper/ink theme with a dark
toggle, print-ready, OG meta for shareable links.
Why this exists: html.to.design, Locofy, Builder, Polypane all ship
extraction or layout cloning. None of them grade the design system
itself in a form you can post on Twitter or send to a client.
- src/formatters/grade.js — new formatHTML + formatMarkdown formatters
- bin/design-extract.js — `grade` subcommand with --format / --open
- 6 new tests covering shape, escaping, scale-shape regression
- README — trimmed 649 → 241 lines, restructured around outputs/moats
- website — adds /features#grade section, bumps v12 → v12.1 throughout
- icon-system.js fingerprints the icon library from stroke/fill
dominance, stroke width, grid size, and rounded-caps presence.
Recognises Lucide, Heroicons (outline+solid), Phosphor, Tabler,
Feather, Remix, Material.
- background-patterns.js classifies noise/dot-grid/line-grid/
gradient-mesh/svg-pattern/plain from background-image values.
- stack-intel.js extends stack-fingerprint with 12 CMS, 13 analytics,
and 7 experimentation platform detectors.
- Bin now reads its version from package.json — no more drift.
- New outputs: *-icon-system.json, *-stack-intel.json.
- background-patterns merged into *-visual-dna.json.
- 313/313 tests passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- perf.js: fresh Playwright context measures LCP/CLS/INP via
PerformanceObserver, categorises network responses into
js/css/font/image/document/other bytes, counts third-party requests
against a known-host list, synthesises an interaction so INP
reports. Returns grade buckets per vital.
- seo.js: pure extractor for OG, Twitter, canonical, manifest,
theme-color, viewport, favicons, and inline JSON-LD blocks.
- Crawler captures favicons + manifest link + JSON-LD script text.
- New flag --perf, auto-on with --full.
- New outputs: *-seo.json, *-perf.json.
- 307/307 tests passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- dark-mode-pair.js joins light+dark passes into semantic role pairs
(primary/secondary/accent/background/text), pairs CSS variables
that actually differ, and emits a drop-in Tailwind darkMode config
plus an audit of tokens missing from either pass
- responsive-screenshots.js captures full-page PNGs at mobile/tablet/
desktop/wide × (light,dark) with a JSON index
- New flag --responsive-shots, auto-on with --full
- New outputs: *-dark-mode.json, *-responsive.json
- CLI version test now reads from package.json so future minor
releases don't need to update the test
- 303/303 tests passing
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the v9 hardcoded-selector screenshot pass with a cluster-
aware, retina (2×), multi-variant pipeline.
- New src/extractors/component-screenshots.js groups candidates by
kind + variantHint + sizeHint and captures up to three reps per
group. Falls back to the v9 hardcoded list when nothing clusters
(auth / docs pages).
- Dedicated Playwright context at deviceScaleFactor: 2 for crisp crops
- New *-screenshots.json index maps every crop to cluster/bounds
- Markdown gains a Component Screenshots section
- No new CLI flags — --screenshots / --full still opt into capture
- 299/299 tests passing
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six new extractors (page-intent, section-roles, component-library,
material-language, imagery-style, logo), a multi-page crawl
orchestrator with cross-page consistency, an optional --smart LLM
fallback, and a ready-to-paste prompt pack for v0 / Lovable / Cursor /
Claude Artifacts.
v9 captured *how* a site looks. v10 captures *what it is* — the
semantic signal an LLM needs to rebuild a site faithfully.
- page-intent.js: landing/pricing/docs/blog/product/about/dashboard/auth/legal
- section-roles.js: hero/feature-grid/logo-wall/stats/testimonial/
pricing-table/faq/steps/comparison/bento/cta/footer + slot copy +
reading order
- component-library.js: shadcn/Radix/MUI/Chakra/Mantine/AntD/HeroUI/
Vuetify/Bootstrap/Tailwind detection with evidence + alternates
- material-language.js: glass/neumorph/flat/brutalist/soft-ui/
material-you/skeuo from shadow + geometry + saturation
- imagery-style.js: photo/3d/iso/flat-illustration/mesh/icon-only/
screenshot + dominant aspect + image-radius profile
- logo.js: SVG/PNG extraction + sampled clearspace
- multipage.js: auto-discovers canonical pages from nav, runs full
pipeline per page, emits Jaccard consistency
- classifiers/smart.js: opt-in LLM refinement via OPENAI_API_KEY or
ANTHROPIC_API_KEY, zero-dep fetch
- formatters/prompt-pack.js: v0.txt, lovable.txt, cursor.md,
claude-artifacts.md, per-component recipe cards
New CLI flags: --smart, --pages <n>, --no-prompts.
New output files: *-intent.json, *-visual-dna.json, *-library.json,
*-logo.{svg,png,json}, *-multipage.json, *-prompts/.
Extended markdown: 6 new sections.
297/297 tests passing.
Website: v10 capabilities section, version stamps bumped to v10,
and a fix for the Specimens IntersectionObserver that was hijacking
--accent on page load before the specimens strip was ever in view.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reliability fixes that restore trust on real sites:
- colors.js: brand-color detection rewritten — ranks chromatic clusters by
interactiveBg x100 + saturation x2 + log(usage), requires HSL sat >25 or
an interactive-bg hit. Linear primary: #d0d6e0 (gray) -> #e4f222 (lime).
Stripe: #533afd. Verified on 5 ground-truth sites.
- accessibility.js + crawler.js: crawler now emits hasText per element
(direct text-node child with visible chars); WCAG extractor filters
decorative glyph spans, transparent overlays, non-text containers.
Linear: 25% (171 failing pairs) -> 83% (1 failing pair).
- scoring.js: rubric recalibrated against Stripe/Linear/Vercel/GitHub/Apple.
Loosened color/shadow/radii/type-scale thresholds; softened spacing
no-base penalty when tokenization is present; cssHealth now weighted in
the overall. Linear 47->76, Stripe 81->88, Apple 83->86.
New CLI flags:
- --selector <css>: scope extraction to a DOM subtree. Stripe: 2409 -> 112
elements when scoped to footer. Falls back to full doc if invalid.
- --system-chrome: channel: 'chrome' in Playwright launch; skips the
bundled Chromium download.
- --json: full extraction to stdout (already partial in v7, now first-class
and documented).
New distribution surfaces (each publishes to its own registry):
- vscode-extension/: Extract-from-URL + inject-into-workspace commands.
- raycast-extension/: Extract, Score, Copy-CLI commands.
- figma-plugin/: URL or paste-JSON -> Figma Variables with multi-mode.
- github-action/: "Design regression guard" runs designlang on PRs, diffs
tokens vs baseline, comments the delta, optional fail-on-change.
- smithery.yaml + smithery.dockerfile + docs/MCP-REGISTRY.md: MCP registry
submission checklist.
- chrome-extension/PRIVACY.md + STORE_LISTING.md: Chrome/Firefox/Edge
store listing prep (extension code already existed in v7.1).
- docs/LAUNCH.md: PH + Show HN + Twitter copy + day-of checklist.
- docs/demo.tape: VHS tape for the animated README hero.
README updated: hero references demo.gif with PNG fallback, adds "Install
everywhere" table for the new surfaces, documents the three new flags.
.npmignore excludes every companion-surface directory so the npm tarball
stays 109KB / 73 files.
All 275 tests pass.
When --depth >= 1, the crawler now also returns per-route computedStyles.
The orchestrator runs the token extractors for each route and attaches
design.routes; the CLI emits three new artifacts:
- <prefix>-tokens-shared.json — tokens present with the same value across
every route
- <prefix>-tokens-routes/<slug>.json — per-route { added, changed }
- <prefix>-routes-report.md — human-readable summary
A new formatters/routes-reconciliation.js exports reconcileRoutes() and
formatRoutesReport(). Slug generation collapses '/' to 'index' and resolves
collisions with numeric suffixes. The existing merged-depth extraction
continues to work unchanged — this is additive.