openuispec 0.1.0

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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -0
  3. package/cli/index.ts +49 -0
  4. package/cli/init.ts +390 -0
  5. package/drift/index.ts +398 -0
  6. package/examples/taskflow/README.md +103 -0
  7. package/examples/taskflow/contracts/README.md +18 -0
  8. package/examples/taskflow/contracts/action_trigger.yaml +7 -0
  9. package/examples/taskflow/contracts/collection.yaml +7 -0
  10. package/examples/taskflow/contracts/data_display.yaml +7 -0
  11. package/examples/taskflow/contracts/feedback.yaml +7 -0
  12. package/examples/taskflow/contracts/input_field.yaml +7 -0
  13. package/examples/taskflow/contracts/nav_container.yaml +7 -0
  14. package/examples/taskflow/contracts/surface.yaml +7 -0
  15. package/examples/taskflow/contracts/x_media_player.yaml +185 -0
  16. package/examples/taskflow/flows/create_task.yaml +171 -0
  17. package/examples/taskflow/flows/edit_task.yaml +131 -0
  18. package/examples/taskflow/locales/en.json +158 -0
  19. package/examples/taskflow/openuispec.yaml +144 -0
  20. package/examples/taskflow/platform/android.yaml +32 -0
  21. package/examples/taskflow/platform/ios.yaml +39 -0
  22. package/examples/taskflow/platform/web.yaml +35 -0
  23. package/examples/taskflow/screens/calendar.yaml +23 -0
  24. package/examples/taskflow/screens/home.yaml +220 -0
  25. package/examples/taskflow/screens/profile_edit.yaml +70 -0
  26. package/examples/taskflow/screens/project_detail.yaml +65 -0
  27. package/examples/taskflow/screens/projects.yaml +142 -0
  28. package/examples/taskflow/screens/settings.yaml +178 -0
  29. package/examples/taskflow/screens/task_detail.yaml +317 -0
  30. package/examples/taskflow/tokens/color.yaml +88 -0
  31. package/examples/taskflow/tokens/elevation.yaml +27 -0
  32. package/examples/taskflow/tokens/icons.yaml +189 -0
  33. package/examples/taskflow/tokens/layout.yaml +156 -0
  34. package/examples/taskflow/tokens/motion.yaml +41 -0
  35. package/examples/taskflow/tokens/spacing.yaml +23 -0
  36. package/examples/taskflow/tokens/themes.yaml +34 -0
  37. package/examples/taskflow/tokens/typography.yaml +61 -0
  38. package/package.json +43 -0
  39. package/schema/custom-contract.schema.json +257 -0
  40. package/schema/defs/action.schema.json +272 -0
  41. package/schema/defs/adaptive.schema.json +13 -0
  42. package/schema/defs/common.schema.json +330 -0
  43. package/schema/defs/data-binding.schema.json +119 -0
  44. package/schema/defs/validation.schema.json +121 -0
  45. package/schema/flow.schema.json +164 -0
  46. package/schema/locale.schema.json +26 -0
  47. package/schema/openuispec.schema.json +287 -0
  48. package/schema/platform.schema.json +95 -0
  49. package/schema/screen.schema.json +346 -0
  50. package/schema/tokens/color.schema.json +104 -0
  51. package/schema/tokens/elevation.schema.json +84 -0
  52. package/schema/tokens/icons.schema.json +149 -0
  53. package/schema/tokens/layout.schema.json +170 -0
  54. package/schema/tokens/motion.schema.json +83 -0
  55. package/schema/tokens/spacing.schema.json +93 -0
  56. package/schema/tokens/themes.schema.json +92 -0
  57. package/schema/tokens/typography.schema.json +106 -0
  58. package/schema/validate.ts +258 -0
  59. package/spec/openuispec-v0.1.md +3677 -0
