openuispec 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -0
  3. package/cli/index.ts +49 -0
  4. package/cli/init.ts +390 -0
  5. package/drift/index.ts +398 -0
  6. package/examples/taskflow/README.md +103 -0
  7. package/examples/taskflow/contracts/README.md +18 -0
  8. package/examples/taskflow/contracts/action_trigger.yaml +7 -0
  9. package/examples/taskflow/contracts/collection.yaml +7 -0
  10. package/examples/taskflow/contracts/data_display.yaml +7 -0
  11. package/examples/taskflow/contracts/feedback.yaml +7 -0
  12. package/examples/taskflow/contracts/input_field.yaml +7 -0
  13. package/examples/taskflow/contracts/nav_container.yaml +7 -0
  14. package/examples/taskflow/contracts/surface.yaml +7 -0
  15. package/examples/taskflow/contracts/x_media_player.yaml +185 -0
  16. package/examples/taskflow/flows/create_task.yaml +171 -0
  17. package/examples/taskflow/flows/edit_task.yaml +131 -0
  18. package/examples/taskflow/locales/en.json +158 -0
  19. package/examples/taskflow/openuispec.yaml +144 -0
  20. package/examples/taskflow/platform/android.yaml +32 -0
  21. package/examples/taskflow/platform/ios.yaml +39 -0
  22. package/examples/taskflow/platform/web.yaml +35 -0
  23. package/examples/taskflow/screens/calendar.yaml +23 -0
  24. package/examples/taskflow/screens/home.yaml +220 -0
  25. package/examples/taskflow/screens/profile_edit.yaml +70 -0
  26. package/examples/taskflow/screens/project_detail.yaml +65 -0
  27. package/examples/taskflow/screens/projects.yaml +142 -0
  28. package/examples/taskflow/screens/settings.yaml +178 -0
  29. package/examples/taskflow/screens/task_detail.yaml +317 -0
  30. package/examples/taskflow/tokens/color.yaml +88 -0
  31. package/examples/taskflow/tokens/elevation.yaml +27 -0
  32. package/examples/taskflow/tokens/icons.yaml +189 -0
  33. package/examples/taskflow/tokens/layout.yaml +156 -0
  34. package/examples/taskflow/tokens/motion.yaml +41 -0
  35. package/examples/taskflow/tokens/spacing.yaml +23 -0
  36. package/examples/taskflow/tokens/themes.yaml +34 -0
  37. package/examples/taskflow/tokens/typography.yaml +61 -0
  38. package/package.json +43 -0
  39. package/schema/custom-contract.schema.json +257 -0
  40. package/schema/defs/action.schema.json +272 -0
  41. package/schema/defs/adaptive.schema.json +13 -0
  42. package/schema/defs/common.schema.json +330 -0
  43. package/schema/defs/data-binding.schema.json +119 -0
  44. package/schema/defs/validation.schema.json +121 -0
  45. package/schema/flow.schema.json +164 -0
  46. package/schema/locale.schema.json +26 -0
  47. package/schema/openuispec.schema.json +287 -0
  48. package/schema/platform.schema.json +95 -0
  49. package/schema/screen.schema.json +346 -0
  50. package/schema/tokens/color.schema.json +104 -0
  51. package/schema/tokens/elevation.schema.json +84 -0
  52. package/schema/tokens/icons.schema.json +149 -0
  53. package/schema/tokens/layout.schema.json +170 -0
  54. package/schema/tokens/motion.schema.json +83 -0
  55. package/schema/tokens/spacing.schema.json +93 -0
  56. package/schema/tokens/themes.schema.json +92 -0
  57. package/schema/tokens/typography.schema.json +106 -0
  58. package/schema/validate.ts +258 -0
  59. package/spec/openuispec-v0.1.md +3677 -0
