@topogram/cli 0.3.53 → 0.3.55

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 (40) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/review-groups.js +3 -3
  3. package/src/agent-ops/query-builders.js +34 -34
  4. package/src/archive/schema.js +1 -1
  5. package/src/cli.js +177 -22
  6. package/src/generator/adapters.js +2 -2
  7. package/src/generator/context/domain-coverage.js +2 -2
  8. package/src/generator/context/shared.js +6 -22
  9. package/src/generator/context/slice.js +10 -10
  10. package/src/generator/docs.js +1 -1
  11. package/src/generator/registry.js +1 -1
  12. package/src/generator/runtime/app-bundle.js +7 -5
  13. package/src/generator/runtime/compile-check.js +3 -1
  14. package/src/generator/runtime/deployment.js +3 -1
  15. package/src/generator/runtime/environment.js +22 -19
  16. package/src/generator/runtime/shared.js +7 -7
  17. package/src/generator/surfaces/contracts.js +1 -1
  18. package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
  19. package/src/generator/surfaces/databases/postgres/drizzle.js +1 -1
  20. package/src/generator/surfaces/databases/postgres/prisma.js +1 -1
  21. package/src/generator/surfaces/databases/shared.js +3 -3
  22. package/src/generator/surfaces/databases/sqlite/prisma.js +1 -1
  23. package/src/generator/surfaces/services/persistence-wiring.js +1 -1
  24. package/src/generator/surfaces/services/server-contract.js +1 -1
  25. package/src/generator/surfaces/shared.js +1 -1
  26. package/src/generator/surfaces/web/ui-surface-contract.js +1 -1
  27. package/src/generator/widget-conformance.js +6 -6
  28. package/src/generator/widgets.js +1 -1
  29. package/src/generator-policy.js +10 -10
  30. package/src/import/core/runner.js +60 -50
  31. package/src/project-config.js +5 -42
  32. package/src/proofs/contract-audit.js +1 -1
  33. package/src/realization/backend/build-backend-runtime-realization.js +3 -3
  34. package/src/realization/ui/build-ui-shared-realization.js +9 -9
  35. package/src/realization/ui/build-web-realization.js +3 -3
  36. package/src/reconcile/journeys.js +1 -1
  37. package/src/resolver/enrich/widget.js +2 -2
  38. package/src/resolver/index.js +4 -23
  39. package/src/validator/index.js +10 -10
  40. package/src/workflows.js +49 -49
@@ -31,7 +31,7 @@ export const GENERATOR_POLICY_FILE = "topogram.generator-policy.json";
31
31
  * @property {string|null} path
32
32
  * @property {string|null} suggestedFix
33
33
  * @property {string|null} step
34
- * @property {string|null} [componentId]
34
+ * @property {string|null} [runtimeId]
35
35
  * @property {string|null} [generatorId]
36
36
  * @property {string|null} [packageName]
37
37
  * @property {string|null} [version]
@@ -39,8 +39,8 @@ export const GENERATOR_POLICY_FILE = "topogram.generator-policy.json";
39
39
 
