@topogram/cli 0.3.51 → 0.3.53

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 (77) hide show
  1. package/ARCHITECTURE.md +4 -4
  2. package/CHANGELOG.md +11 -11
  3. package/package.json +1 -1
  4. package/src/adoption/plan.js +2 -2
  5. package/src/agent-ops/query-builders.js +42 -33
  6. package/src/cli.js +174 -129
  7. package/src/generator/adapters.d.ts +1 -0
  8. package/src/generator/adapters.js +64 -39
  9. package/src/generator/check.js +19 -12
  10. package/src/generator/context/diff.js +9 -9
  11. package/src/generator/context/domain-coverage.js +11 -10
  12. package/src/generator/context/domain-page.js +6 -6
  13. package/src/generator/context/shared.js +37 -21
  14. package/src/generator/context/slice.js +70 -65
  15. package/src/generator/index.js +12 -12
  16. package/src/generator/output.js +21 -20
  17. package/src/generator/registry.js +71 -49
  18. package/src/generator/runtime/app-bundle.js +15 -15
  19. package/src/generator/runtime/compile-check.js +7 -7
  20. package/src/generator/runtime/deployment.js +9 -9
  21. package/src/generator/runtime/environment.js +39 -39
  22. package/src/generator/runtime/runtime-check.js +5 -5
  23. package/src/generator/runtime/shared.js +40 -38
  24. package/src/generator/runtime/smoke.js +5 -5
  25. package/src/generator/surfaces/databases/contract.js +1 -1
  26. package/src/generator/surfaces/databases/lifecycle-shared.js +6 -5
  27. package/src/generator/surfaces/databases/postgres/drizzle.js +3 -2
  28. package/src/generator/surfaces/databases/postgres/prisma.js +3 -2
  29. package/src/generator/surfaces/databases/shared.js +3 -2
  30. package/src/generator/surfaces/databases/snapshot.js +1 -1
  31. package/src/generator/surfaces/databases/sqlite/prisma.js +3 -2
  32. package/src/generator/surfaces/native/swiftui-app.js +3 -3
  33. package/src/generator/surfaces/native/swiftui-templates/Package.swift.txt +1 -1
  34. package/src/generator/surfaces/native/swiftui-templates/README.generated.md +3 -3
  35. package/src/generator/surfaces/native/swiftui-templates/runtime/DynamicScreens.swift +3 -3
  36. package/src/generator/surfaces/services/persistence-wiring.js +3 -2
  37. package/src/generator/surfaces/services/server-contract.js +4 -4
  38. package/src/generator/surfaces/shared.js +2 -2
  39. package/src/generator/surfaces/web/design-intent.js +1 -1
  40. package/src/generator/surfaces/web/index.js +7 -7
  41. package/src/generator/surfaces/web/{react-components.js → react-widgets.js} +53 -53
  42. package/src/generator/surfaces/web/react.js +36 -36
  43. package/src/generator/surfaces/web/{sveltekit-components.js → sveltekit-widgets.js} +53 -53
  44. package/src/generator/surfaces/web/sveltekit.js +34 -34
  45. package/src/generator/surfaces/web/{ui-web-contract.js → ui-surface-contract.js} +8 -8
  46. package/src/generator/surfaces/web/vanilla.js +6 -6
  47. package/src/generator/{component-conformance.js → widget-conformance.js} +129 -128
  48. package/src/generator/widgets.js +40 -0
  49. package/src/generator-policy.js +10 -12
  50. package/src/import/core/runner.js +34 -34
  51. package/src/import/core/shared.js +1 -1
  52. package/src/import/extractors/ui/android-compose.js +1 -1
  53. package/src/import/extractors/ui/blazor.js +1 -1
  54. package/src/import/extractors/ui/razor-pages.js +1 -1
  55. package/src/import/extractors/ui/react-router.js +4 -4
  56. package/src/import/extractors/ui/sveltekit.js +4 -4
  57. package/src/import/extractors/ui/swiftui.js +1 -1
  58. package/src/import/extractors/ui/uikit.js +1 -1
  59. package/src/new-project.js +19 -18
  60. package/src/project-config.js +104 -44
  61. package/src/proofs/contract-audit.js +1 -1
  62. package/src/proofs/ios-parity.js +1 -1
  63. package/src/proofs/issues-parity.js +1 -1
  64. package/src/realization/backend/build-backend-runtime-realization.js +2 -2
  65. package/src/realization/ui/build-ui-shared-realization.js +33 -33
  66. package/src/realization/ui/build-web-realization.js +23 -20
  67. package/src/reconcile/journeys.js +1 -1
  68. package/src/resolver/index.js +148 -65
  69. package/src/validator/index.js +509 -423
  70. package/src/validator/kinds.js +36 -36
  71. package/src/validator/per-kind/{component.js → widget.js} +47 -47
  72. package/src/{component-behavior.js → widget-behavior.js} +3 -3
  73. package/src/workflows.js +39 -38
  74. package/template-helpers/react.js +4 -4
  75. package/template-helpers/sveltekit.js +4 -4
  76. package/src/generator/components.js +0 -39
  77. /package/src/resolver/enrich/{component.js → widget.js} +0 -0
