openuispec 0.1.20 → 0.1.23

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 CHANGED
@@ -96,7 +96,7 @@ openuispec/
96
96
  │ │ └── icons.schema.json # Icon token schema
97
97
  │ ├── defs/
98
98
  │ │ ├── common.schema.json # Shared types (icons, badges, etc.)
99
- │ │ ├── action.schema.json # 13 action types (discriminated union)
99
+ │ │ ├── action.schema.json # 14 action types (discriminated union)
100
100
  │ │ ├── data-binding.schema.json # Data sources, state, params
101
101
  │ │ ├── adaptive.schema.json # Adaptive override pattern
102
102
  │ │ └── validation.schema.json # Validation rule definitions
@@ -210,7 +210,7 @@ Paths are relative to `openuispec.yaml`. The `.openuispec-state.json` file is st
210
210
  | 6. Navigation flows | Multi-screen journeys with transitions and progress |
211
211
  | 7. Platform adaptation | Per-target overrides for iOS, Android, Web |
212
212
  | 8. AI generation contract | Compliance levels (MUST/SHOULD/MAY), validation, drift detection |
213
- | 9. Action system | 13 action types, composition, optimistic updates |
213
+ | 9. Action system | 14 action types, composition, optimistic updates |
214
214
  | 10. Data binding & state | Sources, paths, format expressions, reactivity, caching |
215
215
  | 11. Internationalization | Locale files, `$t:` references, ICU MessageFormat, RTL, platform mapping |
216
216
  | 12. Custom contract extensions | `x_` prefixed domain-specific contracts, registration, dependencies |
package/cli/index.ts CHANGED
@@ -9,7 +9,33 @@
9
9
  * openuispec validate [group...] Validate spec files against schemas
10
10
  */
11
11
 
12
- import { init } from "./init.js";
12
+ import { init, updateRules, extractRulesVersion, getPackageVersion } from "./init.js";
13
+ import { join } from "node:path";
14
+ import { existsSync, readFileSync } from "node:fs";
15
+
16
+ function checkRulesVersion(): void {
17
+ const cwd = process.cwd();
18
+ const installed = getPackageVersion();
19
+ for (const file of ["CLAUDE.md", "AGENTS.md"]) {
20
+ const filePath = join(cwd, file);
21
+ if (!existsSync(filePath)) continue;
22
+ const rulesVersion = extractRulesVersion(filePath);
23
+ if (rulesVersion && rulesVersion !== installed) {
24
+ console.log(
25
+ `\n⚠ ${file} rules were generated by v${rulesVersion} but v${installed} is installed.` +
26
+ `\n Run: openuispec update-rules\n`
27
+ );
28
+ } else if (!rulesVersion) {
29
+ const content = readFileSync(filePath, "utf-8");
30
+ if (content.includes("OpenUISpec")) {
31
+ console.log(
32
+ `\n⚠ ${file} has OpenUISpec rules without a version marker.` +
33
+ `\n Run: openuispec update-rules\n`
34
+ );
35
+ }
36
+ }
37
+ }
38
+ }
13
39
 
