@topogram/cli 0.3.50 → 0.3.52

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 +61 -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 +92 -42
  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 +501 -409
  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
@@ -19,24 +19,26 @@ import { defaultProjectConfigForGraph, validateProjectConfig } from "../../proje
19
19
  /**
20
20
  * @typedef {Object} RuntimeComponent
21
21
  * @property {string} id
22
- * @property {"api"|"web"|"database"|"native"} type
22
+ * @property {"api_service"|"web_surface"|"ios_surface"|"android_surface"|"database"} kind
23
+ * @property {string} [type]
23
24
  * @property {RuntimeStatement} projection
24
25
  * @property {import("../../project-config.js").GeneratorBinding} generator
25
26
  * @property {number|null} [port]
26
- * @property {string} [api]
27
- * @property {string} [database]
27
+ * @property {string|null} [api]
28
+ * @property {string|null} [database]
28
29
  * @property {Record<string, string>} [env]
29
30
  * @property {RuntimeComponent|null} [apiComponent]
30
31
  * @property {RuntimeComponent|null} [databaseComponent]
31
32
  */
32
33
 
33
34
  /**
34
- * @typedef {import("../../project-config.js").RuntimeTopologyComponent} RuntimeTopologyComponent
35
+ * @typedef {import("../../project-config.js").RuntimeTopologyRuntime} RuntimeTopologyComponent
35
36
  */
36
37
 
37
38
  /**
38
39
  * @typedef {Object} RuntimeTopology
39
40
  * @property {import("../../project-config.js").ProjectConfig} config
41
+ * @property {RuntimeComponent[]} runtimes
40
42
  * @property {RuntimeComponent[]} components
41
43
  * @property {RuntimeComponent[]} apiComponents
42
44
  * @property {RuntimeComponent[]} webComponents
@@ -227,17 +229,17 @@ function apiProjectionCandidates(graph) {
227
229
  */
228
230
  function uiWebProjectionCandidates(graph) {
229
231
  return (graph.byKind.projection || []).filter(
230
- (projection) => projection.platform === "ui_web" && (projection.uiRoutes || []).length > 0
232
+ (projection) => projection.platform === "web_surface" && (projection.uiRoutes || []).length > 0
231
233
  );
232
234
  }
233
235
 
234
- const WEB_UI_FAMILY_PREFIX = "proj_ui_web__";
235
- const NATIVE_UI_FAMILY_PREFIX = "proj_ui_native__";
236
+ const WEB_UI_FAMILY_PREFIX = "proj_web_surface__";
237
+ const NATIVE_UI_FAMILY_PREFIX = "proj_ios_surface__";
236
238
 
237
239
  /** Prefer canonical ids when multiple shipped web stacks exist (deterministic, not lexicographic). */
238
- const DEFAULT_WEB_UI_STACK_ORDER = ["proj_ui_web__sveltekit", "proj_ui_web__react"];
240
+ const DEFAULT_WEB_UI_STACK_ORDER = ["proj_web_surface__sveltekit", "proj_web_surface__react"];
239
241
 
240
- const DEFAULT_NATIVE_UI_PLATFORM_ORDER = ["proj_ui_native__ios"];
242
+ const DEFAULT_NATIVE_UI_PLATFORM_ORDER = ["proj_ios_surface__swiftui"];
241
243
 
242
244
  /**
243
245
  * @param {ResolvedGraph} graph
@@ -245,12 +247,12 @@ const DEFAULT_NATIVE_UI_PLATFORM_ORDER = ["proj_ui_native__ios"];
245
247
  */
246
248
  function uiIosProjectionCandidates(graph) {
247
249
  return (graph.byKind.projection || []).filter(
248
- (projection) => projection.platform === "ui_ios" && (projection.uiRoutes || []).length > 0
250
+ (projection) => projection.platform === "ios_surface" && (projection.uiRoutes || []).length > 0
249
251
  );
250
252
  }
251
253
 
