openuispec 0.2.13 → 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.
- package/README.md +6 -5
- package/cli/index.ts +18 -12
- package/cli/init.ts +78 -13
- package/docs/cli.md +81 -27
- package/docs/file-formats.md +51 -1
- package/drift/index.ts +7 -2
- package/examples/social-app/openuispec/README.md +2 -1
- package/examples/social-app/openuispec/mock/chat_detail.yaml +25 -0
- package/examples/social-app/openuispec/mock/discover.yaml +17 -0
- package/examples/social-app/openuispec/mock/edit_profile.yaml +9 -0
- package/examples/social-app/openuispec/mock/home_feed.yaml +32 -0
- package/examples/social-app/openuispec/mock/messages_inbox.yaml +15 -0
- package/examples/social-app/openuispec/mock/notifications.yaml +30 -0
- package/examples/social-app/openuispec/mock/post_detail.yaml +26 -0
- package/examples/social-app/openuispec/mock/profile_self.yaml +28 -0
- package/examples/social-app/openuispec/mock/profile_user.yaml +32 -0
- package/examples/social-app/openuispec/mock/search_results.yaml +17 -0
- package/examples/social-app/openuispec/mock/settings.yaml +7 -0
- package/examples/social-app/openuispec/openuispec.yaml +3 -2
- package/examples/taskflow/README.md +4 -2
- package/examples/taskflow/openuispec/README.md +2 -1
- package/examples/taskflow/openuispec/components/media_player.yaml +92 -0
- package/examples/taskflow/openuispec/contracts/README.md +2 -2
- package/examples/taskflow/openuispec/locales/en.json +1 -0
- package/examples/taskflow/openuispec/mock/home.yaml +64 -0
- package/examples/taskflow/openuispec/mock/profile_edit.yaml +6 -0
- package/examples/taskflow/openuispec/mock/project_detail.yaml +33 -0
- package/examples/taskflow/openuispec/mock/settings.yaml +13 -0
- package/examples/taskflow/openuispec/mock/task_detail.yaml +18 -0
- package/examples/taskflow/openuispec/openuispec.yaml +3 -4
- package/examples/taskflow/openuispec/platform/ios.yaml +0 -4
- package/examples/taskflow/openuispec/screens/task_detail.yaml +5 -8
- package/examples/taskflow/openuispec/tokens/icons.yaml +16 -0
- package/examples/todo-orbit/README.md +3 -2
- package/examples/todo-orbit/openuispec/README.md +2 -1
- package/examples/todo-orbit/openuispec/components/task_trend_chart.yaml +85 -0
- package/examples/todo-orbit/openuispec/locales/en.json +3 -0
- package/examples/todo-orbit/openuispec/locales/ru.json +3 -0
- package/examples/todo-orbit/openuispec/mock/analytics.yaml +26 -0
- package/examples/todo-orbit/openuispec/mock/home.yaml +33 -0
- package/examples/todo-orbit/openuispec/mock/settings.yaml +7 -0
- package/examples/todo-orbit/openuispec/mock/task_detail.yaml +14 -0
- package/examples/todo-orbit/openuispec/openuispec.yaml +3 -3
- package/examples/todo-orbit/openuispec/platform/android.yaml +0 -3
- package/examples/todo-orbit/openuispec/platform/ios.yaml +0 -3
- package/examples/todo-orbit/openuispec/platform/web.yaml +0 -3
- package/examples/todo-orbit/openuispec/screens/analytics.yaml +1 -4
- package/mcp-server/index.ts +80 -3
- package/mcp-server/preview-render.ts +1922 -0
- package/mcp-server/preview.ts +292 -0
- package/mcp-server/screenshot-shared.ts +38 -0
- package/mcp-server/screenshot.ts +3 -32
- package/package.json +1 -1
- package/prepare/index.ts +1 -1
- package/schema/component.schema.json +278 -0
- package/schema/openuispec.schema.json +5 -1
- package/schema/screen.schema.json +12 -1
- package/schema/semantic-lint.ts +24 -2
- package/schema/validate.ts +21 -0
- package/scripts/regenerate-previews.ts +136 -0
- package/spec/{openuispec-v0.1.md → openuispec-v0.2.md} +266 -8
- package/examples/taskflow/openuispec/contracts/x_media_player.yaml +0 -185
- package/examples/todo-orbit/openuispec/contracts/x_task_trend_chart.yaml +0 -139
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
# ============================================================
|
|
2
|
-
# Custom Contract: x_media_player
|
|
3
|
-
# ============================================================
|
|
4
|
-
# A media playback contract for audio and video content.
|
|
5
|
-
# Demonstrates the custom contract extension mechanism
|
|
6
|
-
# (see spec Section 12).
|
|
7
|
-
# ============================================================
|
|
8
|
-
|
|
9
|
-
x_media_player:
|
|
10
|
-
semantic: "Plays audio and video media with transport controls, state management, and platform-native playback"
|
|
11
|
-
|
|
12
|
-
props:
|
|
13
|
-
source: { type: string, required: true, description: "Media URL or asset path" }
|
|
14
|
-
media_type:
|
|
15
|
-
type: enum
|
|
16
|
-
values: [audio, video]
|
|
17
|
-
required: true
|
|
18
|
-
description: "Type of media content"
|
|
19
|
-
variant:
|
|
20
|
-
type: enum
|
|
21
|
-
values: [inline, fullscreen, mini]
|
|
22
|
-
default: inline
|
|
23
|
-
description: "Player presentation style"
|
|
24
|
-
autoplay: { type: bool, default: false, description: "Begin playback automatically when source loads" }
|
|
25
|
-
loop: { type: bool, default: false, description: "Restart playback when media ends" }
|
|
26
|
-
show_controls: { type: bool, default: true, description: "Display transport controls" }
|
|
27
|
-
poster: { type: media_ref, required: false, description: "Preview image shown before playback (video only)" }
|
|
28
|
-
title: { type: string, required: false, description: "Media title for display and accessibility" }
|
|
29
|
-
subtitle: { type: string, required: false, description: "Secondary text (artist, channel, etc.)" }
|
|
30
|
-
playback_rate: { type: enum, values: ["0.5", "0.75", "1", "1.25", "1.5", "2"], default: "1", description: "Playback speed multiplier" }
|
|
31
|
-
muted: { type: bool, default: false, description: "Start with audio muted" }
|
|
32
|
-
|
|
33
|
-
states:
|
|
34
|
-
idle:
|
|
35
|
-
semantic: "No media loaded or playback not started"
|
|
36
|
-
transitions_to: [loading]
|
|
37
|
-
visual: "Shows poster image or placeholder; controls hidden or minimal"
|
|
38
|
-
loading:
|
|
39
|
-
semantic: "Media source is buffering"
|
|
40
|
-
transitions_to: [playing, error]
|
|
41
|
-
duration: "motion.standard"
|
|
42
|
-
feedback: "Loading indicator visible"
|
|
43
|
-
visual: "Spinner or progress bar overlaid on poster"
|
|
44
|
-
playing:
|
|
45
|
-
semantic: "Media is actively playing"
|
|
46
|
-
transitions_to: [paused, ended, loading, error]
|
|
47
|
-
behavior: "Progress bar advances, elapsed time updates"
|
|
48
|
-
visual: "Active playback with visible progress and controls"
|
|
49
|
-
paused:
|
|
50
|
-
semantic: "Playback is paused at current position"
|
|
51
|
-
transitions_to: [playing, loading]
|
|
52
|
-
visual: "Frozen frame (video) or paused waveform (audio); play button prominent"
|
|
53
|
-
ended:
|
|
54
|
-
semantic: "Playback reached the end of the media"
|
|
55
|
-
transitions_to: [playing, loading]
|
|
56
|
-
behavior: "If loop is true, transitions to playing automatically"
|
|
57
|
-
visual: "Replay button visible; poster or last frame shown"
|
|
58
|
-
error:
|
|
59
|
-
semantic: "Media failed to load or playback encountered an error"
|
|
60
|
-
transitions_to: [loading]
|
|
61
|
-
feedback: "Error message displayed with retry option"
|
|
62
|
-
visual: "Error icon and message; retry button visible"
|
|
63
|
-
|
|
64
|
-
a11y:
|
|
65
|
-
role: "media"
|
|
66
|
-
label: "props.title"
|
|
67
|
-
traits:
|
|
68
|
-
playing: { announces: "Playing" }
|
|
69
|
-
paused: { announces: "Paused" }
|
|
70
|
-
loading: { announces: "Loading media" }
|
|
71
|
-
error: { announces: "Media playback error" }
|
|
72
|
-
focus:
|
|
73
|
-
keyboard:
|
|
74
|
-
play_pause: "Space"
|
|
75
|
-
seek_forward: "ArrowRight"
|
|
76
|
-
seek_backward: "ArrowLeft"
|
|
77
|
-
volume_up: "ArrowUp"
|
|
78
|
-
volume_down: "ArrowDown"
|
|
79
|
-
fullscreen: "f"
|
|
80
|
-
mute: "m"
|
|
81
|
-
|
|
82
|
-
tokens:
|
|
83
|
-
inline:
|
|
84
|
-
min_height: [200, 280]
|
|
85
|
-
radius: "spacing.md"
|
|
86
|
-
background: "color.surface.secondary"
|
|
87
|
-
controls_background: "rgba(0, 0, 0, 0.5)"
|
|
88
|
-
controls_color: "#FFFFFF"
|
|
89
|
-
progress_active: "color.brand.primary"
|
|
90
|
-
progress_inactive: "rgba(255, 255, 255, 0.3)"
|
|
91
|
-
progress_height: [3, 4]
|
|
92
|
-
title_style: "typography.caption"
|
|
93
|
-
subtitle_style: "typography.small"
|
|
94
|
-
fullscreen:
|
|
95
|
-
background: "#000000"
|
|
96
|
-
controls_background: "rgba(0, 0, 0, 0.6)"
|
|
97
|
-
controls_color: "#FFFFFF"
|
|
98
|
-
progress_active: "color.brand.primary"
|
|
99
|
-
progress_inactive: "rgba(255, 255, 255, 0.3)"
|
|
100
|
-
progress_height: [4, 6]
|
|
101
|
-
title_style: "typography.subtitle"
|
|
102
|
-
subtitle_style: "typography.body"
|
|
103
|
-
mini:
|
|
104
|
-
height: [48, 64]
|
|
105
|
-
radius: "spacing.sm"
|
|
106
|
-
background: "color.surface.secondary"
|
|
107
|
-
progress_active: "color.brand.primary"
|
|
108
|
-
progress_height: [2, 3]
|
|
109
|
-
title_style: "typography.caption"
|
|
110
|
-
|
|
111
|
-
platform_mapping:
|
|
112
|
-
ios:
|
|
113
|
-
inline: { component: "VideoPlayer", framework: "AVKit" }
|
|
114
|
-
fullscreen: { component: "AVPlayerViewController", framework: "AVKit" }
|
|
115
|
-
mini: { component: "Custom mini player view", framework: "AVKit" }
|
|
116
|
-
audio: { component: "Custom audio player", framework: "AVFoundation" }
|
|
117
|
-
android:
|
|
118
|
-
inline: { component: "PlayerView", library: "androidx.media3" }
|
|
119
|
-
fullscreen: { component: "PlayerView (fullscreen activity)", library: "androidx.media3" }
|
|
120
|
-
mini: { component: "Custom mini player composable", library: "androidx.media3" }
|
|
121
|
-
audio: { component: "PlayerView (audio mode)", library: "androidx.media3" }
|
|
122
|
-
web:
|
|
123
|
-
inline: { element: "video", fallback: "audio" }
|
|
124
|
-
fullscreen: { element: "video", api: "Fullscreen API" }
|
|
125
|
-
mini: { element: "Custom mini player component" }
|
|
126
|
-
audio: { element: "audio" }
|
|
127
|
-
|
|
128
|
-
dependencies:
|
|
129
|
-
ios:
|
|
130
|
-
frameworks: [AVKit, AVFoundation]
|
|
131
|
-
android:
|
|
132
|
-
libraries: ["androidx.media3:media3-ui", "androidx.media3:media3-exoplayer"]
|
|
133
|
-
web:
|
|
134
|
-
packages: []
|
|
135
|
-
|
|
136
|
-
generation:
|
|
137
|
-
must_handle:
|
|
138
|
-
- "All 6 states (idle, loading, playing, paused, ended, error) with correct transitions"
|
|
139
|
-
- "Keyboard shortcuts for accessibility (Space, arrows, f, m)"
|
|
140
|
-
- "Poster/placeholder display in idle state"
|
|
141
|
-
- "Error state with retry action"
|
|
142
|
-
- "Platform-native player component per platform_mapping"
|
|
143
|
-
should_handle:
|
|
144
|
-
- "Playback rate selection UI"
|
|
145
|
-
- "Progress bar with seek interaction"
|
|
146
|
-
- "Volume control"
|
|
147
|
-
- "Elapsed / remaining time display"
|
|
148
|
-
- "Mini variant with compact controls"
|
|
149
|
-
may_handle:
|
|
150
|
-
- "Picture-in-picture support (iOS, web)"
|
|
151
|
-
- "AirPlay / Cast integration"
|
|
152
|
-
- "Subtitle / closed caption support"
|
|
153
|
-
- "Background audio playback"
|
|
154
|
-
- "Gesture controls (swipe to seek, pinch to zoom)"
|
|
155
|
-
|
|
156
|
-
test_cases:
|
|
157
|
-
- id: play_pause_toggle
|
|
158
|
-
description: "Tapping play starts playback; tapping pause freezes it"
|
|
159
|
-
given: "Player is in idle state with a valid source"
|
|
160
|
-
when: "User taps the play button"
|
|
161
|
-
then: "State transitions to loading, then playing; tapping pause transitions to paused"
|
|
162
|
-
|
|
163
|
-
- id: error_retry
|
|
164
|
-
description: "Failed media shows error with retry"
|
|
165
|
-
given: "Player source URL is unreachable"
|
|
166
|
-
when: "Player attempts to load the media"
|
|
167
|
-
then: "State transitions to error; retry button is visible; tapping retry transitions to loading"
|
|
168
|
-
|
|
169
|
-
- id: ended_loop
|
|
170
|
-
description: "Loop restarts playback at end"
|
|
171
|
-
given: "Player is playing with loop=true"
|
|
172
|
-
when: "Media reaches the end"
|
|
173
|
-
then: "State transitions to playing from the beginning without user interaction"
|
|
174
|
-
|
|
175
|
-
- id: keyboard_controls
|
|
176
|
-
description: "Keyboard shortcuts control playback"
|
|
177
|
-
given: "Player is focused and in playing state"
|
|
178
|
-
when: "User presses Space"
|
|
179
|
-
then: "State transitions to paused; pressing Space again transitions to playing"
|
|
180
|
-
|
|
181
|
-
- id: fullscreen_variant
|
|
182
|
-
description: "Fullscreen variant fills the viewport"
|
|
183
|
-
given: "Player variant is fullscreen"
|
|
184
|
-
when: "Player renders"
|
|
185
|
-
then: "Player fills the screen with black background; controls overlay the content"
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
x_task_trend_chart:
|
|
2
|
-
semantic: "Visualizes task creation and completion trends over time for productivity analysis"
|
|
3
|
-
|
|
4
|
-
props:
|
|
5
|
-
series: { type: "list<trend_point>", required: true, description: "Ordered trend points for the selected period" }
|
|
6
|
-
metric:
|
|
7
|
-
type: enum
|
|
8
|
-
values: [completed, created, completion_rate]
|
|
9
|
-
required: true
|
|
10
|
-
description: "Primary metric emphasized in the chart"
|
|
11
|
-
period:
|
|
12
|
-
type: enum
|
|
13
|
-
values: [week, month, quarter]
|
|
14
|
-
required: true
|
|
15
|
-
description: "Time range represented by the chart"
|
|
16
|
-
variant:
|
|
17
|
-
type: enum
|
|
18
|
-
values: [compact, detail]
|
|
19
|
-
default: compact
|
|
20
|
-
description: "Chart presentation density"
|
|
21
|
-
highlighted_index: { type: int, required: false, description: "Optional highlighted datapoint" }
|
|
22
|
-
show_legend: { type: bool, default: true, description: "Whether to render a legend for created/completed series" }
|
|
23
|
-
empty_message: { type: string, required: false, description: "Message to show when series is empty" }
|
|
24
|
-
|
|
25
|
-
states:
|
|
26
|
-
idle:
|
|
27
|
-
semantic: "Chart has not loaded data yet"
|
|
28
|
-
transitions_to: [loading]
|
|
29
|
-
visual: "Reserved chart frame with no plotted data"
|
|
30
|
-
loading:
|
|
31
|
-
semantic: "Trend data is loading"
|
|
32
|
-
transitions_to: [ready, empty, error]
|
|
33
|
-
feedback: "Skeleton chart and loading copy shown"
|
|
34
|
-
visual: "Placeholder bars or line skeleton"
|
|
35
|
-
ready:
|
|
36
|
-
semantic: "Trend data is available and rendered"
|
|
37
|
-
transitions_to: [highlighted, loading, error]
|
|
38
|
-
behavior: "Chart axes, series, and legend are visible"
|
|
39
|
-
highlighted:
|
|
40
|
-
semantic: "One datapoint is emphasized"
|
|
41
|
-
transitions_to: [ready]
|
|
42
|
-
behavior: "Highlighted datapoint shows stronger color and tooltip/callout"
|
|
43
|
-
empty:
|
|
44
|
-
semantic: "No trend points available"
|
|
45
|
-
transitions_to: [loading]
|
|
46
|
-
feedback: "Empty analytics message displayed"
|
|
47
|
-
visual: "Empty state in chart container"
|
|
48
|
-
error:
|
|
49
|
-
semantic: "Trend data failed to load"
|
|
50
|
-
transitions_to: [loading]
|
|
51
|
-
feedback: "Inline error with retry affordance"
|
|
52
|
-
visual: "Error indicator in chart container"
|
|
53
|
-
|
|
54
|
-
a11y:
|
|
55
|
-
role: "img"
|
|
56
|
-
label: "Analytics trend chart"
|
|
57
|
-
traits:
|
|
58
|
-
loading: { announces: "Loading analytics chart" }
|
|
59
|
-
empty: { announces: "No analytics data available" }
|
|
60
|
-
error: { announces: "Analytics chart failed to load" }
|
|
61
|
-
focus:
|
|
62
|
-
keyboard:
|
|
63
|
-
next_point: "ArrowRight"
|
|
64
|
-
previous_point: "ArrowLeft"
|
|
65
|
-
details: "Enter"
|
|
66
|
-
|
|
67
|
-
tokens:
|
|
68
|
-
compact:
|
|
69
|
-
min_height: 220
|
|
70
|
-
padding: "spacing.md"
|
|
71
|
-
background: "color.surface.secondary"
|
|
72
|
-
border: { width: 1, color: "color.border.default" }
|
|
73
|
-
radius: "spacing.sm"
|
|
74
|
-
axis_color: "color.text.tertiary"
|
|
75
|
-
completed_color: "color.brand.primary"
|
|
76
|
-
created_color: "color.brand.secondary"
|
|
77
|
-
grid_color: "color.border.default"
|
|
78
|
-
label_style: "typography.caption"
|
|
79
|
-
detail:
|
|
80
|
-
min_height: 320
|
|
81
|
-
padding: "spacing.lg"
|
|
82
|
-
background: "color.surface.primary"
|
|
83
|
-
border: { width: 1, color: "color.border.default" }
|
|
84
|
-
radius: "spacing.sm"
|
|
85
|
-
axis_color: "color.text.tertiary"
|
|
86
|
-
completed_color: "color.brand.primary"
|
|
87
|
-
created_color: "color.brand.secondary"
|
|
88
|
-
grid_color: "color.border.default"
|
|
89
|
-
label_style: "typography.body_sm"
|
|
90
|
-
|
|
91
|
-
platform_mapping:
|
|
92
|
-
ios:
|
|
93
|
-
compact: { component: "Chart", framework: "Swift Charts" }
|
|
94
|
-
detail: { component: "Chart", framework: "Swift Charts" }
|
|
95
|
-
android:
|
|
96
|
-
compact: { component: "Canvas chart composable", library: "custom compose drawing" }
|
|
97
|
-
detail: { component: "Canvas chart composable", library: "custom compose drawing" }
|
|
98
|
-
web:
|
|
99
|
-
compact: { component: "SVG chart component" }
|
|
100
|
-
detail: { component: "SVG chart component" }
|
|
101
|
-
|
|
102
|
-
dependencies:
|
|
103
|
-
ios:
|
|
104
|
-
frameworks: ["Charts"]
|
|
105
|
-
android:
|
|
106
|
-
libraries: []
|
|
107
|
-
web:
|
|
108
|
-
packages: []
|
|
109
|
-
|
|
110
|
-
generation:
|
|
111
|
-
must_handle:
|
|
112
|
-
- "Render completed and created series for the supplied period"
|
|
113
|
-
- "Respect compact and detail variants with different density"
|
|
114
|
-
- "Provide accessible summary text for the focused datapoint"
|
|
115
|
-
- "Handle loading, empty, and error states in the chart container"
|
|
116
|
-
should_handle:
|
|
117
|
-
- "Animate transitions when highlighted_index changes"
|
|
118
|
-
- "Render a legend when show_legend is true"
|
|
119
|
-
- "Support touch and pointer hover highlighting"
|
|
120
|
-
may_handle:
|
|
121
|
-
- "Tooltips for datapoints"
|
|
122
|
-
- "Trend delta badges and annotations"
|
|
123
|
-
|
|
124
|
-
test_cases:
|
|
125
|
-
- id: ready_state_renders_series
|
|
126
|
-
description: "Chart renders series lines or bars when data is present"
|
|
127
|
-
given: "Chart receives 7 trend points for period week"
|
|
128
|
-
when: "The component enters ready state"
|
|
129
|
-
then: "Both created and completed series are visible with labels and axes"
|
|
130
|
-
- id: empty_state_message
|
|
131
|
-
description: "Chart shows an empty message when no data exists"
|
|
132
|
-
given: "Chart receives an empty series"
|
|
133
|
-
when: "The component enters empty state"
|
|
134
|
-
then: "The empty_message or default empty copy is rendered in the chart frame"
|
|
135
|
-
- id: highlighted_point
|
|
136
|
-
description: "Highlighted point receives emphasis"
|
|
137
|
-
given: "Chart is in ready state with highlighted_index set"
|
|
138
|
-
when: "The highlighted datapoint is rendered"
|
|
139
|
-
then: "That datapoint is visually emphasized and exposed to accessibility"
|