openuispec 0.1.27 → 0.1.28

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 (113) hide show
  1. package/README.md +22 -19
  2. package/cli/init.ts +7 -7
  3. package/docs/implementation-notes.md +5 -1
  4. package/docs/release-notes-v0.1.28.md +25 -0
  5. package/docs/stress-test-maturity-report.md +1 -1
  6. package/drift/index.ts +21 -4
  7. package/examples/taskflow/AGENTS.md +112 -0
  8. package/examples/taskflow/CLAUDE.md +112 -0
  9. package/examples/taskflow/generated/android/TaskFlow/README.md +43 -0
  10. package/examples/taskflow/generated/android/TaskFlow/app/build.gradle.kts +76 -0
  11. package/examples/taskflow/generated/android/TaskFlow/app/proguard-rules.pro +1 -0
  12. package/examples/taskflow/generated/android/TaskFlow/app/src/main/AndroidManifest.xml +21 -0
  13. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/MainActivity.kt +19 -0
  14. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/TaskFlowApp.kt +283 -0
  15. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/DomainModels.kt +106 -0
  16. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/SampleData.kt +57 -0
  17. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/components/Common.kt +109 -0
  18. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/HomeScreen.kt +112 -0
  19. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/ProjectsScreen.kt +61 -0
  20. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/SettingsScreen.kt +82 -0
  21. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/TaskDetailScreen.kt +111 -0
  22. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/sheets/Sheets.kt +77 -0
  23. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Color.kt +30 -0
  24. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Theme.kt +86 -0
  25. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Type.kt +57 -0
  26. package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/strings.xml +155 -0
  27. package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/themes.xml +4 -0
  28. package/examples/taskflow/generated/android/TaskFlow/build.gradle.kts +5 -0
  29. package/examples/taskflow/generated/android/TaskFlow/gradle/gradle-daemon-jvm.properties +12 -0
  30. package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.jar +0 -0
  31. package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.properties +7 -0
  32. package/examples/taskflow/generated/android/TaskFlow/gradle.properties +4 -0
  33. package/examples/taskflow/generated/android/TaskFlow/gradlew +18 -0
  34. package/examples/taskflow/generated/android/TaskFlow/gradlew.bat +12 -0
  35. package/examples/taskflow/generated/android/TaskFlow/settings.gradle.kts +18 -0
  36. package/examples/taskflow/generated/ios/TaskFlow/README.md +21 -0
  37. package/examples/taskflow/generated/ios/TaskFlow/Resources/en.lproj/Localizable.strings +115 -0
  38. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/App/TaskFlowApp.swift +24 -0
  39. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Components/AppChrome.swift +150 -0
  40. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Flows/TaskEditorSheet.swift +220 -0
  41. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Models/DomainModels.swift +122 -0
  42. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/CalendarView.swift +21 -0
  43. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/HomeView.swift +201 -0
  44. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProfileEditView.swift +48 -0
  45. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectDetailView.swift +59 -0
  46. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectsView.swift +63 -0
  47. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/SettingsView.swift +85 -0
  48. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/TaskDetailView.swift +219 -0
  49. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppModel.swift +320 -0
  50. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppSupport.swift +41 -0
  51. package/examples/taskflow/generated/ios/TaskFlow/project.yml +26 -0
  52. package/examples/taskflow/generated/web/TaskFlow/README.md +19 -0
  53. package/examples/taskflow/generated/web/TaskFlow/index.html +12 -0
  54. package/examples/taskflow/generated/web/TaskFlow/package-lock.json +1908 -0
  55. package/examples/taskflow/generated/web/TaskFlow/package.json +24 -0
  56. package/examples/taskflow/generated/web/TaskFlow/src/App.tsx +58 -0
  57. package/examples/taskflow/generated/web/TaskFlow/src/AppShell.tsx +55 -0
  58. package/examples/taskflow/generated/web/TaskFlow/src/components/Common.tsx +82 -0
  59. package/examples/taskflow/generated/web/TaskFlow/src/components/Modals.tsx +191 -0
  60. package/examples/taskflow/generated/web/TaskFlow/src/components/Nav.tsx +41 -0
  61. package/examples/taskflow/generated/web/TaskFlow/src/generated/messages.ts +131 -0
  62. package/examples/taskflow/generated/web/TaskFlow/src/hooks.ts +25 -0
  63. package/examples/taskflow/generated/web/TaskFlow/src/i18n.ts +39 -0
  64. package/examples/taskflow/generated/web/TaskFlow/src/locales.en.json +111 -0
  65. package/examples/taskflow/generated/web/TaskFlow/src/main.tsx +13 -0
  66. package/examples/taskflow/generated/web/TaskFlow/src/screens/HomeScreen.tsx +111 -0
  67. package/examples/taskflow/generated/web/TaskFlow/src/screens/ProjectsScreen.tsx +82 -0
  68. package/examples/taskflow/generated/web/TaskFlow/src/screens/SettingsScreens.tsx +132 -0
  69. package/examples/taskflow/generated/web/TaskFlow/src/screens/TaskDetail.tsx +105 -0
  70. package/examples/taskflow/generated/web/TaskFlow/src/store.ts +216 -0
  71. package/examples/taskflow/generated/web/TaskFlow/src/styles.css +617 -0
  72. package/examples/taskflow/generated/web/TaskFlow/src/types.ts +64 -0
  73. package/examples/taskflow/generated/web/TaskFlow/src/utils.ts +78 -0
  74. package/examples/taskflow/generated/web/TaskFlow/tsconfig.json +21 -0
  75. package/examples/taskflow/generated/web/TaskFlow/vite.config.ts +6 -0
  76. package/examples/taskflow/openuispec/README.md +49 -0
  77. package/examples/todo-orbit/AGENTS.md +44 -19
  78. package/examples/todo-orbit/CLAUDE.md +44 -19
  79. package/examples/todo-orbit/openuispec/README.md +2 -2
  80. package/package.json +1 -1
  81. package/schema/validate.ts +9 -4
  82. package/status/index.ts +16 -3
  83. /package/examples/taskflow/{contracts → openuispec/contracts}/README.md +0 -0
  84. /package/examples/taskflow/{contracts → openuispec/contracts}/action_trigger.yaml +0 -0
  85. /package/examples/taskflow/{contracts → openuispec/contracts}/collection.yaml +0 -0
  86. /package/examples/taskflow/{contracts → openuispec/contracts}/data_display.yaml +0 -0
  87. /package/examples/taskflow/{contracts → openuispec/contracts}/feedback.yaml +0 -0
  88. /package/examples/taskflow/{contracts → openuispec/contracts}/input_field.yaml +0 -0
  89. /package/examples/taskflow/{contracts → openuispec/contracts}/nav_container.yaml +0 -0
  90. /package/examples/taskflow/{contracts → openuispec/contracts}/surface.yaml +0 -0
  91. /package/examples/taskflow/{contracts → openuispec/contracts}/x_media_player.yaml +0 -0
  92. /package/examples/taskflow/{flows → openuispec/flows}/create_task.yaml +0 -0
  93. /package/examples/taskflow/{flows → openuispec/flows}/edit_task.yaml +0 -0
  94. /package/examples/taskflow/{locales → openuispec/locales}/en.json +0 -0
  95. /package/examples/taskflow/{openuispec.yaml → openuispec/openuispec.yaml} +0 -0
  96. /package/examples/taskflow/{platform → openuispec/platform}/android.yaml +0 -0
  97. /package/examples/taskflow/{platform → openuispec/platform}/ios.yaml +0 -0
  98. /package/examples/taskflow/{platform → openuispec/platform}/web.yaml +0 -0
  99. /package/examples/taskflow/{screens → openuispec/screens}/calendar.yaml +0 -0
  100. /package/examples/taskflow/{screens → openuispec/screens}/home.yaml +0 -0
  101. /package/examples/taskflow/{screens → openuispec/screens}/profile_edit.yaml +0 -0
  102. /package/examples/taskflow/{screens → openuispec/screens}/project_detail.yaml +0 -0
  103. /package/examples/taskflow/{screens → openuispec/screens}/projects.yaml +0 -0
  104. /package/examples/taskflow/{screens → openuispec/screens}/settings.yaml +0 -0
  105. /package/examples/taskflow/{screens → openuispec/screens}/task_detail.yaml +0 -0
  106. /package/examples/taskflow/{tokens → openuispec/tokens}/color.yaml +0 -0
  107. /package/examples/taskflow/{tokens → openuispec/tokens}/elevation.yaml +0 -0
  108. /package/examples/taskflow/{tokens → openuispec/tokens}/icons.yaml +0 -0
  109. /package/examples/taskflow/{tokens → openuispec/tokens}/layout.yaml +0 -0
  110. /package/examples/taskflow/{tokens → openuispec/tokens}/motion.yaml +0 -0
  111. /package/examples/taskflow/{tokens → openuispec/tokens}/spacing.yaml +0 -0
  112. /package/examples/taskflow/{tokens → openuispec/tokens}/themes.yaml +0 -0
  113. /package/examples/taskflow/{tokens → openuispec/tokens}/typography.yaml +0 -0
