openuispec 0.1.18 → 0.1.19
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/docs/stress-test-maturity-report.md +97 -0
- package/examples/todo-orbit/AGENTS.md +127 -0
- package/examples/todo-orbit/CLAUDE.md +127 -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 +139 -0
- package/examples/todo-orbit/openuispec/screens/home.yaml +172 -0
- package/examples/todo-orbit/openuispec/screens/settings.yaml +148 -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/validate.ts +0 -2
- package/spec/openuispec-v0.1.md +76 -12
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "Bundler",
|
|
9
|
+
"allowImportingTsExtensions": false,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src"]
|
|
19
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Todo Orbit — OpenUISpec
|
|
2
|
+
|
|
3
|
+
This directory contains the **OpenUISpec** semantic UI specification for **Todo Orbit**.
|
|
4
|
+
|
|
5
|
+
OpenUISpec is a YAML-based format that describes your app's UI semantically — tokens, screens, flows, and platform overrides. AI reads the spec and generates native code (SwiftUI, Compose, React). The spec is the single source of truth across all platforms.
|
|
6
|
+
|
|
7
|
+
Todo Orbit is a bilingual productivity sample that exercises task management, analytics, settings, and recurring-rule workflows across iOS, Android, and web targets.
|
|
8
|
+
|
|
9
|
+
## Directory structure
|
|
10
|
+
|
|
11
|
+
| Directory | Contents |
|
|
12
|
+
|-----------|----------|
|
|
13
|
+
| `tokens/` | Design tokens — colors, typography, spacing, elevation, motion, icons, themes |
|
|
14
|
+
| `screens/` | Screen definitions — one YAML file per screen |
|
|
15
|
+
| `flows/` | Navigation flows — multi-step user journeys |
|
|
16
|
+
| `contracts/` | Component contracts — standard extensions (variants, tokens) and custom (`x_` prefixed) |
|
|
17
|
+
| `platform/` | Platform overrides — per-target (iOS, Android, Web) behaviors |
|
|
18
|
+
| `locales/` | Localization — i18n strings (JSON, ICU MessageFormat) |
|
|
19
|
+
|
|
20
|
+
All directory paths are configured in `openuispec.yaml` under `includes:` and support relative paths. For example, to share locales across projects:
|
|
21
|
+
```yaml
|
|
22
|
+
includes:
|
|
23
|
+
locales: "../../shared/locales" # resolved relative to openuispec.yaml
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Getting started
|
|
27
|
+
|
|
28
|
+
**Start here:** read `openuispec.yaml` — it's the root manifest that defines the project structure, data model, API endpoints, and generation targets.
|
|
29
|
+
|
|
30
|
+
### New project (no existing UI code)
|
|
31
|
+
|
|
32
|
+
1. Define your data model and API endpoints in `openuispec.yaml`
|
|
33
|
+
2. Create token files in `tokens/` (colors, typography, spacing)
|
|
34
|
+
3. Create screen specs in `screens/` (one YAML per screen)
|
|
35
|
+
4. Create navigation flows in `flows/`
|
|
36
|
+
5. Ask AI to generate native code from the spec
|
|
37
|
+
|
|
38
|
+
### Existing project (adopting OpenUISpec)
|
|
39
|
+
|
|
40
|
+
1. Scan the codebase for existing UI screens
|
|
41
|
+
2. Create a stub for each screen in `screens/`:
|
|
42
|
+
```yaml
|
|
43
|
+
screen_name:
|
|
44
|
+
semantic: "Brief description of what this screen does"
|
|
45
|
+
status: stub
|
|
46
|
+
layout:
|
|
47
|
+
type: scroll_vertical
|
|
48
|
+
```
|
|
49
|
+
3. Extract design tokens (colors, fonts, spacing) into `tokens/`
|
|
50
|
+
4. Fill in `data_model` and `api.endpoints` in `openuispec.yaml`
|
|
51
|
+
5. Spec screens incrementally: `stub` → `draft` → `ready`
|
|
52
|
+
|
|
53
|
+
## Screen and flow status
|
|
54
|
+
|
|
55
|
+
- `stub` — placeholder, not yet specced. Drift detection skips these.
|
|
56
|
+
- `draft` — actively being specced. Tracked by drift.
|
|
57
|
+
- `ready` — fully specified (default if omitted). Tracked by drift.
|
|
58
|
+
|
|
59
|
+
## Learning OpenUISpec — where to find the docs
|
|
60
|
+
|
|
61
|
+
All documentation is included in the installed `openuispec` package. Search for it in this order:
|
|
62
|
+
1. **Local:** `node_modules/openuispec/` (if installed as a project dependency)
|
|
63
|
+
2. **Global:** run `npm root -g` to find the global prefix, then look in `<prefix>/openuispec/`
|
|
64
|
+
3. **Online fallback:** if the package is not installed at all, fetch from:
|
|
65
|
+
- `https://openuispec.rsteam.uz/llms-full.txt` — complete spec + all JSON schemas in one file
|
|
66
|
+
- `https://openuispec.rsteam.uz/llms.txt` — concise summary with links
|
|
67
|
+
|
|
68
|
+
Inside the package:
|
|
69
|
+
- **Full specification:** `spec/openuispec-v0.1.md`
|
|
70
|
+
- **Example app:** `examples/taskflow/`
|
|
71
|
+
- **JSON Schemas:** `schema/`
|
|
72
|
+
|
|
73
|
+
## Token file structure — root wrapper key required
|
|
74
|
+
|
|
75
|
+
Every token file must have a single root key matching the token type. Do NOT put properties at the top level.
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
# ✅ Correct — tokens/typography.yaml
|
|
79
|
+
typography:
|
|
80
|
+
font_family: ...
|
|
81
|
+
scale: ...
|
|
82
|
+
|
|
83
|
+
# ❌ Wrong — missing root wrapper key
|
|
84
|
+
font_family: ...
|
|
85
|
+
scale: ...
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Root keys: `color`, `typography`, `spacing`, `elevation`, `motion`, `layout`, `themes`, `icons`.
|
|
89
|
+
|
|
90
|
+
## File formats and schemas
|
|
91
|
+
|
|
92
|
+
**IMPORTANT:** Before creating or editing any spec file, read the corresponding JSON Schema to understand the valid structure. Do not guess the file format.
|
|
93
|
+
|
|
94
|
+
| File | Schema | Root key |
|
|
95
|
+
|------|--------|----------|
|
|
96
|
+
| `openuispec.yaml` | `openuispec.schema.json` | `spec_version` |
|
|
97
|
+
| `screens/*.yaml` | `screen.schema.json` | `<screen_id>` |
|
|
98
|
+
| `flows/*.yaml` | `flow.schema.json` | `<flow_id>` |
|
|
99
|
+
| `platform/*.yaml` | `platform.schema.json` | `platform` |
|
|
100
|
+
| `locales/*.json` | `locale.schema.json` | (object) |
|
|
101
|
+
| `contracts/<name>.yaml` | `contract.schema.json` | `<contract_name>` |
|
|
102
|
+
| `contracts/x_*.yaml` | `custom-contract.schema.json` | `<x_name>` |
|
|
103
|
+
| `tokens/color.yaml` | `tokens/color.schema.json` | `color` |
|
|
104
|
+
| `tokens/typography.yaml` | `tokens/typography.schema.json` | `typography` |
|
|
105
|
+
| `tokens/spacing.yaml` | `tokens/spacing.schema.json` | `spacing` |
|
|
106
|
+
| `tokens/elevation.yaml` | `tokens/elevation.schema.json` | `elevation` |
|
|
107
|
+
| `tokens/motion.yaml` | `tokens/motion.schema.json` | `motion` |
|
|
108
|
+
| `tokens/layout.yaml` | `tokens/layout.schema.json` | `layout` |
|
|
109
|
+
| `tokens/themes.yaml` | `tokens/themes.schema.json` | `themes` |
|
|
110
|
+
| `tokens/icons.yaml` | `tokens/icons.schema.json` | `icons` |
|
|
111
|
+
|
|
112
|
+
All schemas are in `schema/` inside the installed package. Shared type definitions (actions, data-binding, adaptive, validation, common) are in `schema/defs/`.
|
|
113
|
+
|
|
114
|
+
**Workflow:** read the schema → read an example from `examples/taskflow/` → create the YAML → run `openuispec validate`.
|
|
115
|
+
|
|
116
|
+
## Spec format quick reference
|
|
117
|
+
|
|
118
|
+
- **7 contract families:** nav_container, surface, action_trigger, input_field, data_display, collection, feedback
|
|
119
|
+
- **Custom contracts:** prefixed with `x_` (e.g., `x_media_player`)
|
|
120
|
+
- **Data binding:** `$data:`, `$state:`, `$param:`, `$t:` prefixes
|
|
121
|
+
- **Actions:** typed objects — navigate, api_call, set_state, confirm, sequence, feedback, etc.
|
|
122
|
+
- **Adaptive layout:** size classes (compact, regular, expanded) with per-section overrides
|
|
123
|
+
|
|
124
|
+
## CLI commands
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
openuispec validate # Validate spec files against schemas
|
|
128
|
+
openuispec validate screens # Validate only screens
|
|
129
|
+
openuispec drift --target ios # Check for spec drift
|
|
130
|
+
openuispec drift --snapshot --target ios # Snapshot current state
|
|
131
|
+
openuispec drift --all # Include stubs in drift check
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Targets and output directories
|
|
135
|
+
|
|
136
|
+
This project generates native code for: **ios, android, web**
|
|
137
|
+
|
|
138
|
+
By default, drift stores state in `generated/<target>/<project>/`. To point targets to your actual code directories, add `output_dir` to `openuispec.yaml`:
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
generation:
|
|
142
|
+
targets: [ios, android, web]
|
|
143
|
+
output_dir:
|
|
144
|
+
web: "../web-ui/"
|
|
145
|
+
android: "../kmp-ui/"
|
|
146
|
+
ios: "../kmp-ui/iosApp/"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Paths are relative to `openuispec.yaml`.
|
|
150
|
+
|
|
151
|
+
## Learn more
|
|
152
|
+
|
|
153
|
+
All docs and examples are in the installed `openuispec` package — check `node_modules/openuispec/` or run `npm root -g` for the global install path.
|
|
154
|
+
|
|
155
|
+
- Full spec: `spec/openuispec-v0.1.md`
|
|
156
|
+
- Example app: `examples/taskflow/`
|
|
157
|
+
- JSON Schemas: `schema/`
|
|
158
|
+
- Online reference: `https://openuispec.rsteam.uz/llms-full.txt`
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
action_trigger:
|
|
2
|
+
tokens:
|
|
3
|
+
primary:
|
|
4
|
+
background: "color.brand.primary"
|
|
5
|
+
text: "color.brand.primary.on_color"
|
|
6
|
+
border: { width: 2, color: "color.brand.primary" }
|
|
7
|
+
cut_size: "spacing.sm"
|
|
8
|
+
ghost:
|
|
9
|
+
background: "color.surface.secondary"
|
|
10
|
+
text: "color.text.primary"
|
|
11
|
+
border: { width: 2, color: "color.brand.primary" }
|
|
12
|
+
cut_size: "spacing.sm"
|
|
13
|
+
platform_mapping:
|
|
14
|
+
ios:
|
|
15
|
+
primary: { shape: "CutCornerShape", clip: true, style: ".plain" }
|
|
16
|
+
ghost: { shape: "CutCornerShape", clip: true, style: ".plain" }
|
|
17
|
+
android:
|
|
18
|
+
primary: { shape: "CutCornerShape" }
|
|
19
|
+
ghost: { shape: "CutCornerShape" }
|
|
20
|
+
web:
|
|
21
|
+
primary: { style: "clip-path" }
|
|
22
|
+
ghost: { style: "clip-path" }
|
|
23
|
+
generation:
|
|
24
|
+
must_handle:
|
|
25
|
+
- "Cut top-left and bottom-right corners by cut_size for primary and ghost"
|
|
26
|
+
- "Preserve button hit area and loading state inside the cut shape"
|
|
27
|
+
should_handle:
|
|
28
|
+
- "Animate press/focus feedback without breaking the cut outline"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
collection:
|
|
2
|
+
tokens:
|
|
3
|
+
list:
|
|
4
|
+
item_min_height: 52
|
|
5
|
+
separator_color: "color.border.default"
|
|
6
|
+
separator_inset: "spacing.md"
|
|
7
|
+
content_padding: "spacing.none"
|
|
8
|
+
chips:
|
|
9
|
+
gap: "spacing.sm"
|
|
10
|
+
item_height: 36
|
|
11
|
+
item_padding_h: "spacing.md"
|
|
12
|
+
item_radius: 0
|
|
13
|
+
item_background: "color.surface.secondary"
|
|
14
|
+
item_background_selected: "color.brand.primary"
|
|
15
|
+
platform_mapping:
|
|
16
|
+
ios:
|
|
17
|
+
list: { widget: "List or LazyVStack", style: ".plain" }
|
|
18
|
+
chips: { widget: "ScrollView(.horizontal) + HStack" }
|
|
19
|
+
android:
|
|
20
|
+
list: { composable: "LazyColumn" }
|
|
21
|
+
chips: { composable: "LazyRow" }
|
|
22
|
+
web:
|
|
23
|
+
list: { element: "ul", class: "todo-list" }
|
|
24
|
+
chips: { element: "div", class: "chip-row" }
|
|
25
|
+
generation:
|
|
26
|
+
must_handle:
|
|
27
|
+
- "Preserve empty_state and pull_to_refresh behavior for task lists"
|
|
28
|
+
- "Render chips as single-select filters bound to state.active_filter"
|
|
29
|
+
- "Keep list item leading/trailing slots aligned across long task titles"
|
|
30
|
+
should_handle:
|
|
31
|
+
- "Use horizontal scrolling for chips on compact screens"
|
|
32
|
+
- "Apply selected chip styling with strong contrast against brand color"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
data_display:
|
|
2
|
+
tokens:
|
|
3
|
+
inline:
|
|
4
|
+
title_style: "typography.heading_lg"
|
|
5
|
+
subtitle_style: "typography.body_sm"
|
|
6
|
+
padding: "spacing.xs"
|
|
7
|
+
compact:
|
|
8
|
+
min_height: 52
|
|
9
|
+
padding_v: "spacing.sm"
|
|
10
|
+
padding_h: "spacing.md"
|
|
11
|
+
title_style: "typography.body"
|
|
12
|
+
subtitle_style: "typography.caption"
|
|
13
|
+
separator: { color: "color.border.default", inset_leading: "spacing.md" }
|
|
14
|
+
card:
|
|
15
|
+
background: "color.surface.secondary"
|
|
16
|
+
border: { width: 1, color: "color.border.default" }
|
|
17
|
+
radius: "spacing.sm"
|
|
18
|
+
padding: "spacing.md"
|
|
19
|
+
title_style: "typography.heading_sm"
|
|
20
|
+
subtitle_style: "typography.body_sm"
|
|
21
|
+
platform_mapping:
|
|
22
|
+
ios:
|
|
23
|
+
compact: { container: "HStack inside List row", emphasis: "rounded grouped inset" }
|
|
24
|
+
inline: { container: "VStack(alignment: .leading)" }
|
|
25
|
+
android:
|
|
26
|
+
compact: { composable: "ListItem", style: "supportingContent" }
|
|
27
|
+
inline: { composable: "Column" }
|
|
28
|
+
web:
|
|
29
|
+
compact: { element: "li", class: "todo-row" }
|
|
30
|
+
inline: { element: "div", class: "section-heading" }
|
|
31
|
+
generation:
|
|
32
|
+
must_handle:
|
|
33
|
+
- "Render compact task rows with clear title/subtitle hierarchy"
|
|
34
|
+
- "Support inline title/subtitle blocks for section headers"
|
|
35
|
+
- "Place leading and trailing subcomponents without collapsing text layout"
|
|
36
|
+
should_handle:
|
|
37
|
+
- "Use truncation for long titles while preserving priority/status indicators"
|
|
38
|
+
- "Keep compact rows visually aligned with checkbox leading controls"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
feedback:
|
|
2
|
+
tokens:
|
|
3
|
+
toast:
|
|
4
|
+
background: "color.surface.primary"
|
|
5
|
+
border: { width: 1, color: "color.border.default" }
|
|
6
|
+
radius: "spacing.sm"
|
|
7
|
+
padding: "spacing.md"
|
|
8
|
+
shadow: "elevation.md"
|
|
9
|
+
message_style: "typography.body_sm"
|
|
10
|
+
banner:
|
|
11
|
+
padding_v: "spacing.sm"
|
|
12
|
+
padding_h: "spacing.md"
|
|
13
|
+
border_leading: { width: 3, color_from: "severity" }
|
|
14
|
+
message_style: "typography.body_sm"
|
|
15
|
+
platform_mapping:
|
|
16
|
+
ios:
|
|
17
|
+
toast: { pattern: "custom overlay", position: "top" }
|
|
18
|
+
banner: { pattern: "inline error banner" }
|
|
19
|
+
android:
|
|
20
|
+
toast: { pattern: "custom composable", position: "top" }
|
|
21
|
+
banner: { pattern: "inline error banner" }
|
|
22
|
+
web:
|
|
23
|
+
toast: { pattern: "toast container", position: "top-right" }
|
|
24
|
+
banner: { element: "div", role: "alert", position: "inline-top" }
|
|
25
|
+
generation:
|
|
26
|
+
must_handle:
|
|
27
|
+
- "Map success and error feedback to distinct visuals and live-region behavior"
|
|
28
|
+
- "Auto-dismiss toast feedback after the declared duration"
|
|
29
|
+
- "Render banner feedback inline without shifting focus unexpectedly"
|
|
30
|
+
should_handle:
|
|
31
|
+
- "Stack multiple toasts without overlap"
|
|
32
|
+
- "Keep destructive/error banners visually anchored near the active form"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
input_field:
|
|
2
|
+
tokens:
|
|
3
|
+
text:
|
|
4
|
+
background: "color.surface.secondary"
|
|
5
|
+
border: { width: 2, color: "color.brand.primary" }
|
|
6
|
+
border_focused: { width: 2, color: "color.brand.primary" }
|
|
7
|
+
cut_size: "spacing.sm"
|
|
8
|
+
multiline:
|
|
9
|
+
background: "color.surface.secondary"
|
|
10
|
+
border: { width: 2, color: "color.brand.primary" }
|
|
11
|
+
border_focused: { width: 2, color: "color.brand.primary" }
|
|
12
|
+
cut_size: "spacing.sm"
|
|
13
|
+
select:
|
|
14
|
+
background: "color.surface.secondary"
|
|
15
|
+
border: { width: 2, color: "color.brand.primary" }
|
|
16
|
+
border_focused: { width: 2, color: "color.brand.primary" }
|
|
17
|
+
cut_size: "spacing.sm"
|
|
18
|
+
date:
|
|
19
|
+
background: "color.surface.secondary"
|
|
20
|
+
border: { width: 2, color: "color.brand.primary" }
|
|
21
|
+
border_focused: { width: 2, color: "color.brand.primary" }
|
|
22
|
+
cut_size: "spacing.sm"
|
|
23
|
+
number:
|
|
24
|
+
background: "color.surface.secondary"
|
|
25
|
+
border: { width: 2, color: "color.brand.primary" }
|
|
26
|
+
border_focused: { width: 2, color: "color.brand.primary" }
|
|
27
|
+
cut_size: "spacing.sm"
|
|
28
|
+
platform_mapping:
|
|
29
|
+
ios:
|
|
30
|
+
text: { shape: "CutCornerShape", clip: true }
|
|
31
|
+
multiline: { shape: "CutCornerShape", clip: true }
|
|
32
|
+
select: { shape: "CutCornerShape", clip: true }
|
|
33
|
+
date: { shape: "CutCornerShape", clip: true }
|
|
34
|
+
number: { shape: "CutCornerShape", clip: true }
|
|
35
|
+
android:
|
|
36
|
+
text: { shape: "CutCornerShape" }
|
|
37
|
+
multiline: { shape: "CutCornerShape" }
|
|
38
|
+
select: { shape: "CutCornerShape" }
|
|
39
|
+
date: { shape: "CutCornerShape" }
|
|
40
|
+
number: { shape: "CutCornerShape" }
|
|
41
|
+
web:
|
|
42
|
+
text: { style: "clip-path" }
|
|
43
|
+
multiline: { style: "clip-path" }
|
|
44
|
+
select: { style: "clip-path" }
|
|
45
|
+
date: { style: "clip-path" }
|
|
46
|
+
number: { style: "clip-path" }
|
|
47
|
+
generation:
|
|
48
|
+
must_handle:
|
|
49
|
+
- "Cut top-left and bottom-right corners by cut_size for text-like inputs"
|
|
50
|
+
- "Keep focus, error, and disabled borders aligned with the cut shape"
|
|
51
|
+
should_handle:
|
|
52
|
+
- "Animate the emphasized border on focus without clipping content"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
nav_container:
|
|
2
|
+
tokens:
|
|
3
|
+
tab_bar:
|
|
4
|
+
height: 56
|
|
5
|
+
background: "color.surface.primary"
|
|
6
|
+
border_top: { width: 1, color: "color.border.default" }
|
|
7
|
+
icon_size: 22
|
|
8
|
+
label_style: "typography.caption"
|
|
9
|
+
active_color: "color.brand.primary"
|
|
10
|
+
inactive_color: "color.text.tertiary"
|
|
11
|
+
rail:
|
|
12
|
+
width: 76
|
|
13
|
+
background: "color.surface.secondary"
|
|
14
|
+
icon_size: 22
|
|
15
|
+
label_style: "typography.caption"
|
|
16
|
+
active_color: "color.brand.primary"
|
|
17
|
+
sidebar:
|
|
18
|
+
width: { collapsed: 72, expanded: 240 }
|
|
19
|
+
background: "color.surface.secondary"
|
|
20
|
+
item_height: 48
|
|
21
|
+
item_radius: "spacing.sm"
|
|
22
|
+
item_padding_h: "spacing.md"
|
|
23
|
+
active_background: "color.brand.primary"
|
|
24
|
+
active_text: "color.brand.primary.on_color"
|
|
25
|
+
icon_size: 20
|
|
26
|
+
label_style: "typography.body_sm"
|
|
27
|
+
platform_mapping:
|
|
28
|
+
ios:
|
|
29
|
+
tab_bar: { widget: "TabView" }
|
|
30
|
+
rail: { widget: "NavigationSplitView sidebar (compact rail)" }
|
|
31
|
+
sidebar: { widget: "NavigationSplitView sidebar" }
|
|
32
|
+
android:
|
|
33
|
+
tab_bar: { composable: "NavigationBar" }
|
|
34
|
+
rail: { composable: "NavigationRail" }
|
|
35
|
+
sidebar: { composable: "PermanentNavigationDrawer" }
|
|
36
|
+
web:
|
|
37
|
+
tab_bar: { element: "nav", pattern: "bottom fixed bar" }
|
|
38
|
+
rail: { element: "aside", pattern: "icon rail" }
|
|
39
|
+
sidebar: { element: "aside", pattern: "collapsible sidebar" }
|
|
40
|
+
generation:
|
|
41
|
+
must_handle:
|
|
42
|
+
- "Switch between tab_bar, rail, and sidebar according to adaptive screen config"
|
|
43
|
+
- "Keep the selected item visually distinct with active icon/text treatment"
|
|
44
|
+
- "Preserve keyboard navigation and accessible navigation landmarks"
|
|
45
|
+
should_handle:
|
|
46
|
+
- "Animate rail/sidebar transitions without layout jump"
|
|
47
|
+
- "Respect safe-area insets for bottom navigation on mobile targets"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
surface:
|
|
2
|
+
tokens:
|
|
3
|
+
sheet:
|
|
4
|
+
background: "color.surface.primary"
|
|
5
|
+
border: { width: 2, color: "color.brand.primary" }
|
|
6
|
+
cut_size: "spacing.md"
|
|
7
|
+
shadow: "elevation.lg"
|
|
8
|
+
modal:
|
|
9
|
+
background: "color.surface.primary"
|
|
10
|
+
border: { width: 2, color: "color.brand.primary" }
|
|
11
|
+
cut_size: "spacing.md"
|
|
12
|
+
shadow: "elevation.lg"
|
|
13
|
+
platform_mapping:
|
|
14
|
+
ios:
|
|
15
|
+
sheet: { shape: "CutCornerShape", clip: true }
|
|
16
|
+
modal: { shape: "CutCornerShape", clip: true }
|
|
17
|
+
android:
|
|
18
|
+
sheet: { shape: "CutCornerShape" }
|
|
19
|
+
modal: { shape: "CutCornerShape" }
|
|
20
|
+
web:
|
|
21
|
+
sheet: { style: "clip-path" }
|
|
22
|
+
modal: { style: "clip-path" }
|
|
23
|
+
generation:
|
|
24
|
+
must_handle:
|
|
25
|
+
- "Apply the cut-corner silhouette to sheet and modal surfaces"
|
|
26
|
+
- "Maintain shadows and overlays without square corners bleeding through"
|
|
27
|
+
should_handle:
|
|
28
|
+
- "Preserve drag indicators and detents for sheets with the custom shape"
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
x_schedule_preview:
|
|
2
|
+
semantic: "Previews upcoming occurrences for a recurring schedule based on the current form values"
|
|
3
|
+
|
|
4
|
+
props:
|
|
5
|
+
title: { type: string, required: false, description: "Optional title shown above the preview list" }
|
|
6
|
+
cadence:
|
|
7
|
+
type: enum
|
|
8
|
+
values: [daily, weekly, monthly]
|
|
9
|
+
required: true
|
|
10
|
+
description: "Schedule cadence used to compute upcoming dates"
|
|
11
|
+
interval: { type: int, required: true, description: "Gap between occurrences in cadence units" }
|
|
12
|
+
weekday:
|
|
13
|
+
type: enum
|
|
14
|
+
values: [mon, tue, wed, thu, fri, sat, sun]
|
|
15
|
+
required: false
|
|
16
|
+
description: "Weekday used for weekly schedules"
|
|
17
|
+
month_day: { type: int, required: false, description: "Day of month used for monthly schedules" }
|
|
18
|
+
start_date: { type: date, required: true, description: "Date from which occurrences begin" }
|
|
19
|
+
end_date: { type: date, required: false, description: "Optional date after which no occurrences are shown" }
|
|
20
|
+
preview_count: { type: int, default: 4, description: "Maximum number of upcoming occurrences to render" }
|
|
21
|
+
empty_message: { type: string, required: false, description: "Message shown when no occurrences can be produced" }
|
|
22
|
+
invalid_message: { type: string, required: false, description: "Message shown when the schedule is incomplete or invalid" }
|
|
23
|
+
|
|
24
|
+
states:
|
|
25
|
+
idle:
|
|
26
|
+
semantic: "Preview has not received enough input to compute occurrences"
|
|
27
|
+
transitions_to: [loading, invalid, ready]
|
|
28
|
+
visual: "Muted placeholder frame with instructional copy"
|
|
29
|
+
loading:
|
|
30
|
+
semantic: "Preview is recomputing the next occurrences"
|
|
31
|
+
transitions_to: [ready, invalid, empty]
|
|
32
|
+
duration: "motion.fast"
|
|
33
|
+
feedback: "Skeleton dates shown while the schedule is being recomputed"
|
|
34
|
+
visual: "Placeholder rows in the preview panel"
|
|
35
|
+
ready:
|
|
36
|
+
semantic: "Upcoming occurrences are available"
|
|
37
|
+
transitions_to: [loading, invalid, empty]
|
|
38
|
+
behavior: "Renders an ordered list of upcoming dates derived from the current props"
|
|
39
|
+
visual: "Date chips or list rows with the next occurrences"
|
|
40
|
+
invalid:
|
|
41
|
+
semantic: "The schedule is incomplete or contradictory"
|
|
42
|
+
transitions_to: [loading, ready]
|
|
43
|
+
feedback: "Inline guidance explains which fields must be completed"
|
|
44
|
+
visual: "Notice style with invalid_message copy"
|
|
45
|
+
empty:
|
|
46
|
+
semantic: "The schedule is technically valid but yields no future occurrences"
|
|
47
|
+
transitions_to: [loading, ready]
|
|
48
|
+
feedback: "Empty schedule message displayed"
|
|
49
|
+
visual: "Neutral empty state inside the preview panel"
|
|
50
|
+
|
|
51
|
+
a11y:
|
|
52
|
+
role: "group"
|
|
53
|
+
label: "props.title"
|
|
54
|
+
traits:
|
|
55
|
+
loading: { announces: "Refreshing recurring schedule preview" }
|
|
56
|
+
invalid: { announces: "Recurring schedule preview needs more information" }
|
|
57
|
+
empty: { announces: "No upcoming occurrences available" }
|
|
58
|
+
focus:
|
|
59
|
+
keyboard:
|
|
60
|
+
next_item: "ArrowDown"
|
|
61
|
+
previous_item: "ArrowUp"
|
|
62
|
+
|
|
63
|
+
tokens:
|
|
64
|
+
compact:
|
|
65
|
+
min_height: 132
|
|
66
|
+
padding: "spacing.md"
|
|
67
|
+
background: "color.surface.secondary"
|
|
68
|
+
border: { width: 1, color: "color.border.default" }
|
|
69
|
+
radius: "spacing.sm"
|
|
70
|
+
title_style: "typography.caption"
|
|
71
|
+
item_style: "typography.body_sm"
|
|
72
|
+
item_background: "color.surface.primary"
|
|
73
|
+
item_border: { width: 1, color: "color.border.default" }
|
|
74
|
+
detail:
|
|
75
|
+
min_height: 184
|
|
76
|
+
padding: "spacing.lg"
|
|
77
|
+
background: "color.surface.primary"
|
|
78
|
+
border: { width: 1, color: "color.brand.secondary" }
|
|
79
|
+
radius: "spacing.sm"
|
|
80
|
+
title_style: "typography.heading_sm"
|
|
81
|
+
item_style: "typography.body"
|
|
82
|
+
item_background: "color.surface.secondary"
|
|
83
|
+
item_border: { width: 1, color: "color.border.default" }
|
|
84
|
+
|
|
85
|
+
platform_mapping:
|
|
86
|
+
ios:
|
|
87
|
+
compact: { component: "SchedulePreviewCard", framework: "SwiftUI" }
|
|
88
|
+
detail: { component: "SchedulePreviewPanel", framework: "SwiftUI" }
|
|
89
|
+
android:
|
|
90
|
+
compact: { component: "SchedulePreviewCard", library: "compose foundation" }
|
|
91
|
+
detail: { component: "SchedulePreviewPanel", library: "compose foundation" }
|
|
92
|
+
web:
|
|
93
|
+
compact: { component: "SchedulePreviewCard", implementation: "react component" }
|
|
94
|
+
detail: { component: "SchedulePreviewPanel", implementation: "react component" }
|
|
95
|
+
|
|
96
|
+
dependencies:
|
|
97
|
+
ios:
|
|
98
|
+
frameworks: ["SwiftUI", "Foundation"]
|
|
99
|
+
android:
|
|
100
|
+
libraries: []
|
|
101
|
+
web:
|
|
102
|
+
packages: []
|
|
103
|
+
|
|
104
|
+
generation:
|
|
105
|
+
must_handle:
|
|
106
|
+
- "Derive the next preview_count schedule occurrences from cadence, interval, and start_date"
|
|
107
|
+
- "Respect weekday for weekly schedules and month_day for monthly schedules"
|
|
108
|
+
- "Transition to invalid when required inputs are missing or end_date is earlier than start_date"
|
|
109
|
+
- "Update the preview reactively as props change"
|
|
110
|
+
- "Render loading, ready, invalid, and empty states inside the same container"
|
|
111
|
+
should_handle:
|
|
112
|
+
- "Format preview dates using the active locale"
|
|
113
|
+
- "Highlight the nearest upcoming occurrence"
|
|
114
|
+
- "Animate list updates when the schedule changes"
|
|
115
|
+
may_handle:
|
|
116
|
+
- "Relative labels such as Today or Tomorrow when locale rules allow it"
|
|
117
|
+
- "Secondary captions describing the cadence in natural language"
|
|
118
|
+
|
|
119
|
+
test_cases:
|
|
120
|
+
- id: weekly_preview_generation
|
|
121
|
+
description: "Weekly schedules produce future dates on the selected weekday"
|
|
122
|
+
given: "cadence=weekly, interval=1, weekday=mon, start_date=2026-03-16"
|
|
123
|
+
when: "The preview enters ready state"
|
|
124
|
+
then: "The next preview_count Mondays from the start date are rendered in order"
|
|
125
|
+
- id: monthly_preview_generation
|
|
126
|
+
description: "Monthly schedules use the selected day of month"
|
|
127
|
+
given: "cadence=monthly, interval=1, month_day=15, start_date=2026-03-01"
|
|
128
|
+
when: "The preview enters ready state"
|
|
129
|
+
then: "The 15th of upcoming months is rendered until preview_count is reached"
|
|
130
|
+
- id: invalid_when_end_before_start
|
|
131
|
+
description: "Contradictory date ranges show invalid state"
|
|
132
|
+
given: "start_date is after end_date"
|
|
133
|
+
when: "The preview recomputes"
|
|
134
|
+
then: "The component enters invalid state and shows invalid_message"
|