openuispec 0.2.13 → 0.2.15

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 (63) hide show
  1. package/README.md +6 -5
  2. package/cli/index.ts +18 -12
  3. package/cli/init.ts +79 -13
  4. package/docs/cli.md +134 -27
  5. package/docs/file-formats.md +51 -1
  6. package/drift/index.ts +7 -2
  7. package/examples/social-app/openuispec/README.md +2 -1
  8. package/examples/social-app/openuispec/mock/chat_detail.yaml +25 -0
  9. package/examples/social-app/openuispec/mock/discover.yaml +17 -0
  10. package/examples/social-app/openuispec/mock/edit_profile.yaml +9 -0
  11. package/examples/social-app/openuispec/mock/home_feed.yaml +32 -0
  12. package/examples/social-app/openuispec/mock/messages_inbox.yaml +15 -0
  13. package/examples/social-app/openuispec/mock/notifications.yaml +30 -0
  14. package/examples/social-app/openuispec/mock/post_detail.yaml +26 -0
  15. package/examples/social-app/openuispec/mock/profile_self.yaml +28 -0
  16. package/examples/social-app/openuispec/mock/profile_user.yaml +32 -0
  17. package/examples/social-app/openuispec/mock/search_results.yaml +17 -0
  18. package/examples/social-app/openuispec/mock/settings.yaml +7 -0
  19. package/examples/social-app/openuispec/openuispec.yaml +3 -2
  20. package/examples/taskflow/README.md +4 -2
  21. package/examples/taskflow/openuispec/README.md +2 -1
  22. package/examples/taskflow/openuispec/components/media_player.yaml +92 -0
  23. package/examples/taskflow/openuispec/contracts/README.md +2 -2
  24. package/examples/taskflow/openuispec/locales/en.json +1 -0
  25. package/examples/taskflow/openuispec/mock/home.yaml +64 -0
  26. package/examples/taskflow/openuispec/mock/profile_edit.yaml +6 -0
  27. package/examples/taskflow/openuispec/mock/project_detail.yaml +33 -0
  28. package/examples/taskflow/openuispec/mock/settings.yaml +13 -0
  29. package/examples/taskflow/openuispec/mock/task_detail.yaml +18 -0
  30. package/examples/taskflow/openuispec/openuispec.yaml +3 -4
  31. package/examples/taskflow/openuispec/platform/ios.yaml +0 -4
  32. package/examples/taskflow/openuispec/screens/task_detail.yaml +5 -8
  33. package/examples/taskflow/openuispec/tokens/icons.yaml +16 -0
  34. package/examples/todo-orbit/README.md +3 -2
  35. package/examples/todo-orbit/openuispec/README.md +2 -1
  36. package/examples/todo-orbit/openuispec/components/task_trend_chart.yaml +85 -0
  37. package/examples/todo-orbit/openuispec/locales/en.json +3 -0
  38. package/examples/todo-orbit/openuispec/locales/ru.json +3 -0
  39. package/examples/todo-orbit/openuispec/mock/analytics.yaml +26 -0
  40. package/examples/todo-orbit/openuispec/mock/home.yaml +33 -0
  41. package/examples/todo-orbit/openuispec/mock/settings.yaml +7 -0
  42. package/examples/todo-orbit/openuispec/mock/task_detail.yaml +14 -0
  43. package/examples/todo-orbit/openuispec/openuispec.yaml +3 -3
  44. package/examples/todo-orbit/openuispec/platform/android.yaml +0 -3
  45. package/examples/todo-orbit/openuispec/platform/ios.yaml +0 -3
  46. package/examples/todo-orbit/openuispec/platform/web.yaml +0 -3
  47. package/examples/todo-orbit/openuispec/screens/analytics.yaml +1 -4
  48. package/mcp-server/index.ts +87 -6
  49. package/mcp-server/preview-render.ts +1922 -0
  50. package/mcp-server/preview.ts +292 -0
  51. package/mcp-server/screenshot-shared.ts +41 -4
  52. package/mcp-server/screenshot.ts +283 -97
  53. package/package.json +1 -1
  54. package/prepare/index.ts +1 -1
  55. package/schema/component.schema.json +278 -0
  56. package/schema/openuispec.schema.json +5 -1
  57. package/schema/screen.schema.json +12 -1
  58. package/schema/semantic-lint.ts +29 -5
  59. package/schema/validate.ts +21 -0
  60. package/scripts/regenerate-previews.ts +136 -0
  61. package/spec/{openuispec-v0.1.md → openuispec-v0.2.md} +266 -8
  62. package/examples/taskflow/openuispec/contracts/x_media_player.yaml +0 -185
  63. package/examples/todo-orbit/openuispec/contracts/x_task_trend_chart.yaml +0 -139
package/README.md CHANGED
@@ -47,7 +47,8 @@ This scaffolds a spec directory, starter tokens, and **configures the MCP server
47
47
 
48
48
  - **Tokens** — design values (color, typography, spacing, elevation, motion) with semantic names and constrained ranges
49
49
  - **Contracts** — 7 reusable UI component families defined by role, props, interaction states, and accessibility
50
- - **Screens** — compositions of contracts with data bindings, adaptive layout, and conditional rendering
50
+ - **Components** — reusable compositions of contracts with named slots, states, and variants
51
+ - **Screens** — compositions of contracts and components with data bindings, adaptive layout, and conditional rendering
51
52
  - **Flows** — multi-screen navigation journeys, intent-based and platform-agnostic
52
53
  - **Actions** — 13 typed action types with composition, error handling, and optimistic updates
53
54
  - **Data binding** — reactive state, format expressions, caching, and loading/error/empty states
