dekit-cli 0.4.2 → 0.4.3

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.
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Dekit Editor</title>
7
- <script type="module" crossorigin src="/assets/index-D16Bm-Rw.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-hySfUCIY.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-HLKs1tz-.css">
9
9
  </head>
10
10
  <body>
package/docs/usage.md CHANGED
@@ -132,6 +132,7 @@ Every page and component must be registered:
132
132
 
133
133
  ```yaml
134
134
  version: 1.0
135
+ device: "iPhone 16"
135
136
  global-style: "global.css"
136
137
 
137
138
  components:
@@ -143,6 +144,13 @@ pages:
143
144
  home:
144
145
  template: "pages/home/home.html"
145
146
  style: "pages/home/home.css"
147
+ properties:
148
+ showBanner:
149
+ type: boolean
150
+ default: true
151
+ itemCount:
152
+ type: number
153
+ default: 3
146
154
  about:
147
155
  template: "pages/about/about.html"
148
156
  style: "pages/about/about.css"
@@ -150,6 +158,44 @@ pages:
150
158
 
151
159
  All paths are relative to the `.dekit/` directory.
152
160
 
161
+ ### device
162
+
163
+ Sets the target device for the project. Affects screenshot default viewport and editor initial device.
164
+
165
+ Accepts a preset name (e.g. `"iPhone 16"`, `"iPad Air"`, `"Desktop 1440"`) or custom dimensions:
166
+
167
+ ```yaml
168
+ device:
169
+ width: 400
170
+ height: 800
171
+ dpr: 2
172
+ ```
173
+
174
+ When omitted, screenshots default to 1280×800.
175
+
176
+ ### Page Properties
177
+
178
+ Pages can declare properties — typed parameters with defaults. Properties are injected as `window.__DEKIT_PROPS__` in the page, so your HTML/JS can read them to control rendering.
179
+
180
+ **Supported types:** `boolean`, `number`, `string`.
181
+
182
+ **Using properties in page HTML:**
183
+ ```html
184
+ <section class="banner">...</section>
185
+ <script>
186
+ if (!window.__DEKIT_PROPS__.showBanner) {
187
+ document.querySelector('.banner').remove();
188
+ }
189
+ </script>
190
+ ```
191
+
192
+ **Override properties in screenshots** with `--props`:
193
+ ```bash
194
+ dekit screenshot '$${home}' --props showBanner=false --props itemCount=5
195
+ ```
196
+
197
+ Values are converted based on the declared type (not guessed). Unspecified props use their default.
198
+
153
199
  ## CLI Reference
154
200
 
155
201
  ### dekit init [path] [--template name]
@@ -162,6 +208,7 @@ Templates: `blank`, `landing`, `dashboard`, `mobile`.
162
208
  dekit init # .dekit/ in current directory
163
209
  dekit init my-project # my-project/.dekit/
164
210
  dekit init --template landing # With template
211
+ dekit init --template mobile --device "iPhone 16" # With device
165
212
  dekit init --template # List available templates
166
213
  ```
167
214
 
@@ -202,6 +249,7 @@ dekit screenshot '$${home@.hero}' # Element only
202
249
  dekit screenshot '$${home}' --device iphone-14 # Device size
203
250
  dekit screenshot '$${home}' --full-page # Include scroll area
204
251
  dekit screenshot '$${home}' -o output.png # Custom output path
252
+ dekit screenshot '$${home}' --props showBanner=false --props itemCount=5 # Override properties
205
253
  dekit screenshot --all # All pages