@@ -0,0 +1,78 @@
1
+ import type { Filter, Project, Task } from "./types";
2
+
3
+ export function matchesFilter(task: Task, filter: Filter) {
4
+ const due = task.dueDate ? new Date(task.dueDate) : null;
5
+ const today = new Date();
6
+ today.setHours(0, 0, 0, 0);
7
+ if (filter === "all") return true;
8
+ if (filter === "done") return task.status === "done";
9
+ if (filter === "today") return Boolean(due && due.toDateString() === today.toDateString() && task.status !== "done");
10
+ return Boolean(due && due > today && task.status !== "done");
11
+ }
12
+
13
+ export function matchesQuery(task: Task, projects: Project[], query: string) {
14
+ if (!query.trim()) return true;
15
+ const projectName = projects.find((project) => project.id === task.projectId)?.name ?? "";
16
+ return [task.title, task.description ?? "", projectName, task.tags.join(" ")]
17
+ .join(" ")
18
+ .toLowerCase()
19
+ .includes(query.toLowerCase());
20
+ }
21
+
22
+ export function buildCounts(tasks: Task[]) {
23
+ return {
24
+ all: tasks.length,
25
+ today: tasks.filter((task) => matchesFilter(task, "today")).length,
26
+ upcoming: tasks.filter((task) => matchesFilter(task, "upcoming")).length,
27
+ done: tasks.filter((task) => task.status === "done").length
28
+ };
29
+ }
30
+
31
+ export function parseTags(value: string) {
32
+ return value
33
+ .split(",")
34
+ .map((item) => item.trim())
35
+ .filter(Boolean);
36
+ }
37
+
38
+ export function initials(name: string) {
39
+ return name
40
+ .split(" ")
41
+ .slice(0, 2)
42
+ .map((part) => part[0]?.toUpperCase() ?? "")
43
+ .join("");
44
+ }
45
+
46
+ export function relativeDate(value: string) {
47
+ const date = new Date(value);
48
+ const today = new Date();
49
+ today.setHours(0, 0, 0, 0);
50
+ const target = new Date(date);
51
+ target.setHours(0, 0, 0, 0);
52
+ const diff = Math.round((target.getTime() - today.getTime()) / 86400000);
53
+ if (diff === 0) return "Today";
54
+ if (diff === 1) return "Tomorrow";
55
+ if (diff === -1) return "Yesterday";
56
+ if (diff > 1) return `in ${diff} days`;
57
+ return `${Math.abs(diff)} days ago`;
58
+ }
59
+
60
+ export function formatDate(value: string) {
61
+ return new Date(value).toLocaleDateString("en-US", {
62
+ month: "short",
63
+ day: "numeric",
64
+ year: "numeric"
65
+ });
66
+ }
67
+
68
+ export function addDays(days: number) {
69
+ const next = new Date();
70
+ next.setDate(next.getDate() + days);
71
+ return next.toISOString();
72
+ }
73
+
74
+ export function daysAgo(days: number) {
75
+ const previous = new Date();
76
+ previous.setDate(previous.getDate() - days);
77
+ return previous.toISOString();
78
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "allowJs": false,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "strict": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "module": "ESNext",
13
+ "moduleResolution": "Node",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true,
17
+ "jsx": "react-jsx"
18
+ },
19
+ "include": ["src"],
20
+ "references": []
21
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()]
6
+ });
@@ -0,0 +1,49 @@
1
+ # TaskFlow — OpenUISpec
2
+
3
+ This directory contains the **OpenUISpec** semantic UI specification for **TaskFlow**.
4
+
5
+ **Start here:** read `openuispec.yaml` — it defines the project structure, data model, API endpoints, and generation targets (**ios, android, web**).
6
+
7
+ ## Directory structure
8
+
9
+ | Directory | Contents |
10
+ |-----------|----------|
11
+ | `tokens/` | Design tokens — colors, typography, spacing, elevation, motion, icons, themes |
12
+ | `screens/` | Screen definitions — one YAML file per screen |
13
+ | `flows/` | Navigation flows — multi-step user journeys |
14
+ | `contracts/` | Component contracts — standard extensions and custom (`x_` prefixed) |
15
+ | `platform/` | Platform overrides — per-target (iOS, Android, Web) behaviors |
16
+ | `locales/` | Localization — i18n strings (JSON, ICU MessageFormat) |
17
+
18
+ ## IMPORTANT — Read the specification before working with spec files
19
+
20
+ The spec format, file schemas, and generation rules are defined in the installed `openuispec` package.
21
+ You MUST read these reference files before creating, editing, or generating from any spec file.
22
+ Do NOT guess the file format — skipping this step will produce invalid YAML that fails validation.
23
+
24
+ **Find the package in this order:**
25
+ 1. `node_modules/openuispec/` (project dependency)
26
+ 2. Run `npm root -g` → `<prefix>/openuispec/` (global install)
27
+ 3. Online: `https://openuispec.rsteam.uz/llms-full.txt` (if not installed)
28
+
29
+ **Reference files inside the package (read in this order):**
30
+ 1. `README.md` — schema tables, file format reference, root keys
31
+ 2. `spec/openuispec-v0.1.md` — full specification (contracts, layout, expressions, etc.)
32
+ 3. `examples/taskflow/openuispec/` — complete working example with all file types
33
+ 4. `schema/` — JSON Schemas for validation
34
+
35
+ ## CLI commands
36
+
37
+ ```bash
38
+ openuispec validate # Validate spec files against schemas
39
+ openuispec validate semantic # Check semantic cross-references
40
+ openuispec validate screens # Validate only screens
41
+ openuispec status # Show which targets are behind
42
+ openuispec drift --target ios --explain # Explain semantic spec drift since ios baseline
43
+ openuispec prepare --target ios # Build an AI-ready ios update bundle
44
+ openuispec drift --snapshot --target ios # Snapshot current state + git baseline after ios output exists
45
+ ```
46
+
47
+ ## Learn more
48
+
49
+ Docs: https://openuispec.rsteam.uz
@@ -1,5 +1,5 @@
1
1
  <!-- openuispec-rules-start -->