40
40
  /**
41
41
  * @typedef {Object} PackageGeneratorBinding
42
- * @property {string} componentId
43
- * @property {string} componentType
42
+ * @property {string} runtimeId
43
+ * @property {string} runtimeKind
44
44
  * @property {string} projection
45
45
  * @property {string} generatorId
46
46
  * @property {string} version
@@ -59,7 +59,7 @@ function generatorPolicyDiagnostic(input) {
59
59
  path: typeof input.path === "string" ? input.path : null,
60
60
  suggestedFix: typeof input.suggestedFix === "string" ? input.suggestedFix : null,
61
61
  step: typeof input.step === "string" ? input.step : null,
62
- componentId: typeof input.componentId === "string" ? input.componentId : null,
62
+ runtimeId: typeof input.runtimeId === "string" ? input.runtimeId : null,
63
63
  generatorId: typeof input.generatorId === "string" ? input.generatorId : null,
64
64
  packageName: typeof input.packageName === "string" ? input.packageName : null,
65
65
  version: typeof input.version === "string" ? input.version : null
@@ -179,8 +179,8 @@ export function packageBackedGeneratorBindings(projectConfig) {
179
179
  return runtimes
180
180
  .filter((runtime) => typeof runtime?.generator?.package === "string" && runtime.generator.package.length > 0)
181
181
  .map((runtime) => ({
182
- componentId: String(runtime.id || "unknown"),
183
- componentType: String(runtime.kind || runtime.type || "unknown"),
182
+ runtimeId: String(runtime.id || "unknown"),
183
+ runtimeKind: String(runtime.kind || "unknown"),
184
184
  projection: String(runtime.projection || "unknown"),
185
185
  generatorId: String(runtime.generator.id || "unknown"),
186
186
  version: String(runtime.generator.version || "unknown"),
@@ -263,11 +263,11 @@ export function generatorPolicyDiagnosticsForBindings(policyInfo, bindings, step
263
263
  const allowedPackages = policy.allowedPackages.join(", ") || "(none)";
264
264
  diagnostics.push(generatorPolicyDiagnostic({
265
265
  code: "generator_package_denied",
266
- message: `Component '${binding.componentId}' generator package '${binding.packageName}' is not allowed by ${GENERATOR_POLICY_FILE}.`,
266
+ message: `Runtime '${binding.runtimeId}' generator package '${binding.packageName}' is not allowed by ${GENERATOR_POLICY_FILE}.`,
267
267
  path: policyInfo.path,
268
268
  suggestedFix: `Review '${binding.packageName}', then run \`topogram generator policy pin ${binding.packageName}@${binding.version}\` or add '${scope || binding.packageName}' to ${GENERATOR_POLICY_FILE}.`,
269
269
  step,
270
- componentId: binding.componentId,
270
+ runtimeId: binding.runtimeId,
271
271
  generatorId: binding.generatorId,
272
272
  packageName: binding.packageName,
273
273
  version: binding.version
@@ -278,11 +278,11 @@ export function generatorPolicyDiagnosticsForBindings(policyInfo, bindings, step
278
278
  if (pinnedVersion && pinnedVersion !== binding.version) {
279
279
  diagnostics.push(generatorPolicyDiagnostic({
280
280
  code: "generator_version_mismatch",
281
- message: `Component '${binding.componentId}' generator '${binding.generatorId}' uses version '${binding.version}', but ${GENERATOR_POLICY_FILE} pins '${binding.packageName}' to '${pinnedVersion}'.`,
281
+ message: `Runtime '${binding.runtimeId}' generator '${binding.generatorId}' uses version '${binding.version}', but ${GENERATOR_POLICY_FILE} pins '${binding.packageName}' to '${pinnedVersion}'.`,
282
282
  path: policyInfo.path,
283
283
  suggestedFix: `Use generator version '${pinnedVersion}', or run \`topogram generator policy pin ${binding.packageName}@${binding.version}\` after review.`,
284
284
  step,
285
- componentId: binding.componentId,
285
+ runtimeId: binding.runtimeId,
286
286
  generatorId: binding.generatorId,
287
287
  packageName: binding.packageName,
288
288
  version: binding.version
@@ -126,12 +126,19 @@ function capabilityHintsForScreen(screen) {
126
126
  return rawHints.map(normalizeCapabilityHint).filter(Boolean);
127
127
  }
128
128
 
129
- function inferredDataSourceForComponent(component, allCandidates) {
130
- if (component.data_source) {
131
- return component.data_source;
129
+ function uiWidgetCandidates(candidates) {
130
+ return [
131
+ ...(Array.isArray(candidates?.widgets) ? candidates.widgets : []),
132
+ ...(Array.isArray(candidates?.components) ? candidates.components : [])
133
+ ];
134
+ }
135
+
136
+ function inferredDataSourceForWidget(widget, allCandidates) {
137
+ if (widget.data_source) {
138
+ return widget.data_source;
132
139
  }
133
140
  const capabilityIds = importedApiCapabilityIds(allCandidates);
134
- const screenStem = String(component.screen_id || "")
141
+ const screenStem = String(widget.screen_id || "")
135
142
  .replace(/_(list|index|table|grid|results)$/, "")
136
143
  .replace(/^list_/, "");
137
144
  return capabilityIds.find((id) => /^cap_(list|get)_/.test(id) && id.includes(screenStem)) ||
@@ -139,22 +146,22 @@ function inferredDataSourceForComponent(component, allCandidates) {
139
146
  null;
140
147
  }
141
148
 
142
- function deriveUiComponentCandidates(candidates) {
149
+ function deriveUiWidgetCandidates(candidates) {
143
150
  const screens = candidates.screens || [];
144
151
  const actions = candidates.actions || [];
145
152
  const presentations = [...new Set(actions
146
153
  .filter((entry) => entry.kind === "ui_presentation")
147
154
  .map((entry) => entry.presentation)
148
155
  .filter(Boolean))].sort();
149
- const componentScreens = screens.filter((screen) => ["list", "dashboard", "analytics", "report", "feed", "inbox"].includes(screen.screen_kind));
156
+ const widgetScreens = screens.filter((screen) => ["list", "dashboard", "analytics", "report", "feed", "inbox"].includes(screen.screen_kind));
150
157
 
151
- return componentScreens.map((screen) => {
158
+ return widgetScreens.map((screen) => {
152
159
  const pattern = collectionPatternFromPresentations(presentations);
153
- const componentStem = idHintify(`${screen.id_hint}_results`);
160
+ const widgetStem = idHintify(`${screen.id_hint}_results`);
154
161
  const loadCapability = loadCapabilityForScreen(screen);
155
162
  return makeCandidateRecord({
156
163
  kind: "widget",
157
- idHint: `widget_${componentStem}`,
164
+ idHint: `widget_${widgetStem}`,
158
165
  label: `${screen.label || screen.id_hint} results`,
159
166
  confidence: presentations.length > 0 ? "medium" : "low",
160
167
  sourceKind: "ui_projection_inference",
@@ -204,13 +211,13 @@ function normalizeCandidatesForTrack(track, candidates) {
204
211
  };
205
212
  }
206
213
  if (track === "ui") {
207
- const explicitComponents = candidates.components || [];
208
- const derivedComponents = deriveUiComponentCandidates(candidates);
214
+ const explicitWidgets = uiWidgetCandidates(candidates);
215
+ const derivedWidgets = deriveUiWidgetCandidates(candidates);
209
216
  return {
210
217
  screens: dedupeCandidateRecords(candidates.screens || [], (record) => record.id_hint),
211
218
  routes: dedupeCandidateRecords(candidates.routes || [], (record) => record.id_hint),
212
219
  actions: dedupeCandidateRecords(candidates.actions || [], (record) => record.id_hint),
213
- components: dedupeCandidateRecords([...explicitComponents, ...derivedComponents], (record) => record.id_hint),
220
+ widgets: dedupeCandidateRecords([...explicitWidgets, ...derivedWidgets], (record) => record.id_hint),
214
221
  stacks: [...new Set(candidates.stacks || [])].sort()
215
222
  };
216
223
  }
@@ -289,11 +296,12 @@ function reportMarkdown(track, candidates) {
289
296
  );
290
297
  }
291
298
  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}`
299
+ const widgets = uiWidgetCandidates(candidates);
300
+ const widgetLines = widgets.map((widget) =>
301
+ `- \`${widget.id_hint}\` confidence ${widget.confidence || "unknown"} pattern \`${widget.pattern || widget.inferred_pattern || "unknown"}\` region \`${widget.region || widget.inferred_region || "unknown"}\` evidence ${(widget.evidence || widget.provenance || []).length} missing decisions ${(widget.missing_decisions || []).length}`
294
302
  );
295
303
  return ensureTrailingNewline(
296
- `# UI Import Report\n\n- Screens: ${candidates.screens.length}\n- Routes: ${candidates.routes.length}\n- Actions: ${candidates.actions.length}\n- Widgets: ${candidates.components.length}\n- Stacks: ${candidates.stacks.length ? candidates.stacks.join(", ") : "none"}\n\n## Widget Candidates\n\n${componentLines.length ? componentLines.join("\n") : "- none"}\n\n## Next Validation\n\n- Review candidates under \`topogram/candidates/app/ui/drafts/widgets/**\`.\n- Run \`topogram import plan <path>\` before adoption.\n- After adoption, run \`topogram check <path>\`, \`topogram widget check <path>\`, and \`topogram widget behavior <path>\`.\n`
304
+ `# UI Import Report\n\n- Screens: ${candidates.screens.length}\n- Routes: ${candidates.routes.length}\n- Actions: ${candidates.actions.length}\n- Widgets: ${widgets.length}\n- Stacks: ${candidates.stacks.length ? candidates.stacks.join(", ") : "none"}\n\n## Widget Candidates\n\n${widgetLines.length ? widgetLines.join("\n") : "- none"}\n\n## Next Validation\n\n- Review candidates under \`topogram/candidates/app/ui/drafts/widgets/**\`.\n- Run \`topogram import plan <path>\` before adoption.\n- After adoption, run \`topogram check <path>\`, \`topogram widget check <path>\`, and \`topogram widget behavior <path>\`.\n`
297
305
  );
298
306
  }
299
307
  if (track === "verification") {
@@ -318,60 +326,62 @@ function projectionIdStem(workspaceRoot) {
318
326
  .replace(/^_+|_+$/g, "") || "imported_app";
319
327
  }
320
328
 
321
- function componentCandidateFileName(component) {
322
- return `${String(component.id_hint || "widget")
329
+ function widgetCandidateFileName(widget) {
330
+ return `${String(widget.id_hint || "widget")
323
331
  .replace(/^component_/, "")
324
332
  .replace(/_/g, "-")}.tg`;
325
333
  }
326
334
 
327
- function renderComponentCandidate(component) {
328
- const evidenceCount = (component.evidence || component.provenance || []).length;
329
- const missingDecisions = component.missing_decisions || [
335
+ function renderWidgetCandidate(widget) {
336
+ const evidenceCount = (widget.evidence || widget.provenance || []).length;
337
+ const missingDecisions = widget.missing_decisions || [
330
338
  "confirm widget reuse boundary",
331
339
  "confirm prop names and data source",
332
340
  "confirm events and behavior"
333
341
  ];
334
- return `widget ${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"}.
342
+ return `widget ${widget.id_hint} {
343
+ # Import metadata: confidence ${widget.confidence || "unknown"}; evidence ${evidenceCount}; inferred pattern ${widget.pattern || widget.inferred_pattern || "search_results"}; inferred region ${widget.region || widget.inferred_region || "results"}.
336
344
  # Missing decisions: ${missingDecisions.join("; ")}.
337
- name "${component.label || component.id_hint}"
345
+ name "${widget.label || widget.id_hint}"
338
346
  description "Candidate reusable widget inferred from imported UI evidence. Review props, behavior, events, and reuse before adoption."
339
347
  category collection
340
348
  props {
341
- ${component.data_prop || "rows"} array required
349
+ ${widget.data_prop || "rows"} array required
342
350
  }
343
- patterns [${component.pattern || "search_results"}]
344
- regions [${component.region || "results"}]
351
+ patterns [${widget.pattern || "search_results"}]
352
+ regions [${widget.region || "results"}]
345
353
  status proposed
346
354
  }
347
355
  `;
348
356
  }
349
357
 
350
- function uiComponentLinesForCandidates(componentCandidates, allCandidates) {
351
- return componentCandidates
352
- .filter((component) => component.screen_id && component.region && component.id_hint)
353
- .map((component) => {
354
- const dataSource = inferredDataSourceForComponent(component, allCandidates);
358
+ function uiWidgetLinesForCandidates(widgetCandidates, allCandidates) {
359
+ return widgetCandidates
360
+ .filter((widget) => widget.screen_id && widget.region && widget.id_hint)
361
+ .map((widget) => {
362
+ const dataSource = inferredDataSourceForWidget(widget, allCandidates);
355
363
  const dataBinding = dataSource
356
- ? ` data ${component.data_prop || "rows"} from ${dataSource}`
364
+ ? ` data ${widget.data_prop || "rows"} from ${dataSource}`
357
365
  : "";
358
- return ` screen ${component.screen_id} region ${component.region} widget ${component.id_hint}${dataBinding}`;
366
+ return ` screen ${widget.screen_id} region ${widget.region} widget ${widget.id_hint}${dataBinding}`;
359
367
  });
360
368
  }
361
369
 
362
- function enrichUiComponentDataSources(uiCandidates, allCandidates) {
363
- if (!uiCandidates || !Array.isArray(uiCandidates.components)) {
370
+ function enrichUiWidgetDataSources(uiCandidates, allCandidates) {
371
+ if (!uiCandidates) {
364
372
  return uiCandidates;
365
373
  }
374
+ const widgets = uiWidgetCandidates(uiCandidates);
375
+ const { components, ...canonicalCandidates } = uiCandidates;
366
376
  return {
367
- ...uiCandidates,
368
- components: uiCandidates.components.map((component) => {
369
- const dataSource = inferredDataSourceForComponent(component, allCandidates);
370
- const dataProp = component.data_prop || "rows";
377
+ ...canonicalCandidates,
378
+ widgets: widgets.map((widget) => {
379
+ const dataSource = inferredDataSourceForWidget(widget, allCandidates);
380
+ const dataProp = widget.data_prop || "rows";
371
381
  return {
372
- ...component,
373
- data_source: component.data_source || dataSource,
374
- inferred_props: (component.inferred_props || []).map((prop) =>
382
+ ...widget,
383
+ data_source: widget.data_source || dataSource,
384
+ inferred_props: (widget.inferred_props || []).map((prop) =>
375
385
  prop.name === dataProp ? { ...prop, source: prop.source || dataSource } : prop
376
386
  )
377
387
  };
@@ -384,7 +394,7 @@ function draftUiProjectionFiles(context, candidates, allCandidates = {}) {
384
394
  const screens = [...(ui.screens || [])].sort((a, b) => String(a.route_path || "").localeCompare(String(b.route_path || "")) || a.id_hint.localeCompare(b.id_hint));
385
395
  const routes = new Map((ui.routes || []).map((route) => [route.screen_id, route.path]));
386
396
  const actions = ui.actions || [];
387
- const componentCandidates = [...(ui.components || [])].sort((a, b) => a.id_hint.localeCompare(b.id_hint));
397
+ const widgetCandidates = [...uiWidgetCandidates(ui)].sort((a, b) => a.id_hint.localeCompare(b.id_hint));
388
398
  const shell = actions.find((entry) => entry.kind === "ui_shell")?.shell_kind || "topbar";
389
399
  const navigationPatterns = uniqueSorted(actions.filter((entry) => entry.kind === "navigation").map((entry) => entry.navigation_pattern));
390
400
  const presentations = uniqueSorted(actions.filter((entry) => entry.kind === "ui_presentation").map((entry) => entry.presentation));
@@ -486,7 +496,7 @@ function draftUiProjectionFiles(context, candidates, allCandidates = {}) {
486
496
  }
487
497
  }
488
498
  }
489
- const uiComponentLines = uiComponentLinesForCandidates(componentCandidates, allCandidates);
499
+ const uiWidgetLines = uiWidgetLinesForCandidates(widgetCandidates, allCandidates);
490
500
 
491
501
  const uiSharedDraft = `projection proj_ui_contract_imported_${stem} {
492
502
  name "Imported UI Contract Draft"
@@ -525,7 +535,7 @@ ${uiCollectionsLines.length > 0 ? ` collection_views {\n${uiCollectionsLines.jo
525
535
  ${uiNavigationLines.join("\n")}
526
536
  }
527
537
 
528
- ${uiScreenRegionLines.length > 0 ? ` screen_regions {\n${uiScreenRegionLines.join("\n")}\n }\n\n` : ""}${uiComponentLines.length > 0 ? ` widget_bindings {\n${uiComponentLines.join("\n")}\n }\n\n` : ""} status proposed
538
+ ${uiScreenRegionLines.length > 0 ? ` screen_regions {\n${uiScreenRegionLines.join("\n")}\n }\n\n` : ""}${uiWidgetLines.length > 0 ? ` widget_bindings {\n${uiWidgetLines.join("\n")}\n }\n\n` : ""} status proposed
529
539
  }
530
540
  `;
531
541
 
@@ -594,7 +604,7 @@ ${uiWebLines.length > 0 ? ` web_hints {\n${uiWebLines.join("\n")}\n }\n\n` : "
594
604
 
595
605
  - Draft UI contract projection: \`candidates/app/ui/drafts/proj-ui-contract.tg\`
596
606
  - Draft web surface projection: \`candidates/app/ui/drafts/proj-web-surface.tg\`
597
- - Draft widget candidates: ${componentCandidates.length}
607
+ - Draft widget candidates: ${widgetCandidates.length}
598
608
  - Imported screens: ${screens.length}
599
609
  - Imported routes: ${(ui.routes || []).length}
600
610
  - Imported UI actions/presentations: ${actions.length}
@@ -616,8 +626,8 @@ ${uiWebLines.length > 0 ? ` web_hints {\n${uiWebLines.join("\n")}\n }\n\n` : "
616
626
  "candidates/app/ui/drafts/proj-web-surface.tg": ensureTrailingNewline(uiWebDraft),
617
627
  "candidates/app/ui/drafts/README.md": ensureTrailingNewline(coverage)
618
628
  };
619
- for (const component of componentCandidates) {
620
- files[`candidates/app/ui/drafts/widgets/${componentCandidateFileName(component)}`] = ensureTrailingNewline(renderComponentCandidate(component));
629
+ for (const widget of widgetCandidates) {
630
+ files[`candidates/app/ui/drafts/widgets/${widgetCandidateFileName(widget)}`] = ensureTrailingNewline(renderWidgetCandidate(widget));
621
631
  }
622
632
  return files;
623
633
  }
@@ -656,7 +666,7 @@ export function runImportApp(inputPath, options = {}) {
656
666
  }
657
667
 
658
668
  if (candidates.ui) {
659
- candidates.ui = enrichUiComponentDataSources(candidates.ui, candidates);
669
+ candidates.ui = enrichUiWidgetDataSources(candidates.ui, candidates);
660
670
  files["candidates/app/ui/candidates.json"] = `${JSON.stringify(candidates.ui, null, 2)}\n`;
661
671
  files["candidates/app/ui/report.md"] = reportMarkdown("ui", candidates.ui);
662
672
  Object.assign(files, draftUiProjectionFiles(context, candidates.ui, candidates));
@@ -676,7 +686,7 @@ export function runImportApp(inputPath, options = {}) {
676
686
  files["candidates/app/findings.json"] = `${JSON.stringify(findings, null, 2)}\n`;
677
687
  files["candidates/app/candidates.json"] = `${JSON.stringify(candidates, null, 2)}\n`;
678
688
  files["candidates/app/report.md"] = ensureTrailingNewline(
679
- `# App Import Report\n\nTracks: ${tracks.join(", ")}\n\n## DB\n\n- Entities: ${candidates.db?.entities?.length || 0}\n- Enums: ${candidates.db?.enums?.length || 0}\n- Relations: ${candidates.db?.relations?.length || 0}\n\n## API\n\n- Capabilities: ${candidates.api?.capabilities?.length || 0}\n- Routes: ${candidates.api?.routes?.length || 0}\n- Stacks: ${candidates.api?.stacks?.length ? candidates.api.stacks.join(", ") : "none"}\n\n## UI\n\n- Screens: ${candidates.ui?.screens?.length || 0}\n- Routes: ${candidates.ui?.routes?.length || 0}\n- Actions: ${candidates.ui?.actions?.length || 0}\n- Widgets: ${candidates.ui?.components?.length || 0}\n- Stacks: ${candidates.ui?.stacks?.length ? candidates.ui.stacks.join(", ") : "none"}\n\n## Workflows\n\n- Workflows: ${candidates.workflows?.workflows?.length || 0}\n- States: ${candidates.workflows?.workflow_states?.length || 0}\n- Transitions: ${candidates.workflows?.workflow_transitions?.length || 0}\n\n## Verification\n\n- Verifications: ${candidates.verification?.verifications?.length || 0}\n- Scenarios: ${candidates.verification?.scenarios?.length || 0}\n- Frameworks: ${candidates.verification?.frameworks?.length ? candidates.verification.frameworks.join(", ") : "none"}\n- Scripts: ${candidates.verification?.scripts?.length || 0}\n`
689
+ `# App Import Report\n\nTracks: ${tracks.join(", ")}\n\n## DB\n\n- Entities: ${candidates.db?.entities?.length || 0}\n- Enums: ${candidates.db?.enums?.length || 0}\n- Relations: ${candidates.db?.relations?.length || 0}\n\n## API\n\n- Capabilities: ${candidates.api?.capabilities?.length || 0}\n- Routes: ${candidates.api?.routes?.length || 0}\n- Stacks: ${candidates.api?.stacks?.length ? candidates.api.stacks.join(", ") : "none"}\n\n## UI\n\n- Screens: ${candidates.ui?.screens?.length || 0}\n- Routes: ${candidates.ui?.routes?.length || 0}\n- Actions: ${candidates.ui?.actions?.length || 0}\n- Widgets: ${uiWidgetCandidates(candidates.ui).length}\n- Stacks: ${candidates.ui?.stacks?.length ? candidates.ui.stacks.join(", ") : "none"}\n\n## Workflows\n\n- Workflows: ${candidates.workflows?.workflows?.length || 0}\n- States: ${candidates.workflows?.workflow_states?.length || 0}\n- Transitions: ${candidates.workflows?.workflow_transitions?.length || 0}\n\n## Verification\n\n- Verifications: ${candidates.verification?.verifications?.length || 0}\n- Scenarios: ${candidates.verification?.scenarios?.length || 0}\n- Frameworks: ${candidates.verification?.frameworks?.length ? candidates.verification.frameworks.join(", ") : "none"}\n- Scripts: ${candidates.verification?.scripts?.length || 0}\n`
680
690
  );
681
691
 
682
692
  return {
@@ -26,9 +26,6 @@ import { validateProjectGeneratorPolicy } from "./generator-policy.js";
26
26
  * @property {number|null} [port]
27
27
  * @property {string} [uses_api]
28
28
  * @property {string} [uses_database]
29
- * @property {string} [type] Migration diagnostic only.
30
- * @property {string} [api] Migration diagnostic only.
31
- * @property {string} [database] Migration diagnostic only.
32
29
  * @property {Record<string, string>} [env]
33
30
  */