206
254
  ```
207
255
 
@@ -231,19 +279,160 @@ When you see a `$${...}` pattern in user messages:
231
279
 
232
280
  You don't need to parse refs yourself — just pass them to the CLI.
233
281
 
282
+ ## Best Practices
283
+
284
+ ### 1. Start with DESIGN.md
285
+
286
+ Before writing any HTML/CSS, read `.dekit/DESIGN.md`. This is the design system source of truth for the project.
287
+
288
+ - **If DESIGN.md exists** — read it and follow the design specifications (colors, typography, components, spacing) when building pages.
289
+ - **If DESIGN.md does not exist** — generate one based on the current design task requirements before writing any code. Infer the visual style, color palette, and layout rules from the user's request, then write `.dekit/DESIGN.md`.
290
+ - **If the user's request conflicts with DESIGN.md** — update DESIGN.md first to reflect the new requirements, then implement the changes. DESIGN.md is always the single source of truth; code follows the spec, not the other way around.
291
+
292
+ The recommended sections:
293
+
294
+ ```markdown
295
+ # DESIGN.md
296
+
297
+ ## 1. Visual Theme & Atmosphere
298
+ Clean, minimal SaaS aesthetic with generous white space. Light and airy,
299
+ professional but approachable. Rounded geometry, subtle shadows, no hard borders.
300
+
301
+ ## 2. Color Palette & Roles
302
+ - **Royal Blue** (`#2563eb`): Primary brand, CTA backgrounds, active states
303
+ - **Amber** (`#f59e0b`): Accent, highlights, badges
304
+ - **Slate 900** (`#0f172a`): Headings, primary text
305
+ - **Slate 500** (`#64748b`): Body text, descriptions
306
+ - **Slate 100** (`#f1f5f9`): Surface backgrounds, cards
307
+ - **White** (`#ffffff`): Page background
308
+ - **Red 500** (`#ef4444`): Error states, destructive actions
309
+ - **Green 500** (`#22c55e`): Success states, confirmations
310
+
311
+ ## 3. Typography Rules
312
+ - Font: system-ui, -apple-system, sans-serif
313
+ - Headings: weight 700, tight line-height (1.1-1.2)
314
+ - Body: weight 400, relaxed line-height (1.5-1.6), 16px base
315
+ - Small/labels: weight 500, 12-14px, uppercase tracking for overlines
316
+
317
+ ## 4. Component Stylings
318
+ - **Buttons**: 8px radius, 12px 24px padding. Primary: blue bg + white text.
319
+ Outline: transparent bg + blue border + blue text. Hover darkens 10%.
320
+ - **Cards**: white bg, 12px radius, 1px solid #e2e8f0 border,
321
+ shadow 0 1px 3px rgba(0,0,0,0.1). No header stripe.
322
+ - **Inputs**: 8px radius, 1px #d1d5db border, 10px 14px padding.
323
+ Focus: blue border + blue ring shadow.
324
+
325
+ ## 5. Layout & Spacing Principles
326
+ - 4px base grid. Spacing scale: 4, 8, 16, 24, 32, 48, 64, 96.
327
+ - Page max-width: 1200px, centered.
328
+ - Section vertical padding: 64-96px.
329
+ - Card grid: 16-24px gap.
330
+
331
+ ## 6. Pages
332
+ - **home** — hero with CTA, features grid (3 cols), pricing cards, footer
333
+ - **dashboard** — sidebar nav, stats bar, task list with filters
334
+ ```
335
+
336
+ Write DESIGN.md first, then derive `global.css` tokens from it. Update the document as the design evolves.
337
+
338
+ ### 2. Derive global.css from DESIGN.md
339
+
340
+ Translate the colors, typography, and spacing from DESIGN.md into CSS custom properties in `global.css`:
341
+
342
+ ```css
343
+ :root {
344
+ /* From §2 Color Palette */
345
+ --color-primary: #2563eb;
346
+ --color-accent: #f59e0b;
347
+ --color-text: #0f172a;
348
+ --color-muted: #64748b;
349
+ --color-bg: #ffffff;
350
+ --color-surface: #f1f5f9;
351
+ --color-border: #e2e8f0;
352
+ --color-error: #ef4444;
353
+ --color-success: #22c55e;
354
+
355
+ /* From §4 Component Stylings */
356
+ --radius-sm: 4px;
357
+ --radius-md: 8px;
358
+ --radius-lg: 12px;
359
+
360
+ /* From §5 Layout & Spacing */
361
+ --space-xs: 4px;
362
+ --space-sm: 8px;
363
+ --space-md: 16px;
364
+ --space-lg: 24px;
365
+ --space-xl: 48px;
366
+ }
367
+ ```
368
+
369
+ Pages should reference these variables (`var(--color-primary)`) instead of hardcoding values. This ensures consistency and makes global changes a single-file edit.
370
+
371
+ ### 3. Build section by section, screenshot after each
372
+
373
+ Don't write an entire page before checking the result. Build incrementally:
374
+
375
+ ```
376
+ 1. Write the hero section HTML + CSS
377
+ 2. dekit screenshot '$${home@.hero}' # Verify just the hero
378
+ 3. Write the features grid
379
+ 4. dekit screenshot '$${home}' # Verify overall layout
380
+ 5. Continue section by section
381
+ ```
382
+
383
+ Catching issues early avoids cascading problems. A broken layout in the hero affects everything below it.
384
+
385
+ ### 4. Use properties for state variants
386
+
387
+ When a page has multiple states (empty, loading, error, different data counts), use properties instead of duplicating pages:
388
+
389
+ ```yaml
390
+ pages:
391
+ dashboard:
392
+ template: pages/dashboard/dashboard.html
393
+ style: pages/dashboard/dashboard.css
394
+ properties:
395
+ taskCount:
396
+ type: number
397
+ default: 5
398
+ showEmptyState:
399
+ type: boolean
400
+ default: false
401
+ ```
402
+
403
+ Then screenshot each variant:
404
+
405
+ ```bash
406
+ dekit screenshot '$${dashboard}' # Normal state
407
+ dekit screenshot '$${dashboard}' --props showEmptyState=true # Empty state
408
+ dekit screenshot '$${dashboard}' --props taskCount=0 # Zero items
409
+ dekit screenshot '$${dashboard}' --props taskCount=100 # Many items
410
+ ```
411
+
412
+ ### 5. Extract repeated patterns into components
413
+
414
+ When you see the same HTML structure appear twice or more, extract it into a Web Component:
415
+
416
+ ```bash
417
+ dekit add component ui-card
418
+ ```
419
+
420
+ Move the shared markup into the component's `<template>` with `<slot>` for variable content. This keeps pages DRY and makes global updates to the pattern a single-file change.
421
+
234
422
  ## Design Workflow
235
423
 
236
424
  ### Self-directed design loop
237
425
 
238
426
  ```
