@topogram/cli 0.3.48 → 0.3.49

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topogram/cli",
3
- "version": "0.3.48",
3
+ "version": "0.3.49",
4
4
  "description": "Topogram CLI for checking Topogram workspaces and generating app bundles.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -194,6 +194,7 @@ function projectionSlice(graph, projectionId) {
194
194
  verification_targets: recommendedVerificationTargets(graph, [projectionId, ...capabilities, ...entities, ...shapes, ...components], {
195
195
  rationale: "Projection slices affect generated contract and runtime surfaces, so verification should follow the projection closure."
196
196
  }),
197
+ ui_agent_packet: uiAgentPacketForProjection(graph, projection),
197
198
  write_scope: buildDefaultWriteScope(),
198
199
  review_boundary: projection.reviewBoundary || {
199
200
  automation_class: "review_required",
@@ -286,6 +287,7 @@ function componentSlice(graph, componentId) {
286
287
  verification_targets: recommendedVerificationTargets(graph, verificationScope, {
287
288
  rationale: "Component changes affect every related projection — verification should follow the component contract closure."
288
289
  }),
290
+ ui_agent_packet: uiAgentPacketForComponent(graph, component, projections),
289
291
  write_scope: buildDefaultWriteScope(),
290
292
  review_boundary: {
291
293
  automation_class: "review_required",
@@ -331,6 +333,135 @@ function componentDependencyKind(dependency) {
331
333
  return prefix || null;
332
334
  }
333
335
 
336
+ function uiAgentPacketForProjection(graph, projection) {
337
+ if (!String(projection.platform || "").startsWith("ui_")) {
338
+ return null;
339
+ }
340
+ const sharedProjection = projection.platform === "ui_shared"
341
+ ? projection
342
+ : sharedUiProjectionFor(graph, projection);
343
+ const ownerProjection = sharedProjection || projection;
344
+ return {
345
+ type: "ui_agent_packet",
346
+ version: 1,
347
+ ownership: {
348
+ componentPlacement: "ui_shared",
349
+ designIntent: "ui_shared",
350
+ concreteSurfaceOwns: ["routes", "surface_hints"]
351
+ },
352
+ sharedProjection: sharedProjection
353
+ ? {
354
+ id: sharedProjection.id,
355
+ name: sharedProjection.name || sharedProjection.id
356
+ }
357
+ : null,
358
+ screens: (ownerProjection.uiScreens || []).map((screen) => ({
359
+ id: screen.id,
360
+ kind: screen.kind,
361
+ title: screen.title || screen.id
362
+ })),
363
+ routes: (projection.uiRoutes || []).map((route) => ({
364
+ screenId: route.screenId,
365
+ path: route.path
366
+ })),
367
+ components: (ownerProjection.uiComponents || []).map((usage) => componentUsagePacket(usage)),
368
+ design: designIntentPacket(ownerProjection),
369
+ requiredGates: uiRequiredGates(projection.id)
370
+ };
371
+ }
372
+
373
+ function uiAgentPacketForComponent(graph, component, projectionIds) {
374
+ const projectionSet = new Set(projectionIds);
375
+ const sourceUsages = [];
376
+ for (const projection of graph.byKind.projection || []) {
377
+ for (const usage of projection.uiComponents || []) {
378
+ if (usage.component?.id !== component.id) continue;
379
+ sourceUsages.push({
380
+ projection: {
381
+ id: projection.id,
382
+ platform: projection.platform,
383
+ ownership: projection.platform === "ui_shared" ? "owner" : "concrete"
384
+ },
385
+ usage: componentUsagePacket(usage),
386
+ design: designIntentPacket(projection)
387
+ });
388
+ projectionSet.add(projection.id);
389
+ }
390
+ }
391
+
392
+ return {
393
+ type: "ui_agent_packet",
394
+ version: 1,
395
+ ownership: {
396
+ componentContract: "component",
397
+ componentPlacement: "ui_shared",
398
+ concreteSurfacesInherit: true
399
+ },
400
+ component: {
401
+ id: component.id,
402
+ name: component.name || component.id,
403
+ category: component.category || null,
404
+ patterns: component.componentContract?.patterns || [],
405
+ regions: component.componentContract?.regions || [],
406
+ behaviors: component.componentContract?.behaviors || []
407
+ },
408
+ sourceUsages,
409
+ inheritedBy: [...projectionSet]
410
+ .filter((projectionId) => !sourceUsages.some((entry) => entry.projection.id === projectionId))
411
+ .sort(),
412
+ requiredGates: uiRequiredGates(null, component.id)
413
+ };
414
+ }
415
+
416
+ function sharedUiProjectionFor(graph, projection) {
417
+ for (const reference of projection.realizes || []) {
418
+ const candidate = (graph.byKind.projection || []).find((entry) => entry.id === reference.id);
419
+ if (candidate?.platform === "ui_shared") {
420
+ return candidate;
421
+ }
422
+ }
423
+ return null;
424
+ }
425
+
426
+ function componentUsagePacket(usage) {
427
+ return {
428
+ screenId: usage.screenId || null,
429
+ region: usage.region || null,
430
+ componentId: usage.component?.id || null,
431
+ dataBindings: (usage.dataBindings || []).map((binding) => ({
432
+ prop: binding.prop || null,
433
+ source: binding.source?.id || binding.source || null
434
+ })),
435
+ eventBindings: (usage.eventBindings || []).map((binding) => ({
436
+ event: binding.event || null,
437
+ action: binding.action || null,
438
+ target: binding.target?.id || binding.target || null
439
+ }))
440
+ };
441
+ }
442
+
443
+ function designIntentPacket(projection) {
444
+ return (projection.uiDesign || []).map((entry) => ({
445
+ key: entry.key,
446
+ role: entry.role,
447
+ value: entry.value
448
+ }));
449
+ }
450
+
451
+ function uiRequiredGates(projectionId = null, componentId = null) {
452
+ return [
453
+ { command: "topogram check", reason: "Validate shared UI ownership, taxonomy, references, and topology." },
454
+ {
455
+ command: `topogram component check${projectionId ? ` --projection ${projectionId}` : ""}${componentId ? ` --component ${componentId}` : ""}`,
456
+ reason: "Validate component placement, props, events, regions, and patterns."
457
+ },
458
+ {
459
+ command: `topogram component behavior${projectionId ? ` --projection ${projectionId}` : ""}${componentId ? ` --component ${componentId}` : ""}`,
460
+ reason: "Inspect behavior realizations and partial bindings before code changes."
461
+ }
462
+ ];
463
+ }
464
+
334
465
  function journeySlice(graph, journeyId) {
335
466
  const journey = getJourneyDoc(graph, journeyId);
336
467
  const capabilities = [...(journey.relatedCapabilities || [])].sort();
@@ -3,6 +3,7 @@
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { createRequire } from "node:module";
6
+ import { UI_GENERATOR_RENDERED_COMPONENT_PATTERNS } from "../ui/taxonomy.js";
6
7
 
7
8
  /**
8
9
  * @typedef {Object} GeneratorManifest
@@ -23,6 +24,8 @@ import { createRequire } from "node:module";
23
24
  * @property {boolean} [planned]
24
25
  */
25
26
 
27
+ const RENDERED_COMPONENT_PATTERNS = [...UI_GENERATOR_RENDERED_COMPONENT_PATTERNS];
28
+
26
29
  /** @type {GeneratorManifest[]} */
27
30
  export const GENERATOR_MANIFESTS = [
28
31
  {
@@ -76,7 +79,7 @@ export const GENERATOR_MANIFESTS = [
76
79
  stack: { runtime: "node", framework: "sveltekit", language: "typescript" },
77
80
  capabilities: { routes: true, components: true, coverage: true },
78
81
  componentSupport: {
79
- patterns: ["summary_stats", "board_view", "calendar_view", "resource_table", "data_grid_view"],
82
+ patterns: RENDERED_COMPONENT_PATTERNS,
80
83
  behaviors: ["selection", "sorting", "filtering", "search", "pagination", "bulk_action", "optimistic_update"],
81
84
  unsupported: "warning"
82
85
  },
@@ -94,7 +97,7 @@ export const GENERATOR_MANIFESTS = [
94
97
  stack: { runtime: "browser", framework: "react", language: "typescript" },
95
98
  capabilities: { routes: true, components: true, coverage: true },
96
99
  componentSupport: {
97
- patterns: ["summary_stats", "board_view", "calendar_view", "resource_table", "data_grid_view"],
100
+ patterns: RENDERED_COMPONENT_PATTERNS,
98
101
  behaviors: ["selection", "sorting", "filtering", "search", "pagination", "bulk_action", "optimistic_update"],
99
102
  unsupported: "warning"
100
103
  },
@@ -38,7 +38,8 @@ export function uiProjectionCandidates(graph) {
38
38
  (projection.uiAppShell || []).length > 0 ||
39
39
  (projection.uiNavigation || []).length > 0 ||
40
40
  (projection.uiScreenRegions || []).length > 0 ||
41
- (projection.uiComponents || []).length > 0
41
+ (projection.uiComponents || []).length > 0 ||
42
+ (projection.uiDesign || []).length > 0
42
43
  );
43
44
  }
44
45
 
@@ -1,5 +1,7 @@
1
1
  // @ts-check
2
2
 
3
+ import { UI_GENERATOR_RENDERED_COMPONENT_PATTERNS } from "../../../ui/taxonomy.js";
4
+
3
5
  /**
4
6
  * @typedef {{ id?: string, name?: string }} ComponentReference
5
7
  * @typedef {{ component?: ComponentReference, region?: string, pattern?: string }} ComponentUsage
@@ -71,13 +73,7 @@ export function reactComponentUsageSupport(usage, componentContracts) {
71
73
  const pattern = usagePattern(usage, componentContracts);
72
74
  return {
73
75
  pattern,
74
- supported: [
75
- "summary_stats",
76
- "board_view",
77
- "calendar_view",
78
- "resource_table",
79
- "data_grid_view"
80
- ].includes(pattern || "")
76
+ supported: UI_GENERATOR_RENDERED_COMPONENT_PATTERNS.has(pattern || "")
81
77
  };
82
78
  }
83
79
 
@@ -1,5 +1,7 @@
1
1
  // @ts-check
2
2
 
3
+ import { UI_GENERATOR_RENDERED_COMPONENT_PATTERNS } from "../../../ui/taxonomy.js";
4
+
3
5
  /**
4
6
  * @typedef {{ id?: string, name?: string }} ComponentReference
5
7
  * @typedef {{ component?: ComponentReference, region?: string, pattern?: string }} ComponentUsage
@@ -62,13 +64,7 @@ export function svelteKitComponentUsageSupport(usage, componentContracts) {
62
64
  const pattern = usagePattern(usage, componentContracts);
63
65
  return {
64
66
  pattern,
65
- supported: [
66
- "summary_stats",
67
- "board_view",
68
- "calendar_view",
69
- "resource_table",
70
- "data_grid_view"
71
- ].includes(pattern || "")
67
+ supported: UI_GENERATOR_RENDERED_COMPONENT_PATTERNS.has(pattern || "")
72
68
  };
73
69
  }
74
70
 
@@ -2,6 +2,9 @@ import { IMPORT_TRACKS } from "./contracts.js";
2
2
  import { dedupeCandidateRecords, ensureTrailingNewline, idHintify, makeCandidateRecord } from "./shared.js";
3
3
  import { createImportContext } from "./context.js";
4
4
  import { getEnrichersForTrack, getExtractorsForTrack } from "./registry.js";
5
+ import {
6
+ collectionPatternFromPresentations
7
+ } from "../../ui/taxonomy.js";
5
8
 
6
9
  function parseImportTracks(fromValue) {
7
10
  if (!fromValue) {
@@ -95,25 +98,6 @@ function selectDetectionsForTrack(track, detections) {
95
98
  return detections;
96
99
  }
97
100
 
98
- function collectionPatternFromPresentations(presentations) {
99
- if (presentations.includes("data_grid")) return "data_grid_view";
100
- if (presentations.includes("table")) return "resource_table";
101
- if (presentations.includes("cards")) return "resource_cards";
102
- if (presentations.includes("board")) return "board_view";
103
- if (presentations.includes("calendar")) return "calendar_view";
104
- if (presentations.includes("gallery")) return "resource_cards";
105
- return "search_results";
106
- }
107
-
108
- function presentationFromPattern(pattern) {
109
- if (pattern === "data_grid_view") return "data_grid";
110
- if (pattern === "resource_table") return "table";
111
- if (pattern === "resource_cards") return "cards";
112
- if (pattern === "board_view") return "board";
113
- if (pattern === "calendar_view") return "calendar";
114
- return "list";
115
- }
116
-
117
101
  function importedApiCapabilityIds(allCandidates) {
118
102
  return [...(allCandidates?.api?.capabilities || [])]
119
103
  .map((capability) => capability.id_hint)
@@ -181,6 +165,17 @@ function deriveUiComponentCandidates(candidates) {
181
165
  pattern,
182
166
  data_prop: "rows",
183
167
  data_source: loadCapability,
168
+ inferred_props: [{ name: "rows", type: "array", required: true, source: loadCapability }],
169
+ inferred_events: [],
170
+ inferred_region: "results",
171
+ inferred_pattern: pattern,
172
+ evidence: screen.provenance || [],
173
+ missing_decisions: [
174
+ "confirm component reuse boundary",
175
+ "confirm prop names and data source",
176
+ "confirm events and behavior",
177
+ "confirm supported regions and patterns"
178
+ ],
184
179
  notes: [
185
180
  "Imported component candidates are review-only.",
186
181
  "Confirm props, behavior, events, and reuse before adoption."
@@ -294,8 +289,11 @@ function reportMarkdown(track, candidates) {
294
289
  );
295
290
  }
296
291
  if (track === "ui") {
292
+ const componentLines = (candidates.components || []).map((component) =>
293
+ `- \`${component.id_hint}\` confidence ${component.confidence || "unknown"} pattern \`${component.pattern || component.inferred_pattern || "unknown"}\` region \`${component.region || component.inferred_region || "unknown"}\` evidence ${(component.evidence || component.provenance || []).length} missing decisions ${(component.missing_decisions || []).length}`
294
+ );
297
295
  return ensureTrailingNewline(
298
- `# UI Import Report\n\n- Screens: ${candidates.screens.length}\n- Routes: ${candidates.routes.length}\n- Actions: ${candidates.actions.length}\n- Components: ${candidates.components.length}\n- Stacks: ${candidates.stacks.length ? candidates.stacks.join(", ") : "none"}\n`
296
+ `# UI Import Report\n\n- Screens: ${candidates.screens.length}\n- Routes: ${candidates.routes.length}\n- Actions: ${candidates.actions.length}\n- Components: ${candidates.components.length}\n- Stacks: ${candidates.stacks.length ? candidates.stacks.join(", ") : "none"}\n\n## Component Candidates\n\n${componentLines.length ? componentLines.join("\n") : "- none"}\n\n## Next Validation\n\n- Review candidates under \`topogram/candidates/app/ui/drafts/components/**\`.\n- Run \`topogram import plan <path>\` before adoption.\n- After adoption, run \`topogram check <path>\`, \`topogram component check <path>\`, and \`topogram component behavior <path>\`.\n`
299
297
  );
300
298
  }
301
299
  if (track === "verification") {
@@ -327,7 +325,15 @@ function componentCandidateFileName(component) {
327
325
  }
328
326
 
329
327
  function renderComponentCandidate(component) {
328
+ const evidenceCount = (component.evidence || component.provenance || []).length;
329
+ const missingDecisions = component.missing_decisions || [
330
+ "confirm component reuse boundary",
331
+ "confirm prop names and data source",
332
+ "confirm events and behavior"
333
+ ];
330
334
  return `component ${component.id_hint} {
335
+ # Import metadata: confidence ${component.confidence || "unknown"}; evidence ${evidenceCount}; inferred pattern ${component.pattern || component.inferred_pattern || "search_results"}; inferred region ${component.region || component.inferred_region || "results"}.
336
+ # Missing decisions: ${missingDecisions.join("; ")}.
331
337
  name "${component.label || component.id_hint}"
332
338
  description "Candidate reusable component inferred from imported UI evidence. Review props, behavior, events, and reuse before adoption."
333
339
  category collection
@@ -353,6 +359,26 @@ function uiComponentLinesForCandidates(componentCandidates, allCandidates) {
353
359
  });
354
360
  }
355
361
 
362
+ function enrichUiComponentDataSources(uiCandidates, allCandidates) {
363
+ if (!uiCandidates || !Array.isArray(uiCandidates.components)) {
364
+ return uiCandidates;
365
+ }
366
+ return {
367
+ ...uiCandidates,
368
+ components: uiCandidates.components.map((component) => {
369
+ const dataSource = inferredDataSourceForComponent(component, allCandidates);
370
+ const dataProp = component.data_prop || "rows";
371
+ return {
372
+ ...component,
373
+ data_source: component.data_source || dataSource,
374
+ inferred_props: (component.inferred_props || []).map((prop) =>
375
+ prop.name === dataProp ? { ...prop, source: prop.source || dataSource } : prop
376
+ )
377
+ };
378
+ })
379
+ };
380
+ }
381
+
356
382
  function draftUiProjectionFiles(context, candidates, allCandidates = {}) {
357
383
  const ui = candidates || { screens: [], routes: [], actions: [], stacks: [] };
358
384
  const screens = [...(ui.screens || [])].sort((a, b) => String(a.route_path || "").localeCompare(String(b.route_path || "")) || a.id_hint.localeCompare(b.id_hint));
@@ -477,6 +503,20 @@ ${capabilityHints.length > 0 ? capabilityHints.map((hint) => ` ${hint}`).join
477
503
  shell ${shell}
478
504
  ${presentations.includes("search") ? " global_search true\n" : ""}${presentations.includes("multi_window") ? " windowing multi_window\n" : ""} }
479
505
 
506
+ ui_design {
507
+ density comfortable
508
+ tone operational
509
+ radius_scale medium
510
+ color_role primary accent
511
+ color_role danger critical
512
+ typography_role body readable
513
+ typography_role heading prominent
514
+ action_role primary prominent
515
+ action_role destructive danger
516
+ accessibility contrast aa
517
+ accessibility focus visible
518
+ }
519
+
480
520
  ui_screens {
481
521
  ${uiScreensBlock}
482
522
  }
@@ -616,6 +656,9 @@ export function runImportApp(inputPath, options = {}) {
616
656
  }
617
657
 
618
658
  if (candidates.ui) {
659
+ candidates.ui = enrichUiComponentDataSources(candidates.ui, candidates);
660
+ files["candidates/app/ui/candidates.json"] = `${JSON.stringify(candidates.ui, null, 2)}\n`;
661
+ files["candidates/app/ui/report.md"] = reportMarkdown("ui", candidates.ui);
619
662
  Object.assign(files, draftUiProjectionFiles(context, candidates.ui, candidates));
620
663
  }
621
664
 
@@ -1,5 +1,6 @@
1
1
  import { getProjection, uiProjectionCandidates } from "../../generator/surfaces/shared.js";
2
2
  import { buildComponentBehaviorRealizations } from "../../component-behavior.js";
3
+ import { defaultPatternForScreen } from "../../ui/taxonomy.js";
3
4
 
4
5
  function toBooleanFlag(value, fallback = false) {
5
6
  if (value === "true") return true;
@@ -47,21 +48,6 @@ function ownershipFieldByCapability(graph) {
47
48
  return output;
48
49
  }
49
50
 
50
- function deriveDefaultPattern(screen, collectionEntries) {
51
- if (screen.kind === "detail") return "detail_panel";
52
- if (screen.kind === "form") return "edit_form";
53
- if (screen.kind === "board") return "board_view";
54
- if (screen.kind === "calendar") return "calendar_view";
55
- if (screen.kind === "dashboard" || screen.kind === "analytics" || screen.kind === "report") return "summary_stats";
56
- if (screen.kind === "feed" || screen.kind === "inbox") return "activity_feed";
57
- const view = collectionEntries.find((entry) => entry.operation === "view")?.value;
58
- if (view === "data_grid") return "data_grid_view";
59
- if (view === "table") return "resource_table";
60
- if (view === "cards" || view === "gallery") return "resource_cards";
61
- if (screen.kind === "list") return "resource_table";
62
- return null;
63
- }
64
-
65
51
  function componentById(graph, componentId) {
66
52
  return (graph.byKind.component || []).find((component) => component.id === componentId) || null;
67
53
  }
@@ -88,6 +74,50 @@ function regionContractFor(regionEntries, regionName) {
88
74
  return (regionEntries || []).find((entry) => entry.region === regionName) || null;
89
75
  }
90
76
 
77
+ function buildDesignIntentContract(projection) {
78
+ const design = {
79
+ density: "comfortable",
80
+ tone: "operational",
81
+ radiusScale: "medium",
82
+ colorRoles: {},
83
+ typographyRoles: {},
84
+ actionRoles: {},
85
+ accessibility: {}
86
+ };
87
+
88
+ for (const entry of projection.uiDesign || []) {
89
+ if (entry.key === "density" && entry.role) {
90
+ design.density = entry.role;
91
+ continue;
92
+ }
93
+ if (entry.key === "tone" && entry.role) {
94
+ design.tone = entry.role;
95
+ continue;
96
+ }
97
+ if (entry.key === "radius_scale" && entry.role) {
98
+ design.radiusScale = entry.role;
99
+ continue;
100
+ }
101
+ if (entry.key === "color_role" && entry.role && entry.value) {
102
+ design.colorRoles[entry.role] = entry.value;
103
+ continue;
104
+ }
105
+ if (entry.key === "typography_role" && entry.role && entry.value) {
106
+ design.typographyRoles[entry.role] = entry.value;
107
+ continue;
108
+ }
109
+ if (entry.key === "action_role" && entry.role && entry.value) {
110
+ design.actionRoles[entry.role] = entry.value;
111
+ continue;
112
+ }
113
+ if (entry.key === "accessibility" && entry.role && entry.value) {
114
+ design.accessibility[entry.role] = entry.value;
115
+ }
116
+ }
117
+
118
+ return design;
119
+ }
120
+
91
121
  export function buildComponentUsageContract(graph, entry, options = {}) {
92
122
  const componentId = entry.component?.id || null;
93
123
  const contract = componentId ? componentContractFor(graph, componentId) : null;
@@ -184,7 +214,7 @@ function buildUiScreenContract(graph, projection, screen, ownershipFields) {
184
214
  );
185
215
  const visibilityEntries = (projection.uiVisibility || []).filter((entry) => screenActionIds.has(entry.capability?.id));
186
216
  const patterns = new Set(regionEntries.map((entry) => entry.pattern).filter(Boolean));
187
- const derivedDefaultPattern = deriveDefaultPattern(screen, collectionEntries);
217
+ const derivedDefaultPattern = defaultPatternForScreen(screen, collectionEntries);
188
218
  if (derivedDefaultPattern) {
189
219
  patterns.add(derivedDefaultPattern);
190
220
  }
@@ -286,6 +316,7 @@ export function buildUiSharedRealization(graph, options = {}) {
286
316
  realizes: projection.realizes,
287
317
  outputs: projection.outputs,
288
318
  components: buildComponentContractMap(graph, componentUsages),
319
+ design: buildDesignIntentContract(projection),
289
320
  appShell: buildAppShellContract(projection),
290
321
  navigation: buildNavigationContract(projection, screens),
291
322
  screens
@@ -305,6 +336,7 @@ export function buildUiSharedRealization(graph, options = {}) {
305
336
  realizes: projection.realizes,
306
337
  outputs: projection.outputs,
307
338
  components: buildComponentContractMap(graph, componentUsages),
339
+ design: buildDesignIntentContract(projection),
308
340
  appShell: buildAppShellContract(projection),
309
341
  navigation: buildNavigationContract(projection, screens),
310
342
  screens
@@ -1,8 +1,6 @@
1
1
  import { buildApiRealization } from "../api/index.js";
2
2
  import { generatorDefaultsMap, getProjection, sharedUiProjectionForWeb } from "../../generator/surfaces/shared.js";
3
3
  import {
4
- buildComponentContractMap,
5
- buildComponentUsageContract,
6
4
  buildUiSharedRealization
7
5
  } from "./build-ui-shared-realization.js";
8
6
 
@@ -38,6 +36,7 @@ export function buildWebRealization(graph, options = {}) {
38
36
  realizes: [],
39
37
  outputs: [],
40
38
  components: {},
39
+ design: null,
41
40
  screens: []
42
41
  };
43
42
  const concreteContract = buildUiSharedRealization(graph, { projectionId: projection.id });
@@ -70,30 +69,15 @@ export function buildWebRealization(graph, options = {}) {
70
69
  screenMap.set(screen.id, {
71
70
  ...existing,
72
71
  ...screen,
73
- components: [...(existing.components || []), ...(screen.components || [])],
72
+ components: [...(existing.components || [])],
74
73
  regions: mergeByKey(existing.regions || [], screen.regions || [], (entry) => entry.region),
75
74
  patterns: [...new Set([...(existing.patterns || []), ...(screen.patterns || [])])]
76
75
  });
77
76
  }
78
- for (const entry of projection.uiComponents || []) {
79
- if (!screenMap.has(entry.screenId)) {
80
- continue;
81
- }
82
- const screen = screenMap.get(entry.screenId);
83
- if ((screen.components || []).some((usage) => componentUsageFingerprint(usage) === componentUsageFingerprintFromEntry(entry))) {
84
- continue;
85
- }
86
- screen.components = [...(screen.components || []), buildComponentUsageContract(graph, entry, {
87
- region: (screen.regions || []).find((region) => region.region === entry.region) || null
88
- })];
89
- }
90
77
 
91
78
  const appShell = projection.uiAppShell?.length || !sharedProjection ? concreteContract.appShell : sharedContract.appShell;
92
79
  const navigation = projection.uiNavigation?.length || !sharedProjection ? concreteContract.navigation : sharedContract.navigation;
93
- const componentContracts = {
94
- ...(sharedContract.components || {}),
95
- ...buildComponentContractMap(graph, projection.uiComponents || [])
96
- };
80
+ const design = projection.uiDesign?.length || !sharedProjection ? concreteContract.design : sharedContract.design;
97
81
 
98
82
  const contract = {
99
83
  projection: {
@@ -109,7 +93,8 @@ export function buildWebRealization(graph, options = {}) {
109
93
  : null,
110
94
  generatorDefaults: generatorDefaultsMap(projection),
111
95
  outputs: projection.outputs,
112
- components: componentContracts,
96
+ components: sharedProjection ? (sharedContract.components || {}) : (concreteContract.components || {}),
97
+ design: design || null,
113
98
  appShell: appShell || null,
114
99
  navigation: {
115
100
  groups: navigation?.groups || [],
@@ -171,21 +156,3 @@ function mergeByKey(left, right, keyFn) {
171
156
  for (const entry of right) output.set(keyFn(entry), { ...(output.get(keyFn(entry)) || {}), ...entry });
172
157
  return [...output.values()];
173
158
  }
174
-
175
- function componentUsageFingerprint(usage) {
176
- return [
177
- usage?.region || "",
178
- usage?.component?.id || "",
179
- ...(usage?.dataBindings || []).map((binding) => `data:${binding.prop}:${binding.source?.id || ""}`),
180
- ...(usage?.eventBindings || []).map((binding) => `event:${binding.event}:${binding.action}:${binding.target?.id || ""}`)
181
- ].join("|");
182
- }
183
-
184
- function componentUsageFingerprintFromEntry(entry) {
185
- return [
186
- entry?.region || "",
187
- entry?.component?.id || "",
188
- ...(entry?.dataBindings || []).map((binding) => `data:${binding.prop}:${binding.source?.id || ""}`),
189
- ...(entry?.eventBindings || []).map((binding) => `event:${binding.event}:${binding.action}:${binding.target?.id || ""}`)
190
- ].join("|");
191
- }
@@ -503,6 +503,7 @@ function buildProjectionPlan(statement) {
503
503
  uiVisibility: statement.uiVisibility,
504
504
  uiRoutes: statement.uiRoutes,
505
505
  uiWeb: statement.uiWeb,
506
+ uiDesign: statement.uiDesign,
506
507
  uiComponents: statement.uiComponents,
507
508
  dbTables: statement.dbTables,
508
509
  dbColumns: statement.dbColumns,
@@ -1269,6 +1270,17 @@ function parseProjectionUiAppShellBlock(statement) {
1269
1270
  }));
1270
1271
  }
1271
1272
 
1273
+ function parseProjectionUiDesignBlock(statement) {
1274
+ return blockEntries(getFieldValue(statement, "ui_design")).map((entry) => ({
1275
+ type: "ui_design_token",
1276
+ key: tokenValue(entry.items[0]) || null,
1277
+ role: tokenValue(entry.items[1]) || null,
1278
+ value: tokenValue(entry.items[2]) || null,
1279
+ raw: normalizeSequence(entry.items),
1280
+ loc: entry.loc
1281
+ }));
1282
+ }
1283
+
1272
1284
  function parseProjectionUiNavigationBlock(statement) {
1273
1285
  return blockEntries(getFieldValue(statement, "ui_navigation")).map((entry) => {
1274
1286
  const directives = {};
@@ -1869,6 +1881,7 @@ export function normalizeStatement(statement, registry) {
1869
1881
  uiWeb: parseProjectionUiWebBlock(statement, registry),
1870
1882
  uiIos: parseProjectionUiIosBlock(statement, registry),
1871
1883
  uiAppShell: parseProjectionUiAppShellBlock(statement),
1884
+ uiDesign: parseProjectionUiDesignBlock(statement),
1872
1885
  uiNavigation: parseProjectionUiNavigationBlock(statement),
1873
1886
  uiScreenRegions: parseProjectionUiScreenRegionsBlock(statement),
1874
1887
  uiComponents: parseProjectionUiComponentsBlock(statement, registry),
@@ -0,0 +1,201 @@
1
+ // @ts-check
2
+
3
+ export const UI_APP_SHELL_KINDS = new Set([
4
+ "topbar",
5
+ "sidebar",
6
+ "dual_nav",
7
+ "workspace",
8
+ "wizard",
9
+ "bottom_tabs",
10
+ "split_view",
11
+ "menu_bar"
12
+ ]);
13
+
14
+ export const UI_WINDOWING_MODES = new Set(["single_window", "multi_window"]);
15
+
16
+ export const UI_SCREEN_KINDS = new Set([
17
+ "list",
18
+ "detail",
19
+ "form",
20
+ "dashboard",
21
+ "job_status",
22
+ "board",
23
+ "calendar",
24
+ "feed",
25
+ "inbox",
26
+ "settings",
27
+ "wizard",
28
+ "report",
29
+ "analytics"
30
+ ]);
31
+
32
+ export const UI_COLLECTION_PRESENTATIONS = new Set([
33
+ "table",
34
+ "data_grid",
35
+ "cards",
36
+ "list",
37
+ "board",
38
+ "calendar",
39
+ "gallery"
40
+ ]);
41
+
42
+ export const UI_NAVIGATION_PATTERNS = new Set([
43
+ "primary",
44
+ "tabs",
45
+ "stack_navigation",
46
+ "bottom_tabs",
47
+ "segmented_control",
48
+ "command_palette",
49
+ "split_view",
50
+ "navigation_rail"
51
+ ]);
52
+
53
+ export const UI_REGION_KINDS = new Set([
54
+ "hero",
55
+ "toolbar",
56
+ "filters",
57
+ "search",
58
+ "results",
59
+ "summary",
60
+ "metadata",
61
+ "aside",
62
+ "related",
63
+ "activity",
64
+ "comments",
65
+ "timeline",
66
+ "tabs",
67
+ "bulk_actions",
68
+ "footer_actions"
69
+ ]);
70
+
71
+ export const UI_PATTERN_KINDS = new Set([
72
+ "resource_table",
73
+ "data_grid_view",
74
+ "resource_cards",
75
+ "detail_panel",
76
+ "edit_form",
77
+ "lookup_select",
78
+ "action_bar",
79
+ "status_badge",
80
+ "summary_stats",
81
+ "activity_feed",
82
+ "comment_thread",
83
+ "timeline_view",
84
+ "board_view",
85
+ "calendar_view",
86
+ "settings_section",
87
+ "wizard_stepper",
88
+ "audit_log",
89
+ "search_results",
90
+ "empty_state_panel",
91
+ "inspector_pane",
92
+ "master_detail"
93
+ ]);
94
+
95
+ export const UI_ACTION_PRESENTATIONS = new Set([
96
+ "button",
97
+ "menu_item",
98
+ "split_button",
99
+ "bulk_action",
100
+ "modal",
101
+ "drawer",
102
+ "inline_confirm",
103
+ "sheet",
104
+ "bottom_sheet",
105
+ "fab",
106
+ "popover"
107
+ ]);
108
+
109
+ export const UI_STATE_KINDS = new Set([
110
+ "loading",
111
+ "empty",
112
+ "error",
113
+ "unauthorized",
114
+ "not_found",
115
+ "success"
116
+ ]);
117
+
118
+ export const UI_PLATFORM_PATTERNS = new Set([
119
+ "bottom_tabs",
120
+ "stack_navigation",
121
+ "split_view",
122
+ "master_detail",
123
+ "navigation_rail",
124
+ "fab",
125
+ "sheet",
126
+ "bottom_sheet",
127
+ "pull_to_refresh",
128
+ "segmented_control",
129
+ "command_palette",
130
+ "inspector_pane",
131
+ "multi_pane_layout",
132
+ "resizable_split",
133
+ "menu_bar",
134
+ "multi_window"
135
+ ]);
136
+
137
+ export const UI_DESIGN_DENSITIES = new Set(["compact", "comfortable", "spacious"]);
138
+ export const UI_DESIGN_TONES = new Set(["operational", "neutral", "editorial", "playful"]);
139
+ export const UI_DESIGN_RADIUS_SCALES = new Set(["none", "small", "medium", "large"]);
140
+ export const UI_DESIGN_COLOR_ROLES = new Set(["primary", "secondary", "surface", "text", "muted", "danger", "success", "warning", "info"]);
141
+ export const UI_DESIGN_TYPOGRAPHY_ROLES = new Set(["body", "heading", "label", "mono", "numeric"]);
142
+ export const UI_DESIGN_ACTION_ROLES = new Set(["primary", "secondary", "destructive", "contextual", "bulk"]);
143
+ export const UI_DESIGN_ACCESSIBILITY_VALUES = {
144
+ contrast: new Set(["aa", "aaa", "high"]),
145
+ motion: new Set(["standard", "reduced"]),
146
+ focus: new Set(["visible", "required"]),
147
+ min_touch_target: new Set(["compact", "comfortable"])
148
+ };
149
+
150
+ export const UI_GENERATOR_RENDERED_COMPONENT_PATTERNS = new Set([
151
+ "summary_stats",
152
+ "board_view",
153
+ "calendar_view",
154
+ "resource_table",
155
+ "data_grid_view"
156
+ ]);
157
+
158
+ /**
159
+ * @param {string[]} presentations
160
+ * @returns {string}
161
+ */
162
+ export function collectionPatternFromPresentations(presentations = []) {
163
+ if (presentations.includes("data_grid")) return "data_grid_view";
164
+ if (presentations.includes("table")) return "resource_table";
165
+ if (presentations.includes("cards")) return "resource_cards";
166
+ if (presentations.includes("board")) return "board_view";
167
+ if (presentations.includes("calendar")) return "calendar_view";
168
+ if (presentations.includes("gallery")) return "resource_cards";
169
+ return "search_results";
170
+ }
171
+
172
+ /**
173
+ * @param {string|null|undefined} pattern
174
+ * @returns {string}
175
+ */
176
+ export function presentationFromPattern(pattern) {
177
+ if (pattern === "data_grid_view") return "data_grid";
178
+ if (pattern === "resource_table") return "table";
179
+ if (pattern === "resource_cards") return "cards";
180
+ if (pattern === "board_view") return "board";
181
+ if (pattern === "calendar_view") return "calendar";
182
+ return "list";
183
+ }
184
+
185
+ /**
186
+ * @param {{ kind?: string|null }} screen
187
+ * @param {{ operation?: string|null, value?: string|null }[]} collectionEntries
188
+ * @returns {string|null}
189
+ */
190
+ export function defaultPatternForScreen(screen, collectionEntries = []) {
191
+ if (screen.kind === "detail") return "detail_panel";
192
+ if (screen.kind === "form") return "edit_form";
193
+ if (screen.kind === "board") return "board_view";
194
+ if (screen.kind === "calendar") return "calendar_view";
195
+ if (screen.kind === "dashboard" || screen.kind === "analytics" || screen.kind === "report") return "summary_stats";
196
+ if (screen.kind === "feed" || screen.kind === "inbox") return "activity_feed";
197
+ const view = collectionEntries.find((entry) => entry.operation === "view")?.value;
198
+ if (view) return collectionPatternFromPresentations([view]);
199
+ if (screen.kind === "list") return "resource_table";
200
+ return null;
201
+ }
@@ -37,6 +37,16 @@ import {
37
37
  UI_NAVIGATION_PATTERNS,
38
38
  UI_REGION_KINDS,
39
39
  UI_PATTERN_KINDS,
40
+ UI_APP_SHELL_KINDS,
41
+ UI_WINDOWING_MODES,
42
+ UI_STATE_KINDS,
43
+ UI_DESIGN_DENSITIES,
44
+ UI_DESIGN_TONES,
45
+ UI_DESIGN_RADIUS_SCALES,
46
+ UI_DESIGN_COLOR_ROLES,
47
+ UI_DESIGN_TYPOGRAPHY_ROLES,
48
+ UI_DESIGN_ACTION_ROLES,
49
+ UI_DESIGN_ACCESSIBILITY_VALUES,
40
50
  FIELD_SPECS
41
51
  } from "./kinds.js";
42
52
  import { validateComponent } from "./per-kind/component.js";
@@ -78,6 +88,16 @@ export {
78
88
  UI_NAVIGATION_PATTERNS,
79
89
  UI_REGION_KINDS,
80
90
  UI_PATTERN_KINDS,
91
+ UI_APP_SHELL_KINDS,
92
+ UI_WINDOWING_MODES,
93
+ UI_STATE_KINDS,
94
+ UI_DESIGN_DENSITIES,
95
+ UI_DESIGN_TONES,
96
+ UI_DESIGN_RADIUS_SCALES,
97
+ UI_DESIGN_COLOR_ROLES,
98
+ UI_DESIGN_TYPOGRAPHY_ROLES,
99
+ UI_DESIGN_ACTION_ROLES,
100
+ UI_DESIGN_ACCESSIBILITY_VALUES,
81
101
  FIELD_SPECS
82
102
  } from "./kinds.js";
83
103
 
@@ -279,7 +299,7 @@ function validateFieldShapes(errors, statement, fieldMap) {
279
299
  ensureSingleValueField(errors, statement, fieldMap, key, ["list"]);
280
300
  }
281
301
 
282
- for (const key of ["fields", "props", "events", "slots", "behaviors", "keys", "relations", "invariants", "rename", "overrides", "http", "http_errors", "http_fields", "http_responses", "http_preconditions", "http_idempotency", "http_cache", "http_delete", "http_async", "http_status", "http_download", "http_authz", "http_callbacks", "ui_screens", "ui_collections", "ui_actions", "ui_visibility", "ui_lookups", "ui_routes", "ui_web", "ui_ios", "ui_app_shell", "ui_navigation", "ui_screen_regions", "ui_components", "db_tables", "db_columns", "db_keys", "db_indexes", "db_relations", "db_lifecycle", "generator_defaults"]) {
302
+ for (const key of ["fields", "props", "events", "slots", "behaviors", "keys", "relations", "invariants", "rename", "overrides", "http", "http_errors", "http_fields", "http_responses", "http_preconditions", "http_idempotency", "http_cache", "http_delete", "http_async", "http_status", "http_download", "http_authz", "http_callbacks", "ui_screens", "ui_collections", "ui_actions", "ui_visibility", "ui_lookups", "ui_routes", "ui_web", "ui_ios", "ui_app_shell", "ui_navigation", "ui_screen_regions", "ui_components", "ui_design", "db_tables", "db_columns", "db_keys", "db_indexes", "db_relations", "db_lifecycle", "generator_defaults"]) {
283
303
  ensureSingleValueField(errors, statement, fieldMap, key, ["block"]);
284
304
  }
285
305
 
@@ -2320,18 +2340,113 @@ function validateProjectionUiAppShell(errors, statement, fieldMap) {
2320
2340
  }
2321
2341
  seenKeys.add(key);
2322
2342
 
2323
- if (key === "shell" && !["topbar", "sidebar", "dual_nav", "workspace", "wizard", "bottom_tabs", "split_view", "menu_bar"].includes(value)) {
2343
+ if (key === "shell" && !UI_APP_SHELL_KINDS.has(value)) {
2324
2344
  pushError(errors, `Projection ${statement.id} ui_app_shell has invalid shell '${value}'`, entry.loc);
2325
2345
  }
2326
2346
  if (["global_search", "notifications", "account_menu", "workspace_switcher"].includes(key) && !["true", "false"].includes(value)) {
2327
2347
  pushError(errors, `Projection ${statement.id} ui_app_shell '${key}' must be true or false`, entry.loc);
2328
2348
  }
2329
- if (key === "windowing" && !["single_window", "multi_window"].includes(value)) {
2349
+ if (key === "windowing" && !UI_WINDOWING_MODES.has(value)) {
2330
2350
  pushError(errors, `Projection ${statement.id} ui_app_shell has invalid windowing '${value}'`, entry.loc);
2331
2351
  }
2332
2352
  }
2333
2353
  }
2334
2354
 
2355
+ function validateProjectionUiDesign(errors, statement, fieldMap) {
2356
+ if (statement.kind !== "projection") {
2357
+ return;
2358
+ }
2359
+
2360
+ const designField = fieldMap.get("ui_design")?.[0];
2361
+ if (!designField || designField.value.type !== "block") {
2362
+ return;
2363
+ }
2364
+
2365
+ if (symbolValue(getFieldValue(statement, "platform")) !== "ui_shared") {
2366
+ pushError(errors, `Projection ${statement.id} ui_design belongs on shared UI projections; concrete UI projections inherit semantic design intent through 'realizes'`, designField.loc);
2367
+ }
2368
+
2369
+ for (const entry of designField.value.entries) {
2370
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
2371
+ const [key, value, extra] = tokens;
2372
+
2373
+ if (key === "density") {
2374
+ if (!UI_DESIGN_DENSITIES.has(value || "")) {
2375
+ pushError(errors, `Projection ${statement.id} ui_design density has invalid value '${value}'`, entry.loc);
2376
+ }
2377
+ if (tokens.length !== 2) {
2378
+ pushError(errors, `Projection ${statement.id} ui_design density accepts exactly one value`, entry.loc);
2379
+ }
2380
+ continue;
2381
+ }
2382
+
2383
+ if (key === "tone") {
2384
+ if (!UI_DESIGN_TONES.has(value || "")) {
2385
+ pushError(errors, `Projection ${statement.id} ui_design tone has invalid value '${value}'`, entry.loc);
2386
+ }
2387
+ if (tokens.length !== 2) {
2388
+ pushError(errors, `Projection ${statement.id} ui_design tone accepts exactly one value`, entry.loc);
2389
+ }
2390
+ continue;
2391
+ }
2392
+
2393
+ if (key === "radius_scale") {
2394
+ if (!UI_DESIGN_RADIUS_SCALES.has(value || "")) {
2395
+ pushError(errors, `Projection ${statement.id} ui_design radius_scale has invalid value '${value}'`, entry.loc);
2396
+ }
2397
+ if (tokens.length !== 2) {
2398
+ pushError(errors, `Projection ${statement.id} ui_design radius_scale accepts exactly one value`, entry.loc);
2399
+ }
2400
+ continue;
2401
+ }
2402
+
2403
+ if (key === "color_role") {
2404
+ if (!UI_DESIGN_COLOR_ROLES.has(value || "")) {
2405
+ pushError(errors, `Projection ${statement.id} ui_design color_role has invalid role '${value}'`, entry.loc);
2406
+ }
2407
+ if (tokens.length !== 3) {
2408
+ pushError(errors, `Projection ${statement.id} ui_design color_role must use 'color_role <role> <semantic-token>'`, entry.loc);
2409
+ }
2410
+ continue;
2411
+ }
2412
+
2413
+ if (key === "typography_role") {
2414
+ if (!UI_DESIGN_TYPOGRAPHY_ROLES.has(value || "")) {
2415
+ pushError(errors, `Projection ${statement.id} ui_design typography_role has invalid role '${value}'`, entry.loc);
2416
+ }
2417
+ if (tokens.length !== 3) {
2418
+ pushError(errors, `Projection ${statement.id} ui_design typography_role must use 'typography_role <role> <semantic-token>'`, entry.loc);
2419
+ }
2420
+ continue;
2421
+ }
2422
+
2423
+ if (key === "action_role") {
2424
+ if (!UI_DESIGN_ACTION_ROLES.has(value || "")) {
2425
+ pushError(errors, `Projection ${statement.id} ui_design action_role has invalid role '${value}'`, entry.loc);
2426
+ }
2427
+ if (tokens.length !== 3) {
2428
+ pushError(errors, `Projection ${statement.id} ui_design action_role must use 'action_role <role> <semantic-token>'`, entry.loc);
2429
+ }
2430
+ continue;
2431
+ }
2432
+
2433
+ if (key === "accessibility") {
2434
+ const values = UI_DESIGN_ACCESSIBILITY_VALUES[value];
2435
+ if (tokens.length !== 3) {
2436
+ pushError(errors, `Projection ${statement.id} ui_design accessibility must use 'accessibility <setting> <value>'`, entry.loc);
2437
+ }
2438
+ if (!values) {
2439
+ pushError(errors, `Projection ${statement.id} ui_design accessibility has invalid setting '${value}'`, entry.loc);
2440
+ } else if (!values.has(extra || "")) {
2441
+ pushError(errors, `Projection ${statement.id} ui_design accessibility '${value}' has invalid value '${extra}'`, entry.loc);
2442
+ }
2443
+ continue;
2444
+ }
2445
+
2446
+ pushError(errors, `Projection ${statement.id} ui_design has unknown key '${key}'`, entry.loc);
2447
+ }
2448
+ }
2449
+
2335
2450
  function validateProjectionUiNavigation(errors, statement, fieldMap, registry) {
2336
2451
  if (statement.kind !== "projection") {
2337
2452
  return;
@@ -2458,7 +2573,7 @@ function validateProjectionUiScreenRegions(errors, statement, fieldMap, registry
2458
2573
  if (directives.has("placement") && !["primary", "secondary", "supporting"].includes(directives.get("placement"))) {
2459
2574
  pushError(errors, `Projection ${statement.id} ui_screen_regions for '${screenId}' has invalid placement '${directives.get("placement")}'`, entry.loc);
2460
2575
  }
2461
- if (directives.has("state") && !["loading", "empty", "error", "unauthorized", "not_found", "success"].includes(directives.get("state"))) {
2576
+ if (directives.has("state") && !UI_STATE_KINDS.has(directives.get("state"))) {
2462
2577
  pushError(errors, `Projection ${statement.id} ui_screen_regions for '${screenId}' has invalid state '${directives.get("state")}'`, entry.loc);
2463
2578
  }
2464
2579
  }
@@ -2474,6 +2589,10 @@ function validateProjectionUiComponents(errors, statement, fieldMap, registry) {
2474
2589
  return;
2475
2590
  }
2476
2591
 
2592
+ if (symbolValue(getFieldValue(statement, "platform")) !== "ui_shared") {
2593
+ pushError(errors, `Projection ${statement.id} ui_components belongs on shared UI projections; concrete UI projections inherit component placement through 'realizes'`, componentsField.loc);
2594
+ }
2595
+
2477
2596
  const availableScreens = collectAvailableUiScreenIds(statement, fieldMap, registry);
2478
2597
  const availableRegions = collectAvailableUiRegionKeys(statement, registry);
2479
2598
  const availableRegionPatterns = collectAvailableUiRegionPatterns(statement, registry);
@@ -3387,6 +3506,7 @@ export function validateWorkspace(workspaceAst) {
3387
3506
  validateProjectionUiLookups(errors, statement, fieldMap, registry);
3388
3507
  validateProjectionUiRoutes(errors, statement, fieldMap, registry);
3389
3508
  validateProjectionUiAppShell(errors, statement, fieldMap);
3509
+ validateProjectionUiDesign(errors, statement, fieldMap);
3390
3510
  validateProjectionUiNavigation(errors, statement, fieldMap, registry);
3391
3511
  validateProjectionUiScreenRegions(errors, statement, fieldMap, registry);
3392
3512
  validateProjectionUiComponents(errors, statement, fieldMap, registry);
@@ -74,58 +74,26 @@ export const STATUS_SETS_BY_KIND = {
74
74
  bug: BUG_STATUSES
75
75
  };
76
76
  export const VERIFICATION_METHODS = new Set(["smoke", "runtime", "contract", "journey", "manual"]);
77
- export const UI_SCREEN_KINDS = new Set(["list", "detail", "form", "dashboard", "job_status", "board", "calendar", "feed", "inbox", "settings", "wizard", "report", "analytics"]);
78
- export const UI_COLLECTION_PRESENTATIONS = new Set(["table", "data_grid", "cards", "list", "board", "calendar", "gallery"]);
79
- export const UI_NAVIGATION_PATTERNS = new Set([
80
- "primary",
81
- "tabs",
82
- "stack_navigation",
83
- "bottom_tabs",
84
- "segmented_control",
85
- "command_palette",
86
- "split_view",
87
- "navigation_rail"
88
- ]);
89
- export const UI_REGION_KINDS = new Set([
90
- "hero",
91
- "toolbar",
92
- "filters",
93
- "search",
94
- "results",
95
- "summary",
96
- "metadata",
97
- "aside",
98
- "related",
99
- "activity",
100
- "comments",
101
- "timeline",
102
- "tabs",
103
- "bulk_actions",
104
- "footer_actions"
105
- ]);
106
- export const UI_PATTERN_KINDS = new Set([
107
- "resource_table",
108
- "data_grid_view",
109
- "resource_cards",
110
- "detail_panel",
111
- "edit_form",
112
- "lookup_select",
113
- "action_bar",
114
- "status_badge",
115
- "summary_stats",
116
- "activity_feed",
117
- "comment_thread",
118
- "timeline_view",
119
- "board_view",
120
- "calendar_view",
121
- "settings_section",
122
- "wizard_stepper",
123
- "audit_log",
124
- "search_results",
125
- "empty_state_panel",
126
- "inspector_pane",
127
- "master_detail"
128
- ]);
77
+
78
+ export {
79
+ UI_APP_SHELL_KINDS,
80
+ UI_WINDOWING_MODES,
81
+ UI_SCREEN_KINDS,
82
+ UI_COLLECTION_PRESENTATIONS,
83
+ UI_NAVIGATION_PATTERNS,
84
+ UI_REGION_KINDS,
85
+ UI_PATTERN_KINDS,
86
+ UI_ACTION_PRESENTATIONS,
87
+ UI_STATE_KINDS,
88
+ UI_PLATFORM_PATTERNS,
89
+ UI_DESIGN_DENSITIES,
90
+ UI_DESIGN_TONES,
91
+ UI_DESIGN_RADIUS_SCALES,
92
+ UI_DESIGN_COLOR_ROLES,
93
+ UI_DESIGN_TYPOGRAPHY_ROLES,
94
+ UI_DESIGN_ACTION_ROLES,
95
+ UI_DESIGN_ACCESSIBILITY_VALUES
96
+ } from "../ui/taxonomy.js";
129
97
 
130
98
  // Kinds that may carry an optional singular `domain dom_x` field. Keep this
131
99
  // set in sync with the `allowed[]` arrays in FIELD_SPECS below; the cross-kind
@@ -221,6 +189,7 @@ export const FIELD_SPECS = {
221
189
  "ui_navigation",
222
190
  "ui_screen_regions",
223
191
  "ui_components",
192
+ "ui_design",
224
193
  "db_tables",
225
194
  "db_columns",
226
195
  "db_keys",