@@ -76,7 +77,7 @@ openuispec init → configures MCP for your agent → AI calls tools automatical
76
77
 
77
78
  When you ask your AI to "add a settings page" or "update the home feed," the MCP server provides spec context before generation, feeds authoritative spec contents during generation, validates spec integrity after edits, and returns a spec-derived checklist for the AI to review the generated code against.
78
79
 
79
- 15 tools are available as both MCP tools and CLI commands — see the [full reference](./docs/cli.md).
80
+ 16 tools are available as both MCP tools and CLI commands — see the [full reference](./docs/cli.md).
80
81
 
81
82
  **Using without MCP?** You can provide spec context to any AI manually:
82
83
 
@@ -98,19 +99,19 @@ Screenshots of the generated apps are in the [artifacts](./artifacts/) directory
98
99
  |-----|-------------|
99
100
  | [CLI & MCP Tools](./docs/cli.md) | All CLI commands, MCP tools, screenshot params, target workflow |
100
101
  | [File Formats & Schemas](./docs/file-formats.md) | File types, JSON schemas, output directories, spec sections |
101
- | [Full Specification](./spec/openuispec-v0.1.md) | Complete v0.1 spec (14 sections) |
102
+ | [Full Specification](./spec/openuispec-v0.2.md) | Complete v0.2 spec (15 sections) |
102
103
  | [llms-full.txt](https://openuispec.rsteam.uz/llms-full.txt) | Spec + all schemas in one file (for AI consumption) |
103
104
 
104
105
  ## Status
105
106
 
106
- **v0.1 — Draft**. The spec covers all foundational layers with three example apps demonstrating generation across iOS, Android, and Web.
107
+ **v0.2 — Draft**. The spec covers all foundational layers — including component composition — with three example apps demonstrating generation across iOS, Android, and Web.
107
108
 
108
109
  ### Roadmap
109
110
 
110
111
  - [x] Token system, 7 contract families, adaptive layout, action system
111
112
  - [x] Data binding, i18n, form validation, custom contract extensions
112
113
  - [x] JSON Schema validation, CLI tool, MCP server
113
- - [x] Drift detection, visual verification (screenshots)
114
+ - [x] Drift detection, visual verification (screenshots, preview)
114
115
  - [x] Example apps: TaskFlow, Todo Orbit, Social App
115
116
  - [ ] More example apps (e-commerce, dashboard)
116
117
 
package/cli/index.ts CHANGED
@@ -18,6 +18,7 @@
18
18
  * openuispec read-specs [paths...] Read spec file contents
19
19
  * openuispec get-screen <name> Get a single screen spec
20
20
  * openuispec get-contract <name> [--variant v] Get a contract spec
21
+ * openuispec get-component <name> [--variant v] Get a component spec
21
22
  * openuispec get-tokens <category> Get tokens for a category
22
23
  * openuispec get-locale <locale> [--keys k1,k2] Get a locale file
23
24
  * openuispec spec-types List available spec types
@@ -119,6 +120,7 @@ const SCHEMA_CATALOG: Record<string, { file: string; title: string; description:
119
120
  platform: { file: "platform.schema.json", title: "Platform", description: "Platform-specific generation config" },
120
121
  contract: { file: "contract.schema.json", title: "Contract", description: "Built-in UI contract definitions" },
121
122
  "custom-contract":{ file: "custom-contract.schema.json", title: "Custom Contract", description: "User-defined UI contract definitions (x_ prefixed)" },
123
+ component: { file: "component.schema.json", title: "Component", description: "Reusable composition of contracts with named slots" },
122
124
  locale: { file: "locale.schema.json", title: "Locale", description: "Locale translation files" },
123
125
  "tokens/color": { file: "tokens/color.schema.json", title: "Color Tokens", description: "Color tokens" },
124
126
  "tokens/typography": { file: "tokens/typography.schema.json", title: "Typography Tokens", description: "Typography tokens" },
@@ -225,31 +227,34 @@ async function main(): Promise<void> {
225
227
  break;
226
228
  }
227
229
 
228
- case "get-contract": {
230
+ case "get-contract":
231
+ case "get-component": {
232
+ const specType = command === "get-contract" ? "contract" : "component";
233
+ const specDir = specType === "contract" ? "contracts" : "components";
229
234
  const name = rest[0];
230
- if (!name) { console.error("Usage: openuispec get-contract <name> [--variant v]"); process.exit(1); }
235
+ if (!name) { console.error(`Usage: openuispec get-${specType} <name> [--variant v]`); process.exit(1); }
231
236
  const variant = getOption(rest, "--variant");
232
237
  const { findProjectDir } = await import("../drift/index.js");
233
238
  const YAML = (await import("yaml")).default;
234
239
  const projectDir = findProjectDir(cwd);
235
240
  const manifest = YAML.parse(readFileSync(join(projectDir, "openuispec.yaml"), "utf-8"));
236
- const contractsDir = resolveSpecDir(projectDir, manifest, "contracts");
241
+ const dir = resolveSpecDir(projectDir, manifest, specDir);
237
242
 
238
- if (!existsSync(contractsDir)) { console.error(`Contracts directory not found: ${contractsDir}`); process.exit(1); }
243
+ if (!existsSync(dir)) { console.error(`${specDir} directory not found: ${dir}`); process.exit(1); }
239
244
 
240
245
  let found = false;
241
- for (const file of readdirSync(contractsDir).filter(f => f.endsWith(".yaml")).sort()) {
242
- const filePath = join(contractsDir, file);
246
+ for (const file of readdirSync(dir).filter(f => f.endsWith(".yaml")).sort()) {
247
+ const filePath = join(dir, file);
243
248
  const raw = readFileSync(filePath, "utf-8");
244
249
  const content = YAML.parse(raw);
245
- const contractName = Object.keys(content)[0];
246
- if (contractName !== name) continue;
250
+ const rootKey = Object.keys(content)[0];
251
+ if (rootKey !== name) continue;
247
252
  found = true;
248
253
 
249
254
  if (variant) {
250
- const variantDef = content[contractName]?.variants?.[variant];
255
+ const variantDef = content[rootKey]?.variants?.[variant];
251
256
  if (!variantDef) {
252
- const available = Object.keys(content[contractName]?.variants ?? {}).join(", ");
257
+ const available = Object.keys(content[rootKey]?.variants ?? {}).join(", ");
253
258
  console.error(`Variant "${variant}" not found. Available: ${available}`);
254
259
  process.exit(1);
255
260
  }
@@ -259,7 +264,7 @@ async function main(): Promise<void> {
259
264
  }
260
265
  break;
261
266
  }
262
- if (!found) { console.error(`Contract "${name}" not found in ${contractsDir}`); process.exit(1); }
267
+ if (!found) { console.error(`${specType} "${name}" not found in ${dir}`); process.exit(1); }
263
268
  break;
264
269
  }
265
270
 
@@ -471,6 +476,7 @@ Spec access:
471
476
  openuispec read-specs [paths...] Read spec file contents as JSON
472
477
  openuispec get-screen <name> Get a single screen spec (YAML)
473
478
  openuispec get-contract <name> [--variant v] Get a contract spec
479
+ openuispec get-component <name> [--variant v] Get a component spec
474
480
  openuispec get-tokens <category> Get tokens for a category (YAML)
475
481
  openuispec get-locale <locale> [--keys k1,k2] Get a locale file (JSON)
476
482
  openuispec spec-types List available spec types
@@ -493,7 +499,7 @@ Screenshots (batch — build once, capture many):
493
499
  Server:
494
500
  openuispec mcp Start MCP server (stdio transport)
495
501
 
496
- Validate groups: manifest, tokens, screens, flows, platform, locales, contracts, semantic
502
+ Validate groups: manifest, tokens, screens, flows, platform, locales, contracts, components, semantic
497
503
  Exit codes: 0 = success, 1 = missing config/usage error, 2 = validation failure
498
504
  Docs: https://openuispec.rsteam.uz
499
505
  `);
package/cli/init.ts CHANGED
@@ -232,8 +232,8 @@ function manifestTemplate(
232
232
  structureBlock = ` structure:\n${entries}\n`;
233
233
  }
234
234
 
235
- return `# ${name} — OpenUISpec v0.1
236
- spec_version: "0.1"
235
+ return `# ${name} — OpenUISpec v0.2
236
+ spec_version: "0.2"
237
237
 
238
238
  project:
239
239
  name: "${name}"
@@ -242,6 +242,7 @@ project:
242
242
  includes:
243
243
  tokens: "./tokens/"
244
244
  contracts: "./contracts/"
245
+ components: "./components/"
245
246
  screens: "./screens/"
246
247
  flows: "./flows/"
247
248
  platform: "./platform/"
@@ -290,6 +291,7 @@ This directory contains the **OpenUISpec** semantic UI specification for **${nam
290
291
  | \`screens/\` | Screen definitions — one YAML file per screen |
291
292
  | \`flows/\` | Navigation flows — multi-step user journeys |
292
293
  | \`contracts/\` | Component contracts — standard extensions and custom (\`x_\` prefixed) |
294
+ | \`components/\` | Reusable component compositions — contract slot compositions with states and variants |
293
295
  | \`platform/\` | Platform overrides — per-target (iOS, Android, Web) behaviors |
294
296
  | \`locales/\` | Localization — i18n strings (JSON, ICU MessageFormat) |
295
297
 
@@ -306,7 +308,7 @@ Do NOT guess the file format — skipping this step will produce invalid YAML th
306
308
 
307
309
  **Reference files inside the package (read in this order):**
308
310
  1. \`README.md\` — schema tables, file format reference, root wrapper keys
309
- 2. \`spec/openuispec-v0.1.md\` — full specification (contracts, layout, expressions, etc.)
311
+ 2. \`spec/openuispec-v0.2.md\` — full specification (contracts, layout, expressions, etc.)
310
312
  3. \`examples/taskflow/openuispec/\` — complete working example with all file types
311
313
  4. \`schema/\` — JSON Schemas for validation
312
314
 
@@ -432,6 +434,7 @@ Do not baseline on your own initiative — only run the snapshot when the user a
432
434
  **Focused getters (prefer these for incremental edits over \`read_specs\`):**
433
435
  - \`openuispec_get_screen(name)\` — single screen spec
434
436
  - \`openuispec_get_contract(name, variant?)\` — single contract, optionally one variant
437
+ - \`openuispec_get_component(name, variant?)\` — single component, optionally one variant
435
438
  - \`openuispec_get_tokens(category)\` — single token category (color, typography, spacing, etc.)
436
439
  - \`openuispec_get_locale(locale, keys?)\` — single locale file, optionally filtered keys
437
440
  - \`openuispec_check(target, audit?, screens?, contracts?)\` — validation + optional scoped audit checklist
@@ -446,27 +449,37 @@ Use \`read_specs\` for full-project generation; use focused getters when editing
446
449
  ### CLI fallback (when MCP is not available)
447
450
 
448
451
  If MCP tools are not available, use these CLI commands with \`--json\` flag:
449
- - \`openuispec prepare --target <t> --json\` — build AI-ready work bundle
450
- - \`openuispec check --target <t> --json\` — composite validation
452
+
453
+ **Status & discovery:**
451
454
  - \`openuispec status --json\` — cross-target status
452
- - \`openuispec drift --target <t> --explain --json\` — semantic drift
453
- - \`openuispec validate [group...] --json\` — schema validation
455
+ - \`openuispec spec-types\` list available spec types
456
+ - \`openuispec spec-schema <type>\`get JSON schema for a spec type
457
+
458
+ **Spec access:**
454
459
  - \`openuispec read-specs [paths...]\` — read spec file contents
455
460
  - \`openuispec get-screen <name>\` — get a single screen spec
456
461
  - \`openuispec get-contract <name> [--variant v]\` — get a contract spec
462
+ - \`openuispec get-component <name> [--variant v]\` — get a component spec
457
463
  - \`openuispec get-tokens <category>\` — get tokens for a category
458
464
  - \`openuispec get-locale <locale> [--keys k1,k2]\` — get a locale file
459
- - \`openuispec spec-types\` — list available spec types
460
- - \`openuispec spec-schema <type>\` — get JSON schema for a spec type
465
+
466
+ **Validation & generation workflow:**
467
+ - \`openuispec validate [group...] --json\` — validate spec files against JSON Schemas
468
+ - \`openuispec check --target <t> --json\` — validate spec files + check target generation readiness
469
+ - \`openuispec prepare --target <t> --json\` — build AI-ready work bundle
470
+ - \`openuispec drift --target <t> --explain --json\` — semantic drift
471
+
472
+ **Visual verification:**
461
473
  - \`openuispec screenshot --route /path\` — screenshot the web app
474
+ - \`openuispec screenshot --route /path --init-script "..."\` — inject auth/role before rendering (web only; app must implement \`__ous_init\` bootstrapper)
462
475
  - \`openuispec screenshot-android [--project-dir path]\` — screenshot Android app
463
476
  - \`openuispec screenshot-ios [--project-dir path]\` — screenshot iOS app
464
477
 
465
478
  ### Other CLI commands
466
479
  - \`openuispec init\` — scaffold a new spec project
467
- - \`openuispec drift --snapshot --target <t>\` — snapshot current state (user-initiated, after reviewing generated output)
468
480
  - \`openuispec configure-target <t>\` — configure target platform stack
469
481
  - \`openuispec update-rules\` — update AI rules to match installed package version
482
+ - \`openuispec drift --snapshot --target <t>\` — snapshot current state (user-initiated, after reviewing generated output)
470
483
 
471
484
  ## Spec format reference
472
485
 
@@ -478,13 +491,13 @@ You MUST read the reference files before creating or editing spec files — do N
478
491
 
479
492
  **Reference files (read in order):**
480
493
  1. \`README.md\` — schema tables, file format, root wrapper keys
481
- 2. \`spec/openuispec-v0.1.md\` — full specification
494
+ 2. \`spec/openuispec-v0.2.md\` — full specification
482
495
  3. \`examples/taskflow/openuispec/\` — complete working example
483
496
  4. \`schema/\` — JSON Schemas for every file type
484
497
 
485
498
  ## Spec location
486
499
  - Spec root: \`${specDir}/\` — read \`${specDir}/openuispec.yaml\` first for actual paths.
487
- - Default dirs: tokens/, screens/, flows/, contracts/, platform/, locales/
500
+ - Default dirs: tokens/, screens/, flows/, contracts/, components/, platform/, locales/
488
501
 
489
502
  ## When to start from spec vs platform code
490
503
 
@@ -500,7 +513,7 @@ You MUST read the reference files before creating or editing spec files — do N
500
513
 
501
514
  ## If spec directories are empty (first-time setup)
502
515
 
503
- Read \`spec/openuispec-v0.1.md\` from the package first, then:
516
+ Read \`spec/openuispec-v0.2.md\` from the package first, then:
504
517
  1. Scan codebase for UI screens → create \`${specDir}/screens/<name>.yaml\` as \`status: stub\`
505
518
  2. Extract tokens (colors, fonts, spacing) → \`${specDir}/tokens/\`
506
519
  3. Create contract extensions → \`${specDir}/contracts/\`
@@ -590,10 +603,62 @@ export function updateRules(): void {
590
603
  console.log(`\nAI rules updated to v${version}`);
591
604
  }
592
605
 
606
+ // Migrate old spec doc version references to current
607
+ migrateDocVersionRefs(cwd, specDir);
608
+
593
609
  // Ensure MCP server is configured
594
610
  configureMcp(cwd, true);
595
611
  }
596
612
 
613
+ // ── spec doc version migration ──────────────────────────────────────
614
+
615
+ function getCurrentSpecDocVersion(): string | null {
616
+ const pkgDir = dirname(fileURLToPath(import.meta.url));
617
+ const specDir = join(pkgDir, "..", "spec");
618
+ try {
619
+ const files = readdirSync(specDir);
620
+ const match = files
621
+ .map((f) => f.match(/^openuispec-v(.+)\.md$/))
622
+ .filter(Boolean)
623
+ .sort()
624
+ .pop();
625
+ return match ? match[1] : null;
626
+ } catch {
627
+ return null;
628
+ }
629
+ }
630
+
631
+ function migrateDocVersionRefs(cwd: string, specDir: string): void {
632
+ const currentVersion = getCurrentSpecDocVersion();
633
+ if (!currentVersion) return;
634
+
635
+ const currentRef = `openuispec-v${currentVersion}`;
636
+ const pattern = /openuispec-v(\d+\.\d+)/g;
637
+
638
+ const filesToCheck = [
639
+ join(cwd, specDir, "README.md"),
640
+ join(cwd, "CLAUDE.md"),
641
+ join(cwd, "AGENTS.md"),
642
+ ];
643
+
644
+ for (const filePath of filesToCheck) {
645
+ if (!existsSync(filePath)) continue;
646
+
647
+ const content = readFileSync(filePath, "utf-8");
648
+ const oldRefs = [...content.matchAll(pattern)]
649
+ .filter((m) => m[1] !== currentVersion);
650
+ if (oldRefs.length === 0) continue;
651
+
652
+ const migrated = content.replace(pattern, (_match, ver) =>
653
+ ver === currentVersion ? _match : currentRef
654
+ );
655
+ writeFileSync(filePath, migrated);
656
+ const relPath = relative(cwd, filePath);
657
+ const oldVersions = [...new Set(oldRefs.map((m) => m[1]))].join(", ");
658
+ console.log(` updated ${relPath} (migrated v${oldVersions} → v${currentVersion} doc references)`);
659
+ }
660
+ }
661
+
597
662
  // ── shared MCP config ───────────────────────────────────────────────
598
663
 
599
664
  const EXPECTED_MCP_CONFIG = {
@@ -1058,6 +1123,7 @@ export async function init(argv: string[] = []): Promise<void> {
1058
1123
  const dirs = [
1059
1124
  "tokens",
1060
1125
  "contracts",
1126
+ "components",
1061
1127
  "screens",
1062
1128
  "flows",
1063
1129
  "platform",
package/docs/cli.md CHANGED
@@ -38,32 +38,51 @@ Or run directly: `openuispec mcp`
38
38
 
39
39
  ## MCP Tools
40
40
 
41
- | Tool | When | What it does |
42
- |------|------|-------------|
43
- | `openuispec_spec_types` | Before creating spec files | Lists all available spec types with descriptions |
44
- | `openuispec_spec_schema` | Before creating/editing spec files | Returns JSON schema for a spec type. Optional `summary` for top-level overview |
45
- | `openuispec_prepare` | Before UI code generation | Returns spec context, platform config, constraints. Optional `include_specs` embeds all spec contents |
46
- | `openuispec_read_specs` | Before and after generation | Without `paths`: returns file listing. With `paths`: loads spec contents |
47
- | `openuispec_check` | After spec edits or generation | Spec validation (schema + semantic) + prepare readiness. `audit=true` returns a spec-derived checklist for manual code review |
48
- | `openuispec_validate` | After spec edits | Schema-only validation, optionally filtered by group |
49
- | `openuispec_drift` | Before updates / after generation | Detect drift, or `snapshot=true` to create/update baseline |
50
- | `openuispec_status` | Anytime | Cross-target summary: baselines, drift, next steps |
51
- | `openuispec_get_screen` | Incremental edits | Get a single screen spec by name |
52
- | `openuispec_get_contract` | Incremental edits | Get a single contract spec, optionally filtered to one variant |
53
- | `openuispec_get_tokens` | Incremental edits | Get tokens for a specific category |
54
- | `openuispec_get_locale` | Incremental edits | Get a single locale file, optionally filtered to specific keys |
55
- | `openuispec_screenshot` | Visual verification | Screenshot the web app at a route via headless browser |
56
- | `openuispec_screenshot_android` | Visual verification | Screenshot Android app on emulator. Works with any project via `project_dir` |
57
- | `openuispec_screenshot_ios` | Visual verification | Screenshot iOS app on Simulator via XCUITest. Works with any project via `project_dir` |
58
- | `openuispec_screenshot_web_batch` | Visual verification | Multiple web screenshots in one server session |
59
- | `openuispec_screenshot_android_batch` | Visual verification | Multiple Android screenshots in one build+install cycle |
60
- | `openuispec_screenshot_ios_batch` | Visual verification | Multiple iOS screenshots in one build+install cycle |
41
+ ### Status & discovery
42
+
43
+ | Tool | What it does |
44
+ |------|-------------|
45
+ | `openuispec_status` | Cross-target summary: baselines, drift, next steps |
46
+ | `openuispec_spec_types` | Lists all available spec types with descriptions |
47
+ | `openuispec_spec_schema` | Returns JSON schema for a spec type. Optional `summary` for top-level overview |
48
+
49
+ ### Spec access
50
+
51
+ | Tool | What it does |
52
+ |------|-------------|
53
+ | `openuispec_read_specs` | Without `paths`: returns file listing. With `paths`: loads spec contents |
54
+ | `openuispec_get_screen` | Get a single screen spec by name |
55
+ | `openuispec_get_contract` | Get a single contract spec, optionally filtered to one variant |
56
+ | `openuispec_get_component` | Get a single component spec, optionally filtered to one variant |
57
+ | `openuispec_get_tokens` | Get tokens for a specific category |
58
+ | `openuispec_get_locale` | Get a single locale file, optionally filtered to specific keys |
59
+
60
+ ### Validation & generation workflow
61
+
62
+ | Tool | What it does |
63
+ |------|-------------|
64
+ | `openuispec_validate` | Validate spec files against JSON Schemas, optionally filtered by group |
65
+ | `openuispec_check` | Validate spec files (schema + semantic) and check target generation readiness. `audit=true` returns a spec-derived review checklist |
66
+ | `openuispec_prepare` | Returns spec context, platform config, constraints. Optional `include_specs` embeds all spec contents |
67
+ | `openuispec_drift` | Detect drift, or `snapshot=true` to create/update baseline |
68
+
69
+ ### Visual verification
70
+
71
+ | Tool | What it does |
72
+ |------|-------------|
73
+ | `openuispec_preview` | Render a screen spec as HTML with mock data and return a screenshot — no app build needed. **Experimental**: visual approximation, not pixel-accurate |
74
+ | `openuispec_screenshot` | Screenshot the web app at a route via headless browser |
75
+ | `openuispec_screenshot_android` | Screenshot Android app on emulator. Works with any project via `project_dir` |
76
+ | `openuispec_screenshot_ios` | Screenshot iOS app on Simulator via XCUITest. Works with any project via `project_dir` |
77
+ | `openuispec_screenshot_web_batch` | Multiple web screenshots in one server session |
78
+ | `openuispec_screenshot_android_batch` | Multiple Android screenshots in one build+install cycle |
79
+ | `openuispec_screenshot_ios_batch` | Multiple iOS screenshots in one build+install cycle |
61
80
 
62
81
  The server includes **protocol-level instructions** that trigger on UI-related requests independently of CLAUDE.md rules.
63
82
 
64
83
  ## CLI Commands
65
84
 
66
- ### Workflow
85
+ ### Project setup
67
86
 
68
87
  ```bash
69
88
  openuispec init # Scaffold a new spec project
@@ -71,25 +90,36 @@ openuispec init --defaults # Non-interactive with unconfirmed de
71
90
  openuispec init --no-configure-targets # Skip target stack setup
72
91
  openuispec update-rules # Update AI rules to match installed version
73
92
  openuispec configure-target <t> [--defaults] # Configure target stack
74
- openuispec validate [group...] [--json] # Validate spec files
75
- openuispec validate semantic # Semantic cross-reference linting
93
+ ```
94
+
95
+ ### Validation
96
+
97
+ ```bash
98
+ openuispec validate [group...] [--json] # Validate spec files against JSON Schemas
99
+ openuispec validate semantic # Lint cross-references (locale keys, icons, contracts, tokens)
100
+ openuispec check --target <t> [--json] # Validate spec files + check target generation readiness
101
+ ```
102
+
103
+ ### Status & generation workflow
104
+
105
+ ```bash
76
106
  openuispec status [--json] # Cross-target baseline/drift status
77
- openuispec drift --target <t> --explain # Explain semantic spec drift
78
107
  openuispec prepare --target <t> [--json] # Build the target work bundle
79
- openuispec check --target <t> [--json] # Composite validation + prepare readiness
108
+ openuispec drift --target <t> --explain # Explain semantic spec drift
80
109
  openuispec drift --snapshot --target <t> # Snapshot current state + git baseline
81
110
  ```
82
111
 
83
112
  ### Spec access
84
113
 
85
114
  ```bash
115
+ openuispec spec-types # List available spec types
116
+ openuispec spec-schema <type> # Get JSON schema for a spec type
86
117
  openuispec read-specs [paths...] # Read spec file contents as JSON
87
118
  openuispec get-screen <name> # Get a single screen spec (YAML)
88
119
  openuispec get-contract <name> [--variant v] # Get a contract spec
120
+ openuispec get-component <name> [--variant v] # Get a component spec
89
121
  openuispec get-tokens <category> # Get tokens for a category (YAML)
90
122
  openuispec get-locale <locale> [--keys k1,k2] # Get a locale file (JSON)
91
- openuispec spec-types # List available spec types
92
- openuispec spec-schema <type> # Get JSON schema for a spec type
93
123
  ```
94
124
 
95
125
  ### Screenshots
@@ -145,6 +175,83 @@ Each capture supports:
145
175
  - `wait_for`: per-capture wait time in ms
146
176
  - `selector`: CSS selector to screenshot a specific element (web only)
147
177
  - `full_page`: capture full scrollable page (web only)
178
+ - `init_script`: JavaScript to execute before the page renders (web only — see below)
179
+
180
+ ### `init_script` — app-level initialization
181
+
182
+ `init_script` lets you inject auth, switch roles, or set up session state before a screenshot is taken — without Puppeteer executing JS directly. The tool base64-encodes the script and appends it as a `?__ous_init=<encoded>` query param. The generated app's bootstrapper reads and runs it before rendering.
183
+
184
+ **Why app-level instead of `evaluateOnNewDocument`:** the app can `await` login APIs, set framework state, or call any async init — Puppeteer's `evaluateOnNewDocument` is sync-only and has no access to app internals.
185
+
186
+ **Single capture (MCP):**
187
+
188
+ ```json
189
+ {
190
+ "route": "/dashboard",
191
+ "init_script": "window.__auth = { token: 'test-token', role: 'admin' };"
192
+ }
193
+ ```
194
+
195
+ **Batch capture (MCP):**
196
+
197
+ ```json
198
+ {
199
+ "output_dir": "screenshots",
200
+ "init_script": "window.__auth = { token: 'test-token', role: 'viewer' };",
201
+ "captures": [
202
+ { "screen": "dashboard", "route": "/dashboard" },
203
+ { "screen": "admin_panel", "route": "/admin",
204
+ "init_script": "window.__auth = { token: 'test-token', role: 'admin' };" }
205
+ ]
206
+ }
207
+ ```
208
+
209
+ Per-capture `init_script` overrides the shared one. If neither is set, no param is appended and the app renders normally.
210
+
211
+ **Bootstrapper contract** — the generated app must include a bootstrapper that:
212
+
213
+ 1. Checks for `__ous_init` in the URL query string on load
214
+ 2. Base64-decodes it (`atob`) and `eval`s it (or parses it as structured data)
215
+ 3. Runs **before** rendering authenticated content (can be async — app awaits it)
216
+ 4. Strips the param from URL/history after processing (`history.replaceState`)
217
+
218
+ Example bootstrapper (framework-agnostic):
219
+
220
+ ```js
221
+ const param = new URLSearchParams(location.search).get('__ous_init');
222
+ if (param) {
223
+ try { eval(atob(param)); } catch (e) { console.warn('[ous] init_script error', e); }
224
+ const url = new URL(location.href);
225
+ url.searchParams.delete('__ous_init');
226
+ history.replaceState(null, '', url.toString());
227
+ }
228
+ ```
229
+
230
+ This is a **contract between the tool and generated code** — the tool appends the param; the app consumes it.
231
+
232
+ ### Preview (experimental)
233
+
234
+ > **Note:** The preview renderer produces a _visual approximation_ of the spec — it maps contracts to semantic HTML+CSS and applies token values, but does not match the fidelity of a real generated app. Intended for **human inspection only** — AI agents should not use this tool for UI verification and should use `openuispec_screenshot` (or the batch/platform variants) against the real built app instead.
235
+
236
+ ```bash
237
+ # MCP tool (intended for human review, not AI verification)
238
+ openuispec_preview screen=home size_class=compact theme=light
239
+
240
+ # No CLI equivalent yet — preview is MCP-only.
241
+ ```
242
+
243
+ Parameters:
244
+
245
+ | Param | Default | Purpose |
246
+ |-------|---------|---------|
247
+ | `screen` | (required) | Screen name matching `screens/<name>.yaml` |
248
+ | `size_class` | `compact` | Adaptive breakpoint: `compact`, `regular`, `expanded` |
249
+ | `theme` | `light` | Color theme: `light` or `dark` |
250
+ | `locale` | `en` | Locale code for i18n string resolution |
251
+ | `viewport` | auto | Custom `{width, height}` — overrides `size_class` defaults |
252
+ | `include_html` | `false` | Also return the rendered HTML string |
253
+
254
+ Preview reads mock data from `openuispec/mock/<screen>.yaml` — see [File Formats](./file-formats.md) for the mock data convention.
148
255
 
149
256
  ## Target Update Workflow
150
257
 
@@ -12,7 +12,8 @@ Every file type has a corresponding JSON Schema in `schema/`. **Read the schema
12
12
  | `platform/*.yaml` | `platform.schema.json` | `platform` | [ios.yaml](../examples/taskflow/openuispec/platform/ios.yaml) |
13
13
  | `locales/*.json` | `locale.schema.json` | (object) | [en.json](../examples/taskflow/openuispec/locales/en.json) |
14
14
  | `contracts/<name>.yaml` | `contract.schema.json` | `<contract_name>` | [input_field.yaml](../examples/taskflow/openuispec/contracts/input_field.yaml) |
15
- | `contracts/x_*.yaml` | `custom-contract.schema.json` | `<x_name>` | [x_media_player.yaml](../examples/taskflow/openuispec/contracts/x_media_player.yaml) |
15
+ | `contracts/x_*.yaml` | `custom-contract.schema.json` | `<x_name>` | [x_schedule_preview.yaml](../examples/todo-orbit/openuispec/contracts/x_schedule_preview.yaml) |
16
+ | `components/*.yaml` | `component.schema.json` | `<component_name>` | [media_player.yaml](../examples/taskflow/openuispec/components/media_player.yaml) |
16
17
  | `tokens/color.yaml` | `tokens/color.schema.json` | `color` | [color.yaml](../examples/taskflow/openuispec/tokens/color.yaml) |
17
18
  | `tokens/typography.yaml` | `tokens/typography.schema.json` | `typography` | [typography.yaml](../examples/taskflow/openuispec/tokens/typography.yaml) |
18
19
  | `tokens/spacing.yaml` | `tokens/spacing.schema.json` | `spacing` | [spacing.yaml](../examples/taskflow/openuispec/tokens/spacing.yaml) |
@@ -21,6 +22,7 @@ Every file type has a corresponding JSON Schema in `schema/`. **Read the schema
21
22
  | `tokens/layout.yaml` | `tokens/layout.schema.json` | `layout` | [layout.yaml](../examples/taskflow/openuispec/tokens/layout.yaml) |
22
23
  | `tokens/themes.yaml` | `tokens/themes.schema.json` | `themes` | [themes.yaml](../examples/taskflow/openuispec/tokens/themes.yaml) |
23
24
  | `tokens/icons.yaml` | `tokens/icons.schema.json` | `icons` | [icons.yaml](../examples/taskflow/openuispec/tokens/icons.yaml) |
25
+ | `mock/<screen>.yaml` | — | `data` / `params` | [analytics.yaml](../examples/todo-orbit/openuispec/mock/analytics.yaml) |
24
26
 
25
27
  Every token file **must** have a single root wrapper key matching its type:
26
28
 
@@ -35,6 +37,40 @@ brand:
35
37
  primary: ...
36
38
  ```
37
39
 
40
+ ## Components
41
+
42
+ Components are **reusable compositions of contracts with named slots**. They fill the gap between atomic contracts and full-page screens — use them for complex UI blocks like media players, wizards, conversation timelines, and maps.
43
+
44
+ ```
45
+ Tokens → Contracts → Components → Screens → Flows
46
+ (atomic) (composed) (full page)
47
+ ```
48
+
49
+ Each component file lives in `components/*.yaml` with a single root key (the component name). Components define:
50
+
51
+ - **slots** — named contract instances (e.g. `play_button: { contract: action_trigger }`)
52
+ - **layout** — how slots are arranged (stack, row, nested)
53
+ - **states** — composite states that hide slots or override slot props (e.g. `playing`, `loading`)
54
+ - **variants** — named presets that change layout, hide slots, or override tokens (e.g. `mini`, `fullscreen`)
55
+
56
+ Screens reference components with `component:` instead of `contract:` and can override individual slots:
57
+
58
+ ```yaml
59
+ - component: media_player
60
+ variant: mini
61
+ props:
62
+ source: "{task.attachment.url}"
63
+ slots:
64
+ volume_control: { hidden: true }
65
+ play_button:
66
+ variant: branded
67
+ tokens_override: { background: "color.brand.primary" }
68
+ ```
69
+
70
+ **Resolution order:** slot default → variant override → state override → screen-level override. Most specific wins.
71
+
72
+ Simple custom contracts (`x_entity_status_badge`, etc.) stay as `x_` prefixed contracts — components are for composed UI that has internal layout and state.
73
+
38
74
  ## Shared interactive state roles
39
75
 
40
76
  Interactive contracts may optionally express state-specific visual roles inside their token maps with a nested `states:` object. This lets generators use explicit roles for `default`, `active`, `selected`, `pressed`, `focused`, `disabled`, `loading`, and `error` states.
@@ -100,6 +136,20 @@ generation:
100
136
  - Shared layers are not targets — they are tracked alongside targets in `prepare` and `status` output.
101
137
  - `openuispec init --with-shared` scaffolds KMP defaults when both ios and android targets are selected.
102
138
 
139
+ ## Mock data (preview)
140
+
141
+ The preview renderer reads mock data from `openuispec/mock/<screen>.yaml`. Each file has two optional top-level keys:
142
+
143
+ ```yaml
144
+ data:
145
+ tasks: [...] # binds to $data.tasks in the screen spec
146
+ user: { name: "..." } # binds to $data.user
147
+ params:
148
+ id: "task-1" # binds to $params.id
149
+ ```
150
+
151
+ Mock files are not validated by `openuispec validate` and are excluded from drift detection. They exist solely to feed the preview renderer with realistic placeholder data. Preview is intended for **human inspection only** — AI agents should not use it for UI verification.
152
+
103
153
  ## Spec sections overview
104
154
 
105
155
  | Section | What it defines |
package/drift/index.ts CHANGED
@@ -189,6 +189,9 @@ export function discoverSpecFiles(projectDir: string): string[] {
189
189
  if (includes.contracts) {
190
190
  files.push(...listFiles(resolve(projectDir, includes.contracts), ".yaml"));
191
191
  }
192
+ if (includes.components) {
193
+ files.push(...listFiles(resolve(projectDir, includes.components), ".yaml"));
194
+ }
192
195
 
193
196
  return files;
194
197
  }
@@ -203,6 +206,7 @@ export function specCategory(relPath: string): string {
203
206
  if (dir === "platform") return "platform";
204
207
  if (dir === "locales") return "locales";
205
208
  if (dir === "contracts") return "contracts";
209
+ if (dir === "components") return "components";
206
210
  return "other";
207
211
  }
208
212
 
@@ -662,7 +666,7 @@ export function createSharedSnapshot(
662
666
  const { entries, stubs: stubCount } = buildFileEntries(projectDir, files);
663
667
 
664
668
  const state: SharedLayerState = {
665
- spec_version: manifest.spec_version ?? "0.1",
669
+ spec_version: manifest.spec_version ?? "0.2",
666
670
  snapshot_at: new Date().toISOString(),
667
671
  layer_name: layerName,
668
672
  generated_by_target: generatedByTarget,
@@ -770,7 +774,7 @@ export function createSnapshot(cwd: string, target: string): SnapshotResult {
770
774
  const { entries, stubs: stubCount } = buildFileEntries(projectDir, files);
771
775
 
772
776
  const state: StateFile = {
773
- spec_version: manifest.spec_version ?? "0.1",
777
+ spec_version: manifest.spec_version ?? "0.2",
774
778
  snapshot_at: new Date().toISOString(),
775
779
  target,
776
780
  baseline,
@@ -1033,6 +1037,7 @@ function printReport(projectDir: string, result: CheckResult): void {
1033
1037
  "Platform",
1034
1038
  "Locales",
1035
1039
  "Contracts",
1040
+ "Components",
1036
1041
  "Other",
1037
1042
  ];
1038
1043
 
@@ -12,6 +12,7 @@ This directory contains the **OpenUISpec** semantic UI specification for **socia
12
12
  | `screens/` | Screen definitions — one YAML file per screen |
13
13
  | `flows/` | Navigation flows — multi-step user journeys |
14
14
  | `contracts/` | Component contracts — standard extensions and custom (`x_` prefixed) |
15
+ | `components/` | Reusable compositions — contract slot compositions with states and variants |
15
16
  | `platform/` | Platform overrides — per-target (iOS, Android, Web) behaviors |
16
17
  | `locales/` | Localization — i18n strings (JSON, ICU MessageFormat) |
17
18
 
@@ -28,7 +29,7 @@ Do NOT guess the file format — skipping this step will produce invalid YAML th
28
29
 
29
30
  **Reference files inside the package (read in this order):**
30
31
  1. `README.md` — schema tables, file format reference, root wrapper keys
31
- 2. `spec/openuispec-v0.1.md` — full specification (contracts, layout, expressions, etc.)
32
+ 2. `spec/openuispec-v0.2.md` — full specification (contracts, layout, expressions, etc.)
32
33
  3. `examples/taskflow/openuispec/` — complete working example with all file types
33
34
  4. `schema/` — JSON Schemas for validation
34
35