252
254
  /**
253
- * Prefer canonical native projections (`proj_ui_native__{platform}`); otherwise first routed ui_ios projection.
255
+ * Prefer canonical native projections (`proj_ios_surface__{stack}`); otherwise first routed iOS surface projection.
254
256
  *
255
257
  * @param {ResolvedGraph} graph
256
258
  * @returns {RuntimeStatement|undefined}
@@ -271,7 +273,7 @@ export function pickDefaultIosUiProjection(graph) {
271
273
  }
272
274
 
273
275
  /**
274
- * Prefer canonical shipped web projections (`proj_ui_web__{stack}`); otherwise first routed ui_web projection.
276
+ * Prefer canonical shipped web projections (`proj_web_surface__{stack}`); otherwise first routed web surface projection.
275
277
  *
276
278
  * @param {ResolvedGraph} graph
277
279
  * @returns {RuntimeStatement|undefined}
@@ -288,10 +290,6 @@ export function pickDefaultUiWebProjection(graph) {
288
290
  }
289
291
  return hierarchical.sort((a, b) => a.id.localeCompare(b.id))[0];
290
292
  }
291
- const legacySvelteKitProjection = candidates.find((projection) => projection.id === "proj_ui_web");
292
- if (legacySvelteKitProjection) {
293
- return legacySvelteKitProjection;
294
- }
295
293
  return candidates[0];
296
294
  }
297
295
 
@@ -302,7 +300,7 @@ export function pickDefaultUiWebProjection(graph) {
302
300
  */
