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.
- package/README.md +6 -5
- package/cli/index.ts +18 -12
- package/cli/init.ts +79 -13
- package/docs/cli.md +134 -27
- package/docs/file-formats.md +51 -1
- package/drift/index.ts +7 -2
- package/examples/social-app/openuispec/README.md +2 -1
- package/examples/social-app/openuispec/mock/chat_detail.yaml +25 -0
- package/examples/social-app/openuispec/mock/discover.yaml +17 -0
- package/examples/social-app/openuispec/mock/edit_profile.yaml +9 -0
- package/examples/social-app/openuispec/mock/home_feed.yaml +32 -0
- package/examples/social-app/openuispec/mock/messages_inbox.yaml +15 -0
- package/examples/social-app/openuispec/mock/notifications.yaml +30 -0
- package/examples/social-app/openuispec/mock/post_detail.yaml +26 -0
- package/examples/social-app/openuispec/mock/profile_self.yaml +28 -0
- package/examples/social-app/openuispec/mock/profile_user.yaml +32 -0
- package/examples/social-app/openuispec/mock/search_results.yaml +17 -0
- package/examples/social-app/openuispec/mock/settings.yaml +7 -0
- package/examples/social-app/openuispec/openuispec.yaml +3 -2
- package/examples/taskflow/README.md +4 -2
- package/examples/taskflow/openuispec/README.md +2 -1
- package/examples/taskflow/openuispec/components/media_player.yaml +92 -0
- package/examples/taskflow/openuispec/contracts/README.md +2 -2
- package/examples/taskflow/openuispec/locales/en.json +1 -0
- package/examples/taskflow/openuispec/mock/home.yaml +64 -0
- package/examples/taskflow/openuispec/mock/profile_edit.yaml +6 -0
- package/examples/taskflow/openuispec/mock/project_detail.yaml +33 -0
- package/examples/taskflow/openuispec/mock/settings.yaml +13 -0
- package/examples/taskflow/openuispec/mock/task_detail.yaml +18 -0
- package/examples/taskflow/openuispec/openuispec.yaml +3 -4
- package/examples/taskflow/openuispec/platform/ios.yaml +0 -4
- package/examples/taskflow/openuispec/screens/task_detail.yaml +5 -8
- package/examples/taskflow/openuispec/tokens/icons.yaml +16 -0
- package/examples/todo-orbit/README.md +3 -2
- package/examples/todo-orbit/openuispec/README.md +2 -1
- package/examples/todo-orbit/openuispec/components/task_trend_chart.yaml +85 -0
- package/examples/todo-orbit/openuispec/locales/en.json +3 -0
- package/examples/todo-orbit/openuispec/locales/ru.json +3 -0
- package/examples/todo-orbit/openuispec/mock/analytics.yaml +26 -0
- package/examples/todo-orbit/openuispec/mock/home.yaml +33 -0
- package/examples/todo-orbit/openuispec/mock/settings.yaml +7 -0
- package/examples/todo-orbit/openuispec/mock/task_detail.yaml +14 -0
- package/examples/todo-orbit/openuispec/openuispec.yaml +3 -3
- package/examples/todo-orbit/openuispec/platform/android.yaml +0 -3
- package/examples/todo-orbit/openuispec/platform/ios.yaml +0 -3
- package/examples/todo-orbit/openuispec/platform/web.yaml +0 -3
- package/examples/todo-orbit/openuispec/screens/analytics.yaml +1 -4
- package/mcp-server/index.ts +87 -6
- package/mcp-server/preview-render.ts +1922 -0
- package/mcp-server/preview.ts +292 -0
- package/mcp-server/screenshot-shared.ts +41 -4
- package/mcp-server/screenshot.ts +283 -97
- package/package.json +1 -1
- package/prepare/index.ts +1 -1
- package/schema/component.schema.json +278 -0
- package/schema/openuispec.schema.json +5 -1
- package/schema/screen.schema.json +12 -1
- package/schema/semantic-lint.ts +29 -5
- package/schema/validate.ts +21 -0
- package/scripts/regenerate-previews.ts +136 -0
- package/spec/{openuispec-v0.1.md → openuispec-v0.2.md} +266 -8
- package/examples/taskflow/openuispec/contracts/x_media_player.yaml +0 -185
- 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
|
-
- **
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
|
241
|
+
const dir = resolveSpecDir(projectDir, manifest, specDir);
|
|
237
242
|
|
|
238
|
-
if (!existsSync(
|
|
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(
|
|
242
|
-
const filePath = join(
|
|
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
|
|
246
|
-
if (
|
|
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[
|
|
255
|
+
const variantDef = content[rootKey]?.variants?.[variant];
|
|
251
256
|
if (!variantDef) {
|
|
252
|
-
const available = Object.keys(content[
|
|
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(
|
|
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.
|
|
236
|
-
spec_version: "0.
|
|
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.
|
|
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
|
-
|
|
450
|
-
|
|
452
|
+
|
|
453
|
+
**Status & discovery:**
|
|
451
454
|
- \`openuispec status --json\` — cross-target status
|
|
452
|
-
- \`openuispec
|
|
453
|
-
- \`openuispec
|
|
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
|
-
|
|
460
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
|
44
|
-
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
|
52
|
-
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
75
|
-
|
|
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
|
|
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
|
|
package/docs/file-formats.md
CHANGED
|
@@ -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>` | [
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|