2
- <!-- openuispec-rules-version: 0.1.23 -->
2
+ <!-- openuispec-rules-version: 0.1.28 -->
3
3
  # OpenUISpec — AI Assistant Rules
4
4
  # ================================
5
5
  # This project uses OpenUISpec to define UI as a semantic spec.
@@ -20,7 +20,7 @@ Do NOT guess the file format — skipping this step will produce invalid YAML th
20
20
  **Reference files inside the package (read in this order):**
21
21
  1. `README.md` — schema tables, file format reference, root wrapper keys
22
22
  2. `spec/openuispec-v0.1.md` — full specification (contracts, layout, expressions, adaptive, etc.)
23
- 3. `examples/taskflow/` — complete working example with all file types
23
+ 3. `examples/taskflow/openuispec/` — complete working example with all file types
24
24
  4. `schema/` — JSON Schemas for every file type
25
25
 
26
26
  These files are updated with each package version. Always read from the installed package,
@@ -57,22 +57,46 @@ This means the project has existing UI code but hasn't been specced yet. Your jo
57
57
  4. **Extract tokens** — scan for colors, fonts, spacing and create files in `openuispec/tokens/`.
58
58
  5. **Update the manifest** — fill in `data_model` and `api.endpoints` in `openuispec/openuispec.yaml`.
