openuispec 0.2.12 → 0.2.14

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 (64) hide show
  1. package/README.md +8 -7
  2. package/cli/index.ts +18 -12
  3. package/cli/init.ts +78 -13
  4. package/docs/cli.md +81 -27
  5. package/docs/file-formats.md +52 -2
  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 +5 -3
  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 +80 -3
  49. package/mcp-server/preview-render.ts +1922 -0
  50. package/mcp-server/preview.ts +292 -0
  51. package/mcp-server/screenshot-shared.ts +38 -0
  52. package/mcp-server/screenshot.ts +3 -32
  53. package/package.json +1 -1
  54. package/prepare/index.ts +1 -1
  55. package/schema/component.schema.json +278 -0
  56. package/schema/custom-contract.schema.json +2 -2
  57. package/schema/openuispec.schema.json +18 -8
  58. package/schema/screen.schema.json +12 -1
  59. package/schema/semantic-lint.ts +24 -2
  60. package/schema/validate.ts +21 -0
  61. package/scripts/regenerate-previews.ts +136 -0
  62. package/spec/{openuispec-v0.1.md → openuispec-v0.2.md} +275 -17
  63. package/examples/taskflow/openuispec/contracts/x_media_player.yaml +0 -185
  64. package/examples/todo-orbit/openuispec/contracts/x_task_trend_chart.yaml +0 -139
@@ -12,6 +12,7 @@ This directory contains the **OpenUISpec** semantic UI specification for **todo-
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
 
@@ -0,0 +1,85 @@
1
+ # ============================================================
2
+ # Component: task_trend_chart
3
+ # ============================================================
4
+ # Visualizes task creation and completion trends as a composed
5
+ # component with chart area, legend, and period selector slots.
6
+ # ============================================================
7
+
8
+ task_trend_chart:
9
+ semantic: "Visualizes task creation and completion trends over time for productivity analysis"
10
+
11
+ props:
12
+ metric:
13
+ type: enum
14
+ values: [completed, created, completion_rate]
15
+ required: true
16
+ period:
17
+ type: enum
18
+ values: [week, month, quarter]
19
+ required: true
20
+ show_legend: { type: bool, default: true }
21
+
22
+ slots:
23
+ chart_area:
24
+ contract: data_display
25
+ variant: card
26
+ props: { title: "$t:analytics.trend_chart" }
27
+ period_selector:
28
+ contract: input_field
29
+ input_type: select
30
+ props: { label: "$t:analytics.period", options: [week, month, quarter] }
31
+ hideable: true
32
+ legend:
33
+ contract: data_display
34
+ variant: inline
35
+ props: { title: "$t:analytics.legend" }
36
+ hideable: true
37
+
38
+ layout:
39
+ type: stack
40
+ spacing: "spacing.sm"
41
+ sections:
42
+ - slot: period_selector
43
+ - slot: chart_area
44
+ - slot: legend
45
+
46
+ states:
47
+ idle: { semantic: "Chart has not loaded data yet" }
48
+ loading:
49
+ semantic: "Trend data is loading"
50
+ hide_slots: [legend]
51
+ ready:
52
+ semantic: "Trend data is available and rendered"
53
+ empty:
54
+ semantic: "No trend points available"
55
+ hide_slots: [legend]
56
+ error:
57
+ semantic: "Trend data failed to load"
58
+ hide_slots: [legend]
59
+
60
+ variants:
61
+ compact:
62
+ semantic: "Compact chart for dashboard cards"
63
+ hide_slots: [period_selector]
64
+ tokens:
65
+ background: "color.surface.secondary"
66
+ radius: "spacing.sm"
67
+
68
+ tokens:
69
+ background: "color.surface.primary"
70
+ radius: "spacing.sm"
71
+ padding: "spacing.md"
72
+
73
+ a11y:
74
+ role: "img"
75
+ label: "Analytics trend chart"
76
+
77
+ platform_mapping:
78
+ ios: { component: "Chart", framework: "Swift Charts" }
79
+ android: { component: "Canvas chart composable" }
80
+ web: { component: "SVG chart component" }
81
+
82
+ generation:
83
+ must_handle:
84
+ - "Render chart area with completed and created series"
85
+ - "Handle loading, empty, and error states"
@@ -26,6 +26,9 @@
26
26
  "analytics.completion_rate": "Completion rate",
27
27
  "analytics.overdue_section": "Overdue review",
28
28
  "analytics.overdue_subtitle": "Tasks that need attention first.",
29
+ "analytics.trend_chart": "Trend chart",
30
+ "analytics.period": "Period",
31
+ "analytics.legend": "Legend",
29
32
  "analytics.trend_subtitle": "Completion trend",
