designlang 7.1.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +46 -4
- package/bin/design-extract.js +28 -2
- package/package.json +1 -1
- package/src/config.js +4 -1
- package/src/crawler.js +376 -6
- package/src/extractors/accessibility.js +44 -1
- package/src/extractors/colors.js +50 -12
- package/src/extractors/interaction-states.js +57 -0
- package/src/extractors/modern-css.js +100 -0
- package/src/extractors/scoring.js +49 -30
- package/src/extractors/token-sources.js +65 -0
- package/src/extractors/wide-gamut.js +47 -0
- package/src/formatters/routes-reconciliation.js +160 -0
- package/src/index.js +29 -0
- package/src/utils/color-gamut.js +82 -0
- package/.github/FUNDING.yml +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -62
- package/.github/ISSUE_TEMPLATE/config.yml +0 -8
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -28
- package/chrome-extension/README.md +0 -41
- package/chrome-extension/icons/favicon.svg +0 -7
- package/chrome-extension/icons/icon-128.png +0 -0
- package/chrome-extension/icons/icon-16.png +0 -0
- package/chrome-extension/icons/icon-32.png +0 -0
- package/chrome-extension/icons/icon-48.png +0 -0
- package/chrome-extension/manifest.json +0 -26
- package/chrome-extension/popup.html +0 -167
- package/chrome-extension/popup.js +0 -59
- package/docs/superpowers/plans/2026-04-18-designlang-v7.md +0 -1121
- package/docs/superpowers/specs/2026-04-18-designlang-v7-design.md +0 -150
- package/docs/superpowers/specs/2026-04-18-website-redesign-design.md +0 -120
- package/docs/superpowers/specs/2026-04-19-designlang-v7-1-design.md +0 -111
- package/tests/cli.test.js +0 -84
- package/tests/cookies.test.js +0 -98
- package/tests/extractors.test.js +0 -792
- package/tests/formatters.test.js +0 -709
- package/tests/mcp.test.js +0 -68
- package/tests/utils.test.js +0 -413
- package/website/.claude/launch.json +0 -11
- package/website/AGENTS.md +0 -5
- package/website/CLAUDE.md +0 -1
- package/website/README.md +0 -36
- package/website/app/api/extract/route.js +0 -245
- package/website/app/components/A11ySlider.js +0 -369
- package/website/app/components/Comparison.js +0 -286
- package/website/app/components/CssHealth.js +0 -243
- package/website/app/components/Extractor.js +0 -184
- package/website/app/components/HeroExtractor.js +0 -455
- package/website/app/components/Marginalia.js +0 -3
- package/website/app/components/McpSection.js +0 -223
- package/website/app/components/PlatformTabs.js +0 -250
- package/website/app/components/RegionsComponents.js +0 -429
- package/website/app/components/Rule.js +0 -13
- package/website/app/components/Specimens.js +0 -237
- package/website/app/components/StructuredData.js +0 -144
- package/website/app/components/TokenBrowser.js +0 -344
- package/website/app/components/token-browser-sample.js +0 -65
- package/website/app/globals.css +0 -505
- package/website/app/icon.svg +0 -7
- package/website/app/layout.js +0 -126
- package/website/app/opengraph-image.js +0 -170
- package/website/app/page.js +0 -352
- package/website/app/robots.js +0 -15
- package/website/app/seo-config.js +0 -82
- package/website/app/sitemap.js +0 -18
- package/website/jsconfig.json +0 -7
- package/website/lib/cache.js +0 -73
- package/website/lib/rate-limit.js +0 -30
- package/website/lib/rate-limit.test.js +0 -55
- package/website/lib/specimens.json +0 -86
- package/website/lib/token-helpers.js +0 -70
- package/website/lib/url-safety.js +0 -103
- package/website/lib/url-safety.test.js +0 -116
- package/website/lib/zip-files.js +0 -15
- package/website/next.config.mjs +0 -15
- package/website/package-lock.json +0 -1353
- package/website/package.json +0 -19
- package/website/public/favicon.svg +0 -7
- package/website/public/logo-specimen.svg +0 -76
- package/website/public/mark.svg +0 -12
- package/website/public/site.webmanifest +0 -13
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
# designlang v7.0 — Design Spec
|
|
2
|
-
|
|
3
|
-
**Date:** 2026-04-18
|
|
4
|
-
**Status:** Approved for implementation
|
|
5
|
-
**Author:** Manav Arya Singh
|
|
6
|
-
|
|
7
|
-
## Theme
|
|
8
|
-
|
|
9
|
-
*The design context layer for AI agents, across every platform.*
|
|
10
|
-
|
|
11
|
-
v7.0 turns designlang from a web-only token extractor into (a) the default MCP/agent context source for AI IDEs, (b) a real multi-platform design system output (web + iOS + Android + Flutter + WordPress), (c) a CSS auditor, not just an extractor.
|
|
12
|
-
|
|
13
|
-
## Scope — 10 features
|
|
14
|
-
|
|
15
|
-
### 1. MCP server
|
|
16
|
-
New command: `designlang mcp [--output-dir <path>]`. Launches a stdio MCP server built on `@modelcontextprotocol/sdk`. Exposes:
|
|
17
|
-
- **Resources:** `tokens://primitive`, `tokens://semantic`, `tokens://components`, `regions://map`, `health://score`.
|
|
18
|
-
- **Tools:** `search_tokens(query)`, `find_nearest_color(hex, level: AA|AAA)`, `get_region(name)`, `get_component(name)`, `list_failing_contrast_pairs()`.
|
|
19
|
-
- No live extraction tool (deferred to v7.1 with hosted backend).
|
|
20
|
-
|
|
21
|
-
### 2. Agent rules emitter
|
|
22
|
-
New flag: `--emit-agent-rules` (and on by default when `--full`). Writes:
|
|
23
|
-
- `.cursor/rules/designlang.mdc` — Cursor project rules referencing tokens.
|
|
24
|
-
- `.claude/skills/designlang/SKILL.md` — Claude Code skill folder with frontmatter.
|
|
25
|
-
- `CLAUDE.md.fragment` — append-ready fragment for root CLAUDE.md.
|
|
26
|
-
- `agents.md` — generic agent prompt.
|
|
27
|
-
|
|
28
|
-
All four files read from the same source-of-truth JSON.
|
|
29
|
-
|
|
30
|
-
### 3. DTCG strict mode + semantic layer + composite tokens
|
|
31
|
-
Rewrites `*-design-tokens.json` to W3C DTCG v1 format:
|
|
32
|
-
- Every leaf is `{ "$value": ..., "$type": "color|dimension|fontFamily|shadow|...", "$extensions": {...} }`.
|
|
33
|
-
- Two-layer structure: `primitive.*` (raw values) and `semantic.*` (alias references like `"{primitive.color.blue.500}"`).
|
|
34
|
-
- Composite types for `typography`, `shadow`, `border`, `gradient` (single object with family/size/weight/lineHeight, etc.).
|
|
35
|
-
- Auto-infers semantic roles from usage: `semantic.color.action.primary`, `semantic.color.surface.default`, `semantic.text.body`, `semantic.radius.control`, etc.
|
|
36
|
-
- **Default ON.** `--tokens-legacy` preserves pre-v7 shape. Breaking change — documented in CHANGELOG and migration guide.
|
|
37
|
-
|
|
38
|
-
### 4. Multi-platform emitters
|
|
39
|
-
New flag: `--platforms <csv>` (values: `web,ios,android,flutter,wordpress,all`; default `web`).
|
|
40
|
-
- **iOS (SwiftUI):** `*-ios.swift` with `Color.primaryAction`, `CGFloat` spacing/radius, `Font` registrations.
|
|
41
|
-
- **Android (Compose):** `*-colors.xml`, `*-dimens.xml`, `*-Theme.kt` with Compose `val`s.
|
|
42
|
-
- **Flutter (Dart):** `*-theme.dart` exporting a `ThemeData` + color/typography extensions.
|
|
43
|
-
- **WordPress:** `theme.json` (Gutenberg block theme tokens), `style.css` with CSS custom props, minimal `functions.php` + `index.php`/`templates/index.html` skeleton so output is a drop-in block theme.
|
|
44
|
-
|
|
45
|
-
All emitters consume the DTCG semantic layer, not primitives — they stay consistent.
|
|
46
|
-
|
|
47
|
-
### 5. Tech-stack + Tailwind fingerprint
|
|
48
|
-
New extractor `stack-fingerprint`:
|
|
49
|
-
- Detects framework from window globals, script URLs, meta tags, DOM signatures (React/Vue/Svelte/Next/Nuxt/Remix/Astro/Shopify/WooCommerce/Webflow/Framer).
|
|
50
|
-
- Detects CSS layer (Tailwind v3/v4, styled-components, CSS Modules, vanilla).
|
|
51
|
-
- Detects analytics, CDN, fonts host, A/B tools.
|
|
52
|
-
- When Tailwind is detected: scrapes class-name frequency, extracts utility classes in use, and emits `*-tailwind-diff.md` (what classes are used, config delta vs. default preset) instead of only a fresh `tailwind.config.js`.
|
|
53
|
-
|
|
54
|
-
### 6. CSS health audit
|
|
55
|
-
New extractor `css-health`:
|
|
56
|
-
- Runs Playwright `coverage.startCSSCoverage()` → reports unused bytes per stylesheet.
|
|
57
|
-
- Parses all stylesheets for specificity distribution, `!important` count, duplicate declarations, selector-per-rule averages, vendor-prefix and obsolete-property audit.
|
|
58
|
-
- Enumerates `@keyframes` with name/duration/easing/steps.
|
|
59
|
-
- Emits `*-css-health.json` and `*-css-health.md`.
|
|
60
|
-
- Adds three new dimensions to the design score: `cssHealth`, `animationCatalog`, `specificity`. Old score fields remain for backward compat.
|
|
61
|
-
|
|
62
|
-
### 7. A11y remediation suggestions
|
|
63
|
-
Extends a11y extractor:
|
|
64
|
-
- For each failing WCAG fg/bg pair, searches the extracted palette for the nearest color that passes AA (4.5:1) and AAA (7:1).
|
|
65
|
-
- Emits suggestions in `*-a11y.md` and as fixable entries in `*-design-language.md`.
|
|
66
|
-
- MCP tool `find_nearest_color(hex, level)` reuses this engine.
|
|
67
|
-
|
|
68
|
-
### 8. Semantic component segmentation
|
|
69
|
-
New extractor `semantic-regions`:
|
|
70
|
-
- Heuristic classifier using ARIA landmarks (`<nav>`, `<main>`, `<header>`, `<footer>`, `role=banner|contentinfo|complementary`), layout properties (sticky header, footer at page end), and class-name hints (`.hero`, `.pricing`, `.cta`).
|
|
71
|
-
- Labels detected regions: `nav`, `hero`, `features`, `pricing`, `testimonials`, `cta`, `footer`, `content`.
|
|
72
|
-
- Emits `*-regions.json` with bounds, computed styles, and region role.
|
|
73
|
-
- MCP tool `get_region(name)` returns the full region block.
|
|
74
|
-
|
|
75
|
-
### 9. Reusable component detection
|
|
76
|
-
New extractor `component-clusters`:
|
|
77
|
-
- For each DOM element matching button/card/input/badge/tag candidates, computes a structural hash (tag sequence + class pattern) + style vector (flattened computed-style array).
|
|
78
|
-
- Clusters by similarity (structural hash exact, style vector cosine > threshold).
|
|
79
|
-
- Replaces current "one example per component type" with `{ component, instanceCount, variants: [...] }`.
|
|
80
|
-
- Markdown output shows "Button — 24 instances, 2 variants (primary, ghost)" with CSS for each.
|
|
81
|
-
|
|
82
|
-
### 10. WordPress theme export
|
|
83
|
-
Covered under Feature 4; called out separately because it is a full emitter with file-tree skeleton, not only tokens.
|
|
84
|
-
|
|
85
|
-
## Architecture changes
|
|
86
|
-
|
|
87
|
-
### New modules
|
|
88
|
-
- `src/extractors/stack-fingerprint.js`
|
|
89
|
-
- `src/extractors/css-health.js`
|
|
90
|
-
- `src/extractors/semantic-regions.js`
|
|
91
|
-
- `src/extractors/component-clusters.js`
|
|
92
|
-
- `src/extractors/a11y-remediation.js` (or extend existing a11y)
|
|
93
|
-
- `src/formatters/dtcg-tokens.js` (replaces `design-tokens.js` with legacy fallback)
|
|
94
|
-
- `src/formatters/ios-swiftui.js`
|
|
95
|
-
- `src/formatters/android-compose.js`
|
|
96
|
-
- `src/formatters/flutter-dart.js`
|
|
97
|
-
- `src/formatters/wordpress-theme.js`
|
|
98
|
-
- `src/formatters/agent-rules.js`
|
|
99
|
-
- `src/mcp/server.js` + `src/mcp/resources.js` + `src/mcp/tools.js`
|
|
100
|
-
|
|
101
|
-
### New dependencies
|
|
102
|
-
- `@modelcontextprotocol/sdk` — for MCP server.
|
|
103
|
-
- No other additions.
|
|
104
|
-
|
|
105
|
-
### CLI surface
|
|
106
|
-
```
|
|
107
|
-
designlang <url> [...existing flags]
|
|
108
|
-
--platforms <csv> web,ios,android,flutter,wordpress,all (default: web)
|
|
109
|
-
--emit-agent-rules Emit Cursor/Claude Code/generic agent rules
|
|
110
|
-
--tokens-legacy Keep pre-v7 token JSON shape
|
|
111
|
-
|
|
112
|
-
designlang mcp [--output-dir <path>] # NEW: launch MCP server
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
All existing commands (`apply`, `clone`, `score`, `watch`, `diff`, `brands`, `sync`, `history`) unchanged.
|
|
116
|
-
|
|
117
|
-
### Backward compatibility
|
|
118
|
-
- Base extraction stays — same file names, same top-level keys.
|
|
119
|
-
- **Breaking:** token JSON shape changes to DTCG. Mitigated by `--tokens-legacy`, loud CHANGELOG, migration section in README.
|
|
120
|
-
- **Additive:** score JSON gains new fields; existing fields preserved.
|
|
121
|
-
- **Additive:** new files only appear with opt-in flags (`--platforms`, `--emit-agent-rules`).
|
|
122
|
-
|
|
123
|
-
## Testing
|
|
124
|
-
|
|
125
|
-
- Unit tests per extractor module using fixture HTML (existing pattern in `tests/`).
|
|
126
|
-
- Integration test: run full extraction on a local fixture site, snapshot each output file.
|
|
127
|
-
- DTCG output validated against the W3C DTCG JSON schema (vendored).
|
|
128
|
-
- Multi-platform emitters: golden-file tests (check generated Swift/Kotlin/Dart/theme.json against known-good outputs).
|
|
129
|
-
- MCP server: spin up server, run a handful of resource/tool requests over the MCP test harness, assert responses.
|
|
130
|
-
|
|
131
|
-
## Release plan
|
|
132
|
-
|
|
133
|
-
1. Implement features in dependency order (see writing-plans phase).
|
|
134
|
-
2. All tests pass.
|
|
135
|
-
3. Update README with new sections: MCP, agent rules, multi-platform, CSS health, WordPress.
|
|
136
|
-
4. Write CHANGELOG.md with breaking-change callout on DTCG + migration snippet.
|
|
137
|
-
5. Bump `package.json` version `6.0.0` → `7.0.0`.
|
|
138
|
-
6. `npm publish` + `git tag v7.0.0 && git push --tags`.
|
|
139
|
-
7. Website update — deferred to Wave 2.
|
|
140
|
-
|
|
141
|
-
## Out of scope (deferred to v7.1+)
|
|
142
|
-
|
|
143
|
-
- Bidirectional Figma Variables sync.
|
|
144
|
-
- Full JSX/Vue/Svelte component codegen (needs Mitosis-style IR or LLM).
|
|
145
|
-
- Versioned auto-published npm token package with changesets.
|
|
146
|
-
- Hosted shareable reports (needs backend).
|
|
147
|
-
- Hosted "Try it free" web extraction (Wave 2).
|
|
148
|
-
- CMS content-model extraction.
|
|
149
|
-
- Storybook + Chromatic integration.
|
|
150
|
-
- Live clone-to-editable canvas.
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
# designlang website redesign — Wave 2
|
|
2
|
-
|
|
3
|
-
**Date:** 2026-04-18
|
|
4
|
-
**Status:** Approved for implementation
|
|
5
|
-
**Direction:** Live Design DNA — the site embodies what designlang does.
|
|
6
|
-
|
|
7
|
-
## Non-negotiables (from user)
|
|
8
|
-
|
|
9
|
-
- No "AI slop" aesthetic. No purple/blue gradients. No glassmorphism. No floating hero cards.
|
|
10
|
-
- **No emoji. No icon libraries** (Lucide, Heroicons, Phosphor). Iconography only as intent-drawn inline SVG, numerals, or letterforms.
|
|
11
|
-
- No Framer Motion bloat, no fade-up-on-every-element scroll theatrics.
|
|
12
|
-
- Copy must be earned, not templated.
|
|
13
|
-
- Designer voice visible — marginalia, footnotes, document-feel.
|
|
14
|
-
|
|
15
|
-
## Visual system
|
|
16
|
-
|
|
17
|
-
**Base:** warm paper `#F3F1EA`, ink `#0A0908`, accent `#FF4800` (used once per screen, maximum).
|
|
18
|
-
**Grays (warm):** `#D8D3C5`, `#8B8778`, `#403C34`.
|
|
19
|
-
**Type:** Fraunces (display, optical 9pt, massive sizes), Instrument Sans (body), JetBrains Mono (mono/tokens).
|
|
20
|
-
**Grid:** 12-col, 24px gutters, deliberately broken by marginalia and side notes.
|
|
21
|
-
**Rules:** 1px solid ink hairlines everywhere (document feel), no soft dividers.
|
|
22
|
-
**Shadow:** one hard offset shadow (`6px 6px 0 #FF4800`) on the primary CTA only.
|
|
23
|
-
**Dark mode:** deferred. Paper-first matches design-system/archival feel.
|
|
24
|
-
**Motion:** prefers-reduced-motion strict. All motion is content-matched (see §Signature Interactions).
|
|
25
|
-
|
|
26
|
-
## Page structure
|
|
27
|
-
|
|
28
|
-
Single-page scroll. Numbered sections like a monograph: `§00`…`§09`.
|
|
29
|
-
|
|
30
|
-
- §00 `HERO` — URL input + live extraction stream; tokens paint on screen as they compute.
|
|
31
|
-
- §01 `DTCG BROWSER` — interactive alias resolver. Click a semantic token, the line flies to its primitive.
|
|
32
|
-
- §02 `MCP` — split: Cursor/Claude Code rule panel on left, real terminal transcript on right.
|
|
33
|
-
- §03 `MULTI-PLATFORM` — tabs Web / iOS / Android / Flutter / WordPress; same token in 5 languages.
|
|
34
|
-
- §04 `CSS HEALTH` — big numerals (unused %, `!important` count, duplicates) set against real specificity plot.
|
|
35
|
-
- §05 `A11Y REMEDIATION` — before/after contrast slider with live ratio.
|
|
36
|
-
- §06 `REGIONS + COMPONENTS` — annotated screenshot; button cluster animates into variants.
|
|
37
|
-
- §07 `FEATURED SPECIMENS` — stripe / vercel / linear / github / figma / apple; each extracted and displayed like a museum specimen. Page accent shifts as you scroll between them.
|
|
38
|
-
- §08 `COMPARISON` — transparent, opinionated table vs. v0, Builder.io, Style Dictionary, Subframe, Project Wallace.
|
|
39
|
-
- §09 `INSTALL & FOOTER` — `npx designlang`, MCP config, Cursor rule install, GitHub/npm/Discussions/sponsor.
|
|
40
|
-
|
|
41
|
-
## Signature interactions (motion only where content-matched)
|
|
42
|
-
|
|
43
|
-
1. **Hero stream** — user submits URL, server streams back tokens in extraction order; swatches paint from left to right as server events arrive.
|
|
44
|
-
2. **DTCG alias flight** — click `{primitive.color.brand.primary}` string, an SVG line animates from the semantic token to the primitive, primitive highlights, hex resolves inline.
|
|
45
|
-
3. **Accent shift scroll** — as the Featured Specimens section scrolls, page accent CSS var transitions to each specimen's extracted primary.
|
|
46
|
-
4. **A11y slider** — handle drag shows contrast ratio updating live above the color pair.
|
|
47
|
-
5. **MCP terminal transcript** — real JSON-RPC lines, typed at realistic speed, showing `search_tokens` → result.
|
|
48
|
-
6. **Component cluster unfold** — a single button card unfolds into its detected variants on scroll-into-view.
|
|
49
|
-
|
|
50
|
-
No other motion.
|
|
51
|
-
|
|
52
|
-
## Backend
|
|
53
|
-
|
|
54
|
-
**Existing:** `/api/extract` route using `@sparticuz/chromium` + `playwright-core` + designlang `extractDesignLanguage`.
|
|
55
|
-
|
|
56
|
-
**Changes required:**
|
|
57
|
-
|
|
58
|
-
1. **v7.0 output parity** — currently the API returns v6 shape. Add DTCG tokens, agent rules, MCP companion JSON, multi-platform outputs.
|
|
59
|
-
2. **Streaming response** — the extraction produces tokens progressively; stream them over an HTTP stream (Next.js 16 streaming response) so the hero demo paints live. If extraction is atomic today (single `page.evaluate`), we stream the *stages*: crawl → colors → typography → spacing → components → a11y → done.
|
|
60
|
-
3. **Rate limiting** — Vercel Edge Config or in-memory map keyed by IP. 3 extractions per IP per day for anonymous, soft 429 with helpful copy. No login.
|
|
61
|
-
4. **URL validation** — reject `localhost`, `127.x`, `169.254.x`, `10.x`, `172.16–31.x`, `192.168.x`, `file://`, non-http(s). Reject URLs whose DNS resolves to private ranges.
|
|
62
|
-
5. **Timeout cap** — hard 45s; abort Playwright if exceeded.
|
|
63
|
-
6. **Caching** — cache extraction result by URL in Vercel Blob for 24h. Collision: same URL within 24h returns cached zip + a small "cached" annotation.
|
|
64
|
-
7. **Bot detection** — Vercel BotID (free, platform-native).
|
|
65
|
-
8. **Output:** downloadable `.zip` of all generated files (same set the CLI would produce for `--platforms all --emit-agent-rules`) plus a JSON summary for the on-screen preview.
|
|
66
|
-
|
|
67
|
-
**New/changed files:**
|
|
68
|
-
- `website/app/api/extract/route.js` — streaming + rate limit + caching + v7.0 outputs
|
|
69
|
-
- `website/lib/rate-limit.js` (new)
|
|
70
|
-
- `website/lib/url-safety.js` (new)
|
|
71
|
-
- `website/lib/cache.js` (new, Vercel Blob-backed)
|
|
72
|
-
|
|
73
|
-
## Frontend components (new)
|
|
74
|
-
|
|
75
|
-
Kept flat in `website/app/components/`. Each is a self-contained section:
|
|
76
|
-
|
|
77
|
-
- `Hero.js` — URL input + live stream renderer
|
|
78
|
-
- `TokenBrowser.js` — DTCG alias resolver
|
|
79
|
-
- `McpSection.js` — split editor + terminal
|
|
80
|
-
- `PlatformTabs.js` — multi-platform code viewer
|
|
81
|
-
- `CssHealth.js` — numerals + specificity plot (hand-drawn SVG scatter)
|
|
82
|
-
- `A11ySlider.js` — contrast before/after
|
|
83
|
-
- `RegionsComponents.js` — annotated image + cluster unfold
|
|
84
|
-
- `Specimens.js` — featured site grid; orchestrates accent shift
|
|
85
|
-
- `Comparison.js` — opinionated feature matrix
|
|
86
|
-
- `InstallFooter.js` — closing section
|
|
87
|
-
- `Marginalia.js` — shared side-column component for footnotes and command labels
|
|
88
|
-
- `Rule.js` — 1px hairline divider with section label
|
|
89
|
-
|
|
90
|
-
New lib:
|
|
91
|
-
- `website/lib/token-helpers.js` — DTCG alias resolution shared with components
|
|
92
|
-
- `website/lib/specimens.json` — pre-extracted data for the featured section (generated at build time from the CLI so specimens are real)
|
|
93
|
-
|
|
94
|
-
## Deferred (explicit non-goals for Wave 2)
|
|
95
|
-
|
|
96
|
-
- Login / user accounts / saved extractions
|
|
97
|
-
- Paid tiers
|
|
98
|
-
- Dark mode
|
|
99
|
-
- Blog / changelog page
|
|
100
|
-
- i18n
|
|
101
|
-
- Sharable `/x/<slug>` permalinks (nice-to-have but not Wave 2)
|
|
102
|
-
|
|
103
|
-
## Test / verification plan
|
|
104
|
-
|
|
105
|
-
- Lighthouse: LCP ≤ 2.0s on the hero at 3G-Fast, CLS 0, accessibility ≥ 95.
|
|
106
|
-
- `prefers-reduced-motion: reduce` — verify all motion stops or substitutes with non-motion reveal.
|
|
107
|
-
- Keyboard navigation through every interactive element.
|
|
108
|
-
- Extractor end-to-end: submit real URL, receive streamed tokens, download zip, verify zip contains v7.0 shape.
|
|
109
|
-
- Rate limit: 4th request from same IP returns 429 with copy.
|
|
110
|
-
- URL safety: localhost, private IPs, `file://` all rejected with 400.
|
|
111
|
-
|
|
112
|
-
## Implementation chunks (PR per chunk)
|
|
113
|
-
|
|
114
|
-
Each chunk is one PR. Merge before starting the next, same pattern as v7.0 release.
|
|
115
|
-
|
|
116
|
-
- **PR A: Foundation** — fonts, base CSS, grid primitives, `Rule`, `Marginalia`, new `globals.css`, `layout.js`, clean `page.js` scaffold.
|
|
117
|
-
- **PR B: Hero + streaming extraction API** — hero component, API streaming, URL safety, rate limit, basic Blob cache.
|
|
118
|
-
- **PR C: Core showcase sections** — DTCG browser, MCP section, multi-platform tabs.
|
|
119
|
-
- **PR D: Analytics sections** — CSS health, a11y slider, regions + components.
|
|
120
|
-
- **PR E: Specimens + comparison + install + footer + deploy** — pre-extract data at build time, accent-shift scroll, comparison table, final polish, Vercel deploy.
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# designlang v7.1 — Designer Toolkit
|
|
2
|
-
|
|
3
|
-
**Date:** 2026-04-19
|
|
4
|
-
**Status:** Approved for implementation
|
|
5
|
-
|
|
6
|
-
## Theme
|
|
7
|
-
|
|
8
|
-
*From extractor to designer's toolkit.* v7.0 shipped the full extraction surface. v7.1 makes the output composable, auditable, and complete — pull one component at a time, get every brand asset alongside the tokens, drop into any stack (Tailwind v4, Emotion, Stitches, Vanilla Extract, Panda), and fail your CI when production drifts from your design system.
|
|
9
|
-
|
|
10
|
-
## Scope (7 features)
|
|
11
|
-
|
|
12
|
-
### 1. Single-component extraction
|
|
13
|
-
New flag: `designlang <url> --component <css-selector>`.
|
|
14
|
-
- Playwright walks to the matched element, captures its own computed styles + its children's styles + its bounding box + its text content.
|
|
15
|
-
- Emits a tiny extraction: `*-component.md` (a specimen-style card), `*-component.json` (the DTCG subset actually used by this component), `*-component.tsx` / `*-component.vue` / `*-component.svelte` based on `--framework` (or all three by default).
|
|
16
|
-
- `--component` can be combined with `--platforms all` and `--emit-agent-rules`.
|
|
17
|
-
|
|
18
|
-
### 2. Asset / brand-kit extraction
|
|
19
|
-
New flag: `designlang <url> --assets` (also implied by `--full`).
|
|
20
|
-
- Crawler enumerates every `<img>`, `<picture>`, `<source>`, `<svg>` (reuse existing icons work), `<link rel="icon">`, `<meta property="og:image">`, and CSS `background-image` URLs.
|
|
21
|
-
- Downloads each asset with a timeout and size cap (50MB total per extraction).
|
|
22
|
-
- Classifies: `logo`, `icon`, `illustration`, `photo`, `hero`, `og`, `favicon`, `avatar`, `screenshot`, `other`.
|
|
23
|
-
- Emits to `<out>/assets/<kind>/<original-filename-or-hash.ext>` plus `assets-manifest.json` with the type, source URL, dimensions, byte size, and an `srcset`-style variant list for `<picture>` elements.
|
|
24
|
-
- Size-aware: don't download a 20MB hero if it exceeds a per-asset cap (default 5MB). Log skipped assets in the manifest with a `skipped: reason` field.
|
|
25
|
-
|
|
26
|
-
### 3. Illustration detection (folded into #2)
|
|
27
|
-
- SVGs ≥ 200px or with >30 path elements → classified `illustration`, not `icon`.
|
|
28
|
-
- Inline-SVG illustrations captured alongside external file references.
|
|
29
|
-
- Raster illustrations (transparent PNG with flat-color regions) classified separately from photos via dominant-color entropy heuristic.
|
|
30
|
-
|
|
31
|
-
### 4. Tailwind v4 `@theme` formatter
|
|
32
|
-
New output file `<prefix>-tailwind.css` (separate from the existing `*-tailwind.config.js`, which stays for v3 users).
|
|
33
|
-
- Uses the Tailwind v4 `@theme { … }` block syntax inside a `@import "tailwindcss"` wrapper.
|
|
34
|
-
- Maps DTCG semantic tokens → Tailwind v4 CSS custom properties.
|
|
35
|
-
- Respects `--tokens-legacy` (v4 file still emitted but from primitive-only JSON).
|
|
36
|
-
|
|
37
|
-
### 5. CSS-in-JS emitters
|
|
38
|
-
Four new formatters under `src/formatters/css-in-js/`:
|
|
39
|
-
- `emotion.js` → `<prefix>-emotion.theme.ts`
|
|
40
|
-
- `stitches.js` → `<prefix>-stitches.config.ts`
|
|
41
|
-
- `vanilla-extract.js` → `<prefix>-vanilla.css.ts`
|
|
42
|
-
- `panda.js` → `<prefix>-panda.config.ts`
|
|
43
|
-
|
|
44
|
-
Selectable via new flag: `--framework emotion|stitches|vanilla-extract|panda|tailwind4`. Multi-select: `--framework emotion,stitches`. Default behavior unchanged: emit React theme + shadcn theme.
|
|
45
|
-
|
|
46
|
-
### 6. Drift detection + GitHub Action
|
|
47
|
-
New command: `designlang drift <url> <tokens-path> [--out <report-path>] [--fail-on <category>] [--tolerance <n>]`.
|
|
48
|
-
- Extracts from `<url>`, loads DTCG JSON at `<tokens-path>`, walks both trees, and produces a report:
|
|
49
|
-
- `added` tokens (in extraction, not in file)
|
|
50
|
-
- `removed` (in file, not in extraction)
|
|
51
|
-
- `changed` (path matches, `$value` differs beyond tolerance)
|
|
52
|
-
- Tolerance: colors use ΔE threshold (default 3), dimensions use px delta (default 1), others exact match.
|
|
53
|
-
- `--fail-on color|dimension|typography|all` controls exit code (default exits 0 for a report-only run; exit 1 when any matching drift found with `--fail-on`).
|
|
54
|
-
- Emits `drift-report.md` + `drift-report.json`.
|
|
55
|
-
- **GitHub Action:** new `.github/workflows/designlang-drift.yml` template committed to this repo under `templates/github-action/designlang-drift.yml` with a README snippet. Runs on PR + daily cron, posts the report as a PR comment when drift is detected.
|
|
56
|
-
|
|
57
|
-
### 7. Release
|
|
58
|
-
- Bump to `7.1.0`, CHANGELOG entry, README updates, npm publish, git tag.
|
|
59
|
-
|
|
60
|
-
## Non-goals (deferred to v7.2+)
|
|
61
|
-
|
|
62
|
-
- **Remix Mode** / interactive web editor — substantial Next.js work; needs its own spec.
|
|
63
|
-
- **Figma plugin**, **Chrome extension**, **VS Code extension** — each is a separate package with its own store/review cycle.
|
|
64
|
-
- **Brand-voice extraction** (tone, reading level) — out of scope for a token release.
|
|
65
|
-
- **Token marketplace / shared workspaces** — hosted SaaS territory.
|
|
66
|
-
|
|
67
|
-
## Architecture
|
|
68
|
-
|
|
69
|
-
### New files
|
|
70
|
-
- `src/extractors/component.js` — selector-scoped extraction
|
|
71
|
-
- `src/extractors/assets.js` — classifier + downloader for images and illustrations
|
|
72
|
-
- `src/formatters/tailwind-v4.js`
|
|
73
|
-
- `src/formatters/css-in-js/emotion.js`
|
|
74
|
-
- `src/formatters/css-in-js/stitches.js`
|
|
75
|
-
- `src/formatters/css-in-js/vanilla-extract.js`
|
|
76
|
-
- `src/formatters/css-in-js/panda.js`
|
|
77
|
-
- `src/formatters/component-snippet.js` — emits JSX / Vue / Svelte / HTML snippet from a component extraction
|
|
78
|
-
- `src/formatters/drift.js` — produces drift-report.md + drift-report.json
|
|
79
|
-
- `src/drift.js` — orchestrator for the `drift` subcommand
|
|
80
|
-
- `templates/github-action/designlang-drift.yml`
|
|
81
|
-
- `templates/github-action/README.md`
|
|
82
|
-
|
|
83
|
-
### Modified files
|
|
84
|
-
- `src/crawler.js` — add `fetchAssets()` with per-asset timeout + cap; add `page.$(selector).then(el => el.evaluate(...))` path for `--component`
|
|
85
|
-
- `src/index.js` — wire new extractors + output files
|
|
86
|
-
- `bin/design-extract.js` — new flags + `drift` subcommand
|
|
87
|
-
- `src/config.js` — thread new options (`component`, `assets`, `framework`, driftTolerance)
|
|
88
|
-
- `tests/extractors.test.js` + `tests/formatters.test.js` — new tests per extractor/formatter
|
|
89
|
-
- `package.json` — version bump, potentially add `sharp` for optional raster resize (deferred decision — only if it stays zero-runtime-cost)
|
|
90
|
-
- `README.md`
|
|
91
|
-
- `CHANGELOG.md`
|
|
92
|
-
|
|
93
|
-
### Dependencies
|
|
94
|
-
- No new runtime deps. Image downloads use Node 20's native `fetch`. Classifier is heuristic only (no ML).
|
|
95
|
-
|
|
96
|
-
### Security / safety
|
|
97
|
-
- Asset downloader respects the same URL-safety layer as the website API (no private IPs, http(s) only, byte cap).
|
|
98
|
-
- Same-origin preferred; cross-origin assets downloaded with an `Accept` header and a 10s timeout each.
|
|
99
|
-
|
|
100
|
-
## Tests
|
|
101
|
-
|
|
102
|
-
- Unit tests per extractor and formatter, snapshot-style for golden outputs.
|
|
103
|
-
- Integration smoke: `designlang <url> --component "header nav" --assets --framework emotion,stitches,vanilla-extract,panda,tailwind4` produces all expected files.
|
|
104
|
-
- Drift integration test: run against a fixture tokens file and a live URL, confirm report contents.
|
|
105
|
-
|
|
106
|
-
## Release plan
|
|
107
|
-
|
|
108
|
-
1. Implement via subagents in 3 parallel chunks (component+assets, emitters, drift+action).
|
|
109
|
-
2. Tests green.
|
|
110
|
-
3. README + CHANGELOG.
|
|
111
|
-
4. Version bump → PR → merge → npm publish → tag.
|
package/tests/cli.test.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { execFileSync } from 'node:child_process';
|
|
4
|
-
import { resolve } from 'node:path';
|
|
5
|
-
import { parsePlatforms, mergeConfig } from '../src/config.js';
|
|
6
|
-
|
|
7
|
-
const CLI_PATH = resolve(import.meta.dirname, '..', 'bin', 'design-extract.js');
|
|
8
|
-
|
|
9
|
-
describe('CLI', () => {
|
|
10
|
-
it('shows help with --help', () => {
|
|
11
|
-
const output = execFileSync('node', [CLI_PATH, '--help'], { encoding: 'utf-8' });
|
|
12
|
-
assert.ok(output.includes('designlang'));
|
|
13
|
-
assert.ok(output.includes('Extract'));
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('shows version with --version', () => {
|
|
17
|
-
const output = execFileSync('node', [CLI_PATH, '--version'], { encoding: 'utf-8' });
|
|
18
|
-
assert.ok(output.trim().match(/^\d+\.\d+\.\d+$/));
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('shows version number 6.0.0', () => {
|
|
22
|
-
const output = execFileSync('node', [CLI_PATH, '--version'], { encoding: 'utf-8' });
|
|
23
|
-
assert.equal(output.trim(), '6.0.0');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('exits with error when no arguments provided', () => {
|
|
27
|
-
try {
|
|
28
|
-
execFileSync('node', [CLI_PATH], { encoding: 'utf-8', stdio: 'pipe' });
|
|
29
|
-
assert.fail('Should have thrown');
|
|
30
|
-
} catch (err) {
|
|
31
|
-
// Commander exits with code 1 when required argument is missing
|
|
32
|
-
assert.ok(err.status !== 0);
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('lists --platforms option in help output', () => {
|
|
37
|
-
const output = execFileSync('node', [CLI_PATH, '--help'], { encoding: 'utf-8' });
|
|
38
|
-
assert.ok(output.includes('--platforms'));
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe('parsePlatforms', () => {
|
|
43
|
-
it('defaults to web only', () => {
|
|
44
|
-
assert.deepEqual(parsePlatforms('web'), ['web']);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('parses comma-separated values', () => {
|
|
48
|
-
assert.deepEqual(parsePlatforms('ios,android'), ['web', 'ios', 'android']);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('expands "all" to every known platform', () => {
|
|
52
|
-
assert.deepEqual(parsePlatforms('all'), ['web', 'ios', 'android', 'flutter', 'wordpress']);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('always includes web (additive)', () => {
|
|
56
|
-
assert.ok(parsePlatforms('ios').includes('web'));
|
|
57
|
-
assert.ok(parsePlatforms('wordpress').includes('web'));
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('ignores unknown platforms', () => {
|
|
61
|
-
assert.deepEqual(parsePlatforms('ios,badplatform,android'), ['web', 'ios', 'android']);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('accepts arrays', () => {
|
|
65
|
-
assert.deepEqual(parsePlatforms(['ios', 'flutter']), ['web', 'ios', 'flutter']);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe('mergeConfig platforms', () => {
|
|
70
|
-
it('threads CLI --platforms through mergeConfig', () => {
|
|
71
|
-
const merged = mergeConfig({ platforms: 'ios,flutter' }, {});
|
|
72
|
-
assert.deepEqual(merged.platforms, ['web', 'ios', 'flutter']);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('honors platforms from config file', () => {
|
|
76
|
-
const merged = mergeConfig({}, { platforms: 'android' });
|
|
77
|
-
assert.deepEqual(merged.platforms, ['web', 'android']);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('defaults to [web] when neither CLI nor config provides platforms', () => {
|
|
81
|
-
const merged = mergeConfig({}, {});
|
|
82
|
-
assert.deepEqual(merged.platforms, ['web']);
|
|
83
|
-
});
|
|
84
|
-
});
|
package/tests/cookies.test.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { writeFileSync, mkdtempSync } from 'fs';
|
|
4
|
-
import { tmpdir } from 'os';
|
|
5
|
-
import { join } from 'path';
|
|
6
|
-
import { loadCookiesFromFile, mergeCookies } from '../src/utils-cookies.js';
|
|
7
|
-
|
|
8
|
-
const tmp = mkdtempSync(join(tmpdir(), 'dl-cookies-'));
|
|
9
|
-
|
|
10
|
-
function tmpFile(name, text) {
|
|
11
|
-
const p = join(tmp, name);
|
|
12
|
-
writeFileSync(p, text, 'utf-8');
|
|
13
|
-
return p;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe('loadCookiesFromFile', () => {
|
|
17
|
-
it('loads JSON array', () => {
|
|
18
|
-
const f = tmpFile('c.json', JSON.stringify([
|
|
19
|
-
{ name: 'session', value: 'abc', domain: '.example.com', path: '/' },
|
|
20
|
-
]));
|
|
21
|
-
const out = loadCookiesFromFile(f, 'https://example.com');
|
|
22
|
-
assert.equal(out.length, 1);
|
|
23
|
-
assert.equal(out[0].name, 'session');
|
|
24
|
-
assert.equal(out[0].domain, '.example.com');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('loads Playwright storageState', () => {
|
|
28
|
-
const f = tmpFile('state.json', JSON.stringify({
|
|
29
|
-
cookies: [{ name: 'csrf', value: 'xyz', domain: 'example.com', path: '/' }],
|
|
30
|
-
origins: [],
|
|
31
|
-
}));
|
|
32
|
-
const out = loadCookiesFromFile(f, 'https://example.com');
|
|
33
|
-
assert.equal(out.length, 1);
|
|
34
|
-
assert.equal(out[0].name, 'csrf');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('loads Netscape cookies.txt with tab-separated lines and comment', () => {
|
|
38
|
-
const netscape = [
|
|
39
|
-
'# Netscape HTTP Cookie File',
|
|
40
|
-
'# comment',
|
|
41
|
-
'.example.com\tTRUE\t/\tFALSE\t1765000000\tsid\tabc123',
|
|
42
|
-
'#HttpOnly_.example.com\tTRUE\t/\tTRUE\t0\tauth\ttoken-value',
|
|
43
|
-
].join('\n');
|
|
44
|
-
const f = tmpFile('c.txt', netscape);
|
|
45
|
-
const out = loadCookiesFromFile(f, 'https://example.com');
|
|
46
|
-
assert.equal(out.length, 2);
|
|
47
|
-
const sid = out.find((c) => c.name === 'sid');
|
|
48
|
-
assert.equal(sid.value, 'abc123');
|
|
49
|
-
assert.equal(sid.domain, '.example.com');
|
|
50
|
-
assert.equal(sid.secure, false);
|
|
51
|
-
assert.equal(sid.expires, 1765000000);
|
|
52
|
-
const auth = out.find((c) => c.name === 'auth');
|
|
53
|
-
assert.equal(auth.httpOnly, true);
|
|
54
|
-
assert.equal(auth.secure, true);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('throws on invalid JSON shape', () => {
|
|
58
|
-
const f = tmpFile('bad.json', '{"not":"array"}');
|
|
59
|
-
assert.throws(() => loadCookiesFromFile(f, 'https://example.com'));
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe('mergeCookies', () => {
|
|
64
|
-
it('parses CLI name=value strings into cookie objects', () => {
|
|
65
|
-
const out = mergeCookies(['session=abc'], [], 'https://example.com');
|
|
66
|
-
assert.equal(out.length, 1);
|
|
67
|
-
assert.equal(out[0].name, 'session');
|
|
68
|
-
assert.equal(out[0].value, 'abc');
|
|
69
|
-
assert.equal(out[0].url, 'https://example.com');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('lets file cookies override CLI cookies with the same name+domain', () => {
|
|
73
|
-
const out = mergeCookies(
|
|
74
|
-
[{ name: 'session', value: 'cli', domain: '.example.com' }],
|
|
75
|
-
[{ name: 'session', value: 'file', domain: '.example.com' }],
|
|
76
|
-
'https://example.com',
|
|
77
|
-
);
|
|
78
|
-
assert.equal(out.length, 1);
|
|
79
|
-
assert.equal(out[0].value, 'file');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('keeps cookies with different domains separately', () => {
|
|
83
|
-
const out = mergeCookies(
|
|
84
|
-
[],
|
|
85
|
-
[
|
|
86
|
-
{ name: 'x', value: '1', domain: 'a.example.com' },
|
|
87
|
-
{ name: 'x', value: '2', domain: 'b.example.com' },
|
|
88
|
-
],
|
|
89
|
-
'https://example.com',
|
|
90
|
-
);
|
|
91
|
-
assert.equal(out.length, 2);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('strips entries without a name', () => {
|
|
95
|
-
const out = mergeCookies([], [{ value: 'orphan' }], 'https://example.com');
|
|
96
|
-
assert.equal(out.length, 0);
|
|
97
|
-
});
|
|
98
|
-
});
|