59
59
 
60
- ## Making UI changes
61
- 1. Read the relevant spec files before modifying any UI code.
62
- 2. If the change requires a spec update, modify the spec FIRST, then update code.
63
- 3. Never modify generated code without updating the spec.
64
- 4. When adding a new screen, create the corresponding spec file.
65
- 5. When removing a screen, remove the spec file and update flow references.
66
-
67
- ## After modifying spec files
68
- 1. Run `openuispec validate` to check specs against the schema.
69
- 2. Run `openuispec validate semantic`.
70
- 3. Run `openuispec drift --target <target> --explain` to inspect semantic changes since that target's baseline.
71
- 4. Run `openuispec prepare --target <target>` to build the target update bundle.
72
- 5. **Update the generated code** for each affected platform to match the new spec.
73
- 6. Run `openuispec drift --snapshot --target <target>` to baseline the updated state.
74
- 7. Run `openuispec drift --target <target> --explain` again to confirm no spec changes remain for that target.
75
- 8. Run `openuispec status` to see which other targets are still behind.
60
+ ## OpenUISpec Source Of Truth
61
+
62
+ OpenUISpec spec files are the primary source of truth for UI behavior across platforms.
63
+
64
+ ### Start from spec when:
65
+ - the request changes screen structure
66
+ - the request changes navigation
67
+ - the request changes fields, actions, validation, or data binding
68
+ - the request changes tokens, variants, contracts, flows, or localization
69
+ - the request affects more than one platform
70
+ - the request is phrased in product/UI terms rather than platform-code terms
71
+
72
+ Spec-first workflow:
73
+ 1. Read `openuispec/openuispec.yaml` and the relevant spec files first.
74
+ 2. Update the spec first.
75
+ 3. Update the affected generated/native UI code to match the spec.
76
+ 4. Run `openuispec validate`.
77
+ 5. Run `openuispec validate semantic`.
78
+ 6. Run `openuispec drift --target <target> --explain` to inspect semantic changes since that target's baseline.
79
+ 7. Run `openuispec prepare --target <target>` to build the AI/developer work bundle for that target.
80
+ 8. Verify the affected UI targets build/run if possible.
81
+ 9. Only then run `openuispec drift --snapshot --target <target>` for affected targets, after that target output directory exists.
82
+ 10. Run `openuispec drift --target <target> --explain` again to confirm no spec changes remain for that target.
83
+ 11. Use `openuispec status` to see which other targets are still behind the updated spec.
84
+
85
+ ### Start from platform code when:
86
+ - the change is platform-specific polish
87
+ - the change is a local bug fix that does not alter shared semantic behavior
88
+ - the request explicitly asks for an iOS-only, Android-only, or web-only adjustment
89
+
90
+ Platform-first workflow:
91
+ 1. Update native/platform code.
92
+ 2. If the change affects shared semantics, sync the spec afterward.
93
+ 3. If the change is intentionally platform-specific, document it in `platform/*.yaml` when appropriate.
94
+
95
+ ### Never do this:
96
+ - Do not snapshot drift immediately after changing spec unless the UI code has also been updated.
97
+ - Do not treat `openuispec drift` as proof that generated UI matches the spec.
98
+ - Do not skip `--explain` / `prepare` when another platform needs to catch up with shared spec changes.
99
+ - Do not modify generated UI without checking whether the spec must change first.
76
100
 