@@ -0,0 +1,142 @@
1
+ # ============================================================
2
+ # TaskFlow — Projects Screen
3
+ # ============================================================
4
+ # Exercises: collection (grid), data_display (card),
5
+ # action_trigger, surface (modal for new project),
6
+ # adaptive layout (grid columns)
7
+ # ============================================================
8
+
9
+ projects:
10
+ semantic: "Lists all projects as a grid of cards"
11
+
12
+ data:
13
+ projects:
14
+ source: "api.projects.list"
15
+
16
+ layout:
17
+ type: scroll_vertical
18
+ safe_area: true
19
+ padding: "spacing.page_margin"
20
+
21
+ sections:
22
+ - id: header
23
+ layout: { type: row, justify: "space-between", align: "center" }
24
+ children:
25
+ - contract: data_display
26
+ variant: inline
27
+ props:
28
+ title: "$t:projects.title"
29
+ tokens_override:
30
+ title_style: "typography.heading_lg"
31
+ - contract: action_trigger
32
+ variant: tertiary
33
+ size: sm
34
+ props:
35
+ label: "$t:projects.new_project"
36
+ icon: "plus_circle"
37
+ action:
38
+ type: present
39
+ surface: "new_project_dialog"
40
+
41
+ - id: project_grid
42
+ margin_top: "spacing.md"
43
+ contract: collection
44
+ variant: grid
45
+ props:
46
+ data: "projects"
47
+ item_contract: data_display
48
+ item_variant: card
49
+ item_props_map:
50
+ title: "item.name"
51
+ subtitle: "$t:projects.task_count"
52
+ t_params:
53
+ count: "item.task_count"
54
+ leading:
55
+ icon: "item.icon"
56
+ color: "item.color"
57
+ size: 24
58
+ background: "item.color (10% opacity)"
59
+ radius: "spacing.sm"
60
+ padding: "spacing.sm"
61
+ interactive: true
62
+
63
+ empty_state:
64
+ icon: "folder_fill"
65
+ title: "$t:projects.empty_title"
66
+ body: "$t:projects.empty_body"
67
+
68
+ adaptive:
69
+ compact: { columns: 1 }
70
+ regular: { columns: 2 }
71
+ expanded: { columns: 3 }
72
+
73
+ action:
74
+ type: navigate
75
+ destination: "screens/project_detail"
76
+ params: { project_id: "item.id" }
77
+
78
+ surfaces:
79
+ new_project_dialog:
80
+ contract: surface
81
+ variant: modal
82
+ props:
83
+ title: "$t:projects.dialog_title"
84
+ size: sm
85
+ content:
86
+ - layout: { type: stack, spacing: "spacing.md" }
87
+ children:
88
+ - contract: input_field
89
+ input_type: text
90
+ props:
91
+ label: "$t:projects.field_name"
92
+ placeholder: "$t:projects.field_name_placeholder"
93
+ required: true
94
+ data_binding: "form.name"
95
+
96
+ - contract: input_field
97
+ input_type: select
98
+ props:
99
+ label: "$t:projects.field_color"
100
+ options:
101
+ - { value: "#5B4FE8", label: "Indigo" }
102
+ - { value: "#E8634F", label: "Coral" }
103
+ - { value: "#2D9D5E", label: "Green" }
104
+ - { value: "#D4920E", label: "Amber" }
105
+ - { value: "#3B82D4", label: "Blue" }
106
+ - { value: "#9B59B6", label: "Purple" }
107
+ data_binding: "form.color"
108
+
109
+ - contract: input_field
110
+ input_type: select
111
+ props:
112
+ label: "$t:projects.field_icon"
113
+ options:
114
+ - { value: "folder", label: "Folder" }
115
+ - { value: "briefcase", label: "Briefcase" }
116
+ - { value: "rocket", label: "Rocket" }
117
+ - { value: "star", label: "Star" }
118
+ - { value: "heart", label: "Heart" }
119
+ - { value: "lightbulb", label: "Lightbulb" }
120
+ data_binding: "form.icon"
121
+
122
+ - layout: { type: row, spacing: "spacing.sm", justify: "end" }
123
+ children:
124
+ - contract: action_trigger
125
+ variant: secondary
126
+ size: sm
127
+ props: { label: "$t:common.cancel" }
128
+ action: { type: dismiss }
129
+ - contract: action_trigger
130
+ variant: primary
131
+ size: sm
132
+ props: { label: "$t:common.create" }
133
+ action:
134
+ type: api_call
135
+ endpoint: "api.projects.create"
136
+ body: "form"
137
+ on_success:
138
+ type: sequence
139
+ actions:
140
+ - { type: dismiss }
141
+ - { type: feedback, variant: toast, message: "$t:projects.created", severity: success }
142
+ - { type: refresh, target: "screens/projects.projects" }
@@ -0,0 +1,178 @@
1
+ # ============================================================
2
+ # TaskFlow — Settings Screen
3
+ # ============================================================
4
+ # Exercises: collection (list with sections), data_display,
5
+ # input_field (toggle, select), feedback (dialog),
6
+ # action_trigger (destructive)
7
+ # ============================================================
8
+
9
+ settings:
10
+ semantic: "App preferences, account, and data management"
11
+
12
+ data:
13
+ user: { source: "api.auth.currentUser" }
14
+ preferences: { source: "api.preferences.get" }
15
+
16
+ layout:
17
+ type: scroll_vertical
18
+ safe_area: true
19
+
20
+ sections:
21
+ # ---------- Profile card ----------
22
+ - id: profile
23
+ padding: "spacing.page_margin"
24
+ contract: data_display
25
+ variant: card
26
+ interactive: true
27
+ props:
28
+ title: "{user.name}"
29
+ subtitle: "{user.email}"
30
+ leading:
31
+ media: "user.avatar"
32
+ fallback: { initials: "user.name", background: "color.brand.primary" }
33
+ size: 48
34
+ radius: 24
35
+ trailing: { icon: "chevron_right", color: "color.text.tertiary" }
36
+ action:
37
+ type: navigate
38
+ destination: "screens/profile_edit"
39
+
40
+ # ---------- Preferences section ----------
41
+ - id: preferences_section
42
+ margin_top: "spacing.lg"
43
+ padding_h: "spacing.page_margin"
44
+ layout: { type: stack, spacing: "spacing.none" }
45
+ children:
46
+ - contract: data_display
47
+ variant: inline
48
+ props: { title: "$t:settings.preferences" }
49
+ tokens_override:
50
+ title_style: "typography.overline"
51
+ title_color: "color.text.tertiary"
52
+ margin_bottom: "spacing.sm"
53
+
54
+ # Theme picker
55
+ - contract: input_field
56
+ input_type: select
57
+ props:
58
+ label: "$t:settings.theme"
59
+ options:
60
+ - { value: "system", label: "$t:settings.theme_system" }
61
+ - { value: "light", label: "$t:settings.theme_light" }
62
+ - { value: "dark", label: "$t:settings.theme_dark" }
63
+ - { value: "warm", label: "$t:settings.theme_warm" }
64
+ value: "preferences.theme"
65
+ data_binding: "preferences.theme"
66
+ action:
67
+ type: api_call
68
+ endpoint: "api.preferences.update"
69
+ params: { key: "theme", value: "preferences.theme" }
70
+
71
+ # Default priority
72
+ - contract: input_field
73
+ input_type: select
74
+ props:
75
+ label: "$t:settings.default_priority"
76
+ options:
77
+ - { value: "low", label: "$t:priority.low" }
78
+ - { value: "medium", label: "$t:priority.medium" }
79
+ - { value: "high", label: "$t:priority.high" }
80
+ value: "preferences.default_priority"
81
+ data_binding: "preferences.default_priority"
82
+
83
+ # Notifications toggle
84
+ - contract: input_field
85
+ input_type: toggle
86
+ props:
87
+ label: "$t:settings.notifications"
88
+ value: "preferences.notifications_enabled"
89
+ data_binding: "preferences.notifications_enabled"
90
+
91
+ # Due date reminders toggle
92
+ - contract: input_field
93
+ input_type: toggle
94
+ props:
95
+ label: "$t:settings.reminders"
96
+ value: "preferences.reminders_enabled"
97
+ helper_text: "$t:settings.reminders_helper"
98
+ data_binding: "preferences.reminders_enabled"
99
+
100
+ # ---------- Data section ----------
101
+ - id: data_section
102
+ margin_top: "spacing.xl"
103
+ padding_h: "spacing.page_margin"
104
+ layout: { type: stack, spacing: "spacing.none" }
105
+ children:
106
+ - contract: data_display
107
+ variant: inline
108
+ props: { title: "$t:settings.data" }
109
+ tokens_override:
110
+ title_style: "typography.overline"
111
+ title_color: "color.text.tertiary"
112
+ margin_bottom: "spacing.sm"
113
+
114
+ # Export data
115
+ - contract: data_display
116
+ variant: compact
117
+ interactive: true
118
+ props:
119
+ title: "$t:settings.export"
120
+ leading: { icon: "square_arrow_up", color: "color.text.secondary", size: 18 }
121
+ trailing: { icon: "chevron_right", color: "color.text.tertiary" }
122
+ action:
123
+ type: api_call
124
+ endpoint: "api.data.export"
125
+ on_success:
126
+ feedback: { variant: toast, message: "$t:settings.export_success", severity: success }
127
+
128
+ # Delete account
129
+ - contract: action_trigger
130
+ variant: ghost
131
+ full_width: true
132
+ props:
133
+ label: "$t:settings.delete_account"
134
+ icon: "trash"
135
+ tokens_override:
136
+ text: "color.semantic.danger"
137
+ action:
138
+ type: confirm
139
+ confirmation:
140
+ contract: feedback
141
+ variant: dialog
142
+ props:
143
+ title: "$t:settings.delete_title"
144
+ message: "$t:settings.delete_message"
145
+ severity: error
146
+ actions:
147
+ - label: "$t:common.cancel"
148
+ variant: secondary
149
+ action: { type: dismiss }
150
+ - label: "$t:settings.delete_confirm"
151
+ variant: destructive
152
+ action:
153
+ type: api_call
154
+ endpoint: "api.auth.deleteAccount"
155
+ on_success:
156
+ type: navigate
157
+ destination: "$root"
158
+
159
+ # ---------- App info ----------
160
+ - id: app_info
161
+ margin_top: "spacing.xl"
162
+ padding: "spacing.page_margin"
163
+ layout: { type: stack, spacing: "spacing.xs", align: "center" }
164
+ children:
165
+ - contract: data_display
166
+ variant: inline
167
+ props:
168
+ title: "$t:settings.app_version"
169
+ tokens_override:
170
+ title_style: "typography.caption"
171
+ title_color: "color.text.tertiary"
172
+ - contract: data_display
173
+ variant: inline
174
+ props:
175
+ title: "$t:settings.app_credit"
176
+ tokens_override:
177
+ title_style: "typography.caption"
178
+ title_color: "color.text.tertiary"
@@ -0,0 +1,317 @@
1
+ # ============================================================
2
+ # TaskFlow — Task Detail Screen
3
+ # ============================================================
4
+ # Exercises: data_display (hero, stat, compact), action_trigger,
5
+ # feedback (dialog), surface (sheet), input_field,
6
+ # adaptive layout (stat reflow, action arrangement)
7
+ # ============================================================
8
+
9
+ task_detail:
10
+ semantic: "Displays full task information with edit and delete actions"
11
+
12
+ params:
13
+ task_id: { type: string, required: true }
14
+
15
+ data:
16
+ task:
17
+ source: "api.tasks.getById"
18
+ params: { id: "params.task_id" }
19
+
20
+ layout:
21
+ type: scroll_vertical
22
+ safe_area: true
23
+
24
+ sections:
25
+ # ---------- Status + Priority header ----------
26
+ - id: header
27
+ padding: "spacing.page_margin"
28
+ layout: { type: stack, spacing: "spacing.md" }
29
+ children:
30
+ - contract: data_display
31
+ variant: hero
32
+ props:
33
+ title: "{task.title}"
34
+ badge:
35
+ text: "{task.status | format:status_label}"
36
+ severity: "{task.status | map:status_severity}"
37
+ metadata:
38
+ priority: "{task.priority | format:priority_label}"
39
+
40
+ # Stat cards — adaptive: grid on compact, row on regular+
41
+ - id: stat_group
42
+ layout:
43
+ adaptive:
44
+ compact:
45
+ type: grid
46
+ columns: 2
47
+ gap: "spacing.sm"
48
+ regular:
49
+ type: row
50
+ spacing: "spacing.sm"
51
+ children:
52
+ - contract: data_display
53
+ variant: stat
54
+ props:
55
+ title: "$t:task_detail.status"
56
+ body: "{task.status | format:status_label}"
57
+ leading:
58
+ icon: "circle_fill"
59
+ color: "color.status.{task.status}"
60
+ size: 10
61
+
62
+ - contract: data_display
63
+ variant: stat
64
+ props:
65
+ title: "$t:task_detail.priority"
66
+ body: "{task.priority | format:priority_label}"
67
+ leading:
68
+ icon: "flag_fill"
69
+ color: "color.priority.{task.priority}"
70
+ size: 14
71
+
72
+ - contract: data_display
73
+ variant: stat
74
+ props:
75
+ title: "$t:task_detail.due"
76
+ body: "{task.due_date | format:date_relative}"
77
+
78
+ # ---------- Description ----------
79
+ - id: description
80
+ padding_h: "spacing.page_margin"
81
+ margin_top: "spacing.md"
82
+ condition: "task.description != null"
83
+ layout: { type: stack, spacing: "spacing.xs" }
84
+ adaptive:
85
+ expanded:
86
+ max_width: 640
87
+ children:
88
+ - contract: data_display
89
+ variant: inline
90
+ props:
91
+ title: "$t:task_detail.description"
92
+ tokens_override:
93
+ title_style: "typography.overline"
94
+ title_color: "color.text.tertiary"
95
+ - contract: data_display
96
+ variant: inline
97
+ props:
98
+ title: "{task.description}"
99
+ tokens_override:
100
+ title_style: "typography.body"
101
+
102
+ # ---------- Media attachment ----------
103
+ - id: media_attachment
104
+ condition: "task.attachment.media_type != null"
105
+ padding_h: "spacing.page_margin"
106
+ margin_top: "spacing.md"
107
+ children:
108
+ - contract: x_media_player
109
+ variant: inline
110
+ props:
111
+ source: "{task.attachment.url}"
112
+ media_type: "{task.attachment.media_type}"
113
+ title: "{task.attachment.filename}"
114
+ poster: "{task.attachment.thumbnail}"
115
+ show_controls: true
116
+ tokens_override:
117
+ radius: "spacing.md"
118
+ adaptive:
119
+ compact: { variant: inline }
120
+ expanded: { variant: inline, max_width: 640 }
121
+
122
+ # ---------- Details section ----------
123
+ - id: details
124
+ padding_h: "spacing.page_margin"
125
+ margin_top: "spacing.lg"
126
+ layout: { type: stack, spacing: "spacing.none" }
127
+ adaptive:
128
+ expanded:
129
+ max_width: 640
130
+ children:
131
+ - contract: data_display
132
+ variant: inline
133
+ props:
134
+ title: "$t:task_detail.details"
135
+ tokens_override:
136
+ title_style: "typography.overline"
137
+ title_color: "color.text.tertiary"
138
+
139
+ # Project row
140
+ - contract: data_display
141
+ variant: compact
142
+ interactive: true
143
+ props:
144
+ title: "$t:task_detail.project"
145
+ trailing: "{task.project.name}"
146
+ leading: { icon: "folder", color: "color.text.secondary", size: 18 }
147
+ action:
148
+ type: navigate
149
+ destination: "screens/project_detail"
150
+ params: { project_id: "task.project_id" }
151
+
152
+ # Assignee row
153
+ - contract: data_display
154
+ variant: compact
155
+ interactive: true
156
+ props:
157
+ title: "$t:task_detail.assignee"
158
+ trailing: "{task.assignee.name | default:$t:task_detail.unassigned}"
159
+ leading: { icon: "person", color: "color.text.secondary", size: 18 }
160
+ action:
161
+ type: present
162
+ surface: "assignee_picker"
163
+
164
+ # Tags row
165
+ - contract: data_display
166
+ variant: compact
167
+ props:
168
+ title: "$t:task_detail.tags"
169
+ trailing:
170
+ contract: collection
171
+ variant: chips
172
+ props:
173
+ data: "task.tags"
174
+ item_contract: data_display
175
+ item_variant: inline
176
+ item_props_map:
177
+ title: "item"
178
+ leading: { icon: "tag", color: "color.text.secondary", size: 18 }
179
+
180
+ # Created row
181
+ - contract: data_display
182
+ variant: compact
183
+ props:
184
+ title: "$t:task_detail.created"
185
+ trailing: "{task.created_at | format:date}"
186
+ leading: { icon: "clock", color: "color.text.secondary", size: 18 }
187
+
188
+ # ---------- Actions ----------
189
+ - id: actions
190
+ padding: "spacing.page_margin"
191
+ margin_top: "spacing.xl"
192
+ layout:
193
+ adaptive:
194
+ compact:
195
+ type: stack
196
+ spacing: "spacing.sm"
197
+ regular:
198
+ type: row
199
+ spacing: "spacing.sm"
200
+ wrap: true
201
+ children:
202
+ - contract: action_trigger
203
+ variant: primary
204
+ props:
205
+ label: "$t:task_detail.edit"
206
+ icon: "pencil"
207
+ adaptive:
208
+ compact: { full_width: true }
209
+ regular: { full_width: false }
210
+ action:
211
+ type: navigate
212
+ destination: "flows/edit_task"
213
+ params: { task_id: "task.id" }
214
+ presentation: "sheet"
215
+
216
+ - contract: action_trigger
217
+ variant: secondary
218
+ props:
219
+ label: "$t:task_detail.toggle_status"
220
+ t_params:
221
+ is_done: "task.status == done"
222
+ icon: "{task.status == done ? 'arrow_uturn_left' : 'checkmark'}"
223
+ adaptive:
224
+ compact: { full_width: true }
225
+ regular: { full_width: false }
226
+ action:
227
+ type: api_call
228
+ endpoint: "api.tasks.toggleStatus"
229
+ params: { id: "task.id" }
230
+ optimistic:
231
+ target: "task.status"
232
+ value: "{task.status == done ? 'todo' : 'done'}"
233
+ revert_on_error: true
234
+ on_success:
235
+ type: feedback
236
+ variant: toast
237
+ message: "$t:task_detail.task_updated"
238
+ severity: success
239
+ duration: 3000
240
+ on_error:
241
+ type: feedback
242
+ variant: toast
243
+ message: "$t:task_detail.update_error"
244
+ severity: error
245
+
246
+ - contract: action_trigger
247
+ variant: destructive
248
+ props:
249
+ label: "$t:task_detail.delete"
250
+ icon: "trash"
251
+ adaptive:
252
+ compact: { full_width: true }
253
+ regular: { full_width: false }
254
+ action:
255
+ type: confirm
256
+ confirmation:
257
+ contract: feedback
258
+ variant: dialog
259
+ props:
260
+ title: "$t:task_detail.delete_title"
261
+ message: "$t:task_detail.delete_message"
262
+ t_params:
263
+ title: "task.title"
264
+ actions:
265
+ - label: "$t:common.cancel"
266
+ variant: secondary
267
+ action: { type: dismiss }
268
+ - label: "$t:common.delete"
269
+ variant: destructive
270
+ action:
271
+ type: api_call
272
+ endpoint: "api.tasks.delete"
273
+ params: { id: "task.id" }
274
+ on_success:
275
+ type: sequence
276
+ actions:
277
+ - type: navigate
278
+ destination: "$back"
279
+ - type: feedback
280
+ variant: toast
281
+ message: "$t:task_detail.task_deleted"
282
+ severity: neutral
283
+
284
+ # ---------- Surfaces ----------
285
+ surfaces:
286
+ assignee_picker:
287
+ contract: surface
288
+ props:
289
+ title: "$t:task_detail.assign_to"
290
+ content:
291
+ - contract: input_field
292
+ input_type: text
293
+ props:
294
+ label: "$t:task_detail.search_people"
295
+ placeholder: "$t:task_detail.search_people_placeholder"
296
+ icon: { ref: "search", position: leading }
297
+ - contract: collection
298
+ variant: list
299
+ props:
300
+ data: "api.users.list"
301
+ item_contract: data_display
302
+ item_variant: compact
303
+ item_props_map:
304
+ title: "item.name"
305
+ subtitle: "item.email"
306
+ leading:
307
+ media: "item.avatar"
308
+ fallback: { initials: "item.name", background: "color.brand.primary" }
309
+ interactive: true
310
+ action:
311
+ type: api_call
312
+ endpoint: "api.tasks.assignTo"
313
+ params: { task_id: "task.id", user_id: "item.id" }
314
+ on_success: { type: dismiss }
315
+ adaptive:
316
+ compact: { variant: sheet, detents: [medium] }
317
+ expanded: { variant: panel, width: 360 }