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,144 @@
1
+ # ============================================================
2
+ # TaskFlow — OpenUISpec v0.1 Example Application
3
+ # ============================================================
4
+ # A task management app demonstrating all 7 contract families,
5
+ # theming, navigation flows, adaptive layout, and the formal
6
+ # action system and data binding model.
7
+ # ============================================================
8
+
9
+ spec_version: "0.1"
10
+
11
+ project:
12
+ name: "TaskFlow"
13
+ description: "A simple task management app for individuals and small teams"
14
+ icon: "checkmark_circle"
15
+
16
+ includes:
17
+ tokens: "./tokens/"
18
+ contracts: "./contracts/"
19
+ screens: "./screens/"
20
+ flows: "./flows/"
21
+ platform: "./platform/"
22
+ locales: "./locales/"
23
+
24
+ custom_contracts:
25
+ - "./contracts/x_media_player.yaml"
26
+
27
+ i18n:
28
+ default_locale: "en"
29
+ supported_locales: [en]
30
+ fallback_strategy: "default"
31
+
32
+ generation:
33
+ targets: [ios, android, web]
34
+ output_format:
35
+ ios: { language: swift, framework: swiftui, min_version: "17.0" }
36
+ android: { language: kotlin, framework: compose, min_sdk: 26 }
37
+ web: { language: typescript, framework: react, bundler: vite }
38
+
39
+ # ============================================================
40
+ # Custom formatters & mappers (see spec Section 10.5)
41
+ # ============================================================
42
+ formatters:
43
+ greeting:
44
+ input: time_of_day
45
+ output: string
46
+ mapping:
47
+ morning: "morning"
48
+ afternoon: "afternoon"
49
+ evening: "evening"
50
+ # Used as key suffix in $t:home.greeting.{time_of_day | format:greeting}
51
+
52
+ status_label:
53
+ input: enum
54
+ output: string
55
+ mapping:
56
+ todo: "$t:status.todo"
57
+ in_progress: "$t:status.in_progress"
58
+ done: "$t:status.done"
59
+
60
+ priority_label:
61
+ input: enum
62
+ output: string
63
+ mapping:
64
+ low: "$t:priority.low"
65
+ medium: "$t:priority.medium"
66
+ high: "$t:priority.high"
67
+ urgent: "$t:priority.urgent"
68
+
69
+ mappers:
70
+ status_severity:
71
+ todo: neutral
72
+ in_progress: info
73
+ done: success
74
+
75
+ priority_to_severity:
76
+ low: neutral
77
+ medium: info
78
+ high: warning
79
+ urgent: error
80
+
81
+ # ============================================================
82
+ # Data model
83
+ # ============================================================
84
+ data_model:
85
+ task:
86
+ id: { type: string, format: uuid }
87
+ title: { type: string, max_length: 200 }
88
+ description: { type: string, required: false }
89
+ status: { type: enum, values: [todo, in_progress, done] }
90
+ priority: { type: enum, values: [low, medium, high, urgent] }
91
+ due_date: { type: date, required: false }
92
+ project_id: { type: string, required: false }
93
+ assignee: { type: user, required: false }
94
+ tags: { type: "list<string>", default: [] }
95
+ created_at: { type: datetime }
96
+ updated_at: { type: datetime }
97
+
98
+ project:
99
+ id: { type: string, format: uuid }
100
+ name: { type: string, max_length: 100 }
101
+ color: { type: color_ref }
102
+ icon: { type: icon_ref }
103
+ task_count: { type: int }
104
+
105
+ user:
106
+ id: { type: string, format: uuid }
107
+ name: { type: string }
108
+ first_name: { type: string }
109
+ avatar: { type: media_ref, required: false }
110
+ email: { type: string, format: email }
111
+
112
+ # ============================================================
113
+ # API endpoints (informational — helps AI generators produce
114
+ # correct API client code)
115
+ # ============================================================
116
+ api:
117
+ base_url: "/api/v1"
118
+ auth: "bearer_token"
119
+
120
+ endpoints:
121
+ tasks:
122
+ list: { method: GET, path: "/tasks", params: [filter, sort, search] }
123
+ getById: { method: GET, path: "/tasks/:id" }
124
+ create: { method: POST, path: "/tasks", body: "task" }
125
+ update: { method: PUT, path: "/tasks/:id", body: "task" }
126
+ delete: { method: DELETE, path: "/tasks/:id" }
127
+ toggleStatus: { method: PATCH, path: "/tasks/:id/status" }
128
+ assignTo: { method: PATCH, path: "/tasks/:id/assign", body: { user_id: string } }
129
+ counts: { method: GET, path: "/tasks/counts" }
130
+ projects:
131
+ list: { method: GET, path: "/projects" }
132
+ getById: { method: GET, path: "/projects/:id" }
133
+ create: { method: POST, path: "/projects", body: "project" }
134
+ users:
135
+ list: { method: GET, path: "/users", params: [search] }
136
+ auth:
137
+ currentUser: { method: GET, path: "/auth/me" }
138
+ updateProfile: { method: PATCH, path: "/auth/me", body: { name: string, email: string } }
139
+ deleteAccount: { method: DELETE, path: "/auth/account" }
140
+ preferences:
141
+ get: { method: GET, path: "/preferences" }
142
+ update: { method: PATCH, path: "/preferences", body: { key: string, value: any } }
143
+ data:
144
+ export: { method: POST, path: "/data/export" }
@@ -0,0 +1,32 @@
1
+ # TaskFlow — Android Platform Adaptation
2
+
3
+ android:
4
+ framework: compose
5
+ min_sdk: 26
6
+ target_sdk: 35
7
+
8
+ overrides:
9
+ nav_container:
10
+ tab_bar: { height: 56, uses_NavigationBar: true }
11
+ surface:
12
+ sheet: { uses_ModalBottomSheet: true, skip_partial_collapse: false }
13
+ feedback:
14
+ snackbar: { uses_system_snackbar: true }
15
+ action_trigger:
16
+ primary: { ripple: true }
17
+ collection:
18
+ list: { edge_to_edge_items: true }
19
+
20
+ behaviors:
21
+ material_you: true
22
+ predictive_back: true
23
+ edge_to_edge: true
24
+ dynamic_color: true
25
+
26
+ generation:
27
+ dependencies:
28
+ - "material3"
29
+ - "navigation-compose"
30
+ - "lifecycle-viewmodel-compose"
31
+ architecture: "MVVM with ViewModel + StateFlow"
32
+ naming: "Kotlin conventions"
@@ -0,0 +1,39 @@
1
+ # TaskFlow — iOS Platform Adaptation
2
+
3
+ ios:
4
+ framework: swiftui
5
+ min_version: "17.0"
6
+
7
+ overrides:
8
+ nav_container:
9
+ tab_bar: { height: 49, uses_system_tab_bar: true }
10
+ surface:
11
+ sheet: { detents: [.medium, .large], drag_indicator: true }
12
+ modal: { prefers_sheet: true }
13
+ feedback:
14
+ toast: { position: "top" }
15
+ input_field:
16
+ date: { uses_system_picker: true, style: "graphical" }
17
+ collection:
18
+ list: { uses_system_list_style: ".insetGrouped" }
19
+
20
+ x_media_player:
21
+ inline:
22
+ uses_native_player: true
23
+ pip_enabled: true
24
+
25
+ behaviors:
26
+ haptics: true
27
+ large_title_scroll: true
28
+ swipe_back: true
29
+ safe_area_respect: true
30
+ dynamic_type: true
31
+ swipe_actions:
32
+ task_list_item:
33
+ leading: { action: "toggleStatus", color: "color.semantic.success", icon: "checkmark" }
34
+ trailing: { action: "delete", color: "color.semantic.danger", icon: "trash" }
35
+
36
+ generation:
37
+ imports: ["SwiftUI", "Foundation"]
38
+ architecture: "MVVM with @Observable"
39
+ naming: "Swift conventions"
@@ -0,0 +1,35 @@
1
+ # TaskFlow — Web Platform Adaptation
2
+
3
+ web:
4
+ framework: react
5
+ language: typescript
6
+
7
+ overrides:
8
+ # Navigation uses layout.size_classes (defined in tokens/layout.yaml)
9
+ # No need for ad-hoc variant_responsive here — the adaptive key
10
+ # on the nav_container in home.yaml handles this.
11
+
12
+ surface:
13
+ sheet: { falls_back_to: "modal" }
14
+ feedback:
15
+ dialog: { uses_native_dialog_element: true }
16
+
17
+ behaviors:
18
+ prefers_color_scheme: true
19
+ keyboard_navigation: true
20
+
21
+ # Breakpoints now reference layout.size_classes
22
+ # compact: max 600px, regular: 601-1024px, expanded: 1025px+
23
+ # These are defined in tokens/layout.yaml and applied globally.
24
+
25
+ keyboard_shortcuts:
26
+ new_task: { key: "n", modifier: "cmd/ctrl" }
27
+ search: { key: "k", modifier: "cmd/ctrl" }
28
+ toggle_sidebar: { key: "b", modifier: "cmd/ctrl" }
29
+
30
+ generation:
31
+ bundler: "vite"
32
+ css: "tailwind"
33
+ routing: "react_router"
34
+ state: "zustand"
35
+ naming: "React conventions"
@@ -0,0 +1,23 @@
1
+ # ============================================================
2
+ # TaskFlow — Calendar Screen (stub)
3
+ # ============================================================
4
+ # Not yet implemented in v0.1. Referenced by home.yaml nav.
5
+ # Planned contracts: collection (grid/list), data_display, surface
6
+ # ============================================================
7
+
8
+ calendar:
9
+ semantic: "Calendar view of tasks organized by date"
10
+ status: stub
11
+
12
+ layout:
13
+ type: scroll_vertical
14
+ safe_area: true
15
+ padding: "spacing.page_margin"
16
+
17
+ sections:
18
+ - id: placeholder
19
+ contract: data_display
20
+ variant: hero
21
+ props:
22
+ title: "$t:calendar.title"
23
+ subtitle: "$t:calendar.coming_soon"
@@ -0,0 +1,220 @@
1
+ # ============================================================
2
+ # TaskFlow — Home Screen (Task List)
3
+ # ============================================================
4
+ # Exercises: nav_container, collection, data_display,
5
+ # action_trigger, input_field (search),
6
+ # adaptive layout (size-class-aware sections)
7
+ # ============================================================
8
+
9
+ home:
10
+ semantic: "Main task list with filtering, search, and quick actions"
11
+
12
+ data:
13
+ tasks:
14
+ source: "api.tasks.list"
15
+ params:
16
+ filter: "state.active_filter"
17
+ sort: "state.sort_order"
18
+ projects:
19
+ source: "api.projects.list"
20
+ task_counts:
21
+ source: "api.tasks.counts"
22
+
23
+ state:
24
+ active_filter:
25
+ type: enum
26
+ values: [all, today, upcoming, done]
27
+ default: today
28
+ sort_order:
29
+ type: enum
30
+ values: [due_date, priority, created_at]
31
+ default: due_date
32
+ search_query: { type: string, default: "" }
33
+
34
+ # --- Navigation shell (adaptive) ---
35
+ navigation:
36
+ contract: nav_container
37
+ props:
38
+ items:
39
+ - id: "home"
40
+ label: "$t:nav.tasks"
41
+ icon: "checkmark_list"
42
+ icon_active: "checkmark_list_fill"
43
+ destination: "screens/home"
44
+ badge: { count: "task_counts.today", severity: "info" }
45
+ - id: "projects"
46
+ label: "$t:nav.projects"
47
+ icon: "folder"
48
+ icon_active: "folder_fill"
49
+ destination: "screens/projects"
50
+ - id: "calendar"
51
+ label: "$t:nav.calendar"
52
+ icon: "calendar"
53
+ icon_active: "calendar_fill"
54
+ destination: "screens/calendar"
55
+ - id: "settings"
56
+ label: "$t:nav.settings"
57
+ icon: "gear"
58
+ icon_active: "gear_fill"
59
+ destination: "screens/settings"
60
+ selected: "home"
61
+ # Adaptive: nav variant changes with screen size
62
+ adaptive:
63
+ compact: { variant: tab_bar }
64
+ regular: { variant: rail }
65
+ expanded: { variant: sidebar, collapsed: false }
66
+
67
+ # --- Screen-level adaptive layout ---
68
+ layout:
69
+ adaptive:
70
+ compact:
71
+ type: scroll_vertical
72
+ safe_area: true
73
+ expanded:
74
+ type: split_view
75
+ primary_width: 0.38
76
+ primary: { sections: [header, search, filters, task_list] }
77
+ secondary: { sections: ["screens/task_detail"], params_from: "selected_task" }
78
+ collapse_at: compact
79
+
80
+ sections:
81
+ # ---------- Header ----------
82
+ - id: header
83
+ layout: { type: stack, spacing: "spacing.sm" }
84
+ padding: "spacing.page_margin"
85
+ children:
86
+ - contract: data_display
87
+ variant: inline
88
+ props:
89
+ title: "$t:home.greeting.{time_of_day | format:greeting}"
90
+ subtitle: "$t:home.task_count"
91
+ t_params:
92
+ name: "user.first_name"
93
+ count: "task_counts.today"
94
+ emphasis: default
95
+ tokens_override:
96
+ title_style: "typography.heading_lg"
97
+ subtitle_style: "typography.body_sm"
98
+ # Adaptive: larger title on expanded screens
99
+ adaptive:
100
+ expanded:
101
+ tokens_override:
102
+ title_style: "typography.display"
103
+
104
+ # ---------- Search bar ----------
105
+ - id: search
106
+ padding_h: "spacing.page_margin"
107
+ contract: input_field
108
+ input_type: text
109
+ props:
110
+ label: "$t:home.search_label"
111
+ placeholder: "$t:home.search_placeholder"
112
+ value: "state.search_query"
113
+ icon: { ref: "search", position: leading }
114
+ clearable: true
115
+ tokens_override:
116
+ background: "color.surface.secondary"
117
+ border: { width: 0 }
118
+ radius: "spacing.sm"
119
+ # Adaptive: constrain search width on large screens
120
+ adaptive:
121
+ expanded:
122
+ max_width: 480
123
+
124
+ # ---------- Filter chips ----------
125
+ - id: filters
126
+ margin_top: "spacing.sm"
127
+ padding_h: "spacing.page_margin"
128
+ contract: collection
129
+ variant: chips
130
+ props:
131
+ data:
132
+ - { id: "all", label: "$t:home.filter.all", count: "task_counts.all" }
133
+ - { id: "today", label: "$t:home.filter.today", count: "task_counts.today" }
134
+ - { id: "upcoming", label: "$t:home.filter.upcoming", count: "task_counts.upcoming" }
135
+ - { id: "done", label: "$t:home.filter.done", count: "task_counts.done" }
136
+ item_contract: action_trigger
137
+ item_variant: ghost
138
+ item_props_map:
139
+ label: "{item.label} ({item.count})"
140
+ selectable: true
141
+ selection_mode: single
142
+ selected: "state.active_filter"
143
+ action:
144
+ type: set_state
145
+ target: "state.active_filter"
146
+ value: "item.id"
147
+
148
+ # ---------- Task list ----------
149
+ - id: task_list
150
+ margin_top: "spacing.md"
151
+ contract: collection
152
+ variant: list
153
+ props:
154
+ data: "tasks"
155
+ item_contract: data_display
156
+ item_variant: compact
157
+ item_props_map:
158
+ title: "item.title"
159
+ subtitle: "{item.project.name} · {item.due_date | format:date_relative}"
160
+ leading:
161
+ contract: input_field
162
+ input_type: checkbox
163
+ props:
164
+ value: "item.status == done"
165
+ label: "$t:home.mark_complete"
166
+ t_params:
167
+ title: "item.title"
168
+ action:
169
+ type: api_call
170
+ endpoint: "api.tasks.toggleStatus"
171
+ params: { id: "item.id" }
172
+ trailing:
173
+ contract: data_display
174
+ variant: inline
175
+ props:
176
+ badge:
177
+ dot: true
178
+ severity: "{item.priority | map:priority_to_severity}"
179
+ metadata:
180
+ priority: "item.priority"
181
+ status: "item.status"
182
+ interactive: true
183
+ selectable: false
184
+ pull_to_refresh: true
185
+ searchable: false
186
+
187
+ empty_state:
188
+ icon: "checkmark_circle_fill"
189
+ title: "$t:home.empty_title"
190
+ body: "$t:home.empty_body"
191
+
192
+ action:
193
+ type: navigate
194
+ destination: "screens/task_detail"
195
+ params: { task_id: "item.id" }
196
+
197
+ # ---------- Floating action button ----------
198
+ - id: fab
199
+ position: "floating-bottom-trailing"
200
+ contract: action_trigger
201
+ variant: primary
202
+ size: lg
203
+ props:
204
+ label: "$t:home.new_task"
205
+ icon: "plus"
206
+ tokens_override:
207
+ radius: 28
208
+ shadow: "elevation.lg"
209
+ action:
210
+ type: navigate
211
+ destination: "flows/create_task"
212
+ presentation: "sheet"
213
+ # Adaptive: FAB becomes inline button on expanded
214
+ adaptive:
215
+ expanded:
216
+ position: "inline"
217
+ size: md
218
+ tokens_override:
219
+ radius: "spacing.sm"
220
+ shadow: none
@@ -0,0 +1,70 @@
1
+ # ============================================================
2
+ # TaskFlow — Profile Edit Screen (stub)
3
+ # ============================================================
4
+ # Referenced by settings.yaml profile card.
5
+ # ============================================================
6
+
7
+ profile_edit:
8
+ semantic: "Edit user profile (name, email, avatar)"
9
+ status: stub
10
+
11
+ data:
12
+ user: { source: "api.auth.currentUser" }
13
+
14
+ layout:
15
+ type: scroll_vertical
16
+ safe_area: true
17
+ padding: "spacing.page_margin"
18
+
19
+ sections:
20
+ - id: avatar
21
+ layout: { type: stack, align: "center", spacing: "spacing.md" }
22
+ children:
23
+ - contract: data_display
24
+ variant: inline
25
+ props:
26
+ leading:
27
+ media: "user.avatar"
28
+ fallback: { initials: "user.name", background: "color.brand.primary" }
29
+ size: 80
30
+ radius: 40
31
+ - contract: action_trigger
32
+ variant: tertiary
33
+ size: sm
34
+ props: { label: "$t:profile.change_photo" }
35
+
36
+ - id: form
37
+ margin_top: "spacing.lg"
38
+ layout: { type: stack, spacing: "spacing.md" }
39
+ children:
40
+ - contract: input_field
41
+ input_type: text
42
+ props:
43
+ label: "$t:profile.field_name"
44
+ value: "user.name"
45
+ required: true
46
+ data_binding: "form.name"
47
+
48
+ - contract: input_field
49
+ input_type: email
50
+ props:
51
+ label: "$t:profile.field_email"
52
+ value: "user.email"
53
+ required: true
54
+ data_binding: "form.email"
55
+
56
+ - id: save
57
+ margin_top: "spacing.lg"
58
+ contract: action_trigger
59
+ variant: primary
60
+ full_width: true
61
+ props: { label: "$t:profile.save" }
62
+ action:
63
+ type: api_call
64
+ endpoint: "api.auth.updateProfile"
65
+ body: "form"
66
+ on_success:
67
+ type: sequence
68
+ actions:
69
+ - { type: navigate, destination: "$back" }
70
+ - { type: feedback, variant: toast, message: "$t:profile.success", severity: success }
@@ -0,0 +1,65 @@
1
+ # ============================================================
2
+ # TaskFlow — Project Detail Screen (stub)
3
+ # ============================================================
4
+ # Referenced by projects.yaml and task_detail.yaml.
5
+ # Planned: project header, filtered task list, project settings.
6
+ # ============================================================
7
+
8
+ project_detail:
9
+ semantic: "Displays a single project with its tasks"
10
+ status: stub
11
+
12
+ params:
13
+ project_id: { type: string, required: true }
14
+
15
+ data:
16
+ project:
17
+ source: "api.projects.getById"
18
+ params: { id: "params.project_id" }
19
+ tasks:
20
+ source: "api.tasks.list"
21
+ params: { project_id: "params.project_id" }
22
+
23
+ layout:
24
+ type: scroll_vertical
25
+ safe_area: true
26
+ padding: "spacing.page_margin"
27
+
28
+ sections:
29
+ - id: header
30
+ contract: data_display
31
+ variant: hero
32
+ props:
33
+ title: "{project.name}"
34
+ subtitle: "$t:project_detail.task_count"
35
+ t_params:
36
+ count: "project.task_count"
37
+ leading:
38
+ icon: "{project.icon}"
39
+ color: "{project.color}"
40
+
41
+ - id: task_list
42
+ margin_top: "spacing.md"
43
+ contract: collection
44
+ variant: list
45
+ props:
46
+ data: "tasks"
47
+ item_contract: data_display
48
+ item_variant: compact
49
+ item_props_map:
50
+ title: "item.title"
51
+ subtitle: "{item.due_date | format:date_relative}"
52
+ trailing:
53
+ badge:
54
+ dot: true
55
+ severity: "{item.priority | map:priority_to_severity}"
56
+ interactive: true
57
+ pull_to_refresh: true
58
+ empty_state:
59
+ icon: "checkmark_circle_fill"
60
+ title: "$t:project_detail.empty_title"
61
+ body: "$t:project_detail.empty_body"
62
+ action:
63
+ type: navigate
64
+ destination: "screens/task_detail"
65
+ params: { task_id: "item.id" }