239
427
  1. dekit init --template landing # Start project
240
- 2. Edit .dekit/pages/home/home.html # Write HTML
241
- 3. Edit .dekit/pages/home/home.css # Write CSS
242
- 4. dekit screenshot '$${home}' # Take screenshot
243
- 5. Read the screenshot file # Analyze the result
244
- 6. Edit HTML/CSS to fix issues # Iterate
245
- 7. Repeat 4-6 until satisfied # Keep improving
246
- 8. dekit serve # Let human review
428
+ 2. Read .dekit/DESIGN.md or generate one # Design system first
429
+ 3. Derive design tokens in global.css # From DESIGN.md
430
+ 4. Build page section by section # Incremental progress
431
+ 5. dekit screenshot '$${home}' # Verify after each section
432
+ 6. Read the screenshot, iterate # Fix issues early
433
+ 7. Extract components when patterns repeat
434
+ 8. Screenshot all property variants # Cover edge cases
435
+ 9. dekit serve # Let human review
247
436
  ```
248
437
 
249
438
  ### Human feedback loop
@@ -56,6 +56,14 @@ export function App() {
56
56
  if (data.pages.length > 0) {
57
57
  setCurrentPage(data.pages[0]);
58
58
  }
59
+ if (data.device) {
60
+ const preset = DEVICE_PRESETS.find(
61
+ (d) => d.name === data.device!.name
62
+ );
63
+ setDeviceMode(preset ? preset.name : "custom");
64
+ setDeviceWidth(data.device.width);
65
+ setDeviceHeight(data.device.height);
66
+ }
59
67
  });
60
68
  }, []);
61
69
 
@@ -14,10 +14,18 @@ export interface DOMTreeNode {
14
14
  children: DOMTreeNode[];
15
15
  }
16
16
 
17
+ export interface DeviceConfig {
18
+ width: number;
19
+ height: number;
20
+ dpr: number;
21
+ name?: string;
22
+ }
23
+
17
24
  export interface EditorConfig {
18
25
  pages: string[];
19
26
  pagePaths: Record<string, string>;
20
27
  components: string[];
28
+ device: DeviceConfig | null;
21
29
  designServerUrl: string;
22
30
  }
23
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dekit-cli",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "type": "module",
5
5
  "description": "A code-first design toolkit for agents and humans — create, preview, and screenshot HTML/CSS designs",
6
6
  "keywords": [
@@ -48,6 +48,28 @@ describe("runInit", () => {
48
48
  await expect(runInit([tempDir])).rejects.toThrow(/already exists/i);
49
49
  });
50
50
 
51
+ test("mobile template includes device by default", async () => {
52
+ await runInit([tempDir, "--template", "mobile"]);
53
+
54
+ const content = readFileSync(join(tempDir, ".dekit/dekit.yaml"), "utf-8");
55
+ expect(content).toContain("iPhone 16");
56
+ });
57
+
58
+ test("--device flag writes device to dekit.yaml", async () => {
59
+ await runInit([tempDir, "--device", "iPad Air"]);
60
+
61
+ const content = readFileSync(join(tempDir, ".dekit/dekit.yaml"), "utf-8");
62
+ expect(content).toContain("iPad Air");
63
+ });
64
+
65
+ test("--device flag overrides template default device", async () => {
66
+ await runInit([tempDir, "--template", "mobile", "--device", "Pixel 8"]);
67
+
68
+ const content = readFileSync(join(tempDir, ".dekit/dekit.yaml"), "utf-8");
69
+ expect(content).toContain("Pixel 8");
70
+ expect(content).not.toContain("iPhone 16");
71
+ });
72
+
51
73
  test("lists templates when --template given without value", async () => {
52
74
  const consoleSpy = vi.spyOn(console, "log");
53
75
  await runInit(["--template"]);
package/src/cli/init.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { existsSync, mkdirSync, cpSync } from "node:fs";
1
+ import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { resolve, join } from "node:path";
3
+ import yaml from "js-yaml";
3
4
 
4
5
  const TEMPLATES_DIR = join(import.meta.dirname, "../../templates/projects");
5
6
 
@@ -8,6 +9,7 @@ const AVAILABLE_TEMPLATES = ["blank", "landing", "dashboard", "mobile"];
8
9
  export async function runInit(args: string[]) {
9
10
  let targetDir = process.cwd();
10
11
  let template = "blank";
12
+ let deviceArg: string | undefined;
11
13
 
12
14
  for (let i = 0; i < args.length; i++) {
13
15
  const arg = args[i];
@@ -19,6 +21,8 @@ export async function runInit(args: string[]) {
19
21
  }
20
22
  template = next;
21
23
  i++;
24
+ } else if (arg === "--device") {
25
+ deviceArg = args[++i];
22
26
  } else if (!arg.startsWith("-")) {
23
27
  targetDir = resolve(arg);
24
28
  }
@@ -44,5 +48,12 @@ export async function runInit(args: string[]) {
44
48
  mkdirSync(dekitDir, { recursive: true });
45
49
  cpSync(templateDir, dekitDir, { recursive: true });
46
50
 
51
+ if (deviceArg) {
52
+ const yamlPath = join(dekitDir, "dekit.yaml");
53
+ const doc = yaml.load(readFileSync(yamlPath, "utf-8")) as Record<string, unknown>;
54
+ doc.device = deviceArg;
55
+ writeFileSync(yamlPath, yaml.dump(doc, { quotingType: '"', forceQuotes: false }));
56
+ }
57
+
47
58
  console.log(`Created dekit project in ${dekitDir} (template: ${template})`);
48
59
  }
@@ -19,6 +19,7 @@ function parseArgs(args: string[]): {
19
19
  width?: number;
20
20
  height?: number;
21
21
  fullPage: boolean;
22
+ props: Record<string, string>;
22
23
  } {
23
24
  let ref: string | undefined;
24
25
  let outputPath: string | undefined;
@@ -26,6 +27,7 @@ function parseArgs(args: string[]): {
26
27
  let width: number | undefined;
27
28
  let height: number | undefined;
28
29
  let fullPage = false;
30
+ const props: Record<string, string> = {};
29
31
 
30
32
  for (let i = 0; i < args.length; i++) {
31
33
  const arg = args[i];
@@ -39,6 +41,13 @@ function parseArgs(args: string[]): {
39
41
  height = parseInt(args[++i], 10);
40
42
  } else if (arg === "--full-page") {
41
43
  fullPage = true;
44
+ } else if (arg === "--props") {
45
+ const kv = args[++i];
46
+ const eqIdx = kv.indexOf("=");
47
+ if (eqIdx === -1) {
48
+ throw new Error(`Invalid --props format: "${kv}". Expected key=value`);
49
+ }
50
+ props[kv.slice(0, eqIdx)] = kv.slice(eqIdx + 1);
42
51
  } else if (!arg.startsWith("-")) {
43
52
  ref = arg;
44
53
  }
@@ -48,19 +57,19 @@ function parseArgs(args: string[]): {
48
57
  throw new Error("Missing ref argument. Expected format: $${page} or $${page@selector}");
49
58
  }
50
59
 
51
- return { ref, outputPath, deviceName, width, height, fullPage };
60
+ return { ref, outputPath, deviceName, width, height, fullPage, props };
52
61
  }
53
62
 
54
63
  export async function runScreenshot(config: DesignConfig, args: string[]) {
55
- const { ref, outputPath, deviceName, width, height, fullPage } = parseArgs(args);
64
+ const { ref, outputPath, deviceName, width, height, fullPage, props } = parseArgs(args);
56
65
 
57
66
  // Parse and validate the ref (throws on invalid format)
58
67
  const parsedRef = parseRef(ref);
59
68
 
60
- // Resolve viewport from --device or --width/--height flags
61
- let viewportWidth = 1280;
62
- let viewportHeight = 800;
63
- let devicePixelRatio = 1;
69
+ // Resolve viewport from --device or --width/--height flags, falling back to config.device
70
+ let viewportWidth = config.device?.width ?? 1280;
71
+ let viewportHeight = config.device?.height ?? 800;
72
+ let devicePixelRatio = config.device?.dpr ?? 1;
64
73
 
65
74
  if (deviceName) {
66
75
  const normalizedInput = deviceName.toLowerCase().replace(/\s+/g, "-");
@@ -131,7 +140,12 @@ export async function runScreenshot(config: DesignConfig, args: string[]) {
131
140
  });
132
141
 
133
142
  const pageKey = parsedRef.pageKey;
134
- const url = `http://localhost:${port}/page/${pageKey}?noinspector`;
143
+ const urlObj = new URL(`http://localhost:${port}/page/${pageKey}`);
144
+ urlObj.searchParams.set("noinspector", "");
145
+ for (const [key, value] of Object.entries(props)) {
146
+ urlObj.searchParams.set(`props.${key}`, value);
147
+ }
148
+ const url = urlObj.toString();
135
149
  await page.goto(url, { waitUntil: "networkidle0" });