14
40
  async function main(): Promise<void> {
15
41
  const [command, ...rest] = process.argv.slice(2);
@@ -19,6 +45,10 @@ async function main(): Promise<void> {
19
45
  await init();
20
46
  break;
21
47
 
48
+ case "update-rules":
49
+ updateRules();
50
+ break;
51
+
22
52
  case "drift": {
23
53
  const { runDrift } = await import("../drift/index.js");
24
54
  runDrift(rest);
@@ -28,6 +58,7 @@ async function main(): Promise<void> {
28
58
  case "validate": {
29
59
  const { runValidate } = await import("../schema/validate.js");
30
60
  runValidate(rest);
61
+ checkRulesVersion();
31
62
  break;
32
63
  }
33
64
 
@@ -39,6 +70,7 @@ OpenUISpec CLI v0.1
39
70
 
40
71
  Usage:
41
72
  openuispec init Create a new spec project
73
+ openuispec update-rules Update AI rules to match installed version
42
74
  openuispec drift [--target <t>] Check for spec drift
43
75
  openuispec drift --snapshot --target <t> Snapshot current state
44
76
  openuispec validate [group...] Validate spec files
package/cli/init.ts CHANGED
@@ -15,7 +15,8 @@ import {
15
15
  existsSync,
16
16
  appendFileSync,
17
17
  } from "node:fs";
18
- import { join, relative } from "node:path";
18
+ import { join, relative, dirname } from "node:path";
19
+ import { fileURLToPath } from "node:url";
19
20
 
20
21
  // ── prompts ──────────────────────────────────────────────────────────
21
22
 
@@ -64,6 +65,21 @@ function writeIfMissing(path: string, content: string): boolean {
64
65
  return true;
65
66
  }
66
67
 
68
+ // ── version ─────────────────────────────────────────────────────────
69
+
70
+ const RULES_START_MARKER = "<!-- openuispec-rules-start -->";
71
+ const RULES_END_MARKER = "<!-- openuispec-rules-end -->";
72
+
73
+ function getPackageVersion(): string {
74
+ const pkgPath = join(
75
+ dirname(fileURLToPath(import.meta.url)),
76
+ "..",
77
+ "package.json"
78
+ );
79
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
80
+ return pkg.version;
81
+ }
82
+
67
83
  // ── templates ────────────────────────────────────────────────────────
68
84
 
69
85
  function manifestTemplate(
@@ -175,7 +191,10 @@ Docs: https://openuispec.rsteam.uz
175
191
 
176
192
  function aiRulesBlock(specDir: string, targets: string[]): string {
177
193
  const targetList = targets.map((t) => `"${t}"`).join(", ");
194
+ const version = getPackageVersion();
178
195
  return `
196
+ ${RULES_START_MARKER}
197
+ <!-- openuispec-rules-version: ${version} -->
179
198
  # OpenUISpec — AI Assistant Rules
180
199
  # ================================
181
200
  # This project uses OpenUISpec to define UI as a semantic spec.
@@ -242,18 +261,110 @@ This means the project has existing UI code but hasn't been specced yet. Your jo
242
261
 
243
262
  ## After modifying spec files
244
263
  1. Run \`openuispec validate\` to check specs against the schema.
245
- 2. Run \`openuispec drift --snapshot --target <target>\` for each affected platform.
246
- 3. Run \`openuispec drift\` to verify no untracked drift remains.
264
+ 2. **Update the generated code** for each affected platform to match the new spec.
265
+ 3. Run \`openuispec drift --snapshot --target <target>\` to baseline the updated state.
266
+ 4. Run \`openuispec drift\` to verify no untracked drift remains.
247
267
 
248
268
  ## CLI commands
249
269
  - \`openuispec init\` — scaffold a new spec project
250
270
  - \`openuispec validate [group...]\` — validate spec files against schemas
251
271
  - \`openuispec drift --target <t>\` — check for spec drift
252
272
  - \`openuispec drift --snapshot --target <t>\` — snapshot current state
273
+ - \`openuispec update-rules\` — update AI rules to match installed package version
253
274
  - \`openuispec drift --all\` — include stubs in drift check
275
+ ${RULES_END_MARKER}
254
276
  `;
255
277
  }
256
278
 
279
+ // ── update-rules ────────────────────────────────────────────────────
280
+
281
+ export function updateRules(): void {
282
+ const cwd = process.cwd();
283
+ const version = getPackageVersion();
284
+
285
+ // Detect spec dir from existing openuispec.yaml
286
+ let specDir = "openuispec";
287
+ for (const candidate of ["openuispec", "spec", "."]) {
288
+ if (existsSync(join(cwd, candidate, "openuispec.yaml"))) {
289
+ specDir = candidate;
290
+ break;
291
+ }
292
+ }
293
+
294
+ // Detect targets from manifest
295
+ let targets = ["ios", "android", "web"];
296
+ try {
297
+ const manifest = readFileSync(
298
+ join(cwd, specDir, "openuispec.yaml"),
299
+ "utf-8"
300
+ );
301
+ const match = manifest.match(/targets:\s*\[([^\]]+)\]/);
302
+ if (match) {
303
+ targets = match[1].split(",").map((t) => t.trim().replace(/['"]/g, ""));
304
+ }
305
+ } catch {}
306
+
307
+ const rules = aiRulesBlock(specDir, targets);
308
+ let updated = 0;
309
+
310
+ for (const file of ["CLAUDE.md", "AGENTS.md"]) {
311
+ const filePath = join(cwd, file);
312
+ if (!existsSync(filePath)) continue;
313
+
314
+ const content = readFileSync(filePath, "utf-8");
315
+
316
+ // Try marker-based replacement first
317
+ const startIdx = content.indexOf(RULES_START_MARKER);
318
+ const endIdx = content.indexOf(RULES_END_MARKER);
319
+
320
+ if (startIdx !== -1 && endIdx !== -1) {
321
+ const before = content.slice(0, startIdx);
322
+ const after = content.slice(endIdx + RULES_END_MARKER.length);
323
+ writeFileSync(filePath, before + rules.trimStart().trimEnd() + after);
324
+ console.log(` updated ${file} (v${version})`);
325
+ updated++;
326
+ continue;
327
+ }
328
+
329
+ // Fallback: find the OpenUISpec rules block by header pattern
330
+ // The block runs from the header to EOF (it's always the last content)
331
+ const headerIdx = content.indexOf("# OpenUISpec — AI Assistant Rules");
332
+ if (headerIdx !== -1) {
333
+ const before = content.slice(0, headerIdx);
334
+ const newContent = before + rules.trimStart().trimEnd() + "\n";
335
+ writeFileSync(filePath, newContent);
336
+ console.log(` updated ${file} (v${version}, migrated to markers)`);
337
+ updated++;
338
+ continue;
339
+ }
340
+
341
+ console.log(` skip ${file} (no OpenUISpec rules block found)`);
342
+ }
343
+
344
+ if (updated === 0) {
345
+ console.log(
346
+ "No CLAUDE.md or AGENTS.md with OpenUISpec rules found.\nRun `openuispec init` first."
347
+ );
348
+ } else {
349
+ console.log(`\nAI rules updated to v${version}`);
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Extract the rules version from a CLAUDE.md / AGENTS.md file.
355
+ * Returns null if no version marker is found.
356
+ */
357
+ export function extractRulesVersion(filePath: string): string | null {
358
+ if (!existsSync(filePath)) return null;
359
+ const content = readFileSync(filePath, "utf-8");
360
+ const match = content.match(
361
+ /<!-- openuispec-rules-version:\s*([^\s]+)\s*-->/
362
+ );
363
+ return match ? match[1] : null;
364
+ }
365
+
366
+ export { getPackageVersion };
367
+
257
368
  // ── main ─────────────────────────────────────────────────────────────
258
369
 
259
370
  export async function init(): Promise<void> {
@@ -1,29 +1,52 @@
1
+ <!-- openuispec-rules-start -->
2
+ <!-- openuispec-rules-version: 0.1.22 -->
1
3
  # OpenUISpec — AI Assistant Rules
2
4
  # ================================
3
5
  # This project uses OpenUISpec to define UI as a semantic spec.
4
6
  # Spec files are the single source of truth for all UI across platforms.
5
7
  # Targets: "ios", "android", "web"
6
8
 
9
+ ## IMPORTANT — Read the specification before working with spec files
10
+
11
+ The spec format, file schemas, and generation rules are defined in the installed `openuispec` package.
12
+ You MUST read the reference files listed below before creating, editing, or generating from any spec file.
13
+ Do NOT guess the file format — skipping this step will produce invalid YAML that fails validation.
14
+
15
+ **Find the package in this order:**
16
+ 1. `node_modules/openuispec/` (project dependency)
17
+ 2. Run `npm root -g` → `<prefix>/openuispec/` (global install)
18
+ 3. Online: `https://openuispec.rsteam.uz/llms-full.txt` (if not installed)
19
+
20
+ **Reference files inside the package (read in this order):**
21
+ 1. `README.md` — schema tables, file format reference, root wrapper keys
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
24
+ 4. `schema/` — JSON Schemas for every file type
25
+
26
+ These files are updated with each package version. Always read from the installed package,
27
+ not from cached or memorized content, to ensure you use the latest spec.
28
+
7
29
  ## What is OpenUISpec
8
30
  OpenUISpec is a YAML-based spec format that describes an app's UI semantically — tokens, screens, flows, and platform overrides. AI reads the spec and generates native code (SwiftUI, Compose, React). AI reads native code and updates the spec. The spec is the sync layer between platforms.
9
31
 
10
32
  ## Spec location
11
33
  - Spec root: `openuispec/`
12
34
  - Manifest: `openuispec/openuispec.yaml` — always read this first.
13
- - Tokens: `openuispec/tokens/` — colors, typography, spacing, motion, icons, themes
14
- - Screens: `openuispec/screens/` — one YAML file per screen
15
- - Flows: `openuispec/flows/` — multi-step navigation journeys
16
- - Contracts: `openuispec/contracts/` — UI component definitions
17
- - Platform: `openuispec/platform/` — per-target overrides (iOS, Android, Web)
18
- - Locales: `openuispec/locales/` — i18n strings (JSON, ICU MessageFormat)
35
+ - Tokens: `openuispec/tokens/`
36
+ - Screens: `openuispec/screens/`
37
+ - Flows: `openuispec/flows/`
38
+ - Contracts: `openuispec/contracts/`
39
+ - Platform: `openuispec/platform/`
40
+ - Locales: `openuispec/locales/`
19
41
 
20
- **Note:** These are the default paths. Actual paths are in `includes:` in `openuispec.yaml` and may use relative paths (e.g. `../../shared/locales`). Always read `openuispec.yaml` to find the real directories.
42
+ **Note:** These are the default paths. Actual paths are in `includes:` in `openuispec.yaml` and may use relative paths. Always read `openuispec.yaml` to find the real directories.
21
43
 
22
44
  ## If spec directories are empty (first-time setup)
23
45
  This means the project has existing UI code but hasn't been specced yet. Your job:
24
46
 
25
- 1. **Find existing screens** — scan the codebase for UI screen files (SwiftUI views, Compose screens, React components/pages).
26
- 2. **Create stubs** — for each screen, create `openuispec/screens/<name>.yaml` with:
47
+ 1. **Read the spec first** — find and read `spec/openuispec-v0.1.md` from the installed package.
48
+ 2. **Find existing screens** — scan the codebase for UI screen files.
49
+ 3. **Create stubs** — for each screen, create `openuispec/screens/<name>.yaml` with:
27
50
  ```yaml
28
51
  screen_name:
29
52
  semantic: "Brief description of what this screen does"
@@ -31,14 +54,8 @@ This means the project has existing UI code but hasn't been specced yet. Your jo
31
54
  layout:
32
55
  type: scroll_vertical
33
56
  ```
34
- 3. **Extract tokens** — scan the codebase for colors, fonts, spacing values and create token files in `openuispec/tokens/`.
35
- 4. **Update the manifest** — fill in `data_model` and `api.endpoints` in `openuispec/openuispec.yaml` based on the existing code.
36
- 5. **Spec screens on demand** — when the user asks to spec a screen, read the native code, create a full spec, and change `status: draft` → `ready`.
37
-
38
- ## Screen and flow status
39
- - `stub` — placeholder, not yet specced. Drift detection skips these.
40
- - `draft` — actively being specced. Tracked by drift.
41
- - `ready` — fully specified (default if omitted). Tracked by drift.
57
+ 4. **Extract tokens** — scan for colors, fonts, spacing and create files in `openuispec/tokens/`.
58
+ 5. **Update the manifest** — fill in `data_model` and `api.endpoints` in `openuispec/openuispec.yaml`.
42
59
 
43
60
  ## Making UI changes
44
61
  1. Read the relevant spec files before modifying any UI code.
@@ -49,79 +66,15 @@ This means the project has existing UI code but hasn't been specced yet. Your jo
49
66
 
50
67
  ## After modifying spec files
51
68
  1. Run `openuispec validate` to check specs against the schema.
52
- 2. Run `openuispec drift --snapshot --target <target>` for each affected platform.
53
- 3. Run `openuispec drift` to verify no untracked drift remains.
54
-
55
- ## Learning OpenUISpec — where to find the docs
56
- All documentation is in the installed `openuispec` package. Search in this order:
57
- 1. **Local:** `node_modules/openuispec/` (project dependency)
58
- 2. **Global:** run `npm root -g` to get the global prefix, then look in `<prefix>/openuispec/`
59
- 3. **Online fallback:** if not installed, fetch from:
60
- - `https://openuispec.rsteam.uz/llms-full.txt` — complete spec + all JSON schemas
61
- - `https://openuispec.rsteam.uz/llms.txt` — concise summary with links
62
-
63
- Inside the package:
64
- 1. **Full specification:** `spec/openuispec-v0.1.md` — the complete spec (read this to understand the format)
65
- 2. **Example app:** `examples/taskflow/` — a complete working app with all file types
66
- 3. **JSON Schemas:** `schema/` — validation schemas that define the exact structure of every file type
67
-
68
- ## Token file structure — root wrapper key required
69
- Every token file must have a single root key matching the token type. Do NOT put properties at the top level.
70
- - `tokens/color.yaml` → root key: `color`
71
- - `tokens/typography.yaml` → root key: `typography`
72
- - `tokens/spacing.yaml` → root key: `spacing`
73
- - `tokens/elevation.yaml` → root key: `elevation`
74
- - `tokens/motion.yaml` → root key: `motion`
75
- - `tokens/layout.yaml` → root key: `layout`
76
- - `tokens/themes.yaml` → root key: `themes`
77
- - `tokens/icons.yaml` → root key: `icons`
78
-
79
- ## File formats and schemas — read before creating spec files
80
- Before creating or editing any spec file, read the corresponding JSON Schema. Do not guess the file format.
81
-
82
- | File | Schema (in `schema/` inside the installed package) | Root key |
83
- |------|--------|----------|
84
- | `openuispec.yaml` | `openuispec.schema.json` | `spec_version` |
85
- | `screens/*.yaml` | `screen.schema.json` | `<screen_id>` |
86
- | `flows/*.yaml` | `flow.schema.json` | `<flow_id>` |
87
- | `platform/*.yaml` | `platform.schema.json` | `platform` |
88
- | `locales/*.json` | `locale.schema.json` | (object) |
89
- | `contracts/x_*.yaml` | `custom-contract.schema.json` | `contract` |
90
- | `tokens/color.yaml` | `tokens/color.schema.json` | `color` |
91
- | `tokens/typography.yaml` | `tokens/typography.schema.json` | `typography` |
92
- | `tokens/spacing.yaml` | `tokens/spacing.schema.json` | `spacing` |
93
- | `tokens/elevation.yaml` | `tokens/elevation.schema.json` | `elevation` |
94
- | `tokens/motion.yaml` | `tokens/motion.schema.json` | `motion` |
95
- | `tokens/layout.yaml` | `tokens/layout.schema.json` | `layout` |
96
- | `tokens/themes.yaml` | `tokens/themes.schema.json` | `themes` |
97
- | `tokens/icons.yaml` | `tokens/icons.schema.json` | `icons` |
98
-
99
- Shared type definitions (actions, data-binding, adaptive, validation, common) are in `schema/defs/`.
100
-
101
- Workflow: read the schema → read an example from `examples/taskflow/` → create the YAML → run `openuispec validate`.
102
-
103
- ## Spec format reference
104
- - 7 contract families: nav_container, surface, action_trigger, input_field, data_display, collection, feedback
105
- - Custom contracts: prefixed with `x_` (e.g., `x_media_player`)
106
- - Data binding: `$data:`, `$state:`, `$param:`, `$t:` prefixes
107
- - Actions: typed objects (navigate, api_call, set_state, confirm, sequence, feedback, etc.)
108
- - Adaptive layout: size classes (compact, regular, expanded) with per-section overrides
109
-
110
- ## Output directories
111
- Drift tracks spec changes per target. By default state is stored in `generated/<target>/<project>/`.
112
- To map targets to actual code directories, set `generation.output_dir` in `openuispec.yaml`:
113
- ```yaml
114
- generation:
115
- output_dir:
116
- web: "../web-ui/"
117
- android: "../kmp-ui/"
118
- ios: "../kmp-ui/iosApp/"
119
- ```
120
- Paths are relative to `openuispec.yaml`. The `.openuispec-state.json` file is stored inside each output directory.
69
+ 2. **Update the generated code** for each affected platform to match the new spec.
70
+ 3. Run `openuispec drift --snapshot --target <target>` to baseline the updated state.
71
+ 4. Run `openuispec drift` to verify no untracked drift remains.
121
72
 
122
73
  ## CLI commands
123
74
  - `openuispec init` — scaffold a new spec project
124
75
  - `openuispec validate [group...]` — validate spec files against schemas
125
76
  - `openuispec drift --target <t>` — check for spec drift
126
77
  - `openuispec drift --snapshot --target <t>` — snapshot current state
78
+ - `openuispec update-rules` — update AI rules to match installed package version
127
79
  - `openuispec drift --all` — include stubs in drift check
80
+ <!-- openuispec-rules-end -->
@@ -1,3 +1,5 @@
1
+ <!-- openuispec-rules-start -->
2
+ <!-- openuispec-rules-version: 0.1.22 -->
1
3
  # OpenUISpec — AI Assistant Rules
2
4
  # ================================
3
5
  # This project uses OpenUISpec to define UI as a semantic spec.
@@ -64,12 +66,15 @@ This means the project has existing UI code but hasn't been specced yet. Your jo
64
66
 
65
67
  ## After modifying spec files
66
68
  1. Run `openuispec validate` to check specs against the schema.
67
- 2. Run `openuispec drift --snapshot --target <target>` for each affected platform.
68
- 3. Run `openuispec drift` to verify no untracked drift remains.
69
+ 2. **Update the generated code** for each affected platform to match the new spec.
70
+ 3. Run `openuispec drift --snapshot --target <target>` to baseline the updated state.
71
+ 4. Run `openuispec drift` to verify no untracked drift remains.
69
72
 
70
73
  ## CLI commands
71
74
  - `openuispec init` — scaffold a new spec project
72
75
  - `openuispec validate [group...]` — validate spec files against schemas
73
76
  - `openuispec drift --target <t>` — check for spec drift
74
77
  - `openuispec drift --snapshot --target <t>` — snapshot current state
78
+ - `openuispec update-rules` — update AI rules to match installed package version
75
79
  - `openuispec drift --all` — include stubs in drift check
80
+ <!-- openuispec-rules-end -->
@@ -122,7 +122,7 @@ home:
122
122
  item_variant: compact
123
123
  item_props_map:
124
124
  title: "item.title"
125
- subtitle: "{item.due_date | format:date_relative}"
125
+ subtitle: "{item.due_date | format:date_relative.abbreviated}"
126
126
  leading:
127
127
  contract: input_field
128
128
  input_type: checkbox
@@ -61,6 +61,7 @@ settings:
61
61
  children:
62
62
  - contract: input_field
63
63
  input_type: select
64
+ render_hint: segmented
64
65
  props:
65
66
  label: "$t:settings.language"
66
67
  icon: { ref: "globe", position: leading }
@@ -71,6 +72,7 @@ settings:
71
72
 
72
73
  - contract: input_field
73
74
  input_type: select
75
+ render_hint: segmented
74
76
  props:
75
77
  label: "$t:settings.theme"
76
78
  options:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openuispec",
3
- "version": "0.1.20",
3
+ "version": "0.1.23",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "A semantic UI specification format for AI-native, platform-native app development",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://openuispec.org/schema/defs/action.schema.json",
4
4
  "title": "OpenUISpec Action",
5
- "description": "Discriminated union of the 13 action types in OpenUISpec",
5
+ "description": "Discriminated union of the 14 action types in OpenUISpec",
6
6
 
7
7
  "type": "object",
8
8
  "required": ["type"],