@codepress/design-kit 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ Proprietary License
2
+
3
+ Copyright (c) 2025 Codepress
4
+
5
+ All rights reserved.
6
+
7
+ This software and associated documentation files (the "Software") are the proprietary property of Codepress. Use of the Software is governed by the license agreement that you entered into with Codepress.
8
+
9
+ Unauthorized copying, distribution, modification, public display, public performance, or creation of derivative works of the Software or any part thereof is strictly prohibited. This includes, but is not limited to, integrating or using the Software with third-party services without explicit permission from Codepress.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,229 @@
1
- # Temporary Holding Version
1
+ # @codepress/design-kit
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
+ Your app's real markup for mockups. `design-kit` renders every Storybook story
4
+ and the pages of your running app to static HTML, with the CSS and fonts they
5
+ loaded. The result is a design kit: a folder of snippets and a small,
6
+ dependency-free tool that finds them and composes self-contained pages from
7
+ them. A mockup built from a kit uses the app's own frame and components, so
8
+ you draw only what is new.
9
+
10
+ ```bash
11
+ npm install --save-dev @codepress/design-kit @playwright/test
12
+ npx playwright install chromium
13
+ npx design-kit capture stories # every story → ./design-kit (or "out")
14
+ npx design-kit capture pages # the configured pages of the running app
15
+ node design-kit/kit.mjs find button # the kit's own tool, no install needed
16
+ ```
17
+
18
+ It runs on Node 20 or later. Playwright is a peer dependency, so the capture
19
+ drives the Playwright and Chromium your project already has. The `kit`
20
+ commands need neither.
21
+
22
+ ## Configuration
23
+
24
+ The commands read `design-kit.config.json` from the working directory or the
25
+ nearest folder above it; `--config <file>` names another. Paths are relative
26
+ to the config's folder. The shipped JSON schema
27
+ ([`schema/design-kit.config.schema.json`](./schema/design-kit.config.schema.json),
28
+ also `@codepress/design-kit/schema.json`) documents every field, and
29
+ `design-kit validate` checks a config against it. A config error, such as an
30
+ unknown key or a page without `slots.main`, names the field and exits 2.
31
+
32
+ ```json
33
+ {
34
+ "$schema": "./node_modules/@codepress/design-kit/schema/design-kit.config.schema.json",
35
+ "version": 1,
36
+ "out": "design-kit",
37
+ "theme": { "attribute": "data-theme", "dark": "dark", "light": "light" },
38
+ "stories": {
39
+ "root": ".",
40
+ "buildCommand": "npx storybook build --quiet -o storybook-static",
41
+ "staticDir": "storybook-static",
42
+ "widths": [{ "titles": "^(Pages|Layout)/", "width": 390, "height": 844 }],
43
+ "gaps": { "componentGlob": "src/components/**/[A-Z]*.tsx" }
44
+ },
45
+ "pages": {
46
+ "startCommand": "npm run preview -- --port 4173",
47
+ "waitUrl": "http://localhost:4173/",
48
+ "auth": { "storageState": "e2e/.auth/user.json" },
49
+ "shellReady": "#app-header",
50
+ "forbidStrings": ["the test account's API token"],
51
+ "pages": [
52
+ {
53
+ "name": "dashboard",
54
+ "title": "Dashboard",
55
+ "path": "/dashboard",
56
+ "ready": "[data-testid=dashboard-chart]",
57
+ "sourceFile": "src/routes/dashboard.tsx",
58
+ "slots": { "main": ["main"], "list": ["aside nav"] }
59
+ }
60
+ ]
61
+ }
62
+ }
63
+ ```
64
+
65
+ ### `theme`
66
+
67
+ How the app switches to dark: a class (`"attribute": "class"`, the default,
68
+ `dark` on `<html>` and `<body>`) or an attribute such as `data-theme` on
69
+ `<html>`. Leave `light` out when light is simply the dark value's absence. The
70
+ captures record the theme in the kit's manifest, so `kit compose --theme dark`
71
+ needs no config.
72
+
73
+ ### `stories`
74
+
75
+ Captures every story of a Storybook 7 or 8 static build (any renderer). The
76
+ build comes from `--storybook-dir`, `storybookDir`, or `buildCommand`, which
77
+ writes `staticDir`; without `staticDir`, `{out}` in the command names a
78
+ temporary folder that is removed afterwards.
79
+
80
+ - `viewport`: each story's first capture, unless the story sets
81
+ `parameters.viewport.defaultViewport` (default 1280×800).
82
+ - `widths`: more captures for stories whose title matches the `titles`
83
+ regular expression.
84
+ - `fonts`: `{dir, faces: [{family, file, weight?, style?, variable?,
85
+ fallback?}]}` gives every story these faces (`file` is a name glob in `dir`;
86
+ `variable` sets a CSS variable on `:root` for apps that read their font from
87
+ one). Without it, the @font-face files the build itself serves are copied
88
+ into the kit.
89
+ - `fixedTime`: the instant `Date` returns (default `2026-01-01T12:00:00.000Z`).
90
+ - `gaps`: lists the component files no story covers in `gaps.json`.
91
+ `componentGlob` is relative to `root`; stories, tests and specs never count.
92
+
93
+ ### `pages`
94
+
95
+ Captures real pages of the running app at `baseUrl`, or starts it with
96
+ `startCommand` (stopped afterwards) once `waitUrl` answers. Each page gets a
97
+ fresh browser context, signed in from `auth.storageState` (a Playwright
98
+ storage state file) or from the state the `auth.setup` module's default
99
+ export returns (`async ({ browser, baseUrl, config }) => state`).
100
+
101
+ - `pages[]`: `{name, title?, path, ready, sourceFile?, slots, layoutSlots?}`.
102
+ `ready` is a Playwright selector that shows once the page has its content.
103
+ `slots` names the regions a mockup replaces: the first selector that matches
104
+ a visible, non-empty element gets `data-kit-slot="<name>"`, and `main` is
105
+ required. A page without a `main` slot is a gap, not a frame.
106
+ - `widths`: the sizes every page is captured at (default 1280×800 and 390×844).
107
+ - `shellReady`: a selector the app's frame shows before any page content.
108
+ - `layout`: `{selector, attribute, default?}` reads the layout the app chose,
109
+ for apps that render a different frame at phone width; `layoutSlots[<that
110
+ value>]` then replaces `slots`.
111
+ - `forbidStrings`: values that must never reach the kit, such as a test
112
+ account's token. A page whose output contains one, as written, escaped for
113
+ HTML or JSON, or (for a token) any long segment of it, is not written.
114
+ - `fontsPath`, `copyFonts`: the fonts the pages load are copied into the
115
+ kit's `fonts/` folder (`copyFonts: false` keeps them out); `fontsPath` says
116
+ which URL path holds them when not every font file is one.
117
+ - `hooks`: a module exporting `context(context, info)` (may return a cleanup)
118
+ and `page(page, info)` (may return `{readyFailed, beforeSerialize}`), for
119
+ routing mock data or adding checks.
120
+ - `timeoutMs`, `startTimeoutMs`, `fixedTime`.
121
+
122
+ ## Commands
123
+
124
+ ```bash
125
+ design-kit capture stories [--config f] [--out dir] [--storybook-dir dir]
126
+ [--only id,... | --only-file f] [--base previous-kit]
127
+ design-kit capture pages [--config f] [--out dir] [--base-url url] [--only name,...]
128
+ [--ready name=selector] [--screenshots dir] [--timeout ms]
129
+ design-kit kit info|find|show|compose ... [--kit dir]
130
+ design-kit validate [--config f]
131
+ ```
132
+
133
+ `--only` re-captures just those stories (Storybook ids, with or without the
134
+ `story:` prefix and width) and copies every other row from `--base`, byte for
135
+ byte. The two captures share one kit folder and keep each other's files: run
136
+ both with the same `--out`.
137
+
138
+ Exit codes: 0 done, 1 a capture could not run (no Storybook build, the app
139
+ did not start), 2 a config or usage error. A story or page that fails is a
140
+ `gaps.json` row and does not fail the run.
141
+
142
+ Operational knobs: `DESIGN_KIT_CONCURRENCY` (browser pages at once, default
143
+ 4), `DESIGN_KIT_STORY_TIMEOUT_MS` (render and, separately, settle budget per
144
+ story, default 15000), `DESIGN_KIT_QUIET_MS` (how long the DOM must stay
145
+ unchanged, default 600), `DESIGN_KIT_TIMINGS_FILE` (per-phase capture times),
146
+ `DESIGN_KIT_STOP_TIMEOUT_MS` (how long stopping the app may take).
147
+
148
+ ## Capture rules
149
+
150
+ - Time is frozen at `fixedTime`, the time zone is UTC, the locale en-US,
151
+ `Math.random` and `crypto` random values are seeded, and animations,
152
+ transitions and the caret are off.
153
+ - A story is captured after Storybook's `storyRendered` event (its `play`
154
+ function has finished), fonts, frame and image loads, no request in flight,
155
+ and six frames plus `DESIGN_KIT_QUIET_MS` with no DOM change. A story that
156
+ throws, never renders or never stops changing is a gap.
157
+ - Attributes are serialized in sorted order, and React `useId` values are
158
+ renumbered in pages, so the markup depends on the final state, not on timing.
159
+ - Story captures only reach the local Storybook server. Same-origin images
160
+ become data URIs, canvases become images, iframes lose their documents, and
161
+ form state is written into attributes.
162
+
163
+ ## The kit (`kit_version: 1`)
164
+
165
+ ```
166
+ design-kit/
167
+ manifest.json {kit_version, sha, built_at, counts, sources, theme?, css?}
168
+ index.json story rows, sorted by id
169
+ routes.json page rows
170
+ html/story--<id>@<w>.html, html/route--<name>@<w>.html
171
+ css/stories.css every rule the Storybook preview loaded, deduplicated
172
+ css/routes.css every rule the captured pages loaded, deduplicated
173
+ fonts/ the fonts both use
174
+ gaps.json [{id|component|route, reason, detail?}]
175
+ kit.mjs the tool, copied into every kit so tool and data match
176
+ ```
177
+
178
+ A row is `{id, kind: "story"|"route", title, component?, file, width, height,
179
+ bytes, slots?, source_file?, body_class?, html_attrs?, body_attrs?}`. Ids are
180
+ `story:<storybook-id>@<width>` and `route:<name>@<width>`; file names use `--`
181
+ instead of `:`, so the kit uploads as a CI artifact as it is. Always find a
182
+ row's markup through its `file`. `manifest.css` may name another stylesheet
183
+ per source (`{stories, routes}`).
184
+
185
+ ## `kit.mjs`
186
+
187
+ The tool needs only Node: no install and no network.
188
+
189
+ ```bash
190
+ node kit.mjs info # the manifest
191
+ node kit.mjs find button # ≤ 30 lines: id, kind, width, bytes, title
192
+ node kit.mjs show <id> [--full] # path, size and an outline (≤ 2 KB) or the markup
193
+ node kit.mjs compose --frame route:dashboard@1280 --slot main=new.html \
194
+ [--theme light|dark] [--show-new] --out page.html
195
+ ```
196
+
197
+ `compose` writes one self-contained page: the frame's markup with slots
198
+ filled and every `<kit-snippet id="…">` expanded, its CSS inlined, every
199
+ relative `url()` that names a file in the kit (fonts included) as a data URI,
200
+ and `data-kit-sha`/`data-kit-id` on `<html>`. Scripts, event handlers,
201
+ `<link>`s and SVG animation are removed, and URLs pass an allowlist: only
202
+ relative references, fragments and `data:` survive, read the way the browser
203
+ reads them. `--theme dark` applies the manifest's theme. Elements marked
204
+ `data-kit-new` get a visible outline with `--show-new`. An unknown frame, slot
205
+ or snippet id exits 2, naming near matches. `--kit <dir>` (or
206
+ `DESIGN_KIT_DIR`) points at a kit other than the tool's own folder.
207
+
208
+ ## Programmatic use
209
+
210
+ ```js
211
+ import {
212
+ capturePages,
213
+ captureStories,
214
+ loadConfig,
215
+ } from "@codepress/design-kit";
216
+
217
+ const config = loadConfig("design-kit.config.json");
218
+ await captureStories(config, { out: "design-kit" });
219
+ await capturePages(config, {
220
+ out: "design-kit",
221
+ baseUrl: "http://localhost:4173",
222
+ storageState: await signIn(),
223
+ hooks: { context: (context) => context.route("**/api/**", mockApi) },
224
+ });
225
+ ```
226
+
227
+ `capturePages` also takes `forbidStrings` and `stripOrigins` (more origins to
228
+ remove from markup, such as a mock API's) and a `session` object whose
229
+ `interrupted` flag stops it between pages.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "../src/cli.mjs";
3
+
4
+ process.exitCode = await main();
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
2
  "name": "@codepress/design-kit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Capture your Storybook and real app pages as a static design kit, and compose self-contained mockups from it",
5
+ "type": "module",
6
+ "bin": {
7
+ "design-kit": "./bin/design-kit.mjs"
8
+ },
9
+ "main": "./src/index.mjs",
10
+ "types": "./src/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./src/index.d.ts",
14
+ "default": "./src/index.mjs"
15
+ },
16
+ "./kit": "./src/kit.mjs",
17
+ "./schema.json": "./schema/design-kit.config.schema.json",
18
+ "./package.json": "./package.json"
19
+ },
20
+ "files": [
21
+ "bin/",
22
+ "src/",
23
+ "schema/",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "engines": {
28
+ "node": ">=20"
29
+ },
30
+ "scripts": {
31
+ "test": "TZ=UTC LC_ALL=en_US.UTF-8 node ../../scripts/test-with-deadline.mjs node --test 'test/*.test.mjs'",
32
+ "test:browser": "node ../../scripts/capacity-browser.mjs -- \"node --test 'test/browser/*.test.mjs'\"",
33
+ "lint": "oxlint --ignore-pattern 'test/fixtures/**' .",
34
+ "format": "prettier --write .",
35
+ "format:check": "prettier --check .",
36
+ "pack:check": "node scripts/check-pack.mjs"
37
+ },
38
+ "peerDependencies": {
39
+ "@playwright/test": ">=1.40.0"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "@playwright/test": {
43
+ "optional": true
44
+ }
45
+ },
46
+ "devDependencies": {
47
+ "@playwright/test": "1.58.2",
48
+ "oxlint": "^1.51.0",
49
+ "prettier": "^3.8.1"
50
+ },
51
+ "keywords": [
52
+ "design-kit",
53
+ "storybook",
54
+ "mockups",
55
+ "playwright",
56
+ "design-system"
57
+ ],
58
+ "author": "CodePress",
59
+ "license": "SEE LICENSE IN LICENSE",
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "git+https://github.com/quantfive/codepress.git",
63
+ "directory": "packages/design-kit"
64
+ },
65
+ "publishConfig": {
66
+ "access": "public"
67
+ }
68
+ }
@@ -0,0 +1,335 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://unpkg.com/@codepress/design-kit/schema/design-kit.config.schema.json",
4
+ "title": "design-kit.config.json",
5
+ "description": "Version 1 of the @codepress/design-kit configuration. Paths are relative to the folder that holds the config file.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["version"],
9
+ "properties": {
10
+ "$schema": {
11
+ "type": "string",
12
+ "description": "The schema an editor validates this file against."
13
+ },
14
+ "version": {
15
+ "type": "integer",
16
+ "enum": [1],
17
+ "description": "The config format version. Always 1."
18
+ },
19
+ "out": {
20
+ "type": "string",
21
+ "minLength": 1,
22
+ "description": "Where captures write the kit when --out is not given."
23
+ },
24
+ "theme": { "$ref": "#/$defs/theme" },
25
+ "stories": { "$ref": "#/$defs/stories" },
26
+ "pages": { "$ref": "#/$defs/pages" }
27
+ },
28
+ "$defs": {
29
+ "size": {
30
+ "type": "object",
31
+ "additionalProperties": false,
32
+ "required": ["width", "height"],
33
+ "properties": {
34
+ "width": { "type": "integer", "minimum": 1 },
35
+ "height": { "type": "integer", "minimum": 1 }
36
+ }
37
+ },
38
+ "theme": {
39
+ "type": "object",
40
+ "additionalProperties": false,
41
+ "description": "How the app switches to its dark theme. Recorded in the kit's manifest, so `kit compose --theme dark` needs no config.",
42
+ "properties": {
43
+ "attribute": {
44
+ "type": "string",
45
+ "minLength": 1,
46
+ "description": "\"class\" (default) toggles a class; any other value is an attribute, such as \"data-theme\"."
47
+ },
48
+ "dark": {
49
+ "type": "string",
50
+ "minLength": 1,
51
+ "description": "The class or attribute value for dark. Default \"dark\"."
52
+ },
53
+ "light": {
54
+ "type": "string",
55
+ "minLength": 1,
56
+ "description": "The class or attribute value for light. Leave it out when light is the dark value's absence."
57
+ },
58
+ "targets": {
59
+ "type": "array",
60
+ "minItems": 1,
61
+ "items": { "type": "string", "enum": ["html", "body"] },
62
+ "description": "The elements that carry the value. Default [\"html\", \"body\"] for a class, [\"html\"] for an attribute."
63
+ }
64
+ }
65
+ },
66
+ "stories": {
67
+ "type": "object",
68
+ "additionalProperties": false,
69
+ "description": "`design-kit capture stories`: every story of a Storybook 7 or 8 static build.",
70
+ "properties": {
71
+ "root": {
72
+ "type": "string",
73
+ "minLength": 1,
74
+ "description": "The Storybook project folder (where its .storybook folder is). Default \".\"."
75
+ },
76
+ "storybookDir": {
77
+ "type": "string",
78
+ "minLength": 1,
79
+ "description": "An existing static build to capture. --storybook-dir overrides it."
80
+ },
81
+ "buildCommand": {
82
+ "type": "string",
83
+ "minLength": 1,
84
+ "description": "Builds the static Storybook, run in `root`. `{out}` is replaced by a temporary folder when `staticDir` is not set."
85
+ },
86
+ "staticDir": {
87
+ "type": "string",
88
+ "minLength": 1,
89
+ "description": "Where `buildCommand` writes the build (for example storybook-static)."
90
+ },
91
+ "viewport": {
92
+ "$ref": "#/$defs/size",
93
+ "description": "Every story's first capture, unless it declares parameters.viewport.defaultViewport. Default 1280x800."
94
+ },
95
+ "widths": {
96
+ "type": "array",
97
+ "description": "More captures for stories whose title matches `titles`.",
98
+ "items": {
99
+ "type": "object",
100
+ "additionalProperties": false,
101
+ "required": ["titles", "width", "height"],
102
+ "properties": {
103
+ "titles": {
104
+ "type": "string",
105
+ "minLength": 1,
106
+ "description": "A regular expression tested against the story's title, such as \"^(Pages|Layout)/\"."
107
+ },
108
+ "width": { "type": "integer", "minimum": 1 },
109
+ "height": { "type": "integer", "minimum": 1 }
110
+ }
111
+ }
112
+ },
113
+ "fonts": {
114
+ "type": "object",
115
+ "additionalProperties": false,
116
+ "required": ["dir", "faces"],
117
+ "description": "Faces every story is given. Without it, the @font-face files the Storybook build serves are copied into the kit.",
118
+ "properties": {
119
+ "dir": {
120
+ "type": "string",
121
+ "minLength": 1,
122
+ "description": "The folder that holds the font files."
123
+ },
124
+ "faces": {
125
+ "type": "array",
126
+ "minItems": 1,
127
+ "items": {
128
+ "type": "object",
129
+ "additionalProperties": false,
130
+ "required": ["family", "file"],
131
+ "properties": {
132
+ "family": { "type": "string", "minLength": 1 },
133
+ "file": {
134
+ "type": "string",
135
+ "minLength": 1,
136
+ "description": "A file name glob in `dir`, such as \"inter-latin.*.woff2\"; the first match in sorted order is used."
137
+ },
138
+ "weight": {
139
+ "type": "string",
140
+ "minLength": 1,
141
+ "description": "The font-weight descriptor, such as \"400\" or \"100 900\"."
142
+ },
143
+ "style": { "type": "string", "minLength": 1 },
144
+ "variable": {
145
+ "type": "string",
146
+ "pattern": "^--[A-Za-z0-9_-]+$",
147
+ "description": "A CSS variable on :root set to this family, for apps that read their font from one."
148
+ },
149
+ "fallback": {
150
+ "type": "string",
151
+ "minLength": 1,
152
+ "description": "The fallback list after the family in `variable`."
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ },
159
+ "fixedTime": {
160
+ "type": "string",
161
+ "minLength": 1,
162
+ "description": "The instant every story reads from Date (ISO 8601). Default 2026-01-01T12:00:00.000Z."
163
+ },
164
+ "gaps": {
165
+ "type": "object",
166
+ "additionalProperties": false,
167
+ "required": ["componentGlob"],
168
+ "description": "Lists component files that no story covers in gaps.json.",
169
+ "properties": {
170
+ "sourceRoot": {
171
+ "type": "string",
172
+ "minLength": 1,
173
+ "description": "The folder to scan. Default `root`."
174
+ },
175
+ "componentGlob": {
176
+ "type": "string",
177
+ "minLength": 1,
178
+ "description": "Component files, as a glob relative to `root`, such as \"src/**/[A-Z]*.tsx\". Stories, tests and specs never count."
179
+ }
180
+ }
181
+ }
182
+ }
183
+ },
184
+ "slots": {
185
+ "type": "object",
186
+ "required": ["main"],
187
+ "description": "Replaceable regions, by name: the first selector that matches a visible, non-empty element marks it with data-kit-slot. `main` is required.",
188
+ "properties": {
189
+ "main": { "$ref": "#/$defs/selectors" }
190
+ },
191
+ "additionalProperties": { "$ref": "#/$defs/selectors" }
192
+ },
193
+ "selectors": {
194
+ "type": "array",
195
+ "minItems": 1,
196
+ "items": { "type": "string", "minLength": 1 }
197
+ },
198
+ "page": {
199
+ "type": "object",
200
+ "additionalProperties": false,
201
+ "required": ["name", "path", "ready", "slots"],
202
+ "properties": {
203
+ "name": {
204
+ "type": "string",
205
+ "pattern": "^[a-z0-9][a-z0-9-]*$",
206
+ "description": "The page's id in the kit: route:<name>@<width>."
207
+ },
208
+ "title": { "type": "string", "minLength": 1 },
209
+ "path": {
210
+ "type": "string",
211
+ "pattern": "^/",
212
+ "description": "The URL path, from baseUrl."
213
+ },
214
+ "ready": {
215
+ "type": "string",
216
+ "minLength": 1,
217
+ "description": "A Playwright selector that is visible once the page has rendered its content."
218
+ },
219
+ "sourceFile": {
220
+ "type": "string",
221
+ "minLength": 1,
222
+ "description": "The page's source module, shown by `kit show` and searched by `kit find`."
223
+ },
224
+ "slots": { "$ref": "#/$defs/slots" },
225
+ "layoutSlots": {
226
+ "type": "object",
227
+ "description": "Slots for a layout the detector reports, replacing `slots` there.",
228
+ "additionalProperties": { "$ref": "#/$defs/slots" }
229
+ }
230
+ }
231
+ },
232
+ "pages": {
233
+ "type": "object",
234
+ "additionalProperties": false,
235
+ "required": ["pages"],
236
+ "description": "`design-kit capture pages`: real pages of the running app, through Playwright.",
237
+ "properties": {
238
+ "baseUrl": {
239
+ "type": "string",
240
+ "pattern": "^https?://",
241
+ "description": "The running app. Without it, `startCommand` starts the app and `waitUrl` says when it serves."
242
+ },
243
+ "startCommand": {
244
+ "type": "string",
245
+ "minLength": 1,
246
+ "description": "Starts the app (run in `cwd`); it is stopped when the capture ends."
247
+ },
248
+ "waitUrl": {
249
+ "type": "string",
250
+ "pattern": "^https?://",
251
+ "description": "Polled until it answers; its origin is the base URL unless baseUrl is set."
252
+ },
253
+ "cwd": {
254
+ "type": "string",
255
+ "minLength": 1,
256
+ "description": "Where `startCommand` runs. Default the config's folder."
257
+ },
258
+ "startTimeoutMs": {
259
+ "type": "integer",
260
+ "minimum": 1,
261
+ "description": "How long `waitUrl` may take to answer. Default 120000."
262
+ },
263
+ "auth": {
264
+ "type": "object",
265
+ "additionalProperties": false,
266
+ "description": "Signs the browser in: a Playwright storageState file, or a setup module whose default export returns one.",
267
+ "properties": {
268
+ "storageState": { "type": "string", "minLength": 1 },
269
+ "setup": { "type": "string", "minLength": 1 }
270
+ }
271
+ },
272
+ "hooks": {
273
+ "type": "string",
274
+ "minLength": 1,
275
+ "description": "A module exporting optional `context(context, info)` and `page(page, info)` hooks."
276
+ },
277
+ "widths": {
278
+ "type": "array",
279
+ "minItems": 1,
280
+ "items": { "$ref": "#/$defs/size" },
281
+ "description": "Every page is captured at each size. Default 1280x800 and 390x844."
282
+ },
283
+ "fixedTime": {
284
+ "type": "string",
285
+ "minLength": 1,
286
+ "description": "The instant every page reads from Date (ISO 8601). Default 2026-01-01T12:00:00.000Z."
287
+ },
288
+ "shellReady": {
289
+ "type": "string",
290
+ "minLength": 1,
291
+ "description": "A selector the app's frame shows before any page content."
292
+ },
293
+ "layout": {
294
+ "type": "object",
295
+ "additionalProperties": false,
296
+ "required": ["selector", "attribute"],
297
+ "description": "Reads the layout the app chose (an attribute of one element), which picks `layoutSlots`.",
298
+ "properties": {
299
+ "selector": { "type": "string", "minLength": 1 },
300
+ "attribute": { "type": "string", "minLength": 1 },
301
+ "default": {
302
+ "type": "string",
303
+ "minLength": 1,
304
+ "description": "The layout when the attribute is absent. Default \"default\"."
305
+ }
306
+ }
307
+ },
308
+ "fontsPath": {
309
+ "type": "string",
310
+ "pattern": "^/",
311
+ "description": "The app's font URL path, such as \"/fonts/\". Without it, any .woff2/.woff/.ttf/.otf URL is a font."
312
+ },
313
+ "copyFonts": {
314
+ "type": "boolean",
315
+ "description": "Copy the fonts the pages load into the kit's fonts folder. Default true."
316
+ },
317
+ "timeoutMs": {
318
+ "type": "integer",
319
+ "minimum": 1,
320
+ "description": "How long a page may take to show its ready selector. Default 30000."
321
+ },
322
+ "forbidStrings": {
323
+ "type": "array",
324
+ "items": { "type": "string", "minLength": 1 },
325
+ "description": "Values that must never reach the kit, such as a test account's token. A page whose output holds one, as written or escaped, is not written."
326
+ },
327
+ "pages": {
328
+ "type": "array",
329
+ "minItems": 1,
330
+ "items": { "$ref": "#/$defs/page" }
331
+ }
332
+ }
333
+ }
334
+ }
335
+ }