136
150
 
137
151
  if (parsedRef.type === "element") {
@@ -20,10 +20,18 @@ export async function startEditorServer(
20
20
  for (const [name, def] of Object.entries(config.pages)) {
21
21
  pagePaths[name] = join(config.baseDir, def.template);
22
22
  }
23
+ const pageProperties: Record<string, Record<string, { type: string; default: unknown }>> = {};
24
+ for (const [name, def] of Object.entries(config.pages)) {
25
+ if (def.properties) {
26
+ pageProperties[name] = def.properties;
27
+ }
28
+ }
23
29
  const payload = {
24
30
  pages: Object.keys(config.pages),
25
31
  pagePaths,
32
+ pageProperties,
26
33
  components: Object.keys(config.components),
34
+ device: config.device ?? null,
27
35
  designServerUrl: `http://localhost:${designPort}`,
28
36
  };
29
37
  res.setHeader("Content-Type", "application/json");
@@ -55,6 +55,170 @@ pages:
55
55
  ).rejects.toThrow(/not found|does not exist/i);
56
56
  });
57
57
 
58
+ test("parses device as preset name string", async () => {
59
+ const yaml = `
60
+ version: 1.0
61
+ device: "iPhone 16"
62
+ pages: {}
63
+ `;
64
+ const config = await parseDesignConfig("/fake/dekit.yaml", yaml);
65
+ expect(config.device).toEqual({
66
+ width: 393,
67
+ height: 852,
68
+ dpr: 3,
69
+ name: "iPhone 16",
70
+ });
71
+ });
72
+
73
+ test("parses device as custom dimensions object", async () => {
74
+ const yaml = `
75
+ version: 1.0
76
+ device:
77
+ width: 400
78
+ height: 800
79
+ dpr: 2
80
+ pages: {}
81
+ `;
82
+ const config = await parseDesignConfig("/fake/dekit.yaml", yaml);
83
+ expect(config.device).toEqual({
84
+ width: 400,
85
+ height: 800,
86
+ dpr: 2,
87
+ });
88
+ });
89
+
90
+ test("device defaults dpr to 1 when omitted", async () => {
91
+ const yaml = `
92
+ version: 1.0
93
+ device:
94
+ width: 400
95
+ height: 800
96
+ pages: {}
97
+ `;
98
+ const config = await parseDesignConfig("/fake/dekit.yaml", yaml);
99
+ expect(config.device!.dpr).toBe(1);
100
+ });
101
+
102
+ test("device is undefined when not specified", async () => {
103
+ const yaml = `
104
+ version: 1.0
105
+ pages: {}
106
+ `;
107
+ const config = await parseDesignConfig("/fake/dekit.yaml", yaml);
108
+ expect(config.device).toBeUndefined();
109
+ });
110
+
111
+ test("rejects unknown device preset name", async () => {
112
+ const yaml = `
113
+ version: 1.0
114
+ device: "Nokia 3310"
115
+ pages: {}
116
+ `;
117
+ await expect(
118
+ parseDesignConfig("/fake/dekit.yaml", yaml)
119
+ ).rejects.toThrow(/Unknown device/i);
120
+ });
121
+
122
+ test("rejects device object without width/height", async () => {
123
+ const yaml = `
124
+ version: 1.0
125
+ device:
126
+ dpr: 2
127
+ pages: {}
128
+ `;
129
+ await expect(
130
+ parseDesignConfig("/fake/dekit.yaml", yaml)
131
+ ).rejects.toThrow(/width.*height/i);
132
+ });
133
+
134
+ test("parses page properties", async () => {
135
+ const yaml = `
136
+ version: 1.0
137
+ pages:
138
+ home:
139
+ template: "pages/home/home.html"
140
+ style: "pages/home/home.css"
141
+ properties:
142
+ showBanner:
143
+ type: boolean
144
+ default: true
145
+ itemCount:
146
+ type: number
147
+ default: 3
148
+ title:
149
+ type: string
150
+ default: "Hello"
151
+ `;
152
+ const config = await parseDesignConfig("/fake/dekit.yaml", yaml);
153
+ expect(config.pages["home"].properties).toEqual({
154
+ showBanner: { type: "boolean", default: true },
155
+ itemCount: { type: "number", default: 3 },
156
+ title: { type: "string", default: "Hello" },
157
+ });
158
+ });
159
+
160
+ test("page without properties has undefined properties", async () => {
161
+ const yaml = `
162
+ version: 1.0
163
+ pages:
164
+ home:
165
+ template: "pages/home/home.html"
166
+ style: "pages/home/home.css"
167
+ `;
168
+ const config = await parseDesignConfig("/fake/dekit.yaml", yaml);
169
+ expect(config.pages["home"].properties).toBeUndefined();
170
+ });
171
+
172
+ test("rejects invalid property type", async () => {
173
+ const yaml = `
174
+ version: 1.0
175
+ pages:
176
+ home:
177
+ template: "pages/home/home.html"
178
+ style: "pages/home/home.css"
179
+ properties:
180
+ color:
181
+ type: color
182
+ default: red
183
+ `;
184
+ await expect(
185
+ parseDesignConfig("/fake/dekit.yaml", yaml)
186
+ ).rejects.toThrow(/Invalid type.*color/i);
187
+ });
188
+
189
+ test("rejects property with mismatched default type", async () => {
190
+ const yaml = `
191
+ version: 1.0
192
+ pages:
193
+ home:
194
+ template: "pages/home/home.html"
195
+ style: "pages/home/home.css"
196
+ properties:
197
+ count:
198
+ type: number
199
+ default: "not a number"
200
+ `;
201
+ await expect(
202
+ parseDesignConfig("/fake/dekit.yaml", yaml)
203
+ ).rejects.toThrow(/must be a number/i);
204
+ });
205
+
206
+ test("rejects property missing default", async () => {
207
+ const yaml = `
208
+ version: 1.0
209
+ pages:
210
+ home:
211
+ template: "pages/home/home.html"
212
+ style: "pages/home/home.css"
213
+ properties:
214
+ flag:
215
+ type: boolean
216
+ `;
217
+ await expect(
218
+ parseDesignConfig("/fake/dekit.yaml", yaml)
219
+ ).rejects.toThrow(/Missing default/i);
220
+ });
221
+
58
222
  test("rejects missing version field", async () => {
59
223
  const yaml = `
60
224
  global-style: "global.css"