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.
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/cli/index.ts +49 -0
- package/cli/init.ts +390 -0
- package/drift/index.ts +398 -0
- package/examples/taskflow/README.md +103 -0
- package/examples/taskflow/contracts/README.md +18 -0
- package/examples/taskflow/contracts/action_trigger.yaml +7 -0
- package/examples/taskflow/contracts/collection.yaml +7 -0
- package/examples/taskflow/contracts/data_display.yaml +7 -0
- package/examples/taskflow/contracts/feedback.yaml +7 -0
- package/examples/taskflow/contracts/input_field.yaml +7 -0
- package/examples/taskflow/contracts/nav_container.yaml +7 -0
- package/examples/taskflow/contracts/surface.yaml +7 -0
- package/examples/taskflow/contracts/x_media_player.yaml +185 -0
- package/examples/taskflow/flows/create_task.yaml +171 -0
- package/examples/taskflow/flows/edit_task.yaml +131 -0
- package/examples/taskflow/locales/en.json +158 -0
- package/examples/taskflow/openuispec.yaml +144 -0
- package/examples/taskflow/platform/android.yaml +32 -0
- package/examples/taskflow/platform/ios.yaml +39 -0
- package/examples/taskflow/platform/web.yaml +35 -0
- package/examples/taskflow/screens/calendar.yaml +23 -0
- package/examples/taskflow/screens/home.yaml +220 -0
- package/examples/taskflow/screens/profile_edit.yaml +70 -0
- package/examples/taskflow/screens/project_detail.yaml +65 -0
- package/examples/taskflow/screens/projects.yaml +142 -0
- package/examples/taskflow/screens/settings.yaml +178 -0
- package/examples/taskflow/screens/task_detail.yaml +317 -0
- package/examples/taskflow/tokens/color.yaml +88 -0
- package/examples/taskflow/tokens/elevation.yaml +27 -0
- package/examples/taskflow/tokens/icons.yaml +189 -0
- package/examples/taskflow/tokens/layout.yaml +156 -0
- package/examples/taskflow/tokens/motion.yaml +41 -0
- package/examples/taskflow/tokens/spacing.yaml +23 -0
- package/examples/taskflow/tokens/themes.yaml +34 -0
- package/examples/taskflow/tokens/typography.yaml +61 -0
- package/package.json +43 -0
- package/schema/custom-contract.schema.json +257 -0
- package/schema/defs/action.schema.json +272 -0
- package/schema/defs/adaptive.schema.json +13 -0
- package/schema/defs/common.schema.json +330 -0
- package/schema/defs/data-binding.schema.json +119 -0
- package/schema/defs/validation.schema.json +121 -0
- package/schema/flow.schema.json +164 -0
- package/schema/locale.schema.json +26 -0
- package/schema/openuispec.schema.json +287 -0
- package/schema/platform.schema.json +95 -0
- package/schema/screen.schema.json +346 -0
- package/schema/tokens/color.schema.json +104 -0
- package/schema/tokens/elevation.schema.json +84 -0
- package/schema/tokens/icons.schema.json +149 -0
- package/schema/tokens/layout.schema.json +170 -0
- package/schema/tokens/motion.schema.json +83 -0
- package/schema/tokens/spacing.schema.json +93 -0
- package/schema/tokens/themes.schema.json +92 -0
- package/schema/tokens/typography.schema.json +106 -0
- package/schema/validate.ts +258 -0
- 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" }
|