34
31
 
@@ -36,7 +33,7 @@ import { validateProjectGeneratorPolicy } from "./generator-policy.js";
36
33
  * @typedef {Object} ProjectConfig
37
34
  * @property {string} version
38
35
  * @property {Record<string, { path: string, ownership: "generated"|"maintained" }>} outputs
39
- * @property {{ runtimes: RuntimeTopologyRuntime[], components?: any[] }} topology
36
+ * @property {{ runtimes: RuntimeTopologyRuntime[] }} topology
40
37
  * @property {{ id?: string, module?: string, export?: string, implementation_module?: string, implementation_export?: string }} [implementation]
41
38
  */
42
39
 
@@ -225,40 +222,6 @@ export function defaultProjectConfigForGraph(graph, implementation = null) {
225
222
  };
226
223
  }
227
224
 
228
- /**
229
- * @param {string} kind
230
- * @returns {string}
231
- */
232
- function legacyRuntimeType(kind) {
233
- if (kind === "api_service") return "api";
234
- if (kind === "web_surface") return "web";
235
- if (kind === "ios_surface" || kind === "android_surface") return "native";
236
- return kind;
237
- }
238
-
239
- /**
240
- * @param {any} config
241
- * @returns {any}
242
- */
243
- function normalizeProjectConfigRuntimeAliases(config) {
244
- if (!config?.topology || !Array.isArray(config.topology.runtimes)) {
245
- return config;
246
- }
247
- return {
248
- ...config,
249
- topology: {
250
- ...config.topology,
251
- __normalizedRuntimeAliases: true,
252
- components: config.topology.runtimes.map((/** @type {RuntimeTopologyRuntime} */ runtime) => ({
253
- ...runtime,
254
- type: legacyRuntimeType(runtime.kind),
255
- api: runtime.uses_api,
256
- database: runtime.uses_database
257
- }))
258
- }
259
- };
260
- }
261
-
262
225
  /**
263
226
  * @param {string} root
264
227
  * @returns {ProjectConfigInfo|null}
@@ -270,7 +233,7 @@ export function loadProjectConfig(root) {
270
233
  }
271
234
  return {
272
235
  ...found,
273
- config: normalizeProjectConfigRuntimeAliases(found.config),
236
+ config: found.config,
274
237
  compatibility: false
275
238
  };
276
239
  }
@@ -290,7 +253,7 @@ export function projectConfigOrDefault(root, graph = null, implementation = null
290
253
  return null;
291
254
  }
292
255
  return {
293
- config: normalizeProjectConfigRuntimeAliases(defaultProjectConfigForGraph(graph, implementation)),
256
+ config: defaultProjectConfigForGraph(graph, implementation),
294
257
  configPath: null,
295
258
  configDir: path.dirname(path.resolve(root)),
296
259
  compatibility: true
@@ -485,7 +448,7 @@ export function validateProjectConfig(config, graph = null, options = {}) {
485
448
  pushError(errors, "topogram.project.json version must be a non-empty string");
486
449
  }
487
450
  validateOutputConfig(errors, config);
488
- if (config.topology?.components != null && config.topology.__normalizedRuntimeAliases !== true) {
451
+ if (config.topology?.components != null) {
489
452
  pushError(errors, `topogram.project.json ${renameDiagnostic("'topology.components'", "'topology.runtimes'", `"topology": { "runtimes": [] }`)}`);
490
453
  }
491
454
  if (!config.topology || typeof config.topology !== "object" || !Array.isArray(config.topology.runtimes)) {
@@ -495,7 +458,7 @@ export function validateProjectConfig(config, graph = null, options = {}) {
495
458
  for (const component of config.topology.runtimes) {
496
459
  validateComponentShape(errors, component, seenIds);
497
460
  }
498
- const generatorPolicy = validateProjectGeneratorPolicy(normalizeProjectConfigRuntimeAliases(config), options);
461
+ const generatorPolicy = validateProjectGeneratorPolicy(config, options);
499
462
  for (const error of generatorPolicy.errors) {
500
463
  pushError(errors, error.message, error.loc);
501
464
  }
@@ -117,7 +117,7 @@ function normalizeServerContract(contract) {
117
117
  return {
118
118
  projection: {
119
119
  id: contract.projection?.id ?? null,
120
- platform: contract.projection?.platform ?? null
120
+ type: contract.projection?.type ?? null
121
121
  },
122
122
  routes: (contract.routes ?? []).map(normalizeRoute).sort((a, b) => {
123
123
  const leftKey = `${a.method}:${a.path}:${a.capabilityId}`;
@@ -28,8 +28,8 @@ export function getDefaultBackendDbProjection(graph, options = {}) {
28
28
  return (
29
29
  explicit ||
30
30
  preferred ||
31
- candidates.find((projection) => projection.platform === "db_contract") ||
32
- candidates.find((projection) => projection.platform === "db_contract") ||
31
+ candidates.find((projection) => projection.type === "db_contract") ||
32
+ candidates.find((projection) => projection.type === "db_contract") ||
33
33
  candidates[0] ||
34
34
  null
35
35
  );
@@ -76,7 +76,7 @@ export function buildBackendRuntimeRealization(graph, options = {}) {
76
76
  repositoryReference,
77
77
  dbProjection: {
78
78
  id: dbProjection.id,
79
- platform: dbProjection.platform
79
+ type: dbProjection.type
80
80
  }
81
81
  };
82
82
  }
@@ -49,12 +49,12 @@ function ownershipFieldByCapability(graph) {
49
49
  }
50
50
 
51
51
  function widgetById(graph, widgetId) {
52
- return (graph.byKind.widget || graph.byKind.component || []).find((widget) => widget.id === widgetId) || null;
52
+ return (graph.byKind.widget || []).find((widget) => widget.id === widgetId) || null;
53
53
  }
54
54
 
55
55
  function widgetContractFor(graph, widgetId) {
56
56
  const widget = widgetById(graph, widgetId);
57
- return widget?.widgetContract || widget?.componentContract || null;
57
+ return widget?.widgetContract || null;
58
58
  }
59
59
 
60
60
  function summarizeWidgetRef(graph, widgetId) {
@@ -119,7 +119,7 @@ function buildDesignIntentContract(projection) {
119
119
  }
120
120
 
121
121
  export function buildWidgetUsageContract(graph, entry, options = {}) {
122
- const widgetId = entry.widget?.id || entry.component?.id || null;
122
+ const widgetId = entry.widget?.id || null;
123
123
  const contract = widgetId ? widgetContractFor(graph, widgetId) : null;
124
124
  const region = options.region || null;
125
125
  return {
@@ -143,7 +143,7 @@ export function buildWidgetUsageContract(graph, entry, options = {}) {
143
143
 
144
144
  export function buildWidgetContractMap(graph, widgetUsages) {
145
145
  return Object.fromEntries(
146
- [...new Set(widgetUsages.map((entry) => entry.widget?.id || entry.component?.id).filter(Boolean))]
146
+ [...new Set(widgetUsages.map((entry) => entry.widget?.id).filter(Boolean))]
147
147
  .sort()
148
148
  .map((widgetId) => [widgetId, widgetContractFor(graph, widgetId)])
149
149
  .filter(([, contract]) => contract)
@@ -202,7 +202,7 @@ function buildUiScreenContract(graph, projection, screen, ownershipFields) {
202
202
  const actionEntries = (projection.uiActions || []).filter((entry) => entry.screenId === screen.id);
203
203
  const lookupEntries = (projection.uiLookups || []).filter((entry) => entry.screenId === screen.id);
204
204
  const regionEntries = (projection.uiScreenRegions || []).filter((entry) => entry.screenId === screen.id);
205
- const widgetEntries = (projection.uiComponents || []).filter((entry) => entry.screenId === screen.id);
205
+ const widgetEntries = (projection.widgetBindings || []).filter((entry) => entry.screenId === screen.id);
206
206
  const screenActionIds = new Set(
207
207
  [
208
208
  screen.primaryAction?.id,
@@ -305,13 +305,13 @@ export function buildUiSharedRealization(graph, options = {}) {
305
305
 
306
306
  if (options.projectionId) {
307
307
  const projection = projections[0];
308
- const widgetUsages = projection.uiComponents || [];
308
+ const widgetUsages = projection.widgetBindings || [];
309
309
  const screens = (projection.uiScreens || []).map((screen) => buildUiScreenContract(graph, projection, screen, ownershipFields));
310
310
  return {
311
311
  projection: {
312
312
  id: projection.id,
313
313
  name: projection.name || projection.id,
314
- type: projection.type || projection.platform
314
+ type: projection.type || projection.type
315
315
  },
316
316
  realizes: projection.realizes,
317
317
  outputs: projection.outputs,
@@ -325,13 +325,13 @@ export function buildUiSharedRealization(graph, options = {}) {
325
325
 
326
326
  const output = {};
327
327
  for (const projection of projections) {
328
- const widgetUsages = projection.uiComponents || [];
328
+ const widgetUsages = projection.widgetBindings || [];
329
329
  const screens = (projection.uiScreens || []).map((screen) => buildUiScreenContract(graph, projection, screen, ownershipFields));
330
330
  output[projection.id] = {
331
331
  projection: {
332
332
  id: projection.id,
333
333
  name: projection.name || projection.id,
334
- type: projection.type || projection.platform
334
+ type: projection.type || projection.type
335
335
  },
336
336
  realizes: projection.realizes,
337
337
  outputs: projection.outputs,
@@ -26,7 +26,7 @@ export function buildWebRealization(graph, options = {}) {
26
26
  }
27
27
 
28
28
  const projection = getProjection(graph, options.projectionId);
29
- const projectionType = projection.type || projection.platform;
29
+ const projectionType = projection.type || projection.type;
30
30
  const surfaceHints =
31
31
  projectionType === "ios_surface" ? projection.uiIos || [] : projection.uiWeb || [];
32
32
  const sharedProjection = sharedUiProjectionForWeb(graph, projection);
@@ -85,13 +85,13 @@ export function buildWebRealization(graph, options = {}) {
85
85
  projection: {
86
86
  id: projection.id,
87
87
  name: projection.name || projection.id,
88
- type: projection.type || projection.platform
88
+ type: projection.type || projection.type
89
89
  },
90
90
  uiContract: sharedProjection
91
91
  ? {
92
92
  id: sharedProjection.id,
93
93
  name: sharedProjection.name || sharedProjection.id,
94
- type: sharedProjection.type || sharedProjection.platform
94
+ type: sharedProjection.type || sharedProjection.type
95
95
  }
96
96
  : null,
97
97
  generatorDefaults: generatorDefaultsMap(projection),
@@ -40,7 +40,7 @@ function collectJourneyGenerationContext(graph) {
40
40
  const rules = graph.byKind.rule || [];
41
41
  const projections = graph.byKind.projection || [];
42
42
  const uiSharedScreens = projections
43
- .filter((projection) => projection.platform === "ui_contract")
43
+ .filter((projection) => projection.type === "ui_contract")
44
44
  .flatMap((projection) => (projection.uiScreens || []).map((screen) => ({ ...screen, projectionId: projection.id })));
45
45
  const canonicalJourneys = (graph.docs || []).filter((doc) => doc.kind === "journey");
46
46
  const coveredEntityIds = new Set(canonicalJourneys.flatMap((doc) => doc.relatedEntities || []));
@@ -1,2 +1,2 @@
1
- // Component enrichment is orchestrated from ../index.js in this split.
2
- // The public enriched shape is `statement.componentContract`.
1
+ // Widget enrichment is orchestrated from ../index.js in this split.
2
+ // The public enriched shape is `statement.widgetContract`.
@@ -509,7 +509,7 @@ function buildProjectionPlan(statement) {
509
509
  designTokens: statement.uiDesign,
510
510
  navigation: statement.uiNavigation,
511
511
  screenRegions: statement.uiScreenRegions,
512
- widgetBindings: statement.uiComponents,
512
+ widgetBindings: statement.widgetBindings,
513
513
  tables: statement.dbTables,
514
514
  columns: statement.dbColumns,
515
515
  keys: statement.dbKeys,
@@ -536,7 +536,6 @@ function buildProjectionPlan(statement) {
536
536
  uiRoutes: statement.uiRoutes,
537
537
  uiWeb: statement.uiWeb,
538
538
  uiDesign: statement.uiDesign,
539
- uiComponents: statement.uiComponents,
540
539
  dbTables: statement.dbTables,
541
540
  dbColumns: statement.dbColumns,
542
541
  dbKeys: statement.dbKeys,
@@ -1361,8 +1360,7 @@ function parseProjectionUiScreenRegionsBlock(statement) {
1361
1360
  });
1362
1361
  }
1363
1362
 
1364
- function parseProjectionUiComponentsBlock(statement, registry, options = {}) {
1365
- const includeComponentAlias = options.includeComponentAlias !== false;
1363
+ function parseProjectionUiComponentsBlock(statement, registry) {
1366
1364
  return blockEntries(getFieldValue(statement, "widget_bindings")).map((entry) => {
1367
1365
  const dataBindings = [];
1368
1366
  const eventBindings = [];
@@ -1421,11 +1419,6 @@ function parseProjectionUiComponentsBlock(statement, registry, options = {}) {
1421
1419
  raw: normalizeSequence(entry.items),
1422
1420
  loc: entry.loc
1423
1421
  };
1424
- if (includeComponentAlias) {
1425
- // Internal compatibility for existing generator adapters during the
1426
- // coordinated public DSL rename. Public contracts should expose widget.
1427
- binding.component = widgetRef;
1428
- }
1429
1422
  return binding;
1430
1423
  });
1431
1424
  }
@@ -1897,9 +1890,6 @@ export function normalizeStatement(statement, registry) {
1897
1890
  return {
1898
1891
  ...base,
1899
1892
  type: symbolValue(getFieldValue(statement, "type")),
1900
- // Internal compatibility for existing generator adapters during the
1901
- // coordinated public DSL rename. Public contracts should expose type.
1902
- platform: symbolValue(getFieldValue(statement, "type")),
1903
1893
  realizes: resolveReferenceList(registry, getFieldValue(statement, "realizes")),
1904
1894
  outputs: symbolValues(getFieldValue(statement, "outputs")),
1905
1895
  endpoints: parseProjectionHttpBlock(statement, registry),
@@ -1952,8 +1942,7 @@ export function normalizeStatement(statement, registry) {
1952
1942
  navigation: parseProjectionUiNavigationBlock(statement),
1953
1943
  uiScreenRegions: parseProjectionUiScreenRegionsBlock(statement),
1954
1944
  screenRegions: parseProjectionUiScreenRegionsBlock(statement),
1955
- uiComponents: parseProjectionUiComponentsBlock(statement, registry),
1956
- widgetBindings: parseProjectionUiComponentsBlock(statement, registry, { includeComponentAlias: false }),
1945
+ widgetBindings: parseProjectionUiComponentsBlock(statement, registry),
1957
1946
  dbTables: parseProjectionDbTablesBlock(statement, registry),
1958
1947
  tables: parseProjectionDbTablesBlock(statement, registry),
1959
1948
  dbColumns: parseProjectionDbColumnsBlock(statement, registry),
@@ -2348,10 +2337,7 @@ export function resolveWorkspace(workspaceAst) {
2348
2337
  case "widget":
2349
2338
  return {
2350
2339
  ...statement,
2351
- widgetContract: buildComponentContract(statement),
2352
- // Internal compatibility for existing generator adapters during the
2353
- // coordinated public DSL rename. Public contracts should expose widgetContract.
2354
- componentContract: buildComponentContract(statement)
2340
+ widgetContract: buildComponentContract(statement)
2355
2341
  };
2356
2342
  case "rule":
2357
2343
  return {
@@ -2497,11 +2483,6 @@ export function resolveWorkspace(workspaceAst) {
2497
2483
  };
2498
2484
  });
2499
2485
  const finalByKind = groupBy(finalStatements, (statement) => statement.kind);
2500
- if (finalByKind.widget && !finalByKind.component) {
2501
- // Internal compatibility for existing generator/context modules while the
2502
- // public DSL moves from component to widget in one coordinated release.
2503
- finalByKind.component = finalByKind.widget;
2504
- }
2505
2486
 
2506
2487
  const graph = mergeArchivedIntoGraph({
2507
2488
  root: workspaceAst.root,