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.
- package/README.md +52 -34
- package/cli/index.ts +1 -1
- package/cli/init.ts +48 -211
- package/docs/stress-test-maturity-report.md +97 -0
- package/examples/todo-orbit/AGENTS.md +127 -0
- package/examples/todo-orbit/CLAUDE.md +75 -0
- package/examples/todo-orbit/README.md +62 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/README.md +14 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/build.gradle.kts +58 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/proguard-rules.pro +1 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/AndroidManifest.xml +20 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/MainActivity.kt +14 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/TodoOrbitApp.kt +345 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/AppLogic.kt +231 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Models.kt +169 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Strings.kt +8 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/components/CommonComponents.kt +185 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/AnalyticsScreen.kt +193 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/SettingsScreen.kt +102 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/TasksScreen.kt +342 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/sheets/EditorSheets.kt +344 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/theme/TodoOrbitTheme.kt +59 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values/strings.xml +148 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values-ru/strings.xml +154 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/build.gradle.kts +4 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/gradle.properties +4 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/gradlew +248 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/gradlew.bat +93 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/settings.gradle.kts +18 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/README.md +29 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/en.lproj/Localizable.strings +118 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/ru.lproj/Localizable.strings +118 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/App/TodoOrbitApp.swift +50 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/OrbitChrome.swift +204 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/SchedulePreviewView.swift +126 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/TrendChartView.swift +70 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/RecurringRuleSheet.swift +123 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/TaskEditorSheet.swift +60 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Models/DomainModels.swift +238 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/AnalyticsView.swift +94 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/SettingsView.swift +74 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/TasksHomeView.swift +363 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Support/AppModel.swift +324 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.pbxproj +408 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/xcshareddata/xcschemes/TodoOrbit.xcscheme +79 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/project.yml +24 -0
- package/examples/todo-orbit/generated/web/Todo Orbit/index.html +16 -0
- package/examples/todo-orbit/generated/web/Todo Orbit/package-lock.json +1087 -0
- package/examples/todo-orbit/generated/web/Todo Orbit/package.json +24 -0
- package/examples/todo-orbit/generated/web/Todo Orbit/src/App.tsx +2114 -0
- package/examples/todo-orbit/generated/web/Todo Orbit/src/main.tsx +13 -0
- package/examples/todo-orbit/generated/web/Todo Orbit/src/styles.css +886 -0
- package/examples/todo-orbit/generated/web/Todo Orbit/tsconfig.json +19 -0
- package/examples/todo-orbit/generated/web/Todo Orbit/vite.config.ts +6 -0
- package/examples/todo-orbit/openuispec/README.md +158 -0
- package/examples/todo-orbit/openuispec/contracts/.gitkeep +0 -0
- package/examples/todo-orbit/openuispec/contracts/action_trigger.yaml +28 -0
- package/examples/todo-orbit/openuispec/contracts/collection.yaml +32 -0
- package/examples/todo-orbit/openuispec/contracts/data_display.yaml +38 -0
- package/examples/todo-orbit/openuispec/contracts/feedback.yaml +32 -0
- package/examples/todo-orbit/openuispec/contracts/input_field.yaml +52 -0
- package/examples/todo-orbit/openuispec/contracts/nav_container.yaml +47 -0
- package/examples/todo-orbit/openuispec/contracts/surface.yaml +28 -0
- package/examples/todo-orbit/openuispec/contracts/x_schedule_preview.yaml +134 -0
- package/examples/todo-orbit/openuispec/contracts/x_task_trend_chart.yaml +139 -0
- package/examples/todo-orbit/openuispec/flows/.gitkeep +0 -0
- package/examples/todo-orbit/openuispec/flows/create_recurring_rule.yaml +253 -0
- package/examples/todo-orbit/openuispec/flows/create_task.yaml +118 -0
- package/examples/todo-orbit/openuispec/flows/edit_task.yaml +126 -0
- package/examples/todo-orbit/openuispec/locales/.gitkeep +0 -0
- package/examples/todo-orbit/openuispec/locales/en.json +150 -0
- package/examples/todo-orbit/openuispec/locales/ru.json +150 -0
- package/examples/todo-orbit/openuispec/openuispec.yaml +122 -0
- package/examples/todo-orbit/openuispec/platform/.gitkeep +0 -0
- package/examples/todo-orbit/openuispec/platform/android.yaml +19 -0
- package/examples/todo-orbit/openuispec/platform/ios.yaml +20 -0
- package/examples/todo-orbit/openuispec/platform/web.yaml +22 -0
- package/examples/todo-orbit/openuispec/screens/.gitkeep +0 -0
- package/examples/todo-orbit/openuispec/screens/analytics.yaml +140 -0
- package/examples/todo-orbit/openuispec/screens/home.yaml +173 -0
- package/examples/todo-orbit/openuispec/screens/settings.yaml +149 -0
- package/examples/todo-orbit/openuispec/screens/task_detail.yaml +223 -0
- package/examples/todo-orbit/openuispec/tokens/.gitkeep +0 -0
- package/examples/todo-orbit/openuispec/tokens/color.yaml +93 -0
- package/examples/todo-orbit/openuispec/tokens/elevation.yaml +25 -0
- package/examples/todo-orbit/openuispec/tokens/icons.yaml +92 -0
- package/examples/todo-orbit/openuispec/tokens/layout.yaml +107 -0
- package/examples/todo-orbit/openuispec/tokens/motion.yaml +39 -0
- package/examples/todo-orbit/openuispec/tokens/spacing.yaml +18 -0
- package/examples/todo-orbit/openuispec/tokens/themes.yaml +23 -0
- package/examples/todo-orbit/openuispec/tokens/typography.yaml +52 -0
- package/package.json +1 -1
- package/schema/screen.schema.json +9 -0
- package/schema/validate.ts +0 -2
- 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
|
@@ -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
|
},
|
package/schema/validate.ts
CHANGED
package/spec/openuispec-v0.1.md
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
2026
|
-
3.
|
|
2027
|
-
4.
|
|
2028
|
-
5.
|
|
2029
|
-
6.
|
|
2030
|
-
7.
|
|
2031
|
-
8.
|
|
2032
|
-
9.
|
|
2033
|
-
10.
|
|
2034
|
-
11.
|
|
2035
|
-
12.
|
|
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
|