package/drift/index.ts ADDED
@@ -0,0 +1,398 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Hash-based drift detection for OpenUISpec projects.
4
+ *
5
+ * Tracks spec changes via content hashes so you know what to
6
+ * re-generate or review after editing spec files.
7
+ *
8
+ * Usage:
9
+ * npm run drift -- --target ios # check drift for ios
10
+ * npm run drift # check all targets with snapshots
11
+ * npm run drift -- --snapshot --target ios # snapshot for ios
12
+ * npm run drift -- --json --target ios # machine-readable output
13
+ */
14
+
15
+ import { readFileSync, writeFileSync, readdirSync, existsSync, mkdirSync } from "node:fs";
16
+ import { resolve, join, relative, basename, dirname } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+ import { createHash } from "node:crypto";
19
+ import YAML from "yaml";
20
+
21
+ const __dirname = fileURLToPath(new URL(".", import.meta.url));
22
+ const REPO_ROOT = resolve(__dirname, "..");
23
+ const PROJECT_DIR = resolve(REPO_ROOT, "examples", "taskflow");
24
+ const GENERATED_DIR = resolve(REPO_ROOT, "generated");
25
+ const STATE_FILE = ".openuispec-state.json";
26
+
27
+ // ── types ─────────────────────────────────────────────────────────────
28
+
29
+ interface StateFile {
30
+ spec_version: string;
31
+ snapshot_at: string;
32
+ target: string;
33
+ files: Record<string, string>;
34
+ }
35
+
36
+ interface DriftResult {
37
+ changed: string[];
38
+ added: string[];
39
+ removed: string[];
40
+ unchanged: string[];
41
+ }
42
+
43
+ // ── helpers ───────────────────────────────────────────────────────────
44
+
45
+ function listFiles(dir: string, ext: string): string[] {
46
+ try {
47
+ return readdirSync(dir)
48
+ .filter((f) => f.endsWith(ext))
49
+ .sort()
50
+ .map((f) => join(dir, f));
51
+ } catch {
52
+ return [];
53
+ }
54
+ }
55
+
56
+ function hashFile(filePath: string): string {
57
+ const content = readFileSync(filePath);
58
+ const hash = createHash("sha256").update(content).digest("hex");
59
+ return `sha256:${hash}`;
60
+ }
61
+
62
+ function discoverSpecFiles(projectDir: string): string[] {
63
+ const manifest = join(projectDir, "openuispec.yaml");
64
+ if (!existsSync(manifest)) {
65
+ console.error(`Error: No openuispec.yaml found in ${projectDir}`);
66
+ process.exit(1);
67
+ }
68
+
69
+ const doc = YAML.parse(readFileSync(manifest, "utf-8"));
70
+ const includes = doc.includes ?? {};
71
+ const files: string[] = [manifest];
72
+
73
+ // Tokens
74
+ if (includes.tokens) {
75
+ files.push(...listFiles(resolve(projectDir, includes.tokens), ".yaml"));
76
+ }
77
+
78
+ // Screens
79
+ if (includes.screens) {
80
+ files.push(...listFiles(resolve(projectDir, includes.screens), ".yaml"));
81
+ }
82
+
83
+ // Flows
84
+ if (includes.flows) {
85
+ files.push(...listFiles(resolve(projectDir, includes.flows), ".yaml"));
86
+ }
87
+
88
+ // Platform
89
+ if (includes.platform) {
90
+ files.push(...listFiles(resolve(projectDir, includes.platform), ".yaml"));
91
+ }
92
+
93
+ // Locales
94
+ if (includes.locales) {
95
+ files.push(...listFiles(resolve(projectDir, includes.locales), ".json"));
96
+ }
97
+
98
+ // Custom contracts
99
+ if (includes.contracts) {
100
+ files.push(...listFiles(resolve(projectDir, includes.contracts), ".yaml"));
101
+ }
102
+
103
+ return files;
104
+ }
105
+
106
+ function categorize(relPath: string): string {
107
+ if (relPath === "openuispec.yaml") return "Manifest";
108
+ const dir = dirname(relPath);
109
+ if (dir === "tokens") return "Tokens";
110
+ if (dir === "screens") return "Screens";
111
+ if (dir === "flows") return "Flows";
112
+ if (dir === "platform") return "Platform";
113
+ if (dir === "locales") return "Locales";
114
+ if (dir === "contracts") return "Contracts";
115
+ return "Other";
116
+ }
117
+
118
+ function resolveOutputDir(target: string): string {
119
+ return resolve(GENERATED_DIR, target, "TaskFlow");
120
+ }
121
+
122
+ function stateFilePath(target: string): string {
123
+ return join(resolveOutputDir(target), STATE_FILE);
124
+ }
125
+
126
+ /** Discover all targets that have an existing snapshot. */
127
+ function discoverTargets(): string[] {
128
+ if (!existsSync(GENERATED_DIR)) return [];
129
+ try {
130
+ return readdirSync(GENERATED_DIR)
131
+ .filter((entry) => existsSync(stateFilePath(entry)))
132
+ .sort();
133
+ } catch {
134
+ return [];
135
+ }
136
+ }
137
+
138
+ // ── snapshot ──────────────────────────────────────────────────────────
139
+
140
+ function snapshot(projectDir: string, target: string): void {
141
+ const outDir = resolveOutputDir(target);
142
+ if (!existsSync(outDir)) {
143
+ console.error(
144
+ `Error: Output directory not found: ${relative(REPO_ROOT, outDir)}\n` +
145
+ `Run code generation for "${target}" first.`
146
+ );
147
+ process.exit(1);
148
+ }
149
+
150
+ const files = discoverSpecFiles(projectDir);
151
+ const doc = YAML.parse(readFileSync(join(projectDir, "openuispec.yaml"), "utf-8"));
152
+
153
+ const hashes: Record<string, string> = {};
154
+ for (const f of files) {
155
+ const rel = relative(projectDir, f);
156
+ hashes[rel] = hashFile(f);
157
+ }
158
+
159
+ const state: StateFile = {
160
+ spec_version: doc.spec_version ?? "0.1",
161
+ snapshot_at: new Date().toISOString(),
162
+ target,
163
+ files: hashes,
164
+ };
165
+
166
+ const outPath = stateFilePath(target);
167
+ writeFileSync(outPath, JSON.stringify(state, null, 2) + "\n");
168
+ console.log(`Snapshot saved: ${relative(REPO_ROOT, outPath)}`);
169
+ console.log(` ${Object.keys(hashes).length} files hashed`);
170
+ console.log(` target: ${target}`);
171
+ }
172
+
173
+ // ── check ─────────────────────────────────────────────────────────────
174
+
175
+ function check(projectDir: string, target: string, jsonOutput: boolean): void {
176
+ const statePath = stateFilePath(target);
177
+ if (!existsSync(statePath)) {
178
+ console.error(
179
+ `No snapshot found for target "${target}".\n` +
180
+ `Run: npm run drift -- --snapshot --target ${target}`
181
+ );
182
+ process.exit(1);
183
+ }
184
+
185
+ const state: StateFile = JSON.parse(readFileSync(statePath, "utf-8"));
186
+ const files = discoverSpecFiles(projectDir);
187
+
188
+ const currentHashes: Record<string, string> = {};
189
+ for (const f of files) {
190
+ currentHashes[relative(projectDir, f)] = hashFile(f);
191
+ }
192
+
193
+ const result: DriftResult = {
194
+ changed: [],
195
+ added: [],
196
+ removed: [],
197
+ unchanged: [],
198
+ };
199
+
200
+ // Check current files against snapshot
201
+ for (const [rel, hash] of Object.entries(currentHashes)) {
202
+ if (!(rel in state.files)) {
203
+ result.added.push(rel);
204
+ } else if (state.files[rel] !== hash) {
205
+ result.changed.push(rel);
206
+ } else {
207
+ result.unchanged.push(rel);
208
+ }
209
+ }
210
+
211
+ // Check for removed files
212
+ for (const rel of Object.keys(state.files)) {
213
+ if (!(rel in currentHashes)) {
214
+ result.removed.push(rel);
215
+ }
216
+ }
217
+
218
+ if (jsonOutput) {
219
+ printJson(state, result);
220
+ } else {
221
+ printReport(projectDir, state, result);
222
+ }
223
+
224
+ const hasDrift =
225
+ result.changed.length > 0 ||
226
+ result.added.length > 0 ||
227
+ result.removed.length > 0;
228
+ process.exit(hasDrift ? 1 : 0);
229
+ }
230
+
231
+ function checkAll(projectDir: string, jsonOutput: boolean): void {
232
+ const targets = discoverTargets();
233
+ if (targets.length === 0) {
234
+ console.error(
235
+ "No snapshots found. Run: npm run drift -- --snapshot --target <target>"
236
+ );
237
+ process.exit(1);
238
+ }
239
+
240
+ let anyDrift = false;
241
+
242
+ for (const target of targets) {
243
+ const statePath = stateFilePath(target);
244
+ const state: StateFile = JSON.parse(readFileSync(statePath, "utf-8"));
245
+ const files = discoverSpecFiles(projectDir);
246
+
247
+ const currentHashes: Record<string, string> = {};
248
+ for (const f of files) {
249
+ currentHashes[relative(projectDir, f)] = hashFile(f);
250
+ }
251
+
252
+ const result: DriftResult = {
253
+ changed: [],
254
+ added: [],
255
+ removed: [],
256
+ unchanged: [],
257
+ };
258
+
259
+ for (const [rel, hash] of Object.entries(currentHashes)) {
260
+ if (!(rel in state.files)) {
261
+ result.added.push(rel);
262
+ } else if (state.files[rel] !== hash) {
263
+ result.changed.push(rel);
264
+ } else {
265
+ result.unchanged.push(rel);
266
+ }
267
+ }
268
+
269
+ for (const rel of Object.keys(state.files)) {
270
+ if (!(rel in currentHashes)) {
271
+ result.removed.push(rel);
272
+ }
273
+ }
274
+
275
+ if (jsonOutput) {
276
+ printJson(state, result);
277
+ } else {
278
+ printReport(projectDir, state, result);
279
+ }
280
+
281
+ const hasDrift =
282
+ result.changed.length > 0 ||
283
+ result.added.length > 0 ||
284
+ result.removed.length > 0;
285
+ if (hasDrift) anyDrift = true;
286
+
287
+ if (targets.length > 1 && target !== targets[targets.length - 1]) {
288
+ console.log("");
289
+ }
290
+ }
291
+
292
+ process.exit(anyDrift ? 1 : 0);
293
+ }
294
+
295
+ // ── output ────────────────────────────────────────────────────────────
296
+
297
+ function printJson(state: StateFile, result: DriftResult): void {
298
+ console.log(
299
+ JSON.stringify(
300
+ {
301
+ snapshot_at: state.snapshot_at,
302
+ target: state.target,
303
+ ...result,
304
+ },
305
+ null,
306
+ 2
307
+ )
308
+ );
309
+ }
310
+
311
+ function printReport(
312
+ projectDir: string,
313
+ state: StateFile,
314
+ result: DriftResult
315
+ ): void {
316
+ const doc = YAML.parse(
317
+ readFileSync(join(projectDir, "openuispec.yaml"), "utf-8")
318
+ );
319
+ const projectName = doc.project?.name ?? basename(projectDir);
320
+
321
+ console.log("OpenUISpec Drift Check");
322
+ console.log("======================");
323
+ console.log(`Project: ${projectName}`);
324
+ console.log(`Snapshot: ${state.snapshot_at}`);
325
+ console.log(`Target: ${state.target}`);
326
+
327
+ // Group all files by category
328
+ const allFiles = new Set([
329
+ ...result.unchanged,
330
+ ...result.changed,
331
+ ...result.added,
332
+ ...result.removed,
333
+ ]);
334
+
335
+ const categories = new Map<string, string[]>();
336
+ for (const f of allFiles) {
337
+ const cat = categorize(f);
338
+ if (!categories.has(cat)) categories.set(cat, []);
339
+ categories.get(cat)!.push(f);
340
+ }
341
+
342
+ const order = [
343
+ "Manifest",
344
+ "Tokens",
345
+ "Screens",
346
+ "Flows",
347
+ "Platform",
348
+ "Locales",
349
+ "Contracts",
350
+ "Other",
351
+ ];
352
+
353
+ for (const cat of order) {
354
+ const files = categories.get(cat);
355
+ if (!files) continue;
356
+ files.sort();
357
+
358
+ console.log(`\n${cat}`);
359
+ for (const f of files) {
360
+ const name = basename(f);
361
+ if (result.changed.includes(f)) {
362
+ console.log(` ✗ ${name} (changed)`);
363
+ } else if (result.added.includes(f)) {
364
+ console.log(` + ${name} (added)`);
365
+ } else if (result.removed.includes(f)) {
366
+ console.log(` - ${name} (removed)`);
367
+ } else {
368
+ console.log(` ✓ ${name}`);
369
+ }
370
+ }
371
+ }
372
+
373
+ console.log(
374
+ `\nSummary: ${result.changed.length} changed, ${result.added.length} added, ${result.removed.length} removed`
375
+ );
376
+ }
377
+
378
+ // ── main ──────────────────────────────────────────────────────────────
379
+
380
+ const args = process.argv.slice(2);
381
+ const isSnapshot = args.includes("--snapshot");
382
+ const isJson = args.includes("--json");
383
+
384
+ const targetIdx = args.indexOf("--target");
385
+ const target = targetIdx !== -1 && args[targetIdx + 1] ? args[targetIdx + 1] : null;
386
+
387
+ if (isSnapshot) {
388
+ if (!target) {
389
+ console.error("Error: --target is required for --snapshot");
390
+ console.error("Usage: npm run drift -- --snapshot --target <target>");
391
+ process.exit(1);
392
+ }
393
+ snapshot(PROJECT_DIR, target);
394
+ } else if (target) {
395
+ check(PROJECT_DIR, target, isJson);
396
+ } else {
397
+ checkAll(PROJECT_DIR, isJson);
398
+ }
@@ -0,0 +1,103 @@
1
+ # TaskFlow — OpenUISpec Example Application
2
+
3
+ A complete task management app defined entirely in OpenUISpec v0.1, demonstrating how a single source of truth generates native iOS, Android, and Web applications.
4
+
5
+ ## What this demonstrates
6
+
7
+ ### All 7 contract families in use
8
+
9
+ | Contract | Where it's used | Variants exercised |
10
+ |----------|----------------|-------------------|
11
+ | `action_trigger` | FAB, form buttons, destructive delete, ghost cancel | primary, secondary, tertiary, destructive, ghost |
12
+ | `data_display` | Task items, stat cards, hero header, profile card, settings rows | card, compact, hero, stat, inline |
13
+ | `input_field` | Search, task form (text, multiline, select, date, toggle, checkbox) | text, multiline, select, date, toggle, checkbox |
14
+ | `nav_container` | Main tab bar (4 tabs with badge), adaptive rail/sidebar | tab_bar, rail, sidebar |
15
+ | `feedback` | Success toasts, error banners, delete confirmation dialog | toast, banner, dialog |
16
+ | `surface` | Assignee picker sheet/panel, new project modal, create task sheet | sheet, modal, panel |
17
+ | `collection` | Task list, filter chips, project grid, settings list, tag chips | list, grid, chips |
18
+
19
+ ### Adaptive layout
20
+
21
+ - **Navigation**: tab_bar (compact) → rail (regular) → sidebar (expanded)
22
+ - **Task detail**: stacked stat cards → horizontal row, stacked buttons → inline row
23
+ - **Projects**: 1-column grid → 2-column → 3-column
24
+ - **Surfaces**: assignee picker renders as sheet (compact) or side panel (expanded)
25
+ - **Home**: single-column scroll (compact) → split_view master-detail (expanded)
26
+
27
+ ### Action system
28
+
29
+ - **navigate**: push to detail, present sheet/modal, `$back` destination
30
+ - **api_call**: with `on_success`/`on_error` handlers, optimistic updates on toggle
31
+ - **confirm**: destructive delete with confirmation dialog
32
+ - **sequence**: dismiss + feedback + refresh composed in order
33
+ - **feedback**: success toasts, error banners as side-effect actions
34
+ - **set_state**: filter changes, form submission loading state
35
+
36
+ ### Data binding
37
+
38
+ - **API sources**: `api.tasks.list`, `api.auth.currentUser`, etc.
39
+ - **Local state**: `state.active_filter`, `state.search_query`, `state.is_submitting`
40
+ - **Format expressions**: `{task.due_date | format:date_relative}`, `{item.priority | map:priority_to_severity}`
41
+ - **Computed expressions**: `{task.status == done ? 'Reopen task' : 'Mark complete'}`
42
+ - **Two-way binding**: `data_binding: "form.title"` on input fields
43
+ - **Conditional rendering**: `condition: "task.description != null"`
44
+
45
+ ### Token system
46
+
47
+ - **7 token categories**: color, typography, spacing, elevation, motion, layout, themes
48
+ - **Constrained ranges**: Every token uses Level 2 (range-based) constraints
49
+ - **Platform flex**: ±15% spacing adaptation, system font fallbacks, dynamic color support
50
+
51
+ ### Platform adaptation
52
+
53
+ - **iOS**: System tab bar, `.insetGrouped` list style, haptics, swipe actions, sheet detents
54
+ - **Android**: Material 3 NavigationBar, ripple, predictive back, edge-to-edge, dynamic color
55
+ - **Web**: Keyboard shortcuts (⌘N, ⌘K, ⌘B), native `<dialog>`, CSS custom properties
56
+
57
+ ## File structure
58
+
59
+ ```
60
+ taskflow/
61
+ ├── openuispec.yaml # Root manifest + data model + formatters + API endpoints
62
+ ├── tokens/
63
+ │ ├── color.yaml # Brand + semantic + status colors
64
+ │ ├── typography.yaml # Font family + 9-step type scale
65
+ │ ├── spacing.yaml # 4px base unit, 10-step scale
66
+ │ ├── elevation.yaml # 4-level elevation (none/sm/md/lg)
67
+ │ ├── motion.yaml # Durations, easings, patterns
68
+ │ ├── layout.yaml # Size classes, primitives, reflow rules
69
+ │ └── themes.yaml # Light, dark, warm variants
70
+ ├── contracts/ # 7 contract family stubs (see spec Section 4)
71
+ ├── screens/
72
+ │ ├── home.yaml # Task list with search, filters, FAB, adaptive nav
73
+ │ ├── task_detail.yaml # Full task view with actions + assignee sheet
74
+ │ ├── projects.yaml # Project grid + new project dialog
75
+ │ ├── project_detail.yaml # Single project with task list (stub)
76
+ │ ├── settings.yaml # Preferences, toggles, account management
77
+ │ └── calendar.yaml # Calendar view (stub)
78
+ ├── flows/
79
+ │ └── create_task.yaml # Task creation form (sheet presentation)
80
+ └── platform/
81
+ ├── ios.yaml # SwiftUI overrides + behaviors
82
+ ├── android.yaml # Compose overrides + behaviors
83
+ └── web.yaml # React overrides + responsive rules
84
+ ```
85
+
86
+ ## How to use this with AI
87
+
88
+ Pass the entire `taskflow/` directory as context to an AI code generator with the prompt:
89
+
90
+ > Generate a native {ios|android|web} application from this OpenUISpec. Follow all contract state machines, apply token ranges for the target platform, and implement the navigation flows as defined. Use the platform adaptation file for target-specific overrides.
91
+
92
+ The AI should produce:
93
+ 1. Compilable platform code (Swift/Kotlin/TypeScript)
94
+ 2. All screens with correct contract-to-widget mapping
95
+ 3. Navigation wiring matching the flow definitions
96
+ 4. Theme support (light/dark at minimum)
97
+ 5. Accessibility labels and roles per contract a11y specs
98
+ 6. Loading, empty, and error states for all collections
99
+ 7. Adaptive layout responding to size class changes
100
+
101
+ ---
102
+
103
+ *TaskFlow is a reference implementation of OpenUISpec v0.1, not a production application.*
@@ -0,0 +1,18 @@
1
+ # TaskFlow — Contract Files
2
+
3
+ These files define the 7 standard OpenUISpec contract families used by TaskFlow.
4
+ They are extracted from the OpenUISpec v0.1 specification (Section 4) for use as
5
+ standalone, machine-readable contract definitions.
6
+
7
+ For the full specification of each contract including generation hints and test cases,
8
+ see `spec/openuispec-v0.1.md` Section 4.
9
+
10
+ | File | Contract | Section |
11
+ |------|----------|---------|
12
+ | `action_trigger.yaml` | Initiates actions | 4.1 |
13
+ | `data_display.yaml` | Presents read-only info | 4.2 |
14
+ | `input_field.yaml` | Captures user input | 4.3 |
15
+ | `nav_container.yaml` | Persistent navigation | 4.4 |
16
+ | `feedback.yaml` | System status messages | 4.5 |
17
+ | `surface.yaml` | Contains other components | 4.6 |
18
+ | `collection.yaml` | Repeating data sets | 4.7 |
@@ -0,0 +1,7 @@
1
+ # action_trigger contract — see spec Section 4 for full definition
2
+ # This file serves as a machine-readable reference for AI generators.
3
+ # The canonical definition lives in spec/openuispec-v0.1.md.
4
+
5
+ contract: action_trigger
6
+ spec_section: "4"
7
+ source: "spec/openuispec-v0.1.md"
@@ -0,0 +1,7 @@
1
+ # collection contract — see spec Section 4 for full definition
2
+ # This file serves as a machine-readable reference for AI generators.
3
+ # The canonical definition lives in spec/openuispec-v0.1.md.
4
+
5
+ contract: collection
6
+ spec_section: "4"
7
+ source: "spec/openuispec-v0.1.md"
@@ -0,0 +1,7 @@
1
+ # data_display contract — see spec Section 4 for full definition
2
+ # This file serves as a machine-readable reference for AI generators.
3
+ # The canonical definition lives in spec/openuispec-v0.1.md.
4
+
5
+ contract: data_display
6
+ spec_section: "4"
7
+ source: "spec/openuispec-v0.1.md"
@@ -0,0 +1,7 @@
1
+ # feedback contract — see spec Section 4 for full definition
2
+ # This file serves as a machine-readable reference for AI generators.
3
+ # The canonical definition lives in spec/openuispec-v0.1.md.
4
+
5
+ contract: feedback
6
+ spec_section: "4"
7
+ source: "spec/openuispec-v0.1.md"
@@ -0,0 +1,7 @@
1
+ # input_field contract — see spec Section 4 for full definition
2
+ # This file serves as a machine-readable reference for AI generators.
3
+ # The canonical definition lives in spec/openuispec-v0.1.md.
4
+
5
+ contract: input_field
6
+ spec_section: "4"
7
+ source: "spec/openuispec-v0.1.md"
@@ -0,0 +1,7 @@
1
+ # nav_container contract — see spec Section 4 for full definition
2
+ # This file serves as a machine-readable reference for AI generators.
3
+ # The canonical definition lives in spec/openuispec-v0.1.md.
4
+
5
+ contract: nav_container
6
+ spec_section: "4"
7
+ source: "spec/openuispec-v0.1.md"
@@ -0,0 +1,7 @@
1
+ # surface contract — see spec Section 4 for full definition
2
+ # This file serves as a machine-readable reference for AI generators.
3
+ # The canonical definition lives in spec/openuispec-v0.1.md.
4
+
5
+ contract: surface
6
+ spec_section: "4"
7
+ source: "spec/openuispec-v0.1.md"