303
301
  export function getDefaultEnvironmentProjections(graph, options = {}) {
304
302
  const topology = resolveRuntimeTopology(graph, options);
305
- const dbCandidates = graph.byKind.projection?.filter((projection) => ["db_postgres", "db_sqlite"].includes(projection.platform)) || [];
303
+ const dbCandidates = graph.byKind.projection?.filter((projection) => ["db_contract", "db_contract"].includes(projection.platform)) || [];
306
304
  const apiProjection = /** @type {RuntimeStatement|null} */ (topology.primaryApi?.projection ||
307
305
  (options.projectionId ? getProjection(graph, options.projectionId) : null) ||
308
306
  apiProjectionCandidates(graph).find((projection) => projection.id === "proj_api") ||
@@ -396,14 +394,14 @@ export function generateRuntimeApiContracts(graph) {
396
394
  }
397
395
 
398
396
  /**
399
- * @param {string} componentId
397
+ * @param {string} runtimeId
400
398
  * @param {EnvVarOptions} [options]
401
399
  * @returns {string}
402
400
  */
403
- function envVarPrefix(componentId, options = {}) {
404
- return options.primary || componentId === "db"
401
+ function envVarPrefix(runtimeId, options = {}) {
402
+ return options.primary || runtimeId === "db"
405
403
  ? ""
406
- : `${componentId.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_`;
404
+ : `${runtimeId.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_`;
407
405
  }
408
406
 
409
407
  /**
@@ -426,24 +424,27 @@ export function dbEnvVarsForComponent(component, options = {}) {
426
424
  * @param {import("../../project-config.js").ProjectConfig} config
427
425
  * @returns {RuntimeComponent[]}
428
426
  */
429
- function decorateComponents(graph, config) {
427
+ function decorateRuntimes(graph, config) {
430
428
  const byProjectionId = new Map((graph.byKind.projection || []).map((projection) => [projection.id, projection]));
431
- const rawComponents = config.topology?.components || [];
429
+ const rawRuntimes = config.topology?.runtimes || config.topology?.components || [];
432
430
  /** @type {RuntimeComponent[]} */
433
- const components = rawComponents.map((component) => ({
434
- ...component,
435
- projection: byProjectionId.get(component.projection) || {}
431
+ const runtimes = rawRuntimes.map((runtime) => ({
432
+ ...runtime,
433
+ kind: runtime.kind || runtime.type || null,
434
+ api: runtime.uses_api ?? runtime.api ?? null,
435
+ database: runtime.uses_database ?? runtime.database ?? null,
436
+ projection: byProjectionId.get(runtime.projection) || {}
436
437
  }));
437
- const byId = new Map(components.map((component) => [component.id, component]));
438
- for (const component of components) {
439
- if (component.type === "api" && component.database) {
440
- component.databaseComponent = byId.get(component.database) || null;
438
+ const byId = new Map(runtimes.map((runtime) => [runtime.id, runtime]));
439
+ for (const runtime of runtimes) {
440
+ if (runtime.kind === "api_service" && runtime.database) {
441
+ runtime.databaseComponent = byId.get(runtime.database) || null;
441
442
  }
442
- if (component.type === "web" && component.api) {
443
- component.apiComponent = byId.get(component.api) || null;
443
+ if (runtime.kind === "web_surface" && runtime.api) {
444
+ runtime.apiComponent = byId.get(runtime.api) || null;
444
445
  }
445
446
  }
446
- return components;
447
+ return runtimes;
447
448
  }
448
449
 
449
450
  /**
@@ -460,17 +461,18 @@ export function resolveRuntimeTopology(graph, options = {}) {
460
461
  if (!validation.ok) {
461
462
  throw new Error(validation.errors.map((error) => error.message).join("\n"));
462
463
  }
463
- const components = decorateComponents(graph, config);
464
- const apiComponents = components.filter((component) => component.type === "api");
465
- const webComponents = components.filter((component) => component.type === "web");
466
- const dbComponents = components.filter((component) => component.type === "database");
464
+ const runtimes = decorateRuntimes(graph, config);
465
+ const apiComponents = runtimes.filter((runtime) => runtime.kind === "api_service");
466
+ const webComponents = runtimes.filter((runtime) => runtime.kind === "web_surface");
467
+ const dbComponents = runtimes.filter((runtime) => runtime.kind === "database");
467
468
  const primaryApi = apiComponents[0] || null;
468
469
  const primaryWeb = webComponents[0] || null;
469
470
  const primaryDb = primaryApi?.databaseComponent || dbComponents[0] || null;
470
471
 
471
472
  return {
472
473
  config,
473
- components,
474
+ runtimes,
475
+ components: runtimes,
474
476
  apiComponents,
475
477
  webComponents,
476
478
  dbComponents,
@@ -26,11 +26,11 @@ function buildRuntimeSmokePlan(graph, options = {}) {
26
26
  ui: uiProjection.id
27
27
  },
28
28
  topology: {
29
- components: topology.components.map((component) => ({
30
- id: component.id,
31
- type: component.type,
32
- projection: component.projection.id,
33
- generator: component.generator
29
+ runtimes: topology.runtimes.map((runtime) => ({
30
+ id: runtime.id,
31
+ kind: runtime.kind,
32
+ projection: runtime.projection.id,
33
+ generator: runtime.generator
34
34
  }))
35
35
  },
36
36
  ...(verification ? { verification } : {}),
@@ -16,7 +16,7 @@ export function generateDbContractDebug(graph, options = {}) {
16
16
  for (const contract of contracts) {
17
17
  lines.push(`## \`${contract.projection.id}\` - ${contract.projection.name}`);
18
18
  lines.push("");
19
- lines.push(`Platform: \`${contract.projection.platform}\``);
19
+ lines.push(`Type: \`${contract.projection.type}\``);
20
20
  lines.push(`Profile: \`${contract.profile}\``);
21
21
  lines.push("");
22
22
 
@@ -80,15 +80,16 @@ function dbLifecyclePlan(graph, projection, options = {}) {
80
80
  }
81
81
 
82
82
  function renderEmptySnapshotForProjection(projection) {
83
- const engine = projection.platform === "db_sqlite" ? "sqlite" : "postgres";
83
+ const profile = dbProfileForProjection(projection);
84
+ const engine = profile.startsWith("sqlite") ? "sqlite" : "postgres";
84
85
  return {
85
86
  type: "db_schema_snapshot",
86
87
  projection: {
87
88
  id: projection.id,
88
89
  name: projection.name || projection.id,
89
- platform: projection.platform
90
+ type: projection.type || projection.platform
90
91
  },
91
- profile: dbProfileForProjection(projection),
92
+ profile,
92
93
  generatorDefaults: generatorDefaultsMap(projection),
93
94
  engine,
94
95
  enums: [],
@@ -97,7 +98,7 @@ function renderEmptySnapshotForProjection(projection) {
97
98
  }
98
99
 
99
100
  function renderDbLifecycleEnvExample(projection, plan) {
100
- const engine = projection.platform === "db_sqlite" ? "sqlite" : "postgres";
101
+ const engine = plan.engine || (dbProfileForProjection(projection).startsWith("sqlite") ? "sqlite" : "postgres");
101
102
  const inputPath = "../../../../topogram";
102
103
  if (engine === "sqlite") {
103
104
  return `DATABASE_URL=file:./var/${projection.id}.sqlite\nTOPOGRAM_INPUT_PATH=${inputPath}\n`;
@@ -580,7 +581,7 @@ function generateDbLifecycleBundle(graph, projection, options = {}) {
580
581
 
581
582
  if (plan.bundle.prismaSchema) {
582
583
  files[plan.bundle.prismaSchema] =
583
- projection.platform === "db_sqlite"
584
+ plan.engine === "sqlite"
584
585
  ? generateSqlitePrismaSchema(graph, { projectionId: projection.id })
585
586
  : generatePostgresPrismaSchema(graph, { projectionId: projection.id });
586
587
  }
@@ -50,8 +50,9 @@ function drizzleColumnBuilder(column, relation, targetTableVar) {
50
50
  export function generatePostgresDrizzleSchema(graph, options = {}) {
51
51
  resolvePostgresCapabilities(options.profileId);
52
52
  const projection = getProjection(graph, options.projectionId);
53
- if (projection.platform !== "db_postgres") {
54
- throw new Error(`Drizzle schema generation currently supports db_postgres projections only, found '${projection.platform}'`);
53
+ const projectionType = projection.type || projection.platform;
54
+ if (projectionType !== "db_contract") {
55
+ throw new Error(`Drizzle schema generation currently supports db_contract projections only, found '${projectionType}'`);
55
56
  }
56
57
 
57
58
  const contract = buildDbProjectionContract(graph, projection);
@@ -40,8 +40,9 @@ function prismaDefaultForColumn(column, byId) {
40
40
  export function generatePostgresPrismaSchema(graph, options = {}) {
41
41
  resolvePostgresCapabilities(options.profileId);
42
42
  const projection = getProjection(graph, options.projectionId);
43
- if (projection.platform !== "db_postgres") {
44
- throw new Error(`Prisma schema generation currently supports db_postgres projections only, found '${projection.platform}'`);
43
+ const projectionType = projection.type || projection.platform;
44
+ if (projectionType !== "db_contract") {
45
+ throw new Error(`Prisma schema generation currently supports db_contract projections only, found '${projectionType}'`);
45
46
  }
46
47
 
47
48
  const byId = indexGraphStatements(graph);
@@ -42,6 +42,7 @@ export function indexGraphStatements(graph) {
42
42
  export function dbProjectionCandidates(graph) {
43
43
  return (graph.byKind.projection || []).filter(
44
44
  (projection) =>
45
+ (projection.type || projection.platform) === "db_contract" ||
45
46
  projection.platform?.startsWith("db_") ||
46
47
  (projection.dbTables || []).length > 0 ||
47
48
  (projection.dbColumns || []).length > 0 ||
@@ -66,7 +67,7 @@ export function generatorDefaultsMap(projection) {
66
67
 
67
68
  export function dbProfileForProjection(projection) {
68
69
  const defaults = generatorDefaultsMap(projection);
69
- return defaults.profile || (projection.platform === "db_sqlite" ? "sqlite_sql" : "postgres_sql");
70
+ return defaults.profile || "postgres_sql";
70
71
  }
71
72
 
72
73
  function mergeDbKeys(entity, projection) {
@@ -277,7 +278,7 @@ export function buildDbProjectionContract(graph, projection) {
277
278
  projection: {
278
279
  id: projection.id,
279
280
  name: projection.name || projection.id,
280
- platform: projection.platform
281
+ type: projection.type || projection.platform
281
282
  },
282
283
  profile: dbProfileForProjection(projection),
283
284
  generatorDefaults: generatorDefaultsMap(projection),
@@ -11,7 +11,7 @@ import {
11
11
  } from "./shared.js";
12
12
 
13
13
  export function normalizeDbSchemaSnapshot(contract, byId = null) {
14
- const engine = contract.projection.platform === "db_sqlite" ? "sqlite" : "postgres";
14
+ const engine = contract.profile?.startsWith("sqlite") ? "sqlite" : "postgres";
15
15
  const tables = [...contract.tables]
16
16
  .map((table) => ({
17
17
  table: table.table,
@@ -45,8 +45,9 @@ function prismaDefaultForColumn(column) {
45
45
  export function generateSqlitePrismaSchema(graph, options = {}) {
46
46
  resolveSqliteCapabilities(options.profileId);
47
47
  const projection = getProjection(graph, options.projectionId);
48
- if (projection.platform !== "db_sqlite") {
49
- throw new Error(`Prisma schema generation currently supports db_sqlite projections only, found '${projection.platform}'`);
48
+ const projectionType = projection.type || projection.platform;
49
+ if (projectionType !== "db_contract") {
50
+ throw new Error(`Prisma schema generation currently supports db_contract projections only, found '${projectionType}'`);
50
51
  }
51
52
 
52
53
  const byId = indexGraphStatements(graph);
@@ -8,13 +8,13 @@ import { pickDefaultIosUiProjection, pickDefaultUiWebProjection } from "../../ru
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
9
 
10
10
  /**
11
- * Emits a SwiftPM iOS SwiftUI app driven by the routed UI contract (default: `proj_ui_native__ios` when present, else first `proj_ui_web__*` projection).
11
+ * Emits a SwiftPM iOS SwiftUI app driven by the routed UI contract (default: `proj_ios_surface__swiftui` when present, else first `proj_web_surface__*` projection).
12
12
  */
13
13
  export function generateSwiftUiApp(graph, options = {}) {
14
14
  const fallbackId =
15
15
  pickDefaultIosUiProjection(graph)?.id ||
16
16
  pickDefaultUiWebProjection(graph)?.id ||
17
- "proj_ui_web__sveltekit";
17
+ "proj_web_surface__sveltekit";
18
18
  const projectionId = options.projectionId || fallbackId;
19
19
  const realization = buildWebRealization(graph, { projectionId });
20
20
  const apiContracts = realization.apiContracts;
@@ -31,7 +31,7 @@ export function generateSwiftUiApp(graph, options = {}) {
31
31
 
32
32
  files["Package.swift"] = fs.readFileSync(path.join(__dirname, "swiftui-templates", "Package.swift.txt"), "utf8");
33
33
  files["README.md"] = fs.readFileSync(path.join(__dirname, "swiftui-templates", "README.generated.md"), "utf8");
34
- files["Sources/TodoSwiftUIApp/Resources/ui-web-contract.json"] = contractJson;
34
+ files["Sources/TodoSwiftUIApp/Resources/ui-surface-contract.json"] = contractJson;
35
35
  files["Sources/TodoSwiftUIApp/Resources/api-contracts.json"] = apiJson;
36
36
 
37
37
  return files;
@@ -13,7 +13,7 @@ let package = Package(
13
13
  path: "Sources/TodoSwiftUIApp",
14
14
  resources: [
15
15
  .copy("Resources/api-contracts.json"),
16
- .copy("Resources/ui-web-contract.json")
16
+ .copy("Resources/ui-surface-contract.json")
17
17
  ]
18
18
  )
19
19
  ]
@@ -1,10 +1,10 @@
1
1
  # Todo SwiftUI (generated)
2
2
 
3
- Apple SwiftUI client generated from the same **`buildWebRealization`** routed UI contract as the web stacks. Prefer the **`proj_ui_native__ios`** projection when present; otherwise the generator falls back to a **`proj_ui_web__*`** projection (often **`proj_ui_web__sveltekit`**).
3
+ Apple SwiftUI client generated from the same **`buildWebRealization`** routed UI contract as the web stacks. Prefer the **`proj_ios_surface__swiftui`** projection when present; otherwise the generator falls back to a **`proj_web_surface__*`** projection (often **`proj_web_surface__sveltekit`**).
4
4
 
5
5
  ## Bundle inputs
6
6
 
7
- - **`Resources/ui-web-contract.json`** — same shape as `apps/web/src/lib/topogram/ui-web-contract.json`
7
+ - **`Resources/ui-surface-contract.json`** — same shape as `apps/web/src/lib/topogram/ui-surface-contract.json`
8
8
  - **`Resources/api-contracts.json`** — same shape as `apps/web/src/lib/topogram/api-contracts.json`
9
9
 
10
10
  ## Run
@@ -22,5 +22,5 @@ Configure the API base URL and demo auth token via scheme environment variables
22
22
  From `engine/`:
23
23
 
24
24
  ```bash
25
- topogram ./topogram --generate swiftui-app --projection proj_ui_native__ios --write --out-dir ./app/ios-swiftui
25
+ topogram ./topogram --generate swiftui-app --projection proj_ios_surface__swiftui --write --out-dir ./app/ios-swiftui
26
26
  ```
@@ -1,6 +1,6 @@
1
1
  import SwiftUI
2
2
 
3
- /// Holds decoded ui-web-contract.json for runtime-driven navigation (parity with web bundle).
3
+ /// Holds decoded ui-surface-contract.json for runtime-driven navigation (parity with web bundle).
4
4
  public final class TodoUiContract: ObservableObject {
5
5
  public let raw: [String: Any]
6
6
 
@@ -12,9 +12,9 @@ public final class TodoUiContract: ObservableObject {
12
12
  }
13
13
 
14
14
  public static func loadBundled() throws -> Data {
15
- guard let url = Bundle.module.url(forResource: "ui-web-contract", withExtension: "json"),
15
+ guard let url = Bundle.module.url(forResource: "ui-surface-contract", withExtension: "json"),
16
16
  let data = try? Data(contentsOf: url) else {
17
- throw NSError(domain: "TodoUiContract", code: 2, userInfo: [NSLocalizedDescriptionKey: "Missing ui-web-contract.json"])
17
+ throw NSError(domain: "TodoUiContract", code: 2, userInfo: [NSLocalizedDescriptionKey: "Missing ui-surface-contract.json"])
18
18
  }
19
19
  return data
20
20
  }
@@ -155,8 +155,9 @@ export function generatePersistenceScaffold(graph, options = {}) {
155
155
  const drizzleRepositoryClassName = repositoryReference.drizzleRepositoryClassName;
156
156
  const drizzleHint = repositoryReference.drizzleHint;
157
157
  const projection = getProjection(graph, options.projectionId);
158
- if (!["db_postgres", "db_sqlite"].includes(projection.platform)) {
159
- throw new Error(`Persistence scaffold generation currently supports db_postgres and db_sqlite projections only, found '${projection.platform}'`);
158
+ const projectionType = projection.type || projection.platform;
159
+ if (projectionType !== "db_contract") {
160
+ throw new Error(`Persistence scaffold generation currently supports db_contract projections only, found '${projectionType}'`);
160
161
  }
161
162
 
162
163
  const byId = indexStatements(graph);
@@ -11,7 +11,7 @@ function indexStatements(graph) {
11
11
  }
12
12
 
13
13
  function apiProjectionCandidates(graph) {
14
- return (graph.byKind.projection || []).filter((projection) => (projection.http || []).length > 0);
14
+ return (graph.byKind.projection || []).filter((projection) => (projection.http || projection.endpoints || []).length > 0);
15
15
  }
16
16
 
17
17
  function repositoryMethodName(capabilityId) {
@@ -30,7 +30,7 @@ function buildServerContract(graph, projection) {
30
30
  projection: {
31
31
  id: projection.id,
32
32
  name: projection.name || projection.id,
33
- platform: projection.platform
33
+ type: projection.type || projection.platform
34
34
  },
35
35
  routes: realizedCapabilities.map((capability) => {
36
36
  const apiContract = generateApiContractGraph(graph, { capabilityId: capability.id });
@@ -50,8 +50,8 @@ function buildServerContract(graph, projection) {
50
50
  preconditions: apiContract.endpoint.preconditions || [],
51
51
  idempotency: apiContract.endpoint.idempotency || [],
52
52
  cache: apiContract.endpoint.cache || [],
53
- async: apiContract.endpoint.async || [],
54
- status: apiContract.endpoint.status || [],
53
+ asyncJobs: apiContract.endpoint.async || [],
54
+ asyncStatus: apiContract.endpoint.status || [],
55
55
  download: apiContract.endpoint.download || []
56
56
  }
57
57
  };
@@ -1,8 +1,8 @@
1
1
  export const APP_TARGETS = new Set([
2
2
  "ui-contract-graph",
3
3
  "ui-contract-debug",
4
- "ui-web-contract",
5
- "ui-web-debug",
4
+ "ui-surface-contract",
5
+ "ui-surface-debug",
6
6
  "sveltekit-app",
7
7
  "server-contract",
8
8
  "persistence-scaffold",
@@ -262,7 +262,7 @@ function requiredDesignMarkers(design) {
262
262
  * @returns {{ coverage: any, diagnostics: any[] }}
263
263
  */
264
264
  export function buildDesignIntentCoverage(contract, files, cssPath) {
265
- const design = normalizeDesignIntent(contract?.design);
265
+ const design = normalizeDesignIntent(contract?.designTokens);
266
266
  const css = files[cssPath] || "";
267
267
  const markers = requiredDesignMarkers(design);
268
268
  const mapped = markers.filter((item) => css.includes(item.marker));
@@ -9,9 +9,9 @@ import { generateReactApp } from "./react.js";
9
9
  import { generateSvelteKitApp } from "./sveltekit.js";
10
10
  import { generateVanillaWebApp } from "./vanilla.js";
11
11
  import {
12
- generateUiWebContract,
13
- generateUiWebDebug
14
- } from "./ui-web-contract.js";
12
+ generateUiSurfaceContract,
13
+ generateUiSurfaceDebug
14
+ } from "./ui-surface-contract.js";
15
15
 
16
16
  export function generateWebApp(graph, options = {}) {
17
17
  const projection = getProjection(graph, options.projectionId);
@@ -39,11 +39,11 @@ export function generateWebTarget(target, graph, options = {}) {
39
39
  if (target === "ui-contract-debug") {
40
40
  return generateUiContractDebug(graph, options);
41
41
  }
42
- if (target === "ui-web-contract") {
43
- return generateUiWebContract(graph, options);
42
+ if (target === "ui-surface-contract") {
43
+ return generateUiSurfaceContract(graph, options);
44
44
  }
45
- if (target === "ui-web-debug") {
46
- return generateUiWebDebug(graph, options);
45
+ if (target === "ui-surface-debug") {
46
+ return generateUiSurfaceDebug(graph, options);
47
47
  }
48
48
  if (target === "sveltekit-app") {
49
49
  return generateWebApp(graph, options);