77
101
  ## CLI commands
78
102
  - `openuispec init` — scaffold a new spec project
@@ -80,8 +104,9 @@ This means the project has existing UI code but hasn't been specced yet. Your jo
80
104
  - `openuispec validate semantic` — run semantic cross-reference linting
81
105
  - `openuispec drift --target <t>` — check for spec drift
82
106
  - `openuispec drift --target <t> --explain` — explain semantic spec drift since the target baseline
83
- - `openuispec drift --snapshot --target <t>` — snapshot current state
107
+ - `openuispec drift --snapshot --target <t>` — snapshot current state after the target output exists
84
108
  - `openuispec prepare --target <t>` — build an AI-ready target update bundle
109
+ - `openuispec status` — show cross-target baseline/drift status
85
110
  - `openuispec update-rules` — update AI rules to match installed package version
86
111
  - `openuispec drift --all` — include stubs in drift check
87
112
  <!-- openuispec-rules-end -->
@@ -1,5 +1,5 @@
1
1
  <!-- openuispec-rules-start -->
2
- <!-- openuispec-rules-version: 0.1.23 -->
2
+ <!-- openuispec-rules-version: 0.1.28 -->
3
3
  # OpenUISpec — AI Assistant Rules
4
4
  # ================================
5
5
  # This project uses OpenUISpec to define UI as a semantic spec.