@@ -4,9 +4,9 @@ import { getExampleImplementation } from "../../../example-implementation.js";
4
4
  import { renderApiClientModule, renderLookupModule, renderVisibilityModule } from "./shared.js";
5
5
  import { buildDesignIntentCoverage, renderDesignIntentCss } from "./design-intent.js";
6
6
  import {
7
- renderSvelteKitComponentRegion,
8
- svelteKitComponentUsageSupport
9
- } from "./sveltekit-components.js";
7
+ renderSvelteKitWidgetRegion,
8
+ svelteKitWidgetUsageSupport
9
+ } from "./sveltekit-widgets.js";
10
10
 
11
11
  function routePathToSvelteKitDirectory(routePath) {
12
12
  if (!routePath || routePath === "/") {
@@ -40,7 +40,7 @@ function screenRegions(screen) {
40
40
  for (const region of screen?.regions || []) {
41
41
  if (region?.name) names.add(region.name);
42
42
  }
43
- for (const usage of screen?.components || []) {
43
+ for (const usage of screen?.widgets || []) {
44
44
  if (usage?.region) names.add(usage.region);
45
45
  }
46
46
  return [...names];
@@ -65,7 +65,7 @@ function sampleItemsForScreen(screen) {
65
65
  title: `${title} completed sample`,
66
66
  name: `${title} completed sample`,
67
67
  message: `${title} completed sample`,
68
- description: "Second generated row for component rendering checks.",
68
+ description: "Second generated row for widget rendering checks.",
69
69
  status: "completed",
70
70
  priority: "low",
71
71
  created_at: "2026-01-02",
@@ -82,8 +82,8 @@ function screenRoutePagePath(screen) {
82
82
  return `${routePathToSvelteKitDirectory(screen.route)}/+page.svelte`;
83
83
  }
84
84
 
85
- function screenComponentUsages(screen) {
86
- return Array.isArray(screen?.components) ? screen.components : [];
85
+ function screenWidgetUsages(screen) {
86
+ return Array.isArray(screen?.widgets) ? screen.widgets : [];
87
87
  }
88
88
 
89
89
  function buildGenericSvelteKitScreenFiles(screen, contract, useTypescript) {
@@ -94,8 +94,8 @@ function buildGenericSvelteKitScreenFiles(screen, contract, useTypescript) {
94
94
  const canLoadFromApi = loadCapabilityId && !isDynamicRoute(screen.route);
95
95
  const renderedRegions = screenRegions(screen)
96
96
  .map((region) => {
97
- const rendered = renderSvelteKitComponentRegion(screen, region, {
98
- componentContracts: contract.components,
97
+ const rendered = renderSvelteKitWidgetRegion(screen, region, {
98
+ widgetContracts: contract.widgets,
99
99
  itemsExpression: "data.result.items",
100
100
  useTypescript
101
101
  });
@@ -129,7 +129,7 @@ export const load: PageLoad = async ({ fetch }) => {
129
129
  const result = await requestCapability(fetch, "${loadCapabilityId}");
130
130
  const resultObject = result && typeof result === "object" && !Array.isArray(result) ? result : {};
131
131
  return {
132
- screen: ${JSON.stringify({ id: screen.id, title: screen.title, collection: screen.collection, web: screen.web }, null, 2)},
132
+ screen: ${JSON.stringify({ id: screen.id, title: screen.title, collection: screen.collection, surfaceHints: screen.surfaceHints }, null, 2)},
133
133
  result: Array.isArray(result) ? { items: result } : { items: resultObject.items ?? [], ...resultObject }
134
134
  };
135
135
  };
@@ -138,7 +138,7 @@ export const load: PageLoad = async ({ fetch }) => {
138
138
 
139
139
  files[`${routeDir}/+page.svelte`] = `<script${useTypescript ? ' lang="ts"' : ""}>
140
140
  ${canLoadFromApi ? "export let data;" : `const data = {
141
- screen: ${JSON.stringify({ id: screen.id, title: screen.title, collection: screen.collection, web: screen.web }, null, 2)},
141
+ screen: ${JSON.stringify({ id: screen.id, title: screen.title, collection: screen.collection, surfaceHints: screen.surfaceHints }, null, 2)},
142
142
  result: {
143
143
  items: ${JSON.stringify(sampleItems, null, 2)}
144
144
  }
@@ -189,38 +189,38 @@ function buildSvelteKitGenerationCoverage(contract, files, implementationScreenI
189
189
  suggested_fix: "Check the SvelteKit generator contract-complete route emission for this screen."
190
190
  });
191
191
  }
192
- const componentUsages = screenComponentUsages(screen).map((usage) => {
193
- const componentId = usage.component?.id || null;
194
- const marker = componentId ? `data-topogram-component="${componentId}"` : null;
195
- const support = svelteKitComponentUsageSupport(usage, contract.components);
192
+ const widgetUsages = screenWidgetUsages(screen).map((usage) => {
193
+ const widgetId = usage.widget?.id || null;
194
+ const marker = widgetId ? `data-topogram-widget="${widgetId}"` : null;
195
+ const support = svelteKitWidgetUsageSupport(usage, contract.widgets);
196
196
  const usageRendered = Boolean(marker && contents.includes(marker));
197
- if (componentId && rendered && renderer !== "implementation" && !support.supported) {
197
+ if (widgetId && rendered && renderer !== "implementation" && !support.supported) {
198
198
  diagnostics.push({
199
- code: "component_pattern_not_supported",
199
+ code: "widget_pattern_not_supported",
200
200
  severity: "error",
201
201
  screen: screen.id,
202
202
  route: screen.route,
203
203
  region: usage.region || null,
204
204
  pattern: support.pattern || null,
205
- component: componentId,
206
- message: `Screen '${screen.id}' uses component '${componentId}' with unsupported SvelteKit component pattern '${support.pattern || "(missing)"}'.`,
207
- suggested_fix: "Use a supported component pattern for this generator or provide an implementation override."
205
+ widget: widgetId,
206
+ message: `Screen '${screen.id}' uses widget '${widgetId}' with unsupported SvelteKit widget pattern '${support.pattern || "(missing)"}'.`,
207
+ suggested_fix: "Use a supported widget pattern for this generator or provide an implementation override."
208
208
  });
209
209
  }
210
- if (componentId && rendered && !usageRendered) {
210
+ if (widgetId && rendered && !usageRendered) {
211
211
  diagnostics.push({
212
- code: "component_usage_not_rendered",
212
+ code: "widget_usage_not_rendered",
213
213
  severity: "warning",
214
214
  screen: screen.id,
215
215
  route: screen.route,
216
216
  region: usage.region || null,
217
- component: componentId,
218
- message: `Screen '${screen.id}' uses component '${componentId}' but the generated SvelteKit page does not contain its component marker.`,
219
- suggested_fix: "Render the component region with renderSvelteKitComponentRegion or add a supported component pattern."
217
+ widget: widgetId,
218
+ message: `Screen '${screen.id}' uses widget '${widgetId}' but the generated SvelteKit page does not contain its widget marker.`,
219
+ suggested_fix: "Render the widget region with renderSvelteKitWidgetRegion or add a supported widget pattern."
220
220
  });
221
221
  }
222
222
  return {
223
- component: componentId,
223
+ widget: widgetId,
224
224
  region: usage.region || null,
225
225
  pattern: support.pattern || null,
226
226
  supported: support.supported,
@@ -234,7 +234,7 @@ function buildSvelteKitGenerationCoverage(contract, files, implementationScreenI
234
234
  page: pagePath,
235
235
  rendered,
236
236
  renderer,
237
- component_usages: componentUsages
237
+ widget_usages: widgetUsages
238
238
  };
239
239
  });
240
240
 
@@ -245,16 +245,16 @@ function buildSvelteKitGenerationCoverage(contract, files, implementationScreenI
245
245
  projection: {
246
246
  id: contract.projection.id,
247
247
  name: contract.projection.name,
248
- platform: contract.projection.platform
248
+ type: contract.projection.type
249
249
  },
250
250
  summary: {
251
251
  routed_screens: screens.length,
252
252
  rendered_screens: screens.filter((screen) => screen.rendered).length,
253
253
  implementation_screens: screens.filter((screen) => screen.renderer === "implementation").length,
254
254
  generator_screens: screens.filter((screen) => screen.renderer === "generator").length,
255
- component_usages: screens.reduce((total, screen) => total + screen.component_usages.length, 0),
256
- rendered_component_usages: screens.reduce(
257
- (total, screen) => total + screen.component_usages.filter((usage) => usage.rendered).length,
255
+ widget_usages: screens.reduce((total, screen) => total + screen.widget_usages.length, 0),
256
+ rendered_widget_usages: screens.reduce(
257
+ (total, screen) => total + screen.widget_usages.filter((usage) => usage.rendered).length,
258
258
  0
259
259
  ),
260
260
  diagnostics: diagnostics.length,
@@ -374,8 +374,8 @@ function buildSvelteKitScaffold(contract, apiContracts, options = {}) {
374
374
  files["src/app.html"] =
375
375
  "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n %sveltekit.head%\n </head>\n <body data-sveltekit-preload-data=\"hover\">\n <div style=\"display: contents\">%sveltekit.body%</div>\n </body>\n</html>\n";
376
376
  files["src/app.css"] =
377
- `${renderDesignIntentCss(contract.design)}\n` +
378
- ":root {\n font-family: system-ui, sans-serif;\n color: var(--topogram-text-color);\n background: var(--topogram-surface-background);\n}\nbody {\n margin: 0;\n}\na {\n color: var(--topogram-action-primary-background);\n text-decoration: none;\n}\na:hover {\n text-decoration: underline;\n}\nmain {\n max-width: 72rem;\n margin: 0 auto;\n padding: var(--topogram-page-padding);\n}\n.app-shell {\n min-height: 100vh;\n}\n.app-workspace {\n display: grid;\n grid-template-columns: 18rem minmax(0, 1fr);\n min-height: 100vh;\n}\n.app-main-shell {\n min-width: 0;\n}\n.app-sidebar {\n position: sticky;\n top: 0;\n align-self: start;\n min-height: 100vh;\n display: grid;\n align-content: start;\n gap: var(--topogram-space-unit);\n padding: 1.25rem 1rem;\n border-right: 1px solid rgba(24, 32, 38, 0.08);\n background: rgba(255, 255, 255, 0.86);\n backdrop-filter: blur(12px);\n}\n.app-nav {\n position: sticky;\n top: 0;\n z-index: 10;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--topogram-space-unit);\n padding: 1rem 1.25rem;\n border-bottom: 1px solid rgba(24, 32, 38, 0.08);\n background: rgba(255, 255, 255, 0.9);\n backdrop-filter: blur(12px);\n}\n.app-nav-links,\n.app-nav nav,\n.app-tabbar {\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n}\n.app-nav.menu-bar {\n border-bottom-style: dashed;\n}\n.app-nav.compact {\n justify-content: flex-end;\n}\n.app-tabbar {\n position: sticky;\n bottom: 0;\n z-index: 10;\n justify-content: space-around;\n padding: 0.85rem 1rem calc(0.85rem + env(safe-area-inset-bottom, 0px));\n border-top: 1px solid rgba(24, 32, 38, 0.08);\n background: rgba(255, 255, 255, 0.92);\n backdrop-filter: blur(12px);\n}\n.brand {\n font-weight: 700;\n letter-spacing: 0.01em;\n}\n.brand-mark {\n font-weight: 700;\n color: var(--topogram-muted-color);\n}\n.command-palette-button {\n background: var(--topogram-text-color);\n color: white;\n border: none;\n border-radius: var(--topogram-radius-pill);\n padding: var(--topogram-control-padding);\n font: inherit;\n cursor: pointer;\n}\n.app-footer {\n max-width: 72rem;\n margin: 0 auto;\n padding: 0 1.25rem 2rem;\n color: var(--topogram-muted-color);\n}\n.card {\n background: var(--topogram-surface-card);\n border-radius: var(--topogram-radius-card);\n padding: 1.25rem;\n box-shadow: 0 12px 30px rgba(24, 32, 38, 0.08);\n}\n.hero {\n display: grid;\n gap: var(--topogram-space-unit);\n}\n.grid {\n display: grid;\n gap: var(--topogram-space-unit);\n}\n.grid.two {\n grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));\n}\n.filters {\n display: grid;\n gap: 0.75rem;\n grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));\n margin: 1rem 0 1.25rem;\n}\nlabel {\n display: grid;\n gap: 0.35rem;\n font-size: 0.95rem;\n}\ninput,\ntextarea,\nbutton,\nselect {\n font: inherit;\n}\ninput,\ntextarea,\nselect {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #c9d4e2;\n border-radius: var(--topogram-radius-control);\n padding: var(--topogram-control-padding);\n background: white;\n}\ntextarea {\n min-height: 8rem;\n resize: vertical;\n}\nbutton,\n.button-link {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.35rem;\n border: none;\n border-radius: var(--topogram-radius-pill);\n padding: var(--topogram-control-padding);\n background: var(--topogram-action-primary-background);\n color: var(--topogram-action-primary-color);\n font-weight: 600;\n cursor: pointer;\n}\nbutton:focus-visible,\n.button-link:focus-visible,\na:focus-visible,\ninput:focus-visible,\ntextarea:focus-visible,\nselect:focus-visible {\n outline: var(--topogram-focus-outline);\n outline-offset: 2px;\n}\n.button-link.secondary {\n background: #e9eef6;\n color: var(--topogram-text-color);\n}\n.button-row {\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n align-items: center;\n}\n.stack {\n display: grid;\n gap: var(--topogram-space-unit);\n}\n\n.resource-list {\n list-style: none;\n padding: 0;\n margin: 1rem 0 0;\n display: grid;\n gap: 0.75rem;\n}\n\n.resource-list li {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: var(--topogram-space-unit);\n padding: 1rem;\n border: 1px solid #e0e8f1;\n border-radius: var(--topogram-radius-card);\n background: var(--topogram-surface-subtle);\n}\n.table-wrap {\n margin-top: 1rem;\n overflow-x: auto;\n border: 1px solid var(--topogram-border-color);\n border-radius: var(--topogram-radius-card);\n background: white;\n}\n.resource-table {\n width: 100%;\n border-collapse: collapse;\n min-width: 42rem;\n}\n.resource-table th,\n.resource-table td {\n padding: 0.85rem 1rem;\n text-align: left;\n border-bottom: 1px solid #e7edf5;\n vertical-align: top;\n}\n.resource-table th {\n font-size: 0.85rem;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: #516173;\n background: #f8fbff;\n}\n.resource-table tbody tr:hover {\n background: #fbfdff;\n}\n.data-grid {\n min-width: 64rem;\n font-size: 0.95rem;\n}\n.data-grid thead th {\n position: sticky;\n top: 0;\n z-index: 1;\n background: #eef5ff;\n}\n.data-grid-shell {\n box-shadow: inset 0 0 0 1px rgba(15, 92, 192, 0.04);\n}\n.cell-stack,\n.resource-meta,\n.definition-list {\n display: grid;\n gap: 0.5rem;\n}\n.cell-secondary {\n color: var(--topogram-muted-color);\n font-size: 0.92rem;\n}\n.definition-list {\n grid-template-columns: minmax(8rem, 12rem) 1fr;\n align-items: start;\n}\n.definition-list dt {\n font-weight: 600;\n color: #516173;\n}\n.definition-list dd {\n margin: 0;\n}\n.badge {\n display: inline-flex;\n align-items: center;\n padding: 0.25rem 0.6rem;\n border-radius: var(--topogram-radius-pill);\n background: #eef4ff;\n color: var(--topogram-action-primary-background);\n font-size: 0.85rem;\n font-weight: 600;\n}\n.muted {\n color: var(--topogram-muted-color);\n}\n.empty-state {\n padding: 1rem 0;\n}\n.component-card {\n border: 1px solid var(--topogram-border-color);\n border-radius: var(--topogram-radius-card);\n background: var(--topogram-surface-subtle);\n padding: 1rem;\n margin-top: 1rem;\n}\n.component-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--topogram-space-unit);\n flex-wrap: wrap;\n}\n.component-eyebrow {\n margin: 0 0 0.25rem;\n color: var(--topogram-muted-color);\n font-size: 0.75rem;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n.component-card h2,\n.component-card h3 {\n margin: 0;\n}\n.component-table-wrap {\n margin-top: 1rem;\n}\n.summary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));\n gap: 0.75rem;\n}\n.summary-grid div,\n.board-column {\n border: 1px solid #e0e8f1;\n border-radius: var(--topogram-radius-control);\n background: white;\n padding: 0.85rem;\n}\n.summary-grid strong {\n display: block;\n font-size: 1.5rem;\n}\n.summary-grid span,\n.calendar-list span {\n color: var(--topogram-muted-color);\n font-size: 0.9rem;\n}\n.board-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));\n gap: 0.75rem;\n margin-top: 1rem;\n}\n.board-card,\n.calendar-card {\n display: grid;\n gap: 0.25rem;\n border: 1px solid #e0e8f1;\n border-radius: var(--topogram-radius-control);\n background: #f8fbff;\n padding: 0.75rem;\n}\n.calendar-list {\n display: grid;\n gap: 0.75rem;\n margin-top: 1rem;\n}\nsmall.route-hint {\n display: block;\n color: var(--topogram-muted-color);\n margin-top: 0.25rem;\n}\n@media (max-width: 900px) {\n .app-workspace {\n grid-template-columns: 1fr;\n }\n .app-sidebar {\n position: static;\n min-height: auto;\n border-right: none;\n border-bottom: 1px solid rgba(24, 32, 38, 0.08);\n }\n}\n@media (max-width: 640px) {\n .definition-list {\n grid-template-columns: 1fr;\n }\n .resource-list li {\n flex-direction: column;\n }\n .resource-table {\n min-width: 36rem;\n }\n .app-nav {\n flex-wrap: wrap;\n }\n}\n";
377
+ `${renderDesignIntentCss(contract.designTokens)}\n` +
378
+ ":root {\n font-family: system-ui, sans-serif;\n color: var(--topogram-text-color);\n background: var(--topogram-surface-background);\n}\nbody {\n margin: 0;\n}\na {\n color: var(--topogram-action-primary-background);\n text-decoration: none;\n}\na:hover {\n text-decoration: underline;\n}\nmain {\n max-width: 72rem;\n margin: 0 auto;\n padding: var(--topogram-page-padding);\n}\n.app-shell {\n min-height: 100vh;\n}\n.app-workspace {\n display: grid;\n grid-template-columns: 18rem minmax(0, 1fr);\n min-height: 100vh;\n}\n.app-main-shell {\n min-width: 0;\n}\n.app-sidebar {\n position: sticky;\n top: 0;\n align-self: start;\n min-height: 100vh;\n display: grid;\n align-content: start;\n gap: var(--topogram-space-unit);\n padding: 1.25rem 1rem;\n border-right: 1px solid rgba(24, 32, 38, 0.08);\n background: rgba(255, 255, 255, 0.86);\n backdrop-filter: blur(12px);\n}\n.app-nav {\n position: sticky;\n top: 0;\n z-index: 10;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--topogram-space-unit);\n padding: 1rem 1.25rem;\n border-bottom: 1px solid rgba(24, 32, 38, 0.08);\n background: rgba(255, 255, 255, 0.9);\n backdrop-filter: blur(12px);\n}\n.app-nav-links,\n.app-nav nav,\n.app-tabbar {\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n}\n.app-nav.menu-bar {\n border-bottom-style: dashed;\n}\n.app-nav.compact {\n justify-content: flex-end;\n}\n.app-tabbar {\n position: sticky;\n bottom: 0;\n z-index: 10;\n justify-content: space-around;\n padding: 0.85rem 1rem calc(0.85rem + env(safe-area-inset-bottom, 0px));\n border-top: 1px solid rgba(24, 32, 38, 0.08);\n background: rgba(255, 255, 255, 0.92);\n backdrop-filter: blur(12px);\n}\n.brand {\n font-weight: 700;\n letter-spacing: 0.01em;\n}\n.brand-mark {\n font-weight: 700;\n color: var(--topogram-muted-color);\n}\n.command-palette-button {\n background: var(--topogram-text-color);\n color: white;\n border: none;\n border-radius: var(--topogram-radius-pill);\n padding: var(--topogram-control-padding);\n font: inherit;\n cursor: pointer;\n}\n.app-footer {\n max-width: 72rem;\n margin: 0 auto;\n padding: 0 1.25rem 2rem;\n color: var(--topogram-muted-color);\n}\n.card {\n background: var(--topogram-surface-card);\n border-radius: var(--topogram-radius-card);\n padding: 1.25rem;\n box-shadow: 0 12px 30px rgba(24, 32, 38, 0.08);\n}\n.hero {\n display: grid;\n gap: var(--topogram-space-unit);\n}\n.grid {\n display: grid;\n gap: var(--topogram-space-unit);\n}\n.grid.two {\n grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));\n}\n.filters {\n display: grid;\n gap: 0.75rem;\n grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));\n margin: 1rem 0 1.25rem;\n}\nlabel {\n display: grid;\n gap: 0.35rem;\n font-size: 0.95rem;\n}\ninput,\ntextarea,\nbutton,\nselect {\n font: inherit;\n}\ninput,\ntextarea,\nselect {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #c9d4e2;\n border-radius: var(--topogram-radius-control);\n padding: var(--topogram-control-padding);\n background: white;\n}\ntextarea {\n min-height: 8rem;\n resize: vertical;\n}\nbutton,\n.button-link {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.35rem;\n border: none;\n border-radius: var(--topogram-radius-pill);\n padding: var(--topogram-control-padding);\n background: var(--topogram-action-primary-background);\n color: var(--topogram-action-primary-color);\n font-weight: 600;\n cursor: pointer;\n}\nbutton:focus-visible,\n.button-link:focus-visible,\na:focus-visible,\ninput:focus-visible,\ntextarea:focus-visible,\nselect:focus-visible {\n outline: var(--topogram-focus-outline);\n outline-offset: 2px;\n}\n.button-link.secondary {\n background: #e9eef6;\n color: var(--topogram-text-color);\n}\n.button-row {\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n align-items: center;\n}\n.stack {\n display: grid;\n gap: var(--topogram-space-unit);\n}\n\n.resource-list {\n list-style: none;\n padding: 0;\n margin: 1rem 0 0;\n display: grid;\n gap: 0.75rem;\n}\n\n.resource-list li {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: var(--topogram-space-unit);\n padding: 1rem;\n border: 1px solid #e0e8f1;\n border-radius: var(--topogram-radius-card);\n background: var(--topogram-surface-subtle);\n}\n.table-wrap {\n margin-top: 1rem;\n overflow-x: auto;\n border: 1px solid var(--topogram-border-color);\n border-radius: var(--topogram-radius-card);\n background: white;\n}\n.resource-table {\n width: 100%;\n border-collapse: collapse;\n min-width: 42rem;\n}\n.resource-table th,\n.resource-table td {\n padding: 0.85rem 1rem;\n text-align: left;\n border-bottom: 1px solid #e7edf5;\n vertical-align: top;\n}\n.resource-table th {\n font-size: 0.85rem;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: #516173;\n background: #f8fbff;\n}\n.resource-table tbody tr:hover {\n background: #fbfdff;\n}\n.data-grid {\n min-width: 64rem;\n font-size: 0.95rem;\n}\n.data-grid thead th {\n position: sticky;\n top: 0;\n z-index: 1;\n background: #eef5ff;\n}\n.data-grid-shell {\n box-shadow: inset 0 0 0 1px rgba(15, 92, 192, 0.04);\n}\n.cell-stack,\n.resource-meta,\n.definition-list {\n display: grid;\n gap: 0.5rem;\n}\n.cell-secondary {\n color: var(--topogram-muted-color);\n font-size: 0.92rem;\n}\n.definition-list {\n grid-template-columns: minmax(8rem, 12rem) 1fr;\n align-items: start;\n}\n.definition-list dt {\n font-weight: 600;\n color: #516173;\n}\n.definition-list dd {\n margin: 0;\n}\n.badge {\n display: inline-flex;\n align-items: center;\n padding: 0.25rem 0.6rem;\n border-radius: var(--topogram-radius-pill);\n background: #eef4ff;\n color: var(--topogram-action-primary-background);\n font-size: 0.85rem;\n font-weight: 600;\n}\n.muted {\n color: var(--topogram-muted-color);\n}\n.empty-state {\n padding: 1rem 0;\n}\n.widget-card {\n border: 1px solid var(--topogram-border-color);\n border-radius: var(--topogram-radius-card);\n background: var(--topogram-surface-subtle);\n padding: 1rem;\n margin-top: 1rem;\n}\n.widget-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--topogram-space-unit);\n flex-wrap: wrap;\n}\n.widget-eyebrow {\n margin: 0 0 0.25rem;\n color: var(--topogram-muted-color);\n font-size: 0.75rem;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n.widget-card h2,\n.widget-card h3 {\n margin: 0;\n}\n.widget-table-wrap {\n margin-top: 1rem;\n}\n.summary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));\n gap: 0.75rem;\n}\n.summary-grid div,\n.board-column {\n border: 1px solid #e0e8f1;\n border-radius: var(--topogram-radius-control);\n background: white;\n padding: 0.85rem;\n}\n.summary-grid strong {\n display: block;\n font-size: 1.5rem;\n}\n.summary-grid span,\n.calendar-list span {\n color: var(--topogram-muted-color);\n font-size: 0.9rem;\n}\n.board-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));\n gap: 0.75rem;\n margin-top: 1rem;\n}\n.board-card,\n.calendar-card {\n display: grid;\n gap: 0.25rem;\n border: 1px solid #e0e8f1;\n border-radius: var(--topogram-radius-control);\n background: #f8fbff;\n padding: 0.75rem;\n}\n.calendar-list {\n display: grid;\n gap: 0.75rem;\n margin-top: 1rem;\n}\nsmall.route-hint {\n display: block;\n color: var(--topogram-muted-color);\n margin-top: 0.25rem;\n}\n@media (max-width: 900px) {\n .app-workspace {\n grid-template-columns: 1fr;\n }\n .app-sidebar {\n position: static;\n min-height: auto;\n border-right: none;\n border-bottom: 1px solid rgba(24, 32, 38, 0.08);\n }\n}\n@media (max-width: 640px) {\n .definition-list {\n grid-template-columns: 1fr;\n }\n .resource-list li {\n flex-direction: column;\n }\n .resource-table {\n min-width: 36rem;\n }\n .app-nav {\n flex-wrap: wrap;\n }\n}\n";
379
379
  const navMarkup = navLinks.map((link) => ` <a href="${link.route}">${link.label}</a>`).join("\n");
380
380
  const shellLayout =
381
381
  shellMode === "split_view"
@@ -451,7 +451,7 @@ function buildSvelteKitScaffold(contract, apiContracts, options = {}) {
451
451
  const coverage = buildSvelteKitGenerationCoverage(contract, files, implementationScreenIds);
452
452
  assertGenerationCoverage(coverage);
453
453
  files["src/lib/topogram/generation-coverage.json"] = `${JSON.stringify(coverage, null, 2)}\n`;
454
- files["src/lib/topogram/ui-web-contract.json"] = `${JSON.stringify(contract, null, 2)}\n`;
454
+ files["src/lib/topogram/ui-surface-contract.json"] = `${JSON.stringify(contract, null, 2)}\n`;
455
455
  return files;
456
456
  }
457
457
 
@@ -1,9 +1,9 @@
1
1
  import { buildWebRealization } from "../../../realization/ui/index.js";
2
2
 
3
- export function generateUiWebContract(graph, options = {}) {
3
+ export function generateUiSurfaceContract(graph, options = {}) {
4
4
  if (!options.projectionId) {
5
5
  const output = {};
6
- for (const projection of (graph.byKind.projection || []).filter((entry) => entry.platform === "ui_web")) {
6
+ for (const projection of (graph.byKind.projection || []).filter((entry) => (entry.type || entry.platform) === "web_surface")) {
7
7
  output[projection.id] = buildWebRealization(graph, { ...options, projectionId: projection.id }).contract;
8
8
  }
9
9
  return output;
@@ -12,11 +12,11 @@ export function generateUiWebContract(graph, options = {}) {
12
12
  return buildWebRealization(graph, options).contract;
13
13
  }
14
14
 
15
- export function generateUiWebDebug(graph, options = {}) {
16
- const contracts = options.projectionId ? [generateUiWebContract(graph, options)] : Object.values(generateUiWebContract(graph, options));
15
+ export function generateUiSurfaceDebug(graph, options = {}) {
16
+ const contracts = options.projectionId ? [generateUiSurfaceContract(graph, options)] : Object.values(generateUiSurfaceContract(graph, options));
17
17
  const lines = [];
18
18
 
19
- lines.push("# UI Web Debug");
19
+ lines.push("# UI Surface Debug");
20
20
  lines.push("");
21
21
  lines.push(`Generated from \`${graph.root}\``);
22
22
  lines.push("");
@@ -24,8 +24,8 @@ export function generateUiWebDebug(graph, options = {}) {
24
24
  for (const contract of contracts) {
25
25
  lines.push(`## \`${contract.projection.id}\` - ${contract.projection.name}`);
26
26
  lines.push("");
27
- if (contract.sharedProjection?.id) {
28
- lines.push(`Shared projection: \`${contract.sharedProjection.id}\``);
27
+ if (contract.uiContract?.id) {
28
+ lines.push(`UI contract: \`${contract.uiContract.id}\``);
29
29
  }
30
30
  lines.push(
31
31
  `Generator defaults: ${
@@ -47,7 +47,7 @@ export function generateUiWebDebug(graph, options = {}) {
47
47
  lines.push(`### \`${screen.id}\``);
48
48
  lines.push("");
49
49
  lines.push(`Route: ${screen.route ? `\`${screen.route}\`` : "_none_"}`);
50
- lines.push(`Layout hints: ${Object.keys(screen.web).length > 0 ? Object.entries(screen.web).map(([key, value]) => `\`${key}=${value}\``).join(", ") : "_none_"}`);
50
+ lines.push(`Surface hints: ${Object.keys(screen.surfaceHints || {}).length > 0 ? Object.entries(screen.surfaceHints || {}).map(([key, value]) => `\`${key}=${value}\``).join(", ") : "_none_"}`);
51
51
  lines.push("");
52
52
  }
53
53
 
@@ -183,7 +183,7 @@ function buildVanillaGenerationCoverage(contract, files, routes) {
183
183
  page: route.file,
184
184
  rendered,
185
185
  renderer: rendered ? "generator" : "missing",
186
- component_usages: []
186
+ widget_usages: []
187
187
  };
188
188
  });
189
189
  return {
@@ -193,15 +193,15 @@ function buildVanillaGenerationCoverage(contract, files, routes) {
193
193
  projection: {
194
194
  id: contract.projection.id,
195
195
  name: contract.projection.name,
196
- platform: contract.projection.platform
196
+ type: contract.projection.type
197
197
  },
198
198
  summary: {
199
199
  routed_screens: screens.length,
200
200
  rendered_screens: screens.filter((screen) => screen.rendered).length,
201
201
  implementation_screens: 0,
202
202
  generator_screens: screens.filter((screen) => screen.renderer === "generator").length,
203
- component_usages: 0,
204
- rendered_component_usages: 0,
203
+ widget_usages: 0,
204
+ rendered_widget_usages: 0,
205
205
  diagnostics: diagnostics.length,
206
206
  errors: diagnostics.filter((diagnostic) => diagnostic.severity === "error").length,
207
207
  warnings: diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length
@@ -277,7 +277,7 @@ export function generateVanillaWebApp(graph, options = {}) {
277
277
  check: "node ./scripts/check.mjs"
278
278
  }
279
279
  }, null, 2)}\n`,
280
- "styles.css": renderStyles(contract.design),
280
+ "styles.css": renderStyles(contract.designTokens),
281
281
  "app.js": renderBrowserScript(),
282
282
  "scripts/build.mjs": renderBuildScript(),
283
283
  "scripts/check.mjs": renderCheckScript(),
@@ -300,6 +300,6 @@ export function generateVanillaWebApp(graph, options = {}) {
300
300
  const coverage = buildVanillaGenerationCoverage(contract, files, routes);
301
301
  assertGenerationCoverage(coverage);
302
302
  files["topogram/generation-coverage.json"] = `${JSON.stringify(coverage, null, 2)}\n`;
303
- files["topogram/ui-web-contract.json"] = `${JSON.stringify(contract, null, 2)}\n`;
303
+ files["topogram/ui-surface-contract.json"] = `${JSON.stringify(contract, null, 2)}\n`;
304
304
  return files;
305
305
  }