openuispec 0.1.18 → 0.1.20

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 (98) hide show
  1. package/README.md +52 -34
  2. package/cli/index.ts +1 -1
  3. package/cli/init.ts +48 -211
  4. package/docs/stress-test-maturity-report.md +97 -0
  5. package/examples/todo-orbit/AGENTS.md +127 -0
  6. package/examples/todo-orbit/CLAUDE.md +75 -0
  7. package/examples/todo-orbit/README.md +62 -0
  8. package/examples/todo-orbit/generated/android/Todo Orbit/README.md +14 -0
  9. package/examples/todo-orbit/generated/android/Todo Orbit/app/build.gradle.kts +58 -0
  10. package/examples/todo-orbit/generated/android/Todo Orbit/app/proguard-rules.pro +1 -0
  11. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/AndroidManifest.xml +20 -0
  12. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/MainActivity.kt +14 -0
  13. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/TodoOrbitApp.kt +345 -0
  14. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/AppLogic.kt +231 -0
  15. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Models.kt +169 -0
  16. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Strings.kt +8 -0
  17. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/components/CommonComponents.kt +185 -0
  18. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/AnalyticsScreen.kt +193 -0
  19. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/SettingsScreen.kt +102 -0
  20. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/TasksScreen.kt +342 -0
  21. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/sheets/EditorSheets.kt +344 -0
  22. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/theme/TodoOrbitTheme.kt +59 -0
  23. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values/strings.xml +148 -0
  24. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values-ru/strings.xml +154 -0
  25. package/examples/todo-orbit/generated/android/Todo Orbit/build.gradle.kts +4 -0
  26. package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.jar +0 -0
  27. package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.properties +7 -0
  28. package/examples/todo-orbit/generated/android/Todo Orbit/gradle.properties +4 -0
  29. package/examples/todo-orbit/generated/android/Todo Orbit/gradlew +248 -0
  30. package/examples/todo-orbit/generated/android/Todo Orbit/gradlew.bat +93 -0
  31. package/examples/todo-orbit/generated/android/Todo Orbit/settings.gradle.kts +18 -0
  32. package/examples/todo-orbit/generated/ios/Todo Orbit/README.md +29 -0
  33. package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/en.lproj/Localizable.strings +118 -0
  34. package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/ru.lproj/Localizable.strings +118 -0
  35. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/App/TodoOrbitApp.swift +50 -0
  36. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/OrbitChrome.swift +204 -0
  37. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/SchedulePreviewView.swift +126 -0
  38. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/TrendChartView.swift +70 -0
  39. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/RecurringRuleSheet.swift +123 -0
  40. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/TaskEditorSheet.swift +60 -0
  41. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Models/DomainModels.swift +238 -0
  42. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/AnalyticsView.swift +94 -0
  43. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/SettingsView.swift +74 -0
  44. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/TasksHomeView.swift +363 -0
  45. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Support/AppModel.swift +324 -0
  46. package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.pbxproj +408 -0
  47. package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  48. package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/xcshareddata/xcschemes/TodoOrbit.xcscheme +79 -0
  49. package/examples/todo-orbit/generated/ios/Todo Orbit/project.yml +24 -0
  50. package/examples/todo-orbit/generated/web/Todo Orbit/index.html +16 -0
  51. package/examples/todo-orbit/generated/web/Todo Orbit/package-lock.json +1087 -0
  52. package/examples/todo-orbit/generated/web/Todo Orbit/package.json +24 -0
  53. package/examples/todo-orbit/generated/web/Todo Orbit/src/App.tsx +2114 -0
  54. package/examples/todo-orbit/generated/web/Todo Orbit/src/main.tsx +13 -0
  55. package/examples/todo-orbit/generated/web/Todo Orbit/src/styles.css +886 -0
  56. package/examples/todo-orbit/generated/web/Todo Orbit/tsconfig.json +19 -0
  57. package/examples/todo-orbit/generated/web/Todo Orbit/vite.config.ts +6 -0
  58. package/examples/todo-orbit/openuispec/README.md +158 -0
  59. package/examples/todo-orbit/openuispec/contracts/.gitkeep +0 -0
  60. package/examples/todo-orbit/openuispec/contracts/action_trigger.yaml +28 -0
  61. package/examples/todo-orbit/openuispec/contracts/collection.yaml +32 -0
  62. package/examples/todo-orbit/openuispec/contracts/data_display.yaml +38 -0
  63. package/examples/todo-orbit/openuispec/contracts/feedback.yaml +32 -0
  64. package/examples/todo-orbit/openuispec/contracts/input_field.yaml +52 -0
  65. package/examples/todo-orbit/openuispec/contracts/nav_container.yaml +47 -0
  66. package/examples/todo-orbit/openuispec/contracts/surface.yaml +28 -0
  67. package/examples/todo-orbit/openuispec/contracts/x_schedule_preview.yaml +134 -0
  68. package/examples/todo-orbit/openuispec/contracts/x_task_trend_chart.yaml +139 -0
  69. package/examples/todo-orbit/openuispec/flows/.gitkeep +0 -0
  70. package/examples/todo-orbit/openuispec/flows/create_recurring_rule.yaml +253 -0
  71. package/examples/todo-orbit/openuispec/flows/create_task.yaml +118 -0
  72. package/examples/todo-orbit/openuispec/flows/edit_task.yaml +126 -0
  73. package/examples/todo-orbit/openuispec/locales/.gitkeep +0 -0
  74. package/examples/todo-orbit/openuispec/locales/en.json +150 -0
  75. package/examples/todo-orbit/openuispec/locales/ru.json +150 -0
  76. package/examples/todo-orbit/openuispec/openuispec.yaml +122 -0
  77. package/examples/todo-orbit/openuispec/platform/.gitkeep +0 -0
  78. package/examples/todo-orbit/openuispec/platform/android.yaml +19 -0
  79. package/examples/todo-orbit/openuispec/platform/ios.yaml +20 -0
  80. package/examples/todo-orbit/openuispec/platform/web.yaml +22 -0
  81. package/examples/todo-orbit/openuispec/screens/.gitkeep +0 -0
  82. package/examples/todo-orbit/openuispec/screens/analytics.yaml +140 -0
  83. package/examples/todo-orbit/openuispec/screens/home.yaml +173 -0
  84. package/examples/todo-orbit/openuispec/screens/settings.yaml +149 -0
  85. package/examples/todo-orbit/openuispec/screens/task_detail.yaml +223 -0
  86. package/examples/todo-orbit/openuispec/tokens/.gitkeep +0 -0
  87. package/examples/todo-orbit/openuispec/tokens/color.yaml +93 -0
  88. package/examples/todo-orbit/openuispec/tokens/elevation.yaml +25 -0
  89. package/examples/todo-orbit/openuispec/tokens/icons.yaml +92 -0
  90. package/examples/todo-orbit/openuispec/tokens/layout.yaml +107 -0
  91. package/examples/todo-orbit/openuispec/tokens/motion.yaml +39 -0
  92. package/examples/todo-orbit/openuispec/tokens/spacing.yaml +18 -0
  93. package/examples/todo-orbit/openuispec/tokens/themes.yaml +23 -0
  94. package/examples/todo-orbit/openuispec/tokens/typography.yaml +52 -0
  95. package/package.json +1 -1
  96. package/schema/screen.schema.json +9 -0
  97. package/schema/validate.ts +0 -2
  98. package/spec/openuispec-v0.1.md +129 -27