@@ -20,7 +20,7 @@ Do NOT guess the file format — skipping this step will produce invalid YAML th
20
20
  **Reference files inside the package (read in this order):**
21
21
  1. `README.md` — schema tables, file format reference, root wrapper keys
22
22
  2. `spec/openuispec-v0.1.md` — full specification (contracts, layout, expressions, adaptive, etc.)
23
- 3. `examples/taskflow/` — complete working example with all file types
23
+ 3. `examples/taskflow/openuispec/` — complete working example with all file types
24
24
  4. `schema/` — JSON Schemas for every file type
25
25
 
26
26
  These files are updated with each package version. Always read from the installed package,
@@ -57,22 +57,46 @@ This means the project has existing UI code but hasn't been specced yet. Your jo
57
57
  4. **Extract tokens** — scan for colors, fonts, spacing and create files in `openuispec/tokens/`.
58
58
  5. **Update the manifest** — fill in `data_model` and `api.endpoints` in `openuispec/openuispec.yaml`.
59
59
 
60
- ## Making UI changes
61
- 1. Read the relevant spec files before modifying any UI code.
62
- 2. If the change requires a spec update, modify the spec FIRST, then update code.
63
- 3. Never modify generated code without updating the spec.
64
- 4. When adding a new screen, create the corresponding spec file.
65
- 5. When removing a screen, remove the spec file and update flow references.
66
-
67
- ## After modifying spec files
68
- 1. Run `openuispec validate` to check specs against the schema.
69
- 2. Run `openuispec validate semantic`.
70
- 3. Run `openuispec drift --target <target> --explain` to inspect semantic changes since that target's baseline.
71
- 4. Run `openuispec prepare --target <target>` to build the target update bundle.
72
- 5. **Update the generated code** for each affected platform to match the new spec.
73
- 6. Run `openuispec drift --snapshot --target <target>` to baseline the updated state.
74
- 7. Run `openuispec drift --target <target> --explain` again to confirm no spec changes remain for that target.
75
- 8. Run `openuispec status` to see which other targets are still behind.
60
+ ## OpenUISpec Source Of Truth
61
+
62
+ OpenUISpec spec files are the primary source of truth for UI behavior across platforms.
63
+
64
+ ### Start from spec when:
65
+ - the request changes screen structure
66
+ - the request changes navigation
67
+ - the request changes fields, actions, validation, or data binding
68
+ - the request changes tokens, variants, contracts, flows, or localization
69
+ - the request affects more than one platform
70
+ - the request is phrased in product/UI terms rather than platform-code terms
71
+
72
+ Spec-first workflow:
73
+ 1. Read `openuispec/openuispec.yaml` and the relevant spec files first.
74
+ 2. Update the spec first.
75
+ 3. Update the affected generated/native UI code to match the spec.
76
+ 4. Run `openuispec validate`.
77
+ 5. Run `openuispec validate semantic`.
78
+ 6. Run `openuispec drift --target <target> --explain` to inspect semantic changes since that target's baseline.
79
+ 7. Run `openuispec prepare --target <target>` to build the AI/developer work bundle for that target.
80
+ 8. Verify the affected UI targets build/run if possible.
81
+ 9. Only then run `openuispec drift --snapshot --target <target>` for affected targets, after that target output directory exists.
82
+ 10. Run `openuispec drift --target <target> --explain` again to confirm no spec changes remain for that target.
83
+ 11. Use `openuispec status` to see which other targets are still behind the updated spec.
84
+
85
+ ### Start from platform code when:
86
+ - the change is platform-specific polish
87
+ - the change is a local bug fix that does not alter shared semantic behavior
88
+ - the request explicitly asks for an iOS-only, Android-only, or web-only adjustment
89
+
90
+ Platform-first workflow:
91
+ 1. Update native/platform code.
92
+ 2. If the change affects shared semantics, sync the spec afterward.
93
+ 3. If the change is intentionally platform-specific, document it in `platform/*.yaml` when appropriate.
94
+
95
+ ### Never do this:
96
+ - Do not snapshot drift immediately after changing spec unless the UI code has also been updated.
97
+ - Do not treat `openuispec drift` as proof that generated UI matches the spec.
98
+ - Do not skip `--explain` / `prepare` when another platform needs to catch up with shared spec changes.
99
+ - Do not modify generated UI without checking whether the spec must change first.
76
100
 
