stitch2 0.0.0-stage → 0.7.1

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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -2
  3. package/config.schema.json +109 -0
  4. package/dist/assets.d.ts +1 -0
  5. package/dist/assets.js +59 -0
  6. package/dist/audit.d.ts +10 -0
  7. package/dist/audit.js +259 -0
  8. package/dist/build-design.d.ts +39 -0
  9. package/dist/build-design.js +337 -0
  10. package/dist/build-elements.d.ts +1 -0
  11. package/dist/build-elements.js +33 -0
  12. package/dist/canvas.html +689 -0
  13. package/dist/check.d.ts +1 -0
  14. package/dist/check.js +86 -0
  15. package/dist/cli.d.ts +2 -0
  16. package/dist/cli.js +73 -0
  17. package/dist/colours.d.ts +2 -0
  18. package/dist/colours.js +57 -0
  19. package/dist/compare.d.ts +1 -0
  20. package/dist/compare.js +185 -0
  21. package/dist/config.d.ts +53 -0
  22. package/dist/config.js +38 -0
  23. package/dist/consistency.d.ts +40 -0
  24. package/dist/consistency.js +336 -0
  25. package/dist/design-md.d.ts +27 -0
  26. package/dist/design-md.js +49 -0
  27. package/dist/design-page.d.ts +1 -0
  28. package/dist/design-page.js +127 -0
  29. package/dist/elements.d.ts +32 -0
  30. package/dist/elements.js +69 -0
  31. package/dist/exports.d.ts +25 -0
  32. package/dist/exports.js +86 -0
  33. package/dist/extract.d.ts +1 -0
  34. package/dist/extract.js +197 -0
  35. package/dist/init.d.ts +5 -0
  36. package/dist/init.js +83 -0
  37. package/dist/list.d.ts +1 -0
  38. package/dist/list.js +17 -0
  39. package/dist/pick.d.ts +10 -0
  40. package/dist/pick.js +38 -0
  41. package/dist/report.d.ts +1 -0
  42. package/dist/report.js +110 -0
  43. package/dist/sandbox.d.ts +1 -0
  44. package/dist/sandbox.js +183 -0
  45. package/dist/screens.d.ts +34 -0
  46. package/dist/screens.js +85 -0
  47. package/dist/serve.d.ts +4 -0
  48. package/dist/serve.js +181 -0
  49. package/dist/setup.d.ts +1 -0
  50. package/dist/setup.js +214 -0
  51. package/dist/tokens.d.ts +1 -0
  52. package/dist/tokens.js +128 -0
  53. package/dist/type.d.ts +1 -0
  54. package/dist/type.js +60 -0
  55. package/dist/typecheck.d.ts +35 -0
  56. package/dist/typecheck.js +112 -0
  57. package/package.json +53 -4
  58. package/skills/stitch2/SKILL.md +80 -0
  59. package/skills/stitch2/references/components.md +59 -0
  60. package/skills/stitch2/references/guides.md +20 -0
  61. package/skills/stitch2/references/principles.md +80 -0
  62. package/skills/stitch2/references/screens.md +70 -0
  63. package/skills/stitch2/references/typography.md +106 -0
  64. package/skills/stitch2-app-sync/SKILL.md +65 -0
  65. package/skills/stitch2-consistency/SKILL.md +59 -0
  66. package/skills/stitch2-design-md/SKILL.md +64 -0
  67. package/skills/stitch2-import-from-google-stitch/SKILL.md +47 -0
  68. package/skills/stitch2-import-from-google-stitch/references/how-stitch-works.md +36 -0
  69. package/skills/stitch2-variants/SKILL.md +45 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ifokeev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,206 @@