@@ -0,0 +1,92 @@
1
+ icons:
2
+ sizes:
3
+ sm:
4
+ semantic: "Small inline icon size"
5
+ value: 16
6
+ md:
7
+ semantic: "Default icon size for actions and list items"
8
+ value: 20
9
+ lg:
10
+ semantic: "Prominent icon size for empty states and hero elements"
11
+ value: 24
12
+
13
+ variants:
14
+ default: "outline"
15
+ suffixes:
16
+ _fill: "Filled variant for active or completed states"
17
+
18
+ fallback:
19
+ strategy: "name_passthrough"
20
+ missing_icon: "questionmark_circle"
21
+
22
+ registry:
23
+ actions:
24
+ plus:
25
+ semantic: "Create a new task or item"
26
+ platform:
27
+ ios: "plus"
28
+ android: "add"
29
+ web: "plus"
30
+ search:
31
+ semantic: "Search tasks"
32
+ platform:
33
+ ios: "magnifyingglass"
34
+ android: "search"
35
+ web: "search"
36
+ trash:
37
+ semantic: "Delete a task"
38
+ platform:
39
+ ios: "trash"
40
+ android: "delete"
41
+ web: "trash-2"
42
+ checkmark:
43
+ semantic: "Confirm completion"
44
+ variants: ["circle", "circle_fill", "list"]
45
+ platform:
46
+ ios: "checkmark"
47
+ android: "check"
48
+ web: "check"
49
+
50
+ objects:
51
+ calendar:
52
+ semantic: "Date and scheduling"
53
+ platform:
54
+ ios: "calendar"
55
+ android: "calendar_today"
56
+ web: "calendar"
57
+ gear:
58
+ semantic: "Settings and preferences"
59
+ variants: ["fill"]
60
+ platform:
61
+ ios: "gear"
62
+ android: "settings"
63
+ web: "settings"
64
+ globe:
65
+ semantic: "Language and locale selection"
66
+ platform:
67
+ ios: "globe"
68
+ android: "language"
69
+ web: "languages"
70
+ flag:
71
+ semantic: "Priority marker"
72
+ variants: ["fill"]
73
+ platform:
74
+ ios: "flag"
75
+ android: "flag"
76
+ web: "flag"
77
+
78
+ custom:
79
+ todo_orbit:
80
+ checkmark_list:
81
+ semantic: "Todo list navigation icon"
82
+ platform:
83
+ ios: "checklist"
84
+ android: "checklist"
85
+ web: "list-checks"
86
+ chart_line:
87
+ semantic: "Analytics and trend visualization icon"
88
+ variants: ["fill"]
89
+ platform:
90
+ ios: "chart.line.uptrend.xyaxis"
91
+ android: "insert_chart"
92
+ web: "chart-line"
@@ -0,0 +1,107 @@
1
+ layout:
2
+ size_classes:
3
+ compact:
4
+ semantic: "Phone-first single-column layouts"
5
+ width: { max: 640 }
6
+ columns: 4
7
+ margin: "spacing.md"
8
+ content_max_width: null
9
+ examples: "Phones in portrait, narrow browser windows"
10
+ regular:
11
+ semantic: "Tablet portrait and large phone landscape layouts"
12
+ width: { min: 641, max: 1024 }
13
+ columns: 8
14
+ margin: "spacing.lg"
15
+ content_max_width: 860
16
+ examples: "Tablets in portrait, wide phones in landscape"
17
+ expanded:
18
+ semantic: "Desktop and large-tablet multi-column layouts"
19
+ width: { min: 1025 }
20
+ columns: 12
21
+ margin: "spacing.xl"
22
+ content_max_width: 1240
23
+ examples: "Desktop browsers, tablets in landscape"
24
+
25
+ platform_mapping:
26
+ ios:
27
+ uses: "UIUserInterfaceSizeClass"
28
+ compact: ".compact horizontal"
29
+ regular: ".regular horizontal"
30
+ expanded: ".regular horizontal + width > 1024"
31
+ android:
32
+ uses: "WindowSizeClass"
33
+ compact: "WindowWidthSizeClass.Compact"
34
+ regular: "WindowWidthSizeClass.Medium"
35
+ expanded: "WindowWidthSizeClass.Expanded"
36
+ web:
37
+ uses: "media queries"
38
+ compact: "@media (max-width: 640px)"
39
+ regular: "@media (min-width: 641px) and (max-width: 1024px)"
40
+ expanded: "@media (min-width: 1025px)"
41
+
42
+ primitives:
43
+ stack:
44
+ semantic: "Vertical content flow"
45
+ props:
46
+ spacing: { type: token_ref, default: "spacing.md" }
47
+ align: { type: enum, values: [leading, center, trailing, stretch], default: stretch }
48
+ padding: { type: token_ref, required: false }
49
+ platform_mapping:
50
+ ios: "VStack"
51
+ android: "Column"
52
+ web: "flex-direction: column"
53
+ row:
54
+ semantic: "Horizontal content flow"
55
+ props:
56
+ spacing: { type: token_ref, default: "spacing.sm" }
57
+ align: { type: enum, values: [top, center, bottom, baseline, stretch], default: center }
58
+ justify: { type: enum, values: [start, center, end, space-between, space-around], default: start }
59
+ wrap: { type: bool, default: false }
60
+ platform_mapping:
61
+ ios: "HStack"
62
+ android: "Row"
63
+ web: "flex-direction: row"
64
+ grid:
65
+ semantic: "Responsive 2D arrangement"
66
+ props:
67
+ columns: { type: "int or adaptive_map", default: 2 }
68
+ gap: { type: token_ref, default: "spacing.md" }
69
+ platform_mapping:
70
+ ios: "LazyVGrid"
71
+ android: "LazyVerticalGrid"
72
+ web: "display: grid"
73
+ scroll_vertical:
74
+ semantic: "Scrollable vertical canvas"
75
+ props:
76
+ safe_area: { type: bool, default: true }
77
+ padding: { type: token_ref, required: false }
78
+ platform_mapping:
79
+ ios: "ScrollView(.vertical)"
80
+ android: "LazyColumn or verticalScroll"
81
+ web: "overflow-y: auto"
82
+ split_view:
83
+ semantic: "Master-detail two-pane layout"
84
+ props:
85
+ primary_width: { type: "fraction or token_ref", default: 0.36 }
86
+ divider: { type: bool, default: true }
87
+ collapse_at: { type: size_class, default: "compact" }
88
+ platform_mapping:
89
+ ios: "NavigationSplitView"
90
+ android: "ListDetailPaneScaffold"
91
+ web: "CSS Grid with two columns"
92
+
93
+ reflow_rules:
94
+ action_trigger:
95
+ compact: { full_width: true }
96
+ regular: { full_width: false }
97
+ nav_container:
98
+ compact: { variant: "tab_bar" }
99
+ regular: { variant: "rail" }
100
+ expanded: { variant: "sidebar" }
101
+ task_actions:
102
+ compact: { layout: stack }
103
+ regular: { layout: row }
104
+ stat_group:
105
+ compact: { layout: grid, columns: 2 }
106
+ regular: { layout: row }
107
+ expanded: { layout: row }
@@ -0,0 +1,39 @@
1
+ motion:
2
+ duration:
3
+ instant: 100
4
+ quick: 180
5
+ normal: 280
6
+ slow: 420
7
+
8
+ easing:
9
+ default: "ease-out"
10
+ enter: "ease-out"
11
+ exit: "ease-in"
12
+ emphasis: "cubic-bezier(0.2, 0, 0, 1)"
13
+
14
+ reduced_motion: "remove-animation"
15
+
16
+ patterns:
17
+ press_feedback:
18
+ duration: "instant"
19
+ property: "scale"
20
+ value: 0.98
21
+ state_change:
22
+ duration: "quick"
23
+ property: "opacity, border-color, background"
24
+ screen_enter:
25
+ duration: "normal"
26
+ easing: "enter"
27
+ pattern: "slide-from-trailing"
28
+ screen_exit:
29
+ duration: "quick"
30
+ easing: "exit"
31
+ pattern: "slide-to-leading"
32
+ checkbox_check:
33
+ duration: "quick"
34
+ easing: "emphasis"
35
+ pattern: "scale-bounce"
36
+ modal_present:
37
+ duration: "normal"
38
+ easing: "enter"
39
+ pattern: "fade-and-lift"
@@ -0,0 +1,18 @@
1
+ spacing:
2
+ base_unit: 4
3
+ platform_flex: 0.15
4
+
5
+ scale:
6
+ none: 0
7
+ xs: 4
8
+ sm: 8
9
+ md: { base: 16, range: [12, 16] }
10
+ lg: { base: 24, range: [20, 24] }
11
+ xl: 32
12
+ xxl: 40
13
+
14
+ aliases:
15
+ page_margin: { horizontal: md, vertical: md }
16
+ card_padding: { all: md }
17
+ section_gap: lg
18
+ inline_gap: sm
@@ -0,0 +1,23 @@
1
+ themes:
2
+ default: "light"
3
+
4
+ variants:
5
+ light:
6
+ surface.primary: { lightness: [97, 100] }
7
+ surface.secondary: { lightness: [93, 97] }
8
+ surface.tertiary: { lightness: [89, 94] }
9
+ text.primary: { lightness: [8, 14] }
10
+ text.secondary: { lightness: [36, 46] }
11
+ border.default: { opacity: 0.22 }
12
+ dark:
13
+ surface.primary: { lightness: [10, 14] }
14
+ surface.secondary: { lightness: [14, 18] }
15
+ surface.tertiary: { lightness: [18, 24] }
16
+ text.primary: { lightness: [90, 95] }
17
+ text.secondary: { lightness: [62, 70] }
18
+ border.default: { opacity: 0.18 }
19
+
20
+ platform:
21
+ ios: { supports_dynamic: true }
22
+ android: { dynamic_color: true }
23
+ web: { prefers_color_scheme: true, css_custom_properties: true }
@@ -0,0 +1,52 @@
1
+ typography:
2
+ font_family:
3
+ primary:
4
+ semantic: "Main UI typeface for the todo app"
5
+ value: "Manrope"
6
+ fallback_strategy: "modern-sans"
7
+ platform:
8
+ ios: { system_alternative: "SF Pro" }
9
+ android: { system_alternative: "Google Sans" }
10
+ web: { load_strategy: "swap", source: "google_fonts" }
11
+
12
+ scale:
13
+ display:
14
+ semantic: "Large empty-state and hero copy"
15
+ size: { base: 30, range: [28, 34] }
16
+ weight: 700
17
+ tracking: -0.02
18
+ line_height: 1.15
19
+ heading_lg:
20
+ semantic: "Primary screen titles"
21
+ size: { base: 24, range: [22, 28] }
22
+ weight: 700
23
+ tracking: -0.02
24
+ line_height: 1.2
25
+ heading:
26
+ semantic: "Section headings and modal titles"
27
+ size: { base: 18, range: [17, 20] }
28
+ weight: 600
29
+ tracking: -0.01
30
+ line_height: 1.3
31
+ heading_sm:
32
+ semantic: "Compact emphasized labels"
33
+ size: { base: 15, range: [14, 17] }
34
+ weight: 600
35
+ line_height: 1.35
36
+ body:
37
+ semantic: "Primary body copy"
38
+ size: { base: 16, range: [15, 17] }
39
+ weight: 400
40
+ line_height: 1.5
41
+ body_sm:
42
+ semantic: "Supporting text and subtitles"
43
+ size: { base: 14, range: [13, 15] }
44
+ weight: 400
45
+ tracking: 0.004
46
+ line_height: 1.45
47
+ caption:
48
+ semantic: "Tiny labels and metadata"
49
+ size: { base: 12, range: [11, 13] }
50
+ weight: 500
51
+ tracking: 0.015
52
+ line_height: 1.35
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openuispec",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "A semantic UI specification format for AI-native, platform-native app development",
@@ -17,6 +17,10 @@
17
17
  "semantic": {
18
18
  "type": "string"
19
19
  },