77
101
  ## CLI commands
78
102
  - `openuispec init` — scaffold a new spec project
@@ -80,8 +104,9 @@ This means the project has existing UI code but hasn't been specced yet. Your jo
80
104
  - `openuispec validate semantic` — run semantic cross-reference linting
81
105
  - `openuispec drift --target <t>` — check for spec drift
82
106
  - `openuispec drift --target <t> --explain` — explain semantic spec drift since the target baseline
83
- - `openuispec drift --snapshot --target <t>` — snapshot current state
107
+ - `openuispec drift --snapshot --target <t>` — snapshot current state after the target output exists
84
108
  - `openuispec prepare --target <t>` — build an AI-ready target update bundle
109
+ - `openuispec status` — show cross-target baseline/drift status
85
110
  - `openuispec update-rules` — update AI rules to match installed package version
86
111
  - `openuispec drift --all` — include stubs in drift check
87
112
  <!-- openuispec-rules-end -->
@@ -29,7 +29,7 @@ Do NOT guess the file format — skipping this step will produce invalid YAML th
29
29
  **Reference files inside the package (read in this order):**
30
30
  1. `README.md` — schema tables, file format reference, root keys
31
31
  2. `spec/openuispec-v0.1.md` — full specification (contracts, layout, expressions, etc.)
32
- 3. `examples/taskflow/` — complete working example with all file types
32
+ 3. `examples/taskflow/openuispec/` — complete working example with all file types
33
33
  4. `schema/` — JSON Schemas for validation
34
34
 
35
35
  ## CLI commands
@@ -41,7 +41,7 @@ openuispec validate screens # Validate only screens
41
41
  openuispec status # Show which targets are behind
42
42
  openuispec drift --target ios --explain # Explain semantic spec drift since ios baseline
43
43
  openuispec prepare --target ios # Build an AI-ready ios update bundle