1
- # Temporary Holding Version
1
+ # stitch2
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A design lab for agent-built UI. Agents draft screens as plain HTML on your design system; stitch2 renders them,
4
+ checks them, keeps every version, and gives you a canvas to compare, approve and comment on them, and gives
5
+ agents skills that tell them how.
6
+
7
+ > stitch2 is inspired by [Google Stitch](https://stitch.withgoogle.com) and works with it, but it is an
8
+ > independent project, not affiliated with or endorsed by Google.
9
+
10
+ ## Quick start with an AI agent
11
+
12
+ Paste this to your coding agent (Codex, Claude Code, Cursor or any agent that runs commands) in your project:
13
+
14
+ ```text
15
+ Set up stitch2 (https://github.com/ifokeev/stitch2) in this project:
16
+ 1. Install it as a dev dependency (pnpm add -D stitch2, or npm install -D stitch2) and
17
+ Chromium for Playwright (npx playwright install chromium).
18
+ 2. Ask me for the product name, the brand colour and whether it needs dark, light or both modes, then run
19
+ npx stitch2 setup --name "<name>" --primary "<#hex>" --modes <modes>
20
+ (add --skills-dir .claude/skills if you read skills from there).
21
+ 3. Read the stitch2 skill it linked and follow it from now on: refine DESIGN.md's prose with me, draft the
22
+ screens I describe from the template, run npx stitch2 check --strict until it passes, and tell me to open the
23
+ canvas (npx stitch2 canvas) to review.
24
+ ```
25
+
26
+ `stitch2 setup` does the mechanical part in one step: it links the skills, writes `stitch2.config.json`,
27
+ DESIGN.md and its tokens, a screen template and a component catalog with a few starter components, and keeps any
28
+ file that already exists. From there the agent spends its effort on the design.
29
+
30
+ ## What it does
31
+
32
+ - **Canvas** (`stitch2 canvas`): every screen and its versions on a pan-and-zoom board, a sidebar with search
33
+ and status filters, your DESIGN.md drawn as tokens and rules, the component catalog, and an inspector to
34
+ approve, archive or leave a note on a version. Live reload; copyable references to paste to an agent.
35
+ Pick mode (`P`) highlights any element of a screen and copies a reference with its source line, such as
36
+ `home mobile v1 (design/screens/home/mobile-v1.html:36) <gg-button> “Start Push”`; Shift-click collects
37
+ several to copy at once or add to the version's note.
38
+ - **Checks** (`stitch2 check`): content past the frame edge, labels that wrap, content under fixed bars, fixed bars that overlap each other, tap
39
+ targets (WCAG 2.2), text contrast, text that is not one of DESIGN.md's type levels, capitals without tracking,
40
+ large numbers in monospace, components built by hand, components that differ between screens, and colours
41
+ written as values instead of DESIGN.md tokens.
42
+ - **Consistency report** (`stitch2 consistency`): every shared component (tab bar, header, buttons, chips, …)
43
+ cropped from every screen, next to the catalog's version, with what differs (sizes, fonts, icon set, icons).
44
+ - **Type audit** (`stitch2 type`): every text style a screen uses, and the level it matches.
45
+ - **Tokens** (`stitch2 tokens`): CSS variables, one `.type-<level>` class per type level (with per-language
46
+ rules for scripts without capitals) and a Tailwind config, from DESIGN.md's front matter, plus Material 3
47
+ colour roles from a seed colour. **Exports** write the same tokens for your app, so DESIGN.md drives the
48
+ product as well as the screens: plain CSS variables, a Tailwind v4 theme (`@theme` plus `@utility type-*`), or
49
+ W3C Design Tokens JSON for Style Dictionary and native platforms. Aliases keep your app's own variable names:
50
+
51
+ ```json
52
+ "exports": [
53
+ { "format": "tailwind4", "path": "src/styles/design-tokens.css",
54
+ "aliases": { "background": "surface", "primary": "primary", "border": "border" } },
55
+ { "format": "dtcg", "path": "design/tokens.json" }
56
+ ]
57
+ ```
58
+ - **Versions and approval**: every file is a version of a screen for a device, with its status (draft, review,
59
+ approved, archived) and your note in meta tags, so agents can read what you decided (`stitch2 screens`).
60
+ - **DESIGN.md builder**: `stitch2 init` writes a complete DESIGN.md from a few choices (brand colour, modes,
61
+ fonts, roundness, density), with colours checked for contrast; `stitch2 extract` measures one from an existing
62
+ site or screens.
63
+ - **Fresh-eyes variants** (`stitch2 sandbox`): a folder with your design system, components and skills but no
64
+ screens, where a fresh agent designs from a brief without being anchored to the existing versions;
65
+ `stitch2 sandbox import` brings the result back as the next version for review. Also the way to test a change
66
+ to the skills on output only the rules produced.
67
+ - **Skills** for agents (`skills/`): `stitch2` (the design workflow, principles, typography, components,
68
+ versions), `stitch2-design-md` (building the design system), `stitch2-variants` (fresh-eyes versions and
69
+ testing skill changes), `stitch2-consistency` (verifying and fixing
70
+ consistency), `stitch2-app-sync` (keeping the app and the designs in step: design first, then the app) and,
71
+ optional, `stitch2-import-from-google-stitch` (drafting screens in Google Stitch through its MCP server).
72
+
73
+ ## Setup
74
+
75
+ 1. Install it as a dev dependency (Node 24 or newer), then Chromium for Playwright, which renders the screens:
76
+
77
+ ```bash
78
+ pnpm add -D stitch2 # or: npm install -D stitch2
79
+ npx playwright install chromium
80
+ ```
81
+
82
+ Every release is also on GitHub: `github:ifokeev/stitch2#release` (latest) or `#v<version>` (pinned).
83
+ 2. Set the project up:
84
+
85
+ ```bash
86
+ npx stitch2 setup --name "My app" --primary "#2F6FEB" [--modes dark,light] [--prefix ma] [--skills-dir .agents/skills]
87
+ ```
88
+
89
+ It links the skills from `skills/` into your agent's skills folder, and writes:
90
+ - `stitch2.config.json` (schema: [config.schema.json](config.schema.json)). `prefix` names your meta tags
91
+ (`ma-status`), your components and their marker (`<ma-button>`, `data-ma`) and your CSS variables (`--ma-*`);
92
+ by default it comes from the product name.
93
+ - `design/DESIGN.md` in [Google's DESIGN.md format](https://github.com/google-labs-code/design.md) (tokens in the
94
+ YAML front matter, rules in the prose) and its tokens. Without `--primary` it leaves DESIGN.md to you:
95
+ `stitch2 init` from a few choices, or `stitch2 extract https://my.app` from an existing product.
96
+ - `design/screens/_template.html`, `design/screens/components/catalog.html` and `design/components/<prefix>.js`:
97
+ a screen template, the component catalog and starter components (header, section, card, row, button, chip) as
98
+ light-DOM custom elements on the tokens. Grow them, or swap in your app's own components (next section).
99
+ 3. Refine DESIGN.md's prose (brand, voice, component rules); `stitch2 lint` validates it and `stitch2 tokens`
100
+ regenerates the tokens. Add a short project skill with your brand, component list, screen names and demo data.
101
+ 4. `stitch2 canvas` and start drafting screens from the template.
102
+
103
+ ## Your app's components on the canvas
104
+
105
+ Screens can use the app's own components, so a screen shows exactly what the app renders and the two cannot
106
+ drift. `stitch2/elements` turns components of any framework into the `<prefix>-<name>` elements screens use:
107
+
108
+ ```tsx
109
+ // design/elements.tsx
110
+ import { createElement } from 'react'
111
+ import { renderToStaticMarkup } from 'react-dom/server'
112
+ import { defineElements } from 'stitch2/elements'
113
+ import { Button, Card, Row } from '../src/ui'
114
+
115
+ defineElements({ button: Button, card: Card, row: Row }, {
116
+ prefix: 'ex',
117
+ render: (C, props) => renderToStaticMarkup(createElement(C, props)),
118
+ })
119
+ ```
120
+
121
+ `stitch2 elements design/elements.tsx` bundles it into `design/components/<prefix>.elements.js`. A screen loads
122
+ that bundle, the Tailwind v4 browser build and the theme from a `tailwind4` export, and writes
123
+ `<ex-row name="Morning run" chevron></ex-row>`:
124
+
125
+ ```html
126
+ <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
127
+ <link rel="stylesheet" type="text/tailwindcss" href="/tokens.tailwind.css" />
128
+ <script src="/components/ex.elements.js" defer></script>
129
+ ```
130
+
131
+ Attributes become props (kebab-case to camelCase, an empty attribute is `true`), the element's content becomes
132
+ `children`, and each element marks its root `data-<prefix>` so the checks and the consistency report find it.
133
+ The stitch2 server inlines `text/tailwindcss` links, which the browser build cannot load itself. Tested with
134
+ React; any framework with a string renderer fits `render` (Preact, Solid, and Vue's asynchronous one).
135
+
136
+ ## Is the app still the design?
137
+
138
+ `stitch2 compare` opens every approved version next to its live page in the app, at the same width, and reports
139
+ what differs in structure: shared components the design has and the app lacks (or the other way round), their
140
+ order, and how the measured ones look. Mock-ups and the app hold different data, so the screenshots are shown side
141
+ by side in `design/compare/index.html` (the canvas links it) rather than diffed. It also runs check's layout
142
+ rules on the app (content past the edge, fixed bars or controls overlapping), which catches a control the app
143
+ squeezed in although no screen drew it. The `stitch2-app-sync` skill describes the workflow around it: a visible
144
+ change goes component, canvas, approval, then app. Configure it in the config:
145
+
146
+ ```json
147
+ "app": {
148
+ "url": "http://localhost:3000",
149
+ "signIn": "design/app/sign-in.mjs",
150
+ "routes": { "home": "/", "exercise-detail": { "path": "/exercises", "prepare": "design/app/open-first.mjs" } }
151
+ }
152
+ ```
153
+
154
+ A route with `"signedOut": true` (a sign-in page, which would redirect a signed-in visitor) is opened in a
155
+ separate browser context that never signed in.
156
+
157
+ `signIn` and `prepare` are modules whose default export gets `{ page, url }` (a Playwright page): sign in once, or
158
+ put a page into the state the design shows (open a sheet, start a workout). The app's components must carry the
159
+ `data-<prefix>` markers, which they do when the screens use them too (see above). A `prepare` that throws becomes
160
+ an error on that version and the run goes on. When it opens a modal, wait for something inside it (a marked
161
+ component): dialog roots are often zero-sized wrappers, which Playwright reports as hidden.
162
+
163
+ ## Commands
164
+
165
+ | Command | What it does |
166
+ |---|---|
167
+ | `stitch2 setup [--name N --primary #hex] [options]` | Sets a project up: skills, config, DESIGN.md and tokens, template, catalog, starter components |
168
+ | `stitch2 init --name N --primary #hex [options]` | Writes a complete DESIGN.md from a few choices |
169
+ | `stitch2 extract <url or file…>` | Writes a DESIGN.md measured from an existing site or screens |
170
+ | `stitch2 canvas` | The canvas at http://localhost:4400 (`PORT` and `HOST` to change) |
171
+ | `stitch2 check [filter] [--shots] [--strict]` | All checks; `--shots` saves renders to `<design>/renders/` |
172
+ | `stitch2 consistency [kind…]` | The consistency report, `<design>/consistency/index.html` |
173
+ | `stitch2 type [filter] [--json]` | The type audit |
174
+ | `stitch2 screens [name] [--status s] [--json]` | Versions, statuses and notes |
175
+ | `stitch2 tokens` | Tokens from DESIGN.md |
176
+ | `stitch2 compare [screen…]` | The live app against the approved screens (`app` in the config) |
177
+ | `stitch2 elements <entry>` | Bundle your components (`stitch2/elements`) for the screens |
178
+ | `stitch2 lint` | Google's DESIGN.md linter |
179
+ | `stitch2 localize [filter]` | Copies a screen's remote images into its `assets/` folder |
180
+ | `stitch2 sandbox create <dir> <brief.md>… [--context <screen>…]` | A fresh-eyes folder for an agent that sees no other versions |
181
+ | `stitch2 sandbox import <dir> [--label …]` | Brings its screens back as new versions, status review |
182
+
183
+ ## Development
184
+
185
+ Agents working on stitch2 itself: see [AGENTS.md](AGENTS.md).
186
+
187
+ ```bash
188
+ git clone https://github.com/ifokeev/stitch2 && cd stitch2
189
+ pnpm install
190
+ node src/cli.ts <command> # runs the TypeScript sources directly (Node 24 strips the types)
191
+ pnpm typecheck
192
+ pnpm test # smoke test in a fresh project; needs Chromium (npx playwright install chromium)
193
+ pnpm build # compiles to dist/, which the installed package runs
194
+ ```
195
+
196
+ Releases: bump `version` in package.json, commit, then `pnpm release`. It builds, packs exactly what npm would
197
+ publish, commits that to the `release` branch (built files only, so installs need no build step) and tags it
198
+ `v<version>`; the tag push runs `.github/workflows/publish.yml`, which publishes that commit to npm through
199
+ trusted publishing. `main` holds sources only; `dist/` is ignored.
200
+
201
+ Point it at a project with `STITCH2_CONFIG=/path/to/stitch2.config.json`, or run it from inside the project.
202
+
203
+ ## Licence
204
+
205
+ MIT, see [LICENSE](LICENSE). It builds on Apache-2.0 projects: Playwright, Google's material-color-utilities and
206
+ `@google/design.md`.
@@ -0,0 +1,109 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "stitch2.config.json",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "properties": {
7
+ "$schema": {
8
+ "type": "string"
9
+ },
10
+ "name": {
11
+ "type": "string",
12
+ "description": "Shown on the canvas.",
13
+ "default": "stitch2"
14
+ },
15
+ "designDir": {
16
+ "type": "string",
17
+ "description": "Folder with DESIGN.md, the generated tokens, components and screens, relative to this file.",
18
+ "default": "design"
19
+ },
20
+ "screenDirs": {
21
+ "type": "array",
22
+ "items": {
23
+ "type": "string"
24
+ },
25
+ "description": "Folders under designDir that hold screens.",
26
+ "default": ["screens"]
27
+ },
28
+ "stitchDir": {
29
+ "type": "string",
30
+ "description": "Folder under designDir with Google Stitch exports (their source defaults to stitch).",
31
+ "default": "stitch"
32
+ },
33
+ "archiveDirs": {
34
+ "type": "array",
35
+ "items": {
36
+ "type": "string"
37
+ },
38
+ "description": "Folders under designDir with past experiments: archived by default, compared only among themselves.",
39
+ "default": []
40
+ },
41
+ "catalogDir": {
42
+ "type": "string",
43
+ "description": "Folder under designDir with the component catalog screen(s), the reference for consistency.",
44
+ "default": "screens/components"
45
+ },
46
+ "prefix": {
47
+ "type": "string",
48
+ "pattern": "^[a-z][a-z0-9]*$",
49
+ "description": "Prefix of meta tags (<prefix>-status), components (<prefix>-button, data-<prefix>) and CSS variables (--<prefix>-*).",
50
+ "default": "s2"
51
+ },
52
+ "order": {
53
+ "type": "array",
54
+ "items": {
55
+ "type": "string"
56
+ },
57
+ "description": "Screen names in canvas order; the rest follow alphabetically.",
58
+ "default": []
59
+ },
60
+ "comfortableTarget": {
61
+ "type": "number",
62
+ "description": "Tap targets smaller than this (px) get a warning.",
63
+ "default": 32
64
+ },
65
+ "skills": {
66
+ "type": "array",
67
+ "items": {
68
+ "type": "string"
69
+ },
70
+ "description": "Skill folders a blind-trial sandbox copies, relative to this file.",
71
+ "default": []
72
+ },
73
+ "sandboxExclude": {
74
+ "type": "array",
75
+ "items": {
76
+ "type": "string"
77
+ },
78
+ "description": "Paths under designDir a sandbox leaves out besides screens.",
79
+ "default": []
80
+ },
81
+ "exports": {
82
+ "type": "array",
83
+ "description": "Token files stitch2 tokens also writes for the app, so DESIGN.md drives the product too.",
84
+ "items": {
85
+ "type": "object",
86
+ "additionalProperties": false,
87
+ "required": ["format", "path"],
88
+ "properties": {
89
+ "format": {
90
+ "enum": ["css", "tailwind4", "dtcg"],
91
+ "description": "css: variables and .type-* classes; tailwind4: also an @theme block and @utility type-*; dtcg: W3C Design Tokens JSON."
92
+ },
93
+ "path": { "type": "string", "description": "Output file, relative to this file." },
94
+ "aliases": {
95
+ "type": "object",
96
+ "additionalProperties": { "type": "string" },
97
+ "description": "The app's variable names mapped to DESIGN.md colours ({\"background\": \"floor\"}) or CSS values."
98
+ },
99
+ "themes": {
100
+ "type": "object",
101
+ "additionalProperties": false,
102
+ "properties": { "dark": { "type": "string" }, "light": { "type": "string" } },
103
+ "description": "Theme selectors; default :root, [data-theme='dark'] and [data-theme='light']."
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/assets.js ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Makes screens self-contained: downloads every remote image a screen uses (Stitch links its images to
3
+ * Google-hosted URLs, which can fail in other browsers or expire) into design/<folder>/assets/ and points
4
+ * the screen at the local copy. Scripts, styles and fonts stay remote. Safe to run again.
5
+ * Usage: stitch2 localize [path filter…]
6
+ */
7
+ import { createHash } from 'node:crypto';
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
9
+ import { dirname, join } from 'node:path';
10
+ import { listScreens, ROOT } from './screens.js';
11
+ const EXT = {
12
+ 'image/png': 'png',
13
+ 'image/jpeg': 'jpg',
14
+ 'image/webp': 'webp',
15
+ 'image/gif': 'gif',
16
+ 'image/svg+xml': 'svg',
17
+ 'image/avif': 'avif',
18
+ };
19
+ // <img src>, <source srcset> (first candidate) and CSS url(...) pointing at http(s) images.
20
+ const PATTERNS = [
21
+ /(<img\b[^>]*?\bsrc=")(https?:\/\/[^"]+)(")/gi,
22
+ /(url\(['"]?)(https?:\/\/[^'")]+)(['"]?\))/gi,
23
+ ];
24
+ const filters = process.argv.slice(2).filter((a) => !a.startsWith('--'));
25
+ let downloaded = 0;
26
+ let rewritten = 0;
27
+ for (const screen of listScreens().filter((s) => !filters.length || filters.some((f) => s.path.includes(f)))) {
28
+ const file = join(ROOT, screen.path);
29
+ let html = readFileSync(file, 'utf8');
30
+ const urls = new Set();
31
+ for (const re of PATTERNS)
32
+ for (const m of html.matchAll(re))
33
+ urls.add(m[2]);
34
+ // Fonts and stylesheets from CDNs are fine to keep remote.
35
+ const images = [...urls].filter((u) => !/fonts\.(googleapis|gstatic)\.com|cdn\.tailwindcss\.com|unpkg\.com/.test(u));
36
+ if (!images.length)
37
+ continue;
38
+ const assetsDir = join(dirname(file), 'assets');
39
+ mkdirSync(assetsDir, { recursive: true });
40
+ for (const url of images) {
41
+ const res = await fetch(url);
42
+ const type = res.headers.get('content-type')?.split(';')[0] ?? '';
43
+ if (!res.ok || !EXT[type]) {
44
+ console.log(`✗ ${screen.path}: ${res.status} ${type || 'unknown type'} for ${url.slice(0, 70)}…`);
45
+ continue;
46
+ }
47
+ const bytes = Buffer.from(await res.arrayBuffer());
48
+ const name = `${createHash('sha256').update(bytes).digest('hex').slice(0, 16)}.${EXT[type]}`;
49
+ if (!existsSync(join(assetsDir, name))) {
50
+ writeFileSync(join(assetsDir, name), bytes);
51
+ downloaded++;
52
+ }
53
+ html = html.split(url).join(`assets/${name}`);
54
+ rewritten++;
55
+ console.log(`✓ ${screen.path}: ${url.slice(0, 50)}… → assets/${name}`);
56
+ }
57
+ writeFileSync(file, html);
58
+ }
59
+ console.log(`${rewritten} image links now local (${downloaded} new files).`);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The layout audit, shared by check (the design screens) and compare (the live app). Runs inside the page.
3
+ */
4
+ import type { Issue } from './typecheck.ts';
5
+ /** Layout errors that mean the same bug wherever they appear; compare reports these for the app too. */
6
+ export declare const LAYOUT_ERRORS: string[];
7
+ /** Runs inside the page. Plain JS on purpose: Playwright sends its source to the browser. */
8
+ export declare function audit(opts: {
9
+ comfortable: number;
10
+ }): Promise<Issue[]>;