30
33
  "analytics.legend_completed": "Completed",
31
34
  "analytics.legend_created": "Created",
@@ -26,6 +26,9 @@
26
26
  "analytics.completion_rate": "Процент выполнения",
27
27
  "analytics.overdue_section": "Просроченные задачи",
28
28
  "analytics.overdue_subtitle": "Задачи, которым нужно уделить внимание в первую очередь.",
29
+ "analytics.trend_chart": "График тренда",
30
+ "analytics.period": "Период",
31
+ "analytics.legend": "Легенда",
29
32
  "analytics.trend_subtitle": "Динамика выполнения",
30
33
  "analytics.legend_completed": "Выполнено",
31
34
  "analytics.legend_created": "Создано",
@@ -0,0 +1,26 @@
1
+ # Mock data for the analytics screen
2
+ data:
3
+ overview:
4
+ completed_today: 3
5
+ open_tasks: 8
6
+ overdue_tasks: 2
7
+ completion_rate: 67
8
+
9
+ trend:
10
+ - { label: "Mon", completed: 4, created: 2 }
11
+ - { label: "Tue", completed: 3, created: 5 }
12
+ - { label: "Wed", completed: 6, created: 3 }
13
+ - { label: "Thu", completed: 2, created: 4 }
14
+ - { label: "Fri", completed: 5, created: 1 }
15
+ - { label: "Sat", completed: 1, created: 0 }
16
+ - { label: "Sun", completed: 0, created: 2 }
17
+
18
+ overdue:
19
+ - id: "t6"
20
+ title: "Submit tax forms"
21
+ priority: high
22
+ due_date: "2026-03-15"
23
+ - id: "t7"
24
+ title: "Renew gym membership"
25
+ priority: low
26
+ due_date: "2026-03-14"
@@ -0,0 +1,33 @@
1
+ # Mock data for the home screen
2
+ data:
3
+ tasks:
4
+ - id: "t1"
5
+ title: "Buy groceries"
6
+ due_date: "2026-03-18"
7
+ status: open
8
+ priority: high
9
+ - id: "t2"
10
+ title: "Finish project proposal"
11
+ due_date: "2026-03-19"
12
+ status: open
13
+ priority: medium
14
+ - id: "t3"
15
+ title: "Call dentist"
16
+ due_date: "2026-03-20"
17
+ status: done
18
+ priority: low
19
+ - id: "t4"
20
+ title: "Review pull request"
21
+ due_date: "2026-03-18"
22
+ status: open
23
+ priority: high
24
+ - id: "t5"
25
+ title: "Clean apartment"
26
+ due_date: "2026-03-21"
27
+ status: open
28
+ priority: medium
29
+
30
+ task_counts:
31
+ all: 12
32
+ open: 8
33
+ done: 4
@@ -0,0 +1,7 @@
1
+ # Mock data for the settings screen
2
+ data:
3
+ preferences:
4
+ locale: "en"
5
+ theme: "light"
6
+ reminders_enabled: true
7
+ daily_summary_enabled: false
@@ -0,0 +1,14 @@
1
+ # Mock data for the task_detail screen
2
+ data:
3
+ task:
4
+ id: "t1"
5
+ title: "Buy groceries"
6
+ status: open
7
+ priority: high
8
+ due_date: "2026-03-18"
9
+ notes: "Get milk, eggs, bread, and vegetables. Check the weekly deals at the store."
10
+ created_at: "2026-03-10"
11
+ updated_at: "2026-03-17"
12
+
13
+ params:
14
+ task_id: "t1"
@@ -1,5 +1,5 @@
1
- # openuispec-sample — OpenUISpec v0.1
2
- spec_version: "0.1"
1
+ # openuispec-sample — OpenUISpec v0.2
2
+ spec_version: "0.2"
3
3
 
4
4
  project:
5
5
  name: "Todo Orbit"
@@ -9,13 +9,13 @@ project:
9
9
  includes:
10
10
  tokens: "./tokens/"
11
11
  contracts: "./contracts/"
12
+ components: "./components/"
12
13
  screens: "./screens/"
13
14
  flows: "./flows/"
14
15
  platform: "./platform/"
15
16
  locales: "./locales/"
16
17
 
17
18
  custom_contracts:
18
- - "./contracts/x_task_trend_chart.yaml"
19
19
  - "./contracts/x_schedule_preview.yaml"
20
20
 
21
21
  i18n:
@@ -4,9 +4,6 @@ android:
4
4
  min_sdk: 26
5
5
 
6
6
  overrides:
7
- x_task_trend_chart:
8
- compact: { uses_canvas_rendering: true }
9
- detail: { uses_canvas_rendering: true, supports_pointer_highlight: true }
10
7
  x_schedule_preview:
11
8
  compact: { uses_lazy_column: true }
12
9
  detail: { uses_lazy_column: true, supports_animated_content: true }
@@ -3,9 +3,6 @@ ios:
3
3
  min_version: "17.0"
4
4
 
5
5
  overrides:
6
- x_task_trend_chart:
7
- compact: { uses_native_chart_framework: true }
8
- detail: { uses_native_chart_framework: true, supports_rule_mark: true }
9
6
  x_schedule_preview:
10
7
  compact: { uses_timeline_view: true }
11
8
  detail: { uses_timeline_view: true, supports_content_transition: true }
@@ -3,9 +3,6 @@ web:
3
3
  language: typescript
4
4
 
5
5
  overrides:
6
- x_task_trend_chart:
7
- compact: { implementation: "svg", responsive: true }
8
- detail: { implementation: "svg", responsive: true, supports_tooltip: true }
9
6
  x_schedule_preview:
10
7
  compact: { implementation: "semantic_list", responsive: true }
11
8
  detail: { implementation: "semantic_list", responsive: true, supports_staggered_updates: true }
@@ -102,14 +102,11 @@ analytics:
102
102
 
103
103
  - id: trend_chart
104
104
  margin_top: "spacing.lg"
105
- contract: x_task_trend_chart
106
- variant: detail
105
+ component: task_trend_chart
107
106
  props:
108
- series: "trend"
109
107
  metric: completed
110
108
  period: "state.period"
111
109
  show_legend: true
112
- empty_message: "$t:analytics.empty_trend"
113
110
 
114
111
  - id: overdue_header
115
112
  margin_top: "spacing.xl"
@@ -25,6 +25,7 @@ import YAML from "yaml";
25
25
  import { takeScreenshot, takeScreenshotBatch } from "./screenshot.js";
26
26
  import { takeAndroidScreenshot, takeAndroidScreenshotBatch } from "./screenshot-android.js";
27
27
  import { takeIOSScreenshot, takeIOSScreenshotBatch } from "./screenshot-ios.js";
28
+ import { renderPreview } from "./preview.js";
28
29
 
29
30
  // ── resolve project cwd ──────────────────────────────────────────────
30
31
 
@@ -121,8 +122,9 @@ WORKFLOW — each tool response includes a next_tool hint, follow it:
121
122
  5. Remind the user to baseline when satisfied: openuispec drift --snapshot --target <t>
122
123
  Do not baseline on your own initiative — the user decides when output is accepted.
123
124
 
124
- FOCUSED GETTERS (prefer for incremental edits): get_screen, get_contract, get_tokens, get_locale
125
+ FOCUSED GETTERS (prefer for incremental edits): get_screen, get_contract, get_component, get_tokens, get_locale
125
126
  SPEC AUTHORING: spec_types → spec_schema(type, summary?) → write YAML
127
+ PREVIEW: openuispec_preview(screen) → render spec as HTML with mock data, returns screenshot (no app needed)
126
128
  SCREENSHOTS: screenshot (web), screenshot_android, screenshot_ios — single + batch variants
127
129
 
128
130
  Skip only for purely non-UI requests.`,
@@ -387,10 +389,10 @@ server.registerTool(
387
389
  server.registerTool(
388
390
  "openuispec_validate",
389
391
  {
390
- description: "Validate spec files against JSON schemas. Returns validation errors grouped by type (manifest, tokens, screens, flows, platform, locales, contracts, semantic).",
392
+ description: "Validate spec files against JSON schemas. Returns validation errors grouped by type (manifest, tokens, screens, flows, platform, locales, contracts, components, semantic).",
391
393
  inputSchema: {
392
394
  groups: z
393
- .array(z.enum(["manifest", "tokens", "screens", "flows", "platform", "locales", "contracts", "semantic"]))
395
+ .array(z.enum(["manifest", "tokens", "screens", "flows", "platform", "locales", "contracts", "components", "semantic"]))
394
396
  .optional()
395
397
  .describe("Specific groups to validate. If omitted, validates all groups."),
396
398
  },
@@ -491,6 +493,7 @@ const SCHEMA_CATALOG: Record<string, { file: string; title: string; description:
491
493
  platform: { file: "platform.schema.json", title: "Platform", description: "Platform-specific generation config: architecture, naming, CSS framework, component mapping" },
492
494
  contract: { file: "contract.schema.json", title: "Contract", description: "Built-in UI contract definitions: variants, props, must_handle states, generation hints" },
493
495
  "custom-contract":{ file: "custom-contract.schema.json", title: "Custom Contract", description: "User-defined UI contract definitions (x_ prefixed)" },
496
+ component: { file: "component.schema.json", title: "Component", description: "Reusable composition of contracts with named slots, states, variants, and layout" },
494
497
  locale: { file: "locale.schema.json", title: "Locale", description: "Locale translation files: flat key-value string maps" },
495
498
  "tokens/color": { file: "tokens/color.schema.json", title: "Color Tokens", description: "Color tokens: brand, surface, text, semantic, border groups with HSL ranges and contrast" },
496
499
  "tokens/typography": { file: "tokens/typography.schema.json", title: "Typography Tokens", description: "Typography tokens: font families, sizes, weights, line heights, letter spacing" },
@@ -634,6 +637,53 @@ server.registerTool(
634
637
  }
635
638
  );
636
639
 
640
+ // ── tool: openuispec_get_component ──────────────────────────────────
641
+
642
+ server.registerTool(
643
+ "openuispec_get_component",
644
+ {
645
+ description: "Get a single component spec. Components are reusable compositions of contracts with named slots.",
646
+ inputSchema: {
647
+ name: z.string().describe("Component name, e.g. 'media_player'"),
648
+ variant: z.string().optional().describe("Optional variant name. If given, returns only that variant's definition."),
649
+ },
650
+ },
651
+ async ({ name, variant }) => {
652
+ try {
653
+ const projectDir = findProjectDir(projectCwd);
654
+ const manifest = YAML.parse(readFileSync(join(projectDir, "openuispec.yaml"), "utf-8"));
655
+ const componentsDir = resolveSpecDir(projectDir, manifest, "components");
656
+
657
+ if (!existsSync(componentsDir)) {
658
+ return toolError(`Components directory not found: ${componentsDir}`);
659
+ }
660
+
661
+ for (const file of readdirSync(componentsDir).filter(f => f.endsWith(".yaml")).sort()) {
662
+ const filePath = join(componentsDir, file);
663
+ const raw = readFileSync(filePath, "utf-8");
664
+ const content = YAML.parse(raw);
665
+ const componentName = Object.keys(content)[0];
666
+ if (componentName !== name) continue;
667
+
668
+ if (variant) {
669
+ const component = content[componentName];
670
+ const variantDef = component?.variants?.[variant];
671
+ if (!variantDef) {
672
+ return toolError(`Variant "${variant}" not found in component "${name}". Available variants: ${Object.keys(component?.variants ?? {}).join(", ")}`);
673
+ }
674
+ return toolResult({ name, variant, definition: variantDef });
675
+ }
676
+
677
+ return toolResult({ name, path: relative(projectDir, filePath), content: raw });
678
+ }
679
+
680
+ return toolError(`Component "${name}" not found in ${componentsDir}`);
681
+ } catch (err) {
682
+ return toolError(err);
683
+ }
684
+ }
685
+ );
686
+
637
687
  // ── tool: openuispec_get_tokens ─────────────────────────────────────
638
688
 
639
689
  server.registerTool(
@@ -928,6 +978,33 @@ server.registerTool(
928
978
  }
929
979
  );
930
980
 
981
+ // ── tool: openuispec_preview ────────────────────────────────────────────
982
+
983
+ server.registerTool(
984
+ "openuispec_preview",
985
+ {
986
+ description: "Render a screen spec as an HTML preview with mock data and return a screenshot. Uses token values, locale strings, and contract-to-HTML mapping to produce a visual approximation without generating a full app. Mock data should be placed in openuispec/mock/<screen>.yaml.",
987
+ inputSchema: {
988
+ screen: z.string().describe("Screen name (e.g. 'home', 'settings', 'task_detail')"),
989
+ size_class: z.enum(["compact", "regular", "expanded"]).optional().default("compact").describe("Adaptive size class — compact (phone), regular (tablet), expanded (desktop)"),
990
+ theme: z.enum(["light", "dark"]).optional().default("light").describe("Color theme"),
991
+ locale: z.string().optional().default("en").describe("Locale code for i18n strings"),
992
+ viewport: z.object({
993
+ width: z.number(),
994
+ height: z.number(),
995
+ }).optional().describe("Custom viewport size (overrides size_class default)"),
996
+ include_html: z.boolean().optional().default(false).describe("Also return the rendered HTML string in the response"),
997
+ },
998
+ },
999
+ async ({ screen, size_class, theme, locale, viewport, include_html }) => {
1000
+ try {
1001
+ return await renderPreview(projectCwd, { screen, size_class, theme, locale, viewport, include_html });
1002
+ } catch (err) {
1003
+ return toolError(err);
1004
+ }
1005
+ }
1006
+ );
1007
+
931
1008
  // ── start server ─────────────────────────────────────────────────────
932
1009
 
933
1010
  export async function startMcpServer() {