44
- openuispec drift --snapshot --target ios # Snapshot current state + git baseline
44
+ openuispec drift --snapshot --target ios # Snapshot current state + git baseline after ios output exists
45
45
  ```
46
46
 
47
47
  If a target snapshot was created before baseline metadata was added, `--explain` and `status` will ask you to re-run `openuispec drift --snapshot --target <target>` for that target.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openuispec",
3
- "version": "0.1.27",
3
+ "version": "0.1.28",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "A semantic UI specification format for AI-native, platform-native app development",
@@ -5,7 +5,7 @@
5
5
  * Usage:
6
6
  * openuispec validate # validate all spec files
7
7
  * openuispec validate tokens screens # validate specific groups
8
- * npm run validate # from repo (uses examples/taskflow)
8
+ * npm run validate # from repo (uses examples/taskflow/openuispec)
9
9
  */
10
10
 
11
11
  import { readFileSync, readdirSync, existsSync } from "node:fs";
@@ -572,9 +572,14 @@ function findProjectDir(cwd: string): string {
572
572
  }
573
573
  }
574
574
  // Fallback for running from repo root with examples/
575
- const examplesDir = join(cwd, "examples", "taskflow");
576
- if (existsSync(join(examplesDir, "openuispec.yaml"))) {
577
- return examplesDir;
575
+ const exampleCandidates = [
576
+ join(cwd, "examples", "taskflow", "openuispec"),
577
+ join(cwd, "examples", "taskflow"),
578
+ ];
579
+ for (const dir of exampleCandidates) {
580
+ if (existsSync(join(dir, "openuispec.yaml"))) {
581
+ return dir;
582
+ }
578
583
  }
579
584
  console.error(
580
585
  "Error: No openuispec.yaml found.\n" +
package/status/index.ts CHANGED
@@ -26,6 +26,7 @@ import { readFileSync } from "node:fs";
26
26
  interface TargetStatus {
27
27
  target: string;
28
28
  output_dir: string;
29
+ output_exists: boolean;
29
30
  snapshot: boolean;
30
31
  snapshot_at: string | null;
31
32
  baseline: {
@@ -74,12 +75,14 @@ function readState(statePath: string): StateFile {
74
75
 
75
76
  function buildTargetStatus(cwd: string, projectDir: string, projectName: string, target: string): TargetStatus {
76
77
  const outputDir = resolveOutputDir(projectDir, projectName, target);
78
+ const outputExists = existsSync(outputDir);
77
79
  const path = stateFilePath(projectDir, projectName, target);
78
80
 
79
81
  if (!existsSync(path)) {
80
82
  return {
81
83
  target,
82
84
  output_dir: outputDir,
85
+ output_exists: outputExists,
83
86
  snapshot: false,
84
87
  snapshot_at: null,
85
88
  baseline: {
@@ -93,7 +96,9 @@ function buildTargetStatus(cwd: string, projectDir: string, projectName: string,
93
96
  removed: 0,
94
97
  behind: false,
95
98
  explain_available: false,
96
- note: "No snapshot found for this target.",
99
+ note: outputExists
100
+ ? "No snapshot found for this target."
101
+ : `Output directory not found. Run code generation for "${target}" first.`,
97
102
  };
98
103
  }
99
104
 
@@ -107,6 +112,7 @@ function buildTargetStatus(cwd: string, projectDir: string, projectName: string,
107
112
  return {
108
113
  target,
109
114
  output_dir: outputDir,
115
+ output_exists: outputExists,
110
116
  snapshot: true,
111
117
  snapshot_at: state.snapshot_at,
112
118
  baseline: {
@@ -147,11 +153,18 @@ function printReport(result: StatusResult): void {
147
153
  for (const target of result.targets) {
148
154
  const summary = target.snapshot
149
155
  ? `${target.changed} changed, ${target.added} added, ${target.removed} removed`
150
- : "no snapshot";
151
- const status = target.snapshot ? (target.behind ? "behind" : "up to date") : "needs baseline";
156
+ : target.output_exists
157
+ ? "no snapshot"
158
+ : "output missing";
159
+ const status = target.snapshot
160
+ ? (target.behind ? "behind" : "up to date")
161
+ : target.output_exists
162
+ ? "needs baseline"
163
+ : "needs generation";
152
164
 
153
165
  console.log(`${target.target}`);
154
166
  console.log(` output: ${target.output_dir}`);
167
+ console.log(` output exists: ${target.output_exists ? "yes" : "no"}`);
155
168
  console.log(` snapshot: ${target.snapshot ? target.snapshot_at : "missing"}`);
156
169
  if (target.baseline.label) {
157
170
  console.log(` baseline: ${target.baseline.label}`);