openuispec 0.1.3 → 0.1.5

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 (2) hide show
  1. package/cli/init.ts +92 -176
  2. package/package.json +1 -1
package/cli/init.ts CHANGED
@@ -35,13 +35,9 @@ async function askList(
35
35
  options: string[],
36
36
  defaults: string[]
37
37
  ): Promise<string[]> {
38
- console.log(`${question}`);
39
- for (const opt of options) {
40
- const mark = defaults.includes(opt) ? "[x]" : "[ ]";
41
- console.log(` ${mark} ${opt}`);
42
- }
38
+ const defaultStr = defaults.join(", ");
43
39
  const raw = (
44
- await rl.question(`Enter choices (comma-separated, enter for defaults): `)
40
+ await rl.question(`${question} [${options.join(", ")}] (${defaultStr}): `)
45
41
  ).trim();
46
42
  if (!raw) return defaults;
47
43
  return raw
@@ -122,84 +118,6 @@ api:
122
118
  `;
123
119
  }
124
120
 
125
- function starterColorTokens(): string {
126
- return `# Design tokens: Color palette
127
- brand:
128
- primary:
129
- "50": "#EEF2FF"
130
- "100": "#E0E7FF"
131
- "500": "#6366F1"
132
- "600": "#4F46E5"
133
- "900": "#312E81"
134
-
135
- surface:
136
- background: "#FFFFFF"
137
- card: "#F9FAFB"
138
- elevated: "#FFFFFF"
139
-
140
- text:
141
- primary: "#111827"
142
- secondary: "#6B7280"
143
- disabled: "#D1D5DB"
144
-
145
- semantic:
146
- success: "#10B981"
147
- warning: "#F59E0B"
148
- error: "#EF4444"
149
- info: "#3B82F6"
150
- `;
151
- }
152
-
153
- function starterTypographyTokens(): string {
154
- return `# Design tokens: Typography
155
- font_families:
156
- sans: { default: "System" }
157
-
158
- type_scale:
159
- title_lg: { size: 28, weight: bold, tracking: 0, line_height: 1.2 }
160
- title_md: { size: 22, weight: semibold, tracking: 0, line_height: 1.3 }
161
- title_sm: { size: 17, weight: semibold, tracking: 0, line_height: 1.3 }
162
- body_lg: { size: 17, weight: regular, tracking: 0, line_height: 1.5 }
163
- body_md: { size: 15, weight: regular, tracking: 0, line_height: 1.5 }
164
- body_sm: { size: 13, weight: regular, tracking: 0, line_height: 1.4 }
165
- label_lg: { size: 15, weight: medium, tracking: 0.02, line_height: 1.3 }
166
- label_md: { size: 13, weight: medium, tracking: 0.02, line_height: 1.3 }
167
- caption: { size: 11, weight: regular, tracking: 0.02, line_height: 1.3 }
168
- `;
169
- }
170
-
171
- function starterSpacingTokens(): string {
172
- return `# Design tokens: Spacing
173
- base_unit: 4
174
- platform_flex: "10%"
175
-
176
- scale:
177
- "0": 0
178
- "1": 4
179
- "2": 8
180
- "3": 12
181
- "4": 16
182
- "5": 20
183
- "6": 24
184
- "8": 32
185
- "10": 40
186
- "12": 48
187
- "16": 64
188
- `;
189
- }
190
-
191
- function starterLocale(): string {
192
- return JSON.stringify(
193
- {
194
- app: {
195
- name: "My App",
196
- },
197
- },
198
- null,
199
- 2
200
- );
201
- }
202
-
203
121
  function aiRulesBlock(specDir: string, targets: string[]): string {
204
122
  const targetList = targets.map((t) => `"${t}"`).join(", ");
205
123
  return `
@@ -207,49 +125,67 @@ function aiRulesBlock(specDir: string, targets: string[]): string {
207
125
  # ================================
208
126
  # This project uses OpenUISpec to define UI as a semantic spec.
209
127
  # Spec files are the single source of truth for all UI across platforms.
128
+ # Targets: ${targetList}
129
+
130
+ ## What is OpenUISpec
131
+ 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.
210
132
 
211
133
  ## Spec location
212
134
  - Spec root: \`${specDir}/\`
213
- - Manifest: \`${specDir}/openuispec.yaml\`
214
- - Always read \`openuispec.yaml\` first to understand the project structure.
215
-
216
- ## Before making UI changes
217
- 1. Read the relevant spec files (screens, tokens, flows) before modifying any UI code.
218
- 2. If the change requires a spec update, modify the spec files FIRST, then update generated code.
219
- 3. Never modify generated code directly change the spec and regenerate.
220
-
221
- ## After modifying spec files
222
- 1. Run \`openuispec drift --snapshot --target <target>\` for each affected platform.
223
- 2. Targets in this project: ${targetList}.
224
- 3. Run \`openuispec drift\` to verify no untracked drift remains.
135
+ - Manifest: \`${specDir}/openuispec.yaml\` — always read this first.
136
+ - Tokens: \`${specDir}/tokens/\` colors, typography, spacing, motion, icons, themes
137
+ - Screens: \`${specDir}/screens/\` — one YAML file per screen
138
+ - Flows: \`${specDir}/flows/\` multi-step navigation journeys
139
+ - Contracts: \`${specDir}/contracts/\` UI component definitions
140
+ - Platform: \`${specDir}/platform/\` per-target overrides (iOS, Android, Web)
141
+ - Locales: \`${specDir}/locales/\`i18n strings (JSON, ICU MessageFormat)
142
+
143
+ ## If spec directories are empty (first-time setup)
144
+ This means the project has existing UI code but hasn't been specced yet. Your job:
145
+
146
+ 1. **Find existing screens** scan the codebase for UI screen files (SwiftUI views, Compose screens, React components/pages).
147
+ 2. **Create stubs** — for each screen, create \`${specDir}/screens/<name>.yaml\` with:
148
+ \`\`\`yaml
149
+ screen_name:
150
+ semantic: "Brief description of what this screen does"
151
+ status: stub
152
+ layout:
153
+ type: scroll_vertical
154
+ \`\`\`
155
+ 3. **Extract tokens** — scan the codebase for colors, fonts, spacing values and create token files in \`${specDir}/tokens/\`.
156
+ 4. **Update the manifest** — fill in \`data_model\` and \`api.endpoints\` in \`${specDir}/openuispec.yaml\` based on the existing code.
157
+ 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\`.
225
158
 
226
159
  ## Screen and flow status
227
- Screens and flows have a \`status\` field that controls drift tracking:
228
160
  - \`stub\` — placeholder, not yet specced. Drift detection skips these.
229
- - \`draft\` — in progress, actively being specced. Tracked by drift.
161
+ - \`draft\` — actively being specced. Tracked by drift.
230
162
  - \`ready\` — fully specified (default if omitted). Tracked by drift.
231
163
 
232
- When adopting OpenUISpec in an existing project:
233
- 1. Create spec files for existing screens as \`status: stub\` initially.
234
- 2. When speccing a screen from existing code, change status to \`draft\`.
235
- 3. Once the spec is complete and reviewed, change to \`ready\` (or remove the field).
236
- 4. Only \`draft\` and \`ready\` screens trigger drift failures in CI.
237
-
238
- ## Spec file conventions
239
- - Tokens (colors, typography, spacing, motion, icons) live in \`${specDir}/tokens/\`.
240
- - Contracts (UI component definitions) live in \`${specDir}/contracts/\`.
241
- - Screens live in \`${specDir}/screens/\`. One screen per file.
242
- - Navigation flows live in \`${specDir}/flows/\`.
243
- - Platform overrides live in \`${specDir}/platform/\`.
244
- - Localization strings live in \`${specDir}/locales/\`.
245
-
246
- ## Key rules
247
- - The spec uses 7 contract families: nav_container, surface, action_trigger, input_field, data_display, collection, feedback.
248
- - Custom contracts are prefixed with \`x_\` (e.g., \`x_media_player\`).
249
- - Data binding uses \`$data:\`, \`$state:\`, \`$param:\`, \`$t:\` prefixes.
250
- - Actions use typed action objects (navigate, api_call, set_state, confirm, etc.).
251
- - When adding a new screen, also create the corresponding spec file.
252
- - When renaming or removing a screen, update the spec and all flow references.
164
+ ## Making UI changes
165
+ 1. Read the relevant spec files before modifying any UI code.
166
+ 2. If the change requires a spec update, modify the spec FIRST, then update code.
167
+ 3. Never modify generated code without updating the spec.
168
+ 4. When adding a new screen, create the corresponding spec file.
169
+ 5. When removing a screen, remove the spec file and update flow references.
170
+
171
+ ## After modifying spec files
172
+ 1. Run \`openuispec validate\` to check specs against the schema.
173
+ 2. Run \`openuispec drift --snapshot --target <target>\` for each affected platform.
174
+ 3. Run \`openuispec drift\` to verify no untracked drift remains.
175
+
176
+ ## Spec format reference
177
+ - 7 contract families: nav_container, surface, action_trigger, input_field, data_display, collection, feedback
178
+ - Custom contracts: prefixed with \`x_\` (e.g., \`x_media_player\`)
179
+ - Data binding: \`$data:\`, \`$state:\`, \`$param:\`, \`$t:\` prefixes
180
+ - Actions: typed objects (navigate, api_call, set_state, confirm, sequence, feedback, etc.)
181
+ - Adaptive layout: size classes (compact, regular, expanded) with per-section overrides
182
+
183
+ ## CLI commands
184
+ - \`openuispec init\` scaffold a new spec project
185
+ - \`openuispec validate [group...]\` — validate spec files against schemas
186
+ - \`openuispec drift --target <t>\` — check for spec drift
187
+ - \`openuispec drift --snapshot --target <t>\` — snapshot current state
188
+ - \`openuispec drift --all\` — include stubs in drift check
253
189
  `;
254
190
  }
255
191
 
@@ -261,10 +197,9 @@ export async function init(): Promise<void> {
261
197
  console.log("\nOpenUISpec — Project Setup\n");
262
198
 
263
199
  try {
264
- // 1. Project name
200
+ // 1. Project name (display name in manifest, derived from current folder)
265
201
  const cwd = process.cwd();
266
- const defaultName =
267
- cwd.split("/").pop()?.replace(/[^a-zA-Z0-9]/g, "") || "MyApp";
202
+ const defaultName = cwd.split("/").pop() || "MyApp";
268
203
  const name = await ask(rl, "Project name", defaultName);
269
204
 
270
205
  // 2. Spec directory
@@ -276,7 +211,7 @@ export async function init(): Promise<void> {
276
211
  rl,
277
212
  "\nWhich platforms?",
278
213
  allTargets,
279
- ["ios", "android"]
214
+ allTargets
280
215
  );
281
216
 
282
217
  if (targets.length === 0) {
@@ -284,16 +219,6 @@ export async function init(): Promise<void> {
284
219
  process.exit(1);
285
220
  }
286
221
 
287
- // 4. Starter tokens?
288
- const wantTokens = await ask(rl, "Include starter tokens? (y/n)", "y");
289
-
290
- // 5. AI rules?
291
- const wantRules = await ask(
292
- rl,
293
- "Add rules to CLAUDE.md and AGENTS.md? (y/n)",
294
- "y"
295
- );
296
-
297
222
  rl.close();
298
223
 
299
224
  // ── create folders ─────────────────────────────────────────────
@@ -322,27 +247,6 @@ export async function init(): Promise<void> {
322
247
  manifestTemplate(name, targets, specDir)
323
248
  );
324
249
 
325
- // ── starter tokens ─────────────────────────────────────────────
326
-
327
- if (wantTokens.toLowerCase().startsWith("y")) {
328
- writeIfMissing(join(root, "tokens", "color.yaml"), starterColorTokens());
329
- writeIfMissing(
330
- join(root, "tokens", "typography.yaml"),
331
- starterTypographyTokens()
332
- );
333
- writeIfMissing(
334
- join(root, "tokens", "spacing.yaml"),
335
- starterSpacingTokens()
336
- );
337
- }
338
-
339
- // ── starter locale ─────────────────────────────────────────────
340
-
341
- writeIfMissing(
342
- join(root, "locales", "en.json"),
343
- starterLocale() + "\n"
344
- );
345
-
346
250
  // ── .gitkeep for empty dirs ────────────────────────────────────
347
251
 
348
252
  for (const d of dirs) {
@@ -361,23 +265,21 @@ export async function init(): Promise<void> {
361
265
 
362
266
  // ── AI assistant rules ─────────────────────────────────────────
363
267
 
364
- if (wantRules.toLowerCase().startsWith("y")) {
365
- const rules = aiRulesBlock(specDir, targets);
366
-
367
- for (const file of ["CLAUDE.md", "AGENTS.md"]) {
368
- const filePath = join(cwd, file);
369
- if (existsSync(filePath)) {
370
- const existing = readFileSync(filePath, "utf-8");
371
- if (existing.includes("OpenUISpec")) {
372
- console.log(` skip ${file} (already has OpenUISpec rules)`);
373
- continue;
374
- }
375
- appendFileSync(filePath, "\n" + rules);
376
- console.log(` update ${file} (appended rules)`);
377
- } else {
378
- writeFileSync(filePath, rules.trimStart());
379
- console.log(` create ${file}`);
268
+ const rules = aiRulesBlock(specDir, targets);
269
+
270
+ for (const file of ["CLAUDE.md", "AGENTS.md"]) {
271
+ const filePath = join(cwd, file);
272
+ if (existsSync(filePath)) {
273
+ const existing = readFileSync(filePath, "utf-8");
274
+ if (existing.includes("OpenUISpec")) {
275
+ console.log(` skip ${file} (already has OpenUISpec rules)`);
276
+ continue;
380
277
  }
278
+ appendFileSync(filePath, "\n" + rules);
279
+ console.log(` update ${file} (appended rules)`);
280
+ } else {
281
+ writeFileSync(filePath, rules.trimStart());
282
+ console.log(` create ${file}`);
381
283
  }
382
284
  }
383
285
 
@@ -386,14 +288,28 @@ export async function init(): Promise<void> {
386
288
  console.log(`
387
289
  Done! Your spec project is ready at ./${specDir}/
388
290
 
389
- Next steps:
390
- 1. Edit ${specDir}/openuispec.yaml to define your data model and API
391
- 2. Add screens in ${specDir}/screens/
392
- 3. Add flows in ${specDir}/flows/
393
- 4. Generate code for your target platform
291
+ Getting started (new project):
292
+ 1. Edit ${specDir}/openuispec.yaml define your data model and API
293
+ 2. Create screens in ${specDir}/screens/ (one YAML per screen)
294
+ 3. Create flows in ${specDir}/flows/ (multi-step navigation)
295
+ 4. Ask AI to generate native code from the spec
394
296
  5. Run \`openuispec drift --snapshot --target ${targets[0]}\` to baseline
395
297
 
396
- Learn more: https://github.com/anthropics/openuispec
298
+ Getting started (existing project):
299
+ 1. Ask AI to read your existing UI code and generate spec files:
300
+ "Read src/screens/HomeScreen.swift and create ${specDir}/screens/home.yaml as status: stub"
301
+ 2. Spec screens incrementally: stub → draft → ready
302
+ 3. Only ready/draft screens are tracked by drift detection
303
+ 4. Run \`openuispec validate\` to check specs against the schema
304
+
305
+ Commands:
306
+ openuispec validate Validate spec files
307
+ openuispec drift --target ios Check for spec changes
308
+ openuispec drift --snapshot --target ios Save current state
309
+
310
+ AI rules have been added to CLAUDE.md and AGENTS.md.
311
+
312
+ Learn more: https://github.com/rsktash/openuispec
397
313
  `);
398
314
  } catch (err) {
399
315
  rl.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openuispec",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "A semantic UI specification format for AI-native, platform-native app development",