20
+ "title": {
21
+ "type": "string",
22
+ "description": "Optional screen title for navigation bars and headers. When omitted, inferred from the first data_display title in the layout or the nav item label."
23
+ },
20
24
  "status": {
21
25
  "type": "string",
22
26
  "enum": [
@@ -182,6 +186,11 @@
182
186
  "input_type": {
183
187
  "type": "string"
184
188
  },
189
+ "render_hint": {
190
+ "type": "string",
191
+ "enum": ["dropdown", "segmented", "radio_group", "bottom_sheet"],
192
+ "description": "Optional hint for how an input_field with input_type select should render. When omitted, the platform default is used (e.g., Picker on iOS, ExposedDropdownMenuBox on Android, select on Web)."
193
+ },
185
194
  "size": {
186
195
  "type": "string"
187
196
  },
@@ -407,8 +407,6 @@ function validateFile(
407
407
  return errors.length;
408
408
  }
409
409
 
410
- // ── validation groups ────────────────────────────────────────────────
411
-
412
410
  // ── includes resolution ──────────────────────────────────────────────
413
411
 
414
412
  interface Includes {
@@ -13,6 +13,8 @@
13
13
 
14
14
  OpenUISpec is not a cross-platform framework. It is a **semantic design language specification** from which AI generates native platform code. The spec describes *what* UI does and *how it should feel* — never *which widget to use*.
15
15
 
16
+ OpenUISpec is a shared UI sync language for native products, optimized for solo developers but also intended for teams that need a durable synchronization layer between product intent and platform code. It is not a pixel-perfect design description and it does not aim to erase platform differences. Its goal is semantic consistency with bounded native variation.
17
+
16
18
  ### Core principles
17
19
 
18
20
  1. **Semantic over visual.** The spec defines behavioral intent, not pixel layouts. A "primary action trigger" maps to `Button` in SwiftUI, `Button` in Compose, and `<button>` in HTML — the spec never says "button."
@@ -833,6 +835,12 @@ input_field:
833
835
  suffix: { type: string, required: false }
834
836
  icon: { type: icon_ref, required: false, position: [leading, trailing] }
835
837
  clearable: { type: bool, default: false }
838
+ render_hint:
839
+ type: enum
840
+ values: [dropdown, segmented, radio_group, bottom_sheet]
841
+ required: false
842
+ condition: "input_type == select"
843
+ description: "Overrides the default platform widget for select fields. When omitted, the platform default is used."
836
844
 
837
845
  states:
838
846
  empty:
@@ -907,6 +915,7 @@ input_field:
907
915
  text: { widget: "TextField" }
908
916
  multiline: { widget: "TextEditor" }
909
917
  select: { widget: "Picker" }
918
+ select[segmented]: { widget: "Picker(style: .segmented)" }
910
919
  toggle: { widget: "Toggle" }
911
920
  slider: { widget: "Slider" }
912
921
  date: { widget: "DatePicker" }
@@ -914,6 +923,7 @@ input_field:
914
923
  text: { composable: "OutlinedTextField" }
915
924
  multiline: { composable: "OutlinedTextField(singleLine=false)" }
916
925
  select: { composable: "ExposedDropdownMenuBox" }
926
+ select[segmented]: { composable: "SegmentedButton / SingleChoiceSegmentedButtonRow" }
917
927
  toggle: { composable: "Switch" }
918
928
  slider: { composable: "Slider" }
919
929
  date: { composable: "DatePicker" }
@@ -921,6 +931,7 @@ input_field:
921
931
  text: { element: "input", type: "text" }
922
932
  multiline: { element: "textarea" }
923
933
  select: { element: "select" }
934
+ select[segmented]: { element: "fieldset > input[type=radio]", styled: "segmented control" }
924
935
  toggle: { element: "input", type: "checkbox", role: "switch" }
925
936
  slider: { element: "input", type: "range" }
926
937
  date: { element: "input", type: "date" }
@@ -1482,7 +1493,8 @@ Screens compose contracts into layouts. A screen never references platform widge
1482
1493
  # Example: screens/order_detail.yaml
1483
1494
  order_detail:
1484
1495
  semantic: "Displays detailed information about a single order"
1485
-
1496
+ title: "$t:order_detail.title" # Optional — shown in nav bar / browser tab
1497
+
1486
1498
  params:
1487
1499
  order_id: { type: string, required: true }
1488
1500
 
@@ -1559,7 +1571,19 @@ order_detail:
1559
1571
 
1560
1572
  ### 5.1 Screen-level keys
1561
1573
 
1562
- Beyond contract props and layout primitives, screen files use several keys that modify how sections and contract instances behave. These keys are available on any section or contract instance within a screen's `sections:` array.
1574
+ Screen definitions support the following top-level keys alongside `semantic`, `layout`, `data`, `state`, `params`, `navigation`, and `surfaces`:
1575
+
1576
+ #### `title`
1577
+
1578
+ Optional display title for the screen, shown in navigation bars, browser tabs, and back-button labels. When omitted, generators should infer the title from the first `data_display` title in the layout, or from the corresponding `nav_container` item label.
1579
+
1580
+ ```yaml
1581
+ settings:
1582
+ semantic: "Preferences screen for language and theme"
1583
+ title: "$t:settings.title" # "Preferences"
1584
+ ```
1585
+
1586
+ Beyond these, screen files use several keys that modify how sections and contract instances behave. These keys are available on any section or contract instance within a screen's `sections:` array.
1563
1587
 
1564
1588
  #### `tokens_override`
1565
1589
 
@@ -1720,6 +1744,8 @@ Layout primitives are the building blocks for arranging content. They replace th
1720
1744
  | `split_view` | Side-by-side master-detail | `NavigationSplitView` | `ListDetailPaneScaffold` | CSS Grid |
1721
1745
  | `adaptive` | Changes layout per size class | — | — | — |
1722
1746
 
1747
+ Layout primitives must remain usable across the size classes they are generated for. Multi-pane or master-detail patterns require an explicit compact fallback rather than assuming large-screen behavior will degrade correctly.
1748
+
1723
1749
  Each primitive has typed props:
1724
1750
 
1725
1751
  ```yaml
@@ -2022,17 +2048,21 @@ This section defines the rules any AI code generator must follow when producing
2022
2048
  Every AI generator, regardless of platform target, MUST:
2023
2049
 
2024
2050
  1. Produce **compilable code** that builds without errors on the target platform.
2025
- 2. Map every `contract` reference to the correct native widget per `platform_mapping`.
2026
- 3. Apply all `tokens` values within their declared `range` constraints.
2027
- 4. Implement every `state` declared in each used contract, including transitions.
2028
- 5. Set correct `a11y.role` and `a11y.label` for every component instance. For contextual containers such as `collection`, derive the label from the visible heading/header via `aria-labelledby` or the platform equivalent when possible instead of requiring a dedicated prop.
2029
- 6. Respect `themes` by generating light/dark mode support.
2030
- 7. Handle `empty`, `loading`, and `error` states for `collection` contracts.
2031
- 8. Wire all `action.navigate` declarations to the platform's navigation system.
2032
- 9. Apply `motion.reduced_motion` preferences globally.
2033
- 10. Implement all three size classes (`compact`, `regular`, `expanded`) and apply `adaptive` overrides from screen files.
2034
- 11. Validate all `props` types and report spec errors before generating code.
2035
- 12. Generate platform-native localization resources from JSON locale files when `i18n` config is present (see Section 11).
2051
+ 2. Refresh its knowledge of the current target platform implementation model before generation when the output depends on toolchain-specific conventions, project formats, packaging rules, resource wiring, or fast-moving framework APIs.
2052
+ 3. Map every `contract` reference to the correct native widget per `platform_mapping`.
2053
+ 4. Apply all `tokens` values within their declared `range` constraints.
2054
+ 5. Implement every `state` declared in each used contract, including transitions.
2055
+ 6. Set correct `a11y.role` and `a11y.label` for every component instance. For contextual containers such as `collection`, derive the label from the visible heading/header via `aria-labelledby` or the platform equivalent when possible instead of requiring a dedicated prop.
2056
+ 7. Respect `themes` by generating light/dark mode support.
2057
+ 8. Handle `empty`, `loading`, and `error` states for `collection` contracts.
2058
+ 9. Wire all `action.navigate` declarations to the platform's navigation system.
2059
+ 10. Apply `motion.reduced_motion` preferences globally.
2060
+ 11. Implement all three size classes (`compact`, `regular`, `expanded`) and apply `adaptive` overrides from screen files.
2061
+ 12. Validate all `props` types and report spec errors before generating code.
2062
+ 13. Generate platform-native localization resources from JSON locale files when `i18n` config is present (see Section 11).
2063
+ 14. Emit a valid native project or target configuration that bundles every generated runtime resource, including localization files, assets, and generated metadata. A generated resource file that is not connected to the runnable target is non-compliant.
2064
+ 15. Adapt navigation and container patterns for every supported size class and form factor. A generator may not assume that a large-screen or multi-pane layout will remain usable on compact layouts without an explicit fallback.
2065
+ 16. Preserve required contract semantics when mapping to native widgets. A platform-native substitution may change the implementation primitive, but it may not drop required token-driven shapes, borders, visual states, or interaction semantics declared by the contract.
2036
2066
 
2037
2067
  ### 8.3 Validation
2038
2068
 
@@ -2640,8 +2670,9 @@ Format expressions transform values for display. They appear inside `{}` delimit
2640
2670
  ```
2641
2671
  interpolation := '{' (piped_expr | computed_expr) '}'
2642
2672
  piped_expr := data_path ('|' pipe)*
2643
- pipe := operation ':' argument
2673
+ pipe := operation ':' argument ('.' option)?
2644
2674
  operation := 'format' | 'map' | 'default'
2675
+ option := identifier # e.g., abbreviated, narrow
2645
2676
  computed_expr := data_path comparator value '?' literal ':' literal
2646
2677
  comparator := '==' | '!=' | '>' | '<' | '>=' | '<='
2647
2678
  locale_ref := '$t:' locale_key
@@ -2697,18 +2728,18 @@ subtitle: "{item.quantity} × {item.unit_price | format:currency}"
2697
2728
 
2698
2729
  **Built-in formatters:**
2699
2730
 
2700
- | Formatter | Input | Output | Locale-aware |
2701
- |-----------|-------|--------|-------------|
2702
- | `currency` | number | "$1,234.56" | Yes |
2703
- | `date` | date/datetime | "Mar 13, 2026" | Yes |
2704
- | `date_relative` | date/datetime | "2 hours ago", "yesterday" | Yes |
2705
- | `date_short` | date/datetime | "Mar 13" | Yes |
2706
- | `time` | datetime | "3:45 PM" | Yes |
2707
- | `number` | number | "1,234" | Yes |
2708
- | `percentage` | number (0-1) | "45%" | No |
2709
- | `status_label` | enum string | "In Progress" (title case) | No |
2710
- | `pluralize` | number | "1 task" / "3 tasks" | Yes |
2711
- | `file_size` | number (bytes) | "2.4 MB" | No |
2731
+ | Formatter | Input | Output | Locale-aware | Options |
2732
+ |-----------|-------|--------|-------------|---------|
2733
+ | `currency` | number | "$1,234.56" | Yes | — |
2734
+ | `date` | date/datetime | "Mar 13, 2026" | Yes | — |
2735
+ | `date_relative` | date/datetime | "2 hours ago", "yesterday" | Yes | `style`: full (default), abbreviated, narrow |
2736
+ | `date_short` | date/datetime | "Mar 13" | Yes | — |
2737
+ | `time` | datetime | "3:45 PM" | Yes | — |
2738
+ | `number` | number | "1,234" | Yes | — |
2739
+ | `percentage` | number (0-1) | "45%" | No | — |
2740
+ | `status_label` | enum string | "In Progress" (title case) | No | — |
2741
+ | `pluralize` | number | "1 task" / "3 tasks" | Yes | — |
2742
+ | `file_size` | number (bytes) | "2.4 MB" | No | — |
2712
2743
 
2713
2744
  **Built-in mappers:**
2714
2745
 
@@ -2718,6 +2749,21 @@ subtitle: "{item.quantity} × {item.unit_price | format:currency}"
2718
2749
  | `priority_to_severity` | priority enum → severity enum (e.g., "urgent" → "error") |
2719
2750
  | `bool_to_label` | true/false → "Yes"/"No" (or custom mapping) |
2720
2751
 
2752
+ **Formatter options** — some built-in formatters accept an `options` parameter, passed with a dot suffix:
2753
+
2754
+ ```yaml
2755
+ # Default style (full): "in 2 days", "3 hours ago"
2756
+ subtitle: "{task.due_date | format:date_relative}"
2757
+
2758
+ # Abbreviated: "in 2d", "3h ago"
2759
+ subtitle: "{task.due_date | format:date_relative.abbreviated}"
2760
+
2761
+ # Narrow: "2d", "3h"
2762
+ subtitle: "{task.due_date | format:date_relative.narrow}"
2763
+ ```
2764
+
2765
+ When no option is provided, the default style is used. Generators must respect the option across all platforms to ensure consistent output.
2766
+
2721
2767
  **Custom formatters and mappers** can be defined in the project manifest:
2722
2768
 
2723
2769
  ```yaml
@@ -2975,7 +3021,7 @@ Generators produce platform-native localization resources from the JSON source:
2975
3021
 
2976
3022
  | Platform | Output format | Plurals | Notes |
2977
3023
  |----------|--------------|---------|-------|
2978
- | **iOS** | `.xcstrings` (Xcode 15+) | Built-in plural rules | ICU plurals map to `.stringsdict` entries within `.xcstrings` |
3024
+ | **iOS** | `.xcstrings` (Xcode 15+) or correctly bundled `.strings` resources | Built-in plural rules | ICU plurals map to `.stringsdict` entries within `.xcstrings`; generated locale resources must be attached to the runnable target and resolvable at runtime |
2979
3025
  | **Android** | `res/values-{locale}/strings.xml` + `plurals.xml` | `<plurals>` element | ICU selects map to conditional logic in generated code |
2980
3026
  | **Web** | JSON bundles per locale | `react-intl` / `i18next` ICU plugin | Direct ICU MessageFormat — no conversion needed |
2981
3027
 
@@ -2993,6 +3039,7 @@ i18n:
2993
3039
  **MUST:**
2994
3040
  - Resolve every `$t:key` reference to the corresponding locale string
2995
3041
  - Generate platform-native locale files from JSON sources for each supported locale
3042
+ - Ensure generated locale files are actually bundled by the runnable platform target and available at runtime
2996
3043
  - Pass `t_params` data paths to ICU placeholders at runtime
2997
3044
  - Apply `$direction` from the active locale to layout direction
2998
3045
  - Use the `fallback_strategy` for missing keys (default: fall back to `default_locale`)
@@ -3201,6 +3248,61 @@ ios:
3201
3248
  - Generate test code based on `test_cases`
3202
3249
  - Add platform-specific enhancements beyond what the contract specifies
3203
3250
 
3251
+ ### 12.8 Extending standard contracts
3252
+
3253
+ The 7 built-in contract families (Section 4) can be extended per-project using `contracts/<name>.yaml` files. Extensions add project-specific **variants**, **token overrides**, **platform mapping**, and **generation hints** without redefining the base contract. The base definition (props, states, a11y) remains authoritative from the spec.
3254
+
3255
+ ```yaml
3256
+ # contracts/input_field.yaml
3257
+ input_field:
3258
+ variants:
3259
+ cut_corner:
3260
+ semantic: "Angled corner input for branded forms"
3261
+ tokens:
3262
+ cut_size: "spacing.sm"
3263
+ border: { color: "color.semantic.border", width: 1 }
3264
+ platform_mapping:
3265
+ ios: { shape: "CutCornerShape", clip: true }
3266
+ android: { shape: "CutCornerShape" }
3267
+ web: { style: "clip-path" }
3268
+ generation:
3269
+ must_handle:
3270
+ - "Cut top-right and bottom-left corners by cut_size"
3271
+ - "Maintain focus ring that follows the cut shape"
3272
+ should_handle:
3273
+ - "Animate corner cut on focus"
3274
+ ```
3275
+
3276
+ **Root key:** The contract family name (e.g. `input_field`, `action_trigger`). Not `x_` prefixed — that is reserved for fully custom contracts.
3277
+
3278
+ **Available extension fields** (all optional):
3279
+
3280
+ | Field | Purpose |
3281
+ |-------|---------|
3282
+ | `variants` | Named style/behavior presets with their own tokens, platform_mapping, and generation hints |
3283
+ | `additional_props` | Props beyond what the spec defines for this contract |
3284
+ | `tokens` | Contract-level token overrides |
3285
+ | `platform_mapping` | Per-platform implementation hints |
3286
+ | `generation` | AI generation hints (must_handle, should_handle, may_handle) |
3287
+ | `test_cases` | Behavioral verification scenarios |
3288
+
3289
+ **Usage in screens:** Reference a variant by name in the `variant` field of a contract instance:
3290
+
3291
+ ```yaml
3292
+ - contract: input_field
3293
+ variant: cut_corner
3294
+ props:
3295
+ label: "Email"
3296
+ input_type: email
3297
+ ```
3298
+
3299
+ **Key differences from custom contracts (Section 12.1–12.7):**
3300
+
3301
+ - Standard extensions do **not** redefine `semantic`, `props`, `states`, or `a11y` — these come from the spec
3302
+ - Required props from the base contract still apply (e.g. `input_field` always requires `props.label`)
3303
+ - An empty extension (`input_field: {}`) is valid and means "use the spec definition as-is"
3304
+ - Validated against `contract.schema.json`, not `custom-contract.schema.json`
3305
+
3204
3306
  ---
3205
3307
 
3206
3308
  ## 13. Form validation and field dependencies