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.
- package/LICENSE +21 -0
- package/README.md +205 -2
- package/config.schema.json +109 -0
- package/dist/assets.d.ts +1 -0
- package/dist/assets.js +59 -0
- package/dist/audit.d.ts +10 -0
- package/dist/audit.js +259 -0
- package/dist/build-design.d.ts +39 -0
- package/dist/build-design.js +337 -0
- package/dist/build-elements.d.ts +1 -0
- package/dist/build-elements.js +33 -0
- package/dist/canvas.html +689 -0
- package/dist/check.d.ts +1 -0
- package/dist/check.js +86 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +73 -0
- package/dist/colours.d.ts +2 -0
- package/dist/colours.js +57 -0
- package/dist/compare.d.ts +1 -0
- package/dist/compare.js +185 -0
- package/dist/config.d.ts +53 -0
- package/dist/config.js +38 -0
- package/dist/consistency.d.ts +40 -0
- package/dist/consistency.js +336 -0
- package/dist/design-md.d.ts +27 -0
- package/dist/design-md.js +49 -0
- package/dist/design-page.d.ts +1 -0
- package/dist/design-page.js +127 -0
- package/dist/elements.d.ts +32 -0
- package/dist/elements.js +69 -0
- package/dist/exports.d.ts +25 -0
- package/dist/exports.js +86 -0
- package/dist/extract.d.ts +1 -0
- package/dist/extract.js +197 -0
- package/dist/init.d.ts +5 -0
- package/dist/init.js +83 -0
- package/dist/list.d.ts +1 -0
- package/dist/list.js +17 -0
- package/dist/pick.d.ts +10 -0
- package/dist/pick.js +38 -0
- package/dist/report.d.ts +1 -0
- package/dist/report.js +110 -0
- package/dist/sandbox.d.ts +1 -0
- package/dist/sandbox.js +183 -0
- package/dist/screens.d.ts +34 -0
- package/dist/screens.js +85 -0
- package/dist/serve.d.ts +4 -0
- package/dist/serve.js +181 -0
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +214 -0
- package/dist/tokens.d.ts +1 -0
- package/dist/tokens.js +128 -0
- package/dist/type.d.ts +1 -0
- package/dist/type.js +60 -0
- package/dist/typecheck.d.ts +35 -0
- package/dist/typecheck.js +112 -0
- package/package.json +53 -4
- package/skills/stitch2/SKILL.md +80 -0
- package/skills/stitch2/references/components.md +59 -0
- package/skills/stitch2/references/guides.md +20 -0
- package/skills/stitch2/references/principles.md +80 -0
- package/skills/stitch2/references/screens.md +70 -0
- package/skills/stitch2/references/typography.md +106 -0
- package/skills/stitch2-app-sync/SKILL.md +65 -0
- package/skills/stitch2-consistency/SKILL.md +59 -0
- package/skills/stitch2-design-md/SKILL.md +64 -0
- package/skills/stitch2-import-from-google-stitch/SKILL.md +47 -0
- package/skills/stitch2-import-from-google-stitch/references/how-stitch-works.md +36 -0
- 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
|
-
#
|
|
1
|
+
# stitch2
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
}
|
package/dist/assets.d.ts
ADDED
|
@@ -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).`);
|
package/dist/audit.d.ts
ADDED
|
@@ -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[]>;
|