@topogram/cli 0.3.34

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 (257) hide show
  1. package/ARCHITECTURE.md +67 -0
  2. package/CHANGELOG.md +240 -0
  3. package/README.md +223 -0
  4. package/package.json +51 -0
  5. package/src/adoption/index.js +3 -0
  6. package/src/adoption/plan.js +702 -0
  7. package/src/adoption/reporting.js +464 -0
  8. package/src/adoption/review-groups.js +313 -0
  9. package/src/agent-ops/query-builders.js +5012 -0
  10. package/src/archive/archive.js +141 -0
  11. package/src/archive/compact.js +26 -0
  12. package/src/archive/jsonl.js +70 -0
  13. package/src/archive/resolver-bridge.js +82 -0
  14. package/src/archive/schema.js +87 -0
  15. package/src/archive/unarchive.js +108 -0
  16. package/src/catalog.js +752 -0
  17. package/src/cli/catalog-alias.js +166 -0
  18. package/src/cli.js +9738 -0
  19. package/src/component-behavior.js +173 -0
  20. package/src/example-implementation.js +91 -0
  21. package/src/format.js +19 -0
  22. package/src/generator/adapters.d.ts +4 -0
  23. package/src/generator/adapters.js +325 -0
  24. package/src/generator/api.d.ts +1 -0
  25. package/src/generator/api.js +1196 -0
  26. package/src/generator/check.js +355 -0
  27. package/src/generator/component-conformance.js +767 -0
  28. package/src/generator/components.js +39 -0
  29. package/src/generator/context/bundle.js +291 -0
  30. package/src/generator/context/diff.js +256 -0
  31. package/src/generator/context/digest.js +182 -0
  32. package/src/generator/context/domain-coverage.js +94 -0
  33. package/src/generator/context/domain-page.js +137 -0
  34. package/src/generator/context/index.js +42 -0
  35. package/src/generator/context/report.js +121 -0
  36. package/src/generator/context/shared.js +1397 -0
  37. package/src/generator/context/slice.js +703 -0
  38. package/src/generator/context/task-mode.js +466 -0
  39. package/src/generator/docs.js +327 -0
  40. package/src/generator/index.js +161 -0
  41. package/src/generator/native/parity-bundle.js +311 -0
  42. package/src/generator/output.js +300 -0
  43. package/src/generator/registry.js +482 -0
  44. package/src/generator/runtime/app-bundle.js +456 -0
  45. package/src/generator/runtime/bundle-shared.js +166 -0
  46. package/src/generator/runtime/compile-check.js +163 -0
  47. package/src/generator/runtime/deployment.js +287 -0
  48. package/src/generator/runtime/environment.js +635 -0
  49. package/src/generator/runtime/index.js +32 -0
  50. package/src/generator/runtime/runtime-check.js +554 -0
  51. package/src/generator/runtime/shared.js +515 -0
  52. package/src/generator/runtime/smoke.js +219 -0
  53. package/src/generator/schema.js +204 -0
  54. package/src/generator/sdlc/board.js +66 -0
  55. package/src/generator/sdlc/doc-page.js +53 -0
  56. package/src/generator/sdlc/index.js +23 -0
  57. package/src/generator/sdlc/release-notes.js +62 -0
  58. package/src/generator/sdlc/traceability-matrix.js +65 -0
  59. package/src/generator/shared.js +29 -0
  60. package/src/generator/surfaces/contracts.js +146 -0
  61. package/src/generator/surfaces/databases/contract.js +40 -0
  62. package/src/generator/surfaces/databases/index.js +84 -0
  63. package/src/generator/surfaces/databases/lifecycle-shared.d.ts +1 -0
  64. package/src/generator/surfaces/databases/lifecycle-shared.js +612 -0
  65. package/src/generator/surfaces/databases/migration-plan.js +281 -0
  66. package/src/generator/surfaces/databases/postgres/capabilities.js +14 -0
  67. package/src/generator/surfaces/databases/postgres/drizzle.js +99 -0
  68. package/src/generator/surfaces/databases/postgres/index.js +9 -0
  69. package/src/generator/surfaces/databases/postgres/lifecycle.js +16 -0
  70. package/src/generator/surfaces/databases/postgres/prisma.js +159 -0
  71. package/src/generator/surfaces/databases/postgres/sql-migration.js +102 -0
  72. package/src/generator/surfaces/databases/postgres/sql-schema.js +34 -0
  73. package/src/generator/surfaces/databases/shared.d.ts +1 -0
  74. package/src/generator/surfaces/databases/shared.js +350 -0
  75. package/src/generator/surfaces/databases/snapshot.js +96 -0
  76. package/src/generator/surfaces/databases/sqlite/capabilities.js +14 -0
  77. package/src/generator/surfaces/databases/sqlite/index.js +8 -0
  78. package/src/generator/surfaces/databases/sqlite/lifecycle.js +16 -0
  79. package/src/generator/surfaces/databases/sqlite/prisma.js +143 -0
  80. package/src/generator/surfaces/databases/sqlite/sql-migration.js +65 -0
  81. package/src/generator/surfaces/databases/sqlite/sql-schema.js +27 -0
  82. package/src/generator/surfaces/index.js +25 -0
  83. package/src/generator/surfaces/native/swiftui-app.js +38 -0
  84. package/src/generator/surfaces/native/swiftui-templates/Package.swift.txt +20 -0
  85. package/src/generator/surfaces/native/swiftui-templates/README.generated.md +26 -0
  86. package/src/generator/surfaces/native/swiftui-templates/runtime/DynamicScreens.swift +682 -0
  87. package/src/generator/surfaces/native/swiftui-templates/runtime/TodoAPIClient.swift +156 -0
  88. package/src/generator/surfaces/native/swiftui-templates/runtime/TodoSwiftUIApp.swift +44 -0
  89. package/src/generator/surfaces/native/swiftui-templates/runtime/Visibility.swift +183 -0
  90. package/src/generator/surfaces/services/express.d.ts +1 -0
  91. package/src/generator/surfaces/services/express.js +766 -0
  92. package/src/generator/surfaces/services/hono.d.ts +1 -0
  93. package/src/generator/surfaces/services/hono.js +204 -0
  94. package/src/generator/surfaces/services/index.js +42 -0
  95. package/src/generator/surfaces/services/persistence-wiring.js +240 -0
  96. package/src/generator/surfaces/services/runtime-helpers.js +631 -0
  97. package/src/generator/surfaces/services/server-contract.js +80 -0
  98. package/src/generator/surfaces/services/stateless.d.ts +1 -0
  99. package/src/generator/surfaces/services/stateless.js +97 -0
  100. package/src/generator/surfaces/shared.js +64 -0
  101. package/src/generator/surfaces/web/api-client.js +1 -0
  102. package/src/generator/surfaces/web/forms.js +1 -0
  103. package/src/generator/surfaces/web/index.d.ts +2 -0
  104. package/src/generator/surfaces/web/index.js +53 -0
  105. package/src/generator/surfaces/web/react-components.js +248 -0
  106. package/src/generator/surfaces/web/react.js +538 -0
  107. package/src/generator/surfaces/web/routes.js +1 -0
  108. package/src/generator/surfaces/web/screens.js +1 -0
  109. package/src/generator/surfaces/web/shared.js +369 -0
  110. package/src/generator/surfaces/web/sveltekit-actions.js +28 -0
  111. package/src/generator/surfaces/web/sveltekit-components.js +234 -0
  112. package/src/generator/surfaces/web/sveltekit.js +426 -0
  113. package/src/generator/surfaces/web/ui-web-contract.js +65 -0
  114. package/src/generator/surfaces/web/vanilla.js +239 -0
  115. package/src/generator/verification.js +84 -0
  116. package/src/generator.js +1 -0
  117. package/src/import/core/context.js +52 -0
  118. package/src/import/core/contracts.js +23 -0
  119. package/src/import/core/registry.js +81 -0
  120. package/src/import/core/runner.js +646 -0
  121. package/src/import/core/shared.js +910 -0
  122. package/src/import/enrichers/auth-session.js +18 -0
  123. package/src/import/enrichers/django-rest.js +226 -0
  124. package/src/import/enrichers/doc-linking.js +20 -0
  125. package/src/import/enrichers/rails-controllers.js +246 -0
  126. package/src/import/enrichers/rails-models.js +130 -0
  127. package/src/import/enrichers/workflow-target-state.js +10 -0
  128. package/src/import/extractors/api/aspnet-core.js +304 -0
  129. package/src/import/extractors/api/django-routes.js +318 -0
  130. package/src/import/extractors/api/express.js +154 -0
  131. package/src/import/extractors/api/fastify.js +371 -0
  132. package/src/import/extractors/api/flutter-dio.js +135 -0
  133. package/src/import/extractors/api/generic-route-fallback.js +90 -0
  134. package/src/import/extractors/api/graphql-code-first.js +565 -0
  135. package/src/import/extractors/api/graphql-sdl.js +309 -0
  136. package/src/import/extractors/api/jaxrs.js +303 -0
  137. package/src/import/extractors/api/micronaut.js +213 -0
  138. package/src/import/extractors/api/next-route.js +50 -0
  139. package/src/import/extractors/api/next-server-action.js +51 -0
  140. package/src/import/extractors/api/nextauth.js +52 -0
  141. package/src/import/extractors/api/openapi-code.js +242 -0
  142. package/src/import/extractors/api/openapi.js +232 -0
  143. package/src/import/extractors/api/rails-routes.js +230 -0
  144. package/src/import/extractors/api/react-native-repository.js +128 -0
  145. package/src/import/extractors/api/retrofit.js +103 -0
  146. package/src/import/extractors/api/spring-web.js +372 -0
  147. package/src/import/extractors/api/swift-webapi.js +116 -0
  148. package/src/import/extractors/api/trpc.js +212 -0
  149. package/src/import/extractors/db/django-models.js +232 -0
  150. package/src/import/extractors/db/dotnet-models.js +93 -0
  151. package/src/import/extractors/db/drizzle.js +242 -0
  152. package/src/import/extractors/db/ef-core.js +221 -0
  153. package/src/import/extractors/db/flutter-entities.js +120 -0
  154. package/src/import/extractors/db/jpa.js +120 -0
  155. package/src/import/extractors/db/liquibase.js +180 -0
  156. package/src/import/extractors/db/mybatis-xml.js +145 -0
  157. package/src/import/extractors/db/prisma.js +185 -0
  158. package/src/import/extractors/db/rails-schema.js +175 -0
  159. package/src/import/extractors/db/react-native-entities.js +95 -0
  160. package/src/import/extractors/db/room.js +193 -0
  161. package/src/import/extractors/db/snapshot.js +112 -0
  162. package/src/import/extractors/db/sql.js +180 -0
  163. package/src/import/extractors/db/swiftdata.js +137 -0
  164. package/src/import/extractors/ui/android-compose.js +230 -0
  165. package/src/import/extractors/ui/backend-only.js +70 -0
  166. package/src/import/extractors/ui/blazor.js +227 -0
  167. package/src/import/extractors/ui/flutter-screens.js +152 -0
  168. package/src/import/extractors/ui/maui-xaml.js +135 -0
  169. package/src/import/extractors/ui/next-app-router.js +83 -0
  170. package/src/import/extractors/ui/next-pages-router.js +141 -0
  171. package/src/import/extractors/ui/razor-pages.js +181 -0
  172. package/src/import/extractors/ui/react-native-screens.js +166 -0
  173. package/src/import/extractors/ui/react-router.js +139 -0
  174. package/src/import/extractors/ui/sveltekit.js +123 -0
  175. package/src/import/extractors/ui/swiftui.js +193 -0
  176. package/src/import/extractors/ui/uikit.js +175 -0
  177. package/src/import/extractors/verification/generic.js +290 -0
  178. package/src/import/extractors/workflows/generic.js +137 -0
  179. package/src/import/index.js +7 -0
  180. package/src/import/provenance.js +158 -0
  181. package/src/new-project.js +2107 -0
  182. package/src/parser.js +439 -0
  183. package/src/policy/review-boundaries.js +165 -0
  184. package/src/project-config.js +535 -0
  185. package/src/proofs/backend-parity.js +19 -0
  186. package/src/proofs/contract-audit.js +220 -0
  187. package/src/proofs/ios-parity.js +7 -0
  188. package/src/proofs/issues-parity.js +10 -0
  189. package/src/proofs/web-parity.js +50 -0
  190. package/src/realization/api/build-api-realization.js +5 -0
  191. package/src/realization/api/index.js +1 -0
  192. package/src/realization/backend/build-backend-runtime-realization.js +82 -0
  193. package/src/realization/backend/index.d.ts +1 -0
  194. package/src/realization/backend/index.js +4 -0
  195. package/src/realization/db/build-db-realization.js +17 -0
  196. package/src/realization/db/index.js +3 -0
  197. package/src/realization/db/migration-plan.js +5 -0
  198. package/src/realization/db/snapshot.js +5 -0
  199. package/src/realization/ui/build-ui-shared-realization.js +305 -0
  200. package/src/realization/ui/build-web-realization.js +189 -0
  201. package/src/realization/ui/index.js +2 -0
  202. package/src/reconcile/docs.js +280 -0
  203. package/src/reconcile/index.js +3 -0
  204. package/src/reconcile/journeys.js +441 -0
  205. package/src/resolver/docs.js +1 -0
  206. package/src/resolver/enrich/acceptance-criterion.js +14 -0
  207. package/src/resolver/enrich/bug.js +12 -0
  208. package/src/resolver/enrich/component.js +2 -0
  209. package/src/resolver/enrich/index.js +1 -0
  210. package/src/resolver/enrich/pitch.js +18 -0
  211. package/src/resolver/enrich/requirement.js +20 -0
  212. package/src/resolver/enrich/task.js +16 -0
  213. package/src/resolver/expressions.js +1 -0
  214. package/src/resolver/index.js +2422 -0
  215. package/src/resolver/normalize.js +1 -0
  216. package/src/resolver.js +1 -0
  217. package/src/sdlc/adopt.js +65 -0
  218. package/src/sdlc/check.js +86 -0
  219. package/src/sdlc/dod/acceptance-criterion.js +22 -0
  220. package/src/sdlc/dod/bug.js +26 -0
  221. package/src/sdlc/dod/document.js +23 -0
  222. package/src/sdlc/dod/index.js +25 -0
  223. package/src/sdlc/dod/pitch.js +23 -0
  224. package/src/sdlc/dod/requirement.js +34 -0
  225. package/src/sdlc/dod/task.js +39 -0
  226. package/src/sdlc/explain.js +116 -0
  227. package/src/sdlc/history.js +80 -0
  228. package/src/sdlc/paths.js +11 -0
  229. package/src/sdlc/release.js +106 -0
  230. package/src/sdlc/scaffold.js +89 -0
  231. package/src/sdlc/status-filter.js +54 -0
  232. package/src/sdlc/transition.js +112 -0
  233. package/src/sdlc/transitions/acceptance-criterion.js +28 -0
  234. package/src/sdlc/transitions/bug.js +31 -0
  235. package/src/sdlc/transitions/document.js +29 -0
  236. package/src/sdlc/transitions/index.js +56 -0
  237. package/src/sdlc/transitions/pitch.js +34 -0
  238. package/src/sdlc/transitions/requirement.js +31 -0
  239. package/src/sdlc/transitions/task.js +34 -0
  240. package/src/template-trust.js +597 -0
  241. package/src/validator/expressions.js +1 -0
  242. package/src/validator/index.js +3424 -0
  243. package/src/validator/kinds.js +346 -0
  244. package/src/validator/per-kind/acceptance-criterion.js +91 -0
  245. package/src/validator/per-kind/bug.js +77 -0
  246. package/src/validator/per-kind/component.js +274 -0
  247. package/src/validator/per-kind/domain.js +205 -0
  248. package/src/validator/per-kind/pitch.js +101 -0
  249. package/src/validator/per-kind/requirement.js +75 -0
  250. package/src/validator/per-kind/task.js +96 -0
  251. package/src/validator/registry.js +1 -0
  252. package/src/validator/utils.js +12 -0
  253. package/src/validator.js +1 -0
  254. package/src/workflows.js +7597 -0
  255. package/src/workspace-docs.js +265 -0
  256. package/template-helpers/react.js +5 -0
  257. package/template-helpers/sveltekit.js +5 -0
@@ -0,0 +1,426 @@
1
+ import { buildWebRealization } from "../../../realization/ui/index.js";
2
+ import { lookupRouteSegment } from "../services/runtime-helpers.js";
3
+ import { getExampleImplementation } from "../../../example-implementation.js";
4
+ import { renderApiClientModule, renderLookupModule, renderVisibilityModule } from "./shared.js";
5
+ import { renderSvelteKitComponentRegion } from "./sveltekit-components.js";
6
+
7
+ function routePathToSvelteKitDirectory(routePath) {
8
+ if (!routePath || routePath === "/") {
9
+ return "src/routes";
10
+ }
11
+ const segments = routePath
12
+ .replace(/^\//, "")
13
+ .split("/")
14
+ .filter(Boolean)
15
+ .map((segment) => (segment.startsWith(":") ? `[${segment.slice(1)}]` : segment));
16
+ return `src/routes/${segments.join("/")}`;
17
+ }
18
+
19
+ function prettyScreenKind(kind) {
20
+ return kind ? kind.replace(/_/g, " ") : "screen";
21
+ }
22
+
23
+ function lookupDescriptor(lookup) {
24
+ if (!lookup?.entity?.id) {
25
+ return null;
26
+ }
27
+
28
+ return {
29
+ ...lookup,
30
+ route: `/lookups/${lookupRouteSegment(lookup.entity.id)}`
31
+ };
32
+ }
33
+
34
+ function screenRegions(screen) {
35
+ const names = new Set();
36
+ for (const region of screen?.regions || []) {
37
+ if (region?.name) names.add(region.name);
38
+ }
39
+ for (const usage of screen?.components || []) {
40
+ if (usage?.region) names.add(usage.region);
41
+ }
42
+ return [...names];
43
+ }
44
+
45
+ function sampleItemsForScreen(screen) {
46
+ const title = screen?.title || screen?.id || "Resource";
47
+ return [
48
+ {
49
+ id: "sample-active",
50
+ title: `${title} sample`,
51
+ name: `${title} sample`,
52
+ message: `${title} sample`,
53
+ description: "Generated from Topogram UI contract metadata.",
54
+ status: "active",
55
+ priority: "medium",
56
+ created_at: "2026-01-01",
57
+ due_at: "2026-01-15"
58
+ },
59
+ {
60
+ id: "sample-completed",
61
+ title: `${title} completed sample`,
62
+ name: `${title} completed sample`,
63
+ message: `${title} completed sample`,
64
+ description: "Second generated row for component rendering checks.",
65
+ status: "completed",
66
+ priority: "low",
67
+ created_at: "2026-01-02",
68
+ due_at: "2026-01-22"
69
+ }
70
+ ];
71
+ }
72
+
73
+ function isDynamicRoute(route) {
74
+ return String(route || "").split("/").some((segment) => segment.startsWith(":"));
75
+ }
76
+
77
+ function screenRoutePagePath(screen) {
78
+ return `${routePathToSvelteKitDirectory(screen.route)}/+page.svelte`;
79
+ }
80
+
81
+ function screenComponentUsages(screen) {
82
+ return Array.isArray(screen?.components) ? screen.components : [];
83
+ }
84
+
85
+ function buildGenericSvelteKitScreenFiles(screen, contract, useTypescript) {
86
+ const files = {};
87
+ const routeDir = routePathToSvelteKitDirectory(screen.route);
88
+ const sampleItems = sampleItemsForScreen(screen);
89
+ const loadCapabilityId = screen.loadCapability?.id || null;
90
+ const canLoadFromApi = loadCapabilityId && !isDynamicRoute(screen.route);
91
+ const renderedRegions = screenRegions(screen)
92
+ .map((region) => {
93
+ const rendered = renderSvelteKitComponentRegion(screen, region, {
94
+ componentContracts: contract.components,
95
+ itemsExpression: "data.result.items",
96
+ useTypescript
97
+ });
98
+ if (!rendered) return "";
99
+ return ` <section class="stack" data-topogram-region="${region}">
100
+ ${rendered}
101
+ </section>`;
102
+ })
103
+ .filter(Boolean)
104
+ .join("\n\n");
105
+ const defaultCollection = `<section class="card">
106
+ <h2>Sample rows</h2>
107
+ <ul class="resource-list">
108
+ {#each data.result.items as item}
109
+ <li>
110
+ <div class="resource-meta">
111
+ <strong>{item.title ?? item.name ?? item.message ?? item.id}</strong>
112
+ {#if item.description}<span class="muted">{item.description}</span>{/if}
113
+ </div>
114
+ <span class="badge">{item.status ?? "active"}</span>
115
+ </li>
116
+ {/each}
117
+ </ul>
118
+ </section>`;
119
+
120
+ if (canLoadFromApi) {
121
+ files[`${routeDir}/+page.ts`] = `import type { PageLoad } from "./$types";
122
+ import { requestCapability } from "$lib/api/client";
123
+
124
+ export const load: PageLoad = async ({ fetch }) => {
125
+ const result = await requestCapability(fetch, "${loadCapabilityId}");
126
+ const resultObject = result && typeof result === "object" && !Array.isArray(result) ? result : {};
127
+ return {
128
+ screen: ${JSON.stringify({ id: screen.id, title: screen.title, collection: screen.collection, web: screen.web }, null, 2)},
129
+ result: Array.isArray(result) ? { items: result } : { items: resultObject.items ?? [], ...resultObject }
130
+ };
131
+ };
132
+ `;
133
+ }
134
+
135
+ files[`${routeDir}/+page.svelte`] = `<script${useTypescript ? ' lang="ts"' : ""}>
136
+ ${canLoadFromApi ? "export let data;" : `const data = {
137
+ screen: ${JSON.stringify({ id: screen.id, title: screen.title, collection: screen.collection, web: screen.web }, null, 2)},
138
+ result: {
139
+ items: ${JSON.stringify(sampleItems, null, 2)}
140
+ }
141
+ };`}
142
+ </script>
143
+
144
+ <main>
145
+ <div class="stack">
146
+ <section class="card">
147
+ <div class="button-row" style="justify-content: space-between;">
148
+ <div>
149
+ <p class="muted">${screen.kind || "screen"}</p>
150
+ <h1>${screen.title || screen.id}</h1>
151
+ <p>This SvelteKit page was generated from <code>${screen.id}</code>.</p>
152
+ </div>
153
+ </div>
154
+ </section>
155
+
156
+ ${renderedRegions || ` ${defaultCollection}`}
157
+ </div>
158
+ </main>
159
+ `;
160
+ return files;
161
+ }
162
+
163
+ function buildSvelteKitGenerationCoverage(contract, files, implementationScreenIds) {
164
+ const diagnostics = [];
165
+ const screens = (contract.screens || [])
166
+ .filter((screen) => Boolean(screen.route) && screen.route !== "/")
167
+ .map((screen) => {
168
+ const pagePath = screenRoutePagePath(screen);
169
+ const contents = files[pagePath] || "";
170
+ const rendered = Boolean(contents);
171
+ const renderer = implementationScreenIds.has(screen.id)
172
+ ? "implementation"
173
+ : rendered
174
+ ? "generator"
175
+ : "missing";
176
+ if (!rendered) {
177
+ diagnostics.push({
178
+ code: "screen_route_not_rendered",
179
+ severity: "error",
180
+ screen: screen.id,
181
+ route: screen.route,
182
+ message: `Screen '${screen.id}' has route '${screen.route}' but no SvelteKit page was generated.`,
183
+ suggested_fix: "Check the SvelteKit generator contract-complete route emission for this screen."
184
+ });
185
+ }
186
+ const componentUsages = screenComponentUsages(screen).map((usage) => {
187
+ const componentId = usage.component?.id || null;
188
+ const marker = componentId ? `data-topogram-component="${componentId}"` : null;
189
+ const usageRendered = Boolean(marker && contents.includes(marker));
190
+ if (componentId && rendered && !usageRendered) {
191
+ diagnostics.push({
192
+ code: "component_usage_not_rendered",
193
+ severity: "warning",
194
+ screen: screen.id,
195
+ route: screen.route,
196
+ region: usage.region || null,
197
+ component: componentId,
198
+ message: `Screen '${screen.id}' uses component '${componentId}' but the generated SvelteKit page does not contain its component marker.`,
199
+ suggested_fix: "Render the component region with renderSvelteKitComponentRegion or add a supported component pattern."
200
+ });
201
+ }
202
+ return {
203
+ component: componentId,
204
+ region: usage.region || null,
205
+ rendered: usageRendered,
206
+ marker
207
+ };
208
+ });
209
+ return {
210
+ id: screen.id,
211
+ route: screen.route,
212
+ page: pagePath,
213
+ rendered,
214
+ renderer,
215
+ component_usages: componentUsages
216
+ };
217
+ });
218
+
219
+ return {
220
+ type: "generation_coverage",
221
+ surface: "web",
222
+ generator: "topogram/sveltekit",
223
+ projection: {
224
+ id: contract.projection.id,
225
+ name: contract.projection.name,
226
+ platform: contract.projection.platform
227
+ },
228
+ summary: {
229
+ routed_screens: screens.length,
230
+ rendered_screens: screens.filter((screen) => screen.rendered).length,
231
+ implementation_screens: screens.filter((screen) => screen.renderer === "implementation").length,
232
+ generator_screens: screens.filter((screen) => screen.renderer === "generator").length,
233
+ component_usages: screens.reduce((total, screen) => total + screen.component_usages.length, 0),
234
+ rendered_component_usages: screens.reduce(
235
+ (total, screen) => total + screen.component_usages.filter((usage) => usage.rendered).length,
236
+ 0
237
+ ),
238
+ diagnostics: diagnostics.length,
239
+ errors: diagnostics.filter((diagnostic) => diagnostic.severity === "error").length,
240
+ warnings: diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length
241
+ },
242
+ screens,
243
+ diagnostics
244
+ };
245
+ }
246
+
247
+ function resolveNavLinks(contract, webReference) {
248
+ const contractLinks = (contract.navigation?.items || [])
249
+ .filter((item) => item.visible && item.route && item.placement === "primary")
250
+ .sort((a, b) => String(a.order || "").localeCompare(String(b.order || "")) || a.label.localeCompare(b.label))
251
+ .map((item) => ({ label: item.label, route: item.route }));
252
+ if (contractLinks.length > 0) {
253
+ return contractLinks;
254
+ }
255
+ return Array.isArray(webReference.nav?.links) && webReference.nav.links.length > 0
256
+ ? webReference.nav.links
257
+ : [
258
+ { label: webReference.nav.browseLabel, route: webReference.nav.browseRoute },
259
+ { label: webReference.nav.createLabel, route: webReference.nav.createRoute }
260
+ ];
261
+ }
262
+
263
+ function buildSvelteVisibilityModule() {
264
+ return renderVisibilityModule("sveltekit");
265
+ }
266
+
267
+ function buildSvelteKitClientModule(webReference, defaultApiBaseUrl) {
268
+ return renderApiClientModule("sveltekit", { ...webReference, defaultApiBaseUrl }, { supportsDownload: true });
269
+ }
270
+
271
+ function buildSvelteKitLookupModule(defaultApiBaseUrl) {
272
+ return renderLookupModule("sveltekit", defaultApiBaseUrl);
273
+ }
274
+
275
+ function buildSvelteKitScaffold(contract, apiContracts, options = {}) {
276
+ const implementation = getExampleImplementation(null, options);
277
+ const webReference = implementation.web.reference;
278
+ const runtimeReference = implementation.runtime.reference;
279
+ const webScreenReference = implementation.web.screenReference;
280
+ const webRenderers = implementation.web.renderers;
281
+ const profile = contract.generatorDefaults.profile || "sveltekit";
282
+ const language = contract.generatorDefaults.language || "typescript";
283
+ const useTypescript = language === "typescript";
284
+ const files = {};
285
+
286
+ const brandName = contract.appShell?.brand || webReference.brandName;
287
+ const navLinks = resolveNavLinks(contract, webReference);
288
+ const footerEnabled = contract.appShell?.footer && contract.appShell.footer !== "none";
289
+ const shellMode = contract.appShell?.shell || "topbar";
290
+ const windowingMode = contract.appShell?.windowing || "single_window";
291
+ const navigationPatterns = (contract.navigation?.patterns || []).join(" ");
292
+ const hasCommandPalette = (contract.navigation?.patterns || []).includes("command_palette");
293
+ const homeDescription = webReference.home.heroDescriptionTemplate.replace("PROFILE", `\`${profile}\``);
294
+ const demoTaskEnvVar = webReference.home.demoPrimaryEnvVar;
295
+ const ownerEnvVar = webReference.createPrimary.defaultAssigneeEnvVar;
296
+ const projectEnvVar = webReference.createPrimary.defaultContainerEnvVar;
297
+ files["package.json"] = JSON.stringify(
298
+ {
299
+ name: contract.projection.id,
300
+ private: true,
301
+ version: "0.1.0",
302
+ type: "module",
303
+ scripts: {
304
+ dev: "vite dev",
305
+ build: "vite build",
306
+ preview: "vite preview",
307
+ check: "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json"
308
+ },
309
+ devDependencies: {
310
+ "@sveltejs/adapter-auto": "^3.0.0",
311
+ "@sveltejs/kit": "^2.0.0",
312
+ "@types/node": "^22.10.2",
313
+ "svelte-check": "^4.0.0",
314
+ svelte: "^5.0.0",
315
+ typescript: "^5.6.3",
316
+ vite: "^8.0.0"
317
+ }
318
+ },
319
+ null,
320
+ 2
321
+ ) + "\n";
322
+ files["svelte.config.js"] = `import adapter from "@sveltejs/adapter-auto";\n\nexport default {\n kit: {\n adapter: adapter()\n }\n};\n`;
323
+ files["tsconfig.json"] = JSON.stringify(
324
+ {
325
+ extends: "./.svelte-kit/tsconfig.json",
326
+ compilerOptions: {
327
+ allowJs: true,
328
+ checkJs: false,
329
+ esModuleInterop: true,
330
+ forceConsistentCasingInFileNames: true,
331
+ moduleResolution: "Bundler",
332
+ resolveJsonModule: true,
333
+ skipLibCheck: true,
334
+ sourceMap: true,
335
+ strict: true
336
+ }
337
+ },
338
+ null,
339
+ 2
340
+ ) + "\n";
341
+ files["vite.config.ts"] = `import { sveltekit } from "@sveltejs/kit/vite";\nimport { defineConfig } from "vite";\n\nexport default defineConfig({\n plugins: [sveltekit()]\n});\n`;
342
+ files["src/app.html"] =
343
+ "<!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";
344
+ files["src/app.css"] =
345
+ ":root {\n font-family: system-ui, sans-serif;\n color: #182026;\n background: linear-gradient(180deg, #f5f7fb 0%, #edf2f7 100%);\n}\nbody {\n margin: 0;\n}\na {\n color: #0f5cc0;\n text-decoration: none;\n}\na:hover {\n text-decoration: underline;\n}\nmain {\n max-width: 72rem;\n margin: 0 auto;\n padding: 2rem 1.25rem 4rem;\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: 1rem;\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: 1rem;\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: #607284;\n}\n.command-palette-button {\n background: #182026;\n color: white;\n border: none;\n border-radius: 999px;\n padding: 0.6rem 0.9rem;\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: #607284;\n}\n.card {\n background: white;\n border-radius: 16px;\n padding: 1.25rem;\n box-shadow: 0 12px 30px rgba(24, 32, 38, 0.08);\n}\n.hero {\n display: grid;\n gap: 1rem;\n}\n.grid {\n display: grid;\n gap: 1rem;\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: 12px;\n padding: 0.7rem 0.85rem;\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: 999px;\n padding: 0.7rem 1rem;\n background: #0f5cc0;\n color: white;\n font-weight: 600;\n cursor: pointer;\n}\n.button-link.secondary {\n background: #e9eef6;\n color: #182026;\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: 1rem;\n}\n.task-list,\n.resource-list {\n list-style: none;\n padding: 0;\n margin: 1rem 0 0;\n display: grid;\n gap: 0.75rem;\n}\n.task-list li,\n.resource-list li {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: 1rem;\n padding: 1rem;\n border: 1px solid #e0e8f1;\n border-radius: 14px;\n background: #fbfcfe;\n}\n.table-wrap {\n margin-top: 1rem;\n overflow-x: auto;\n border: 1px solid #d7e1ec;\n border-radius: 14px;\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.task-meta,\n.definition-list {\n display: grid;\n gap: 0.5rem;\n}\n.cell-secondary {\n color: #607284;\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: 999px;\n background: #eef4ff;\n color: #0f5cc0;\n font-size: 0.85rem;\n font-weight: 600;\n}\n.muted {\n color: #607284;\n}\n.empty-state {\n padding: 1rem 0;\n}\n.component-card {\n border: 1px solid #d7e1ec;\n border-radius: 14px;\n background: #fbfcfe;\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: 1rem;\n flex-wrap: wrap;\n}\n.component-eyebrow {\n margin: 0 0 0.25rem;\n color: #607284;\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: 12px;\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: #607284;\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: 10px;\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: #607284;\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 .task-list li,\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";
346
+ const navMarkup = navLinks.map((link) => ` <a href="${link.route}">${link.label}</a>`).join("\n");
347
+ const shellLayout =
348
+ shellMode === "split_view"
349
+ ? `<div class="app-workspace">\n <aside class="app-sidebar">\n <a class="brand" href="/">${brandName}</a>\n <nav class="app-nav-links">\n${navMarkup}\n </nav>\n${hasCommandPalette ? ` <button class="command-palette-button" type="button">Command Palette</button>\n` : ""} </aside>\n <div class="app-main-shell">\n <header class="app-nav compact">\n <div class="brand-mark">${brandName}</div>\n${hasCommandPalette ? ` <button class="command-palette-button" type="button">Command Palette</button>\n` : ""} </header>\n\n <slot />\n </div>\n</div>`
350
+ : shellMode === "bottom_tabs"
351
+ ? `<header class="app-nav">\n <a class="brand" href="/">${brandName}</a>\n${hasCommandPalette ? ` <button class="command-palette-button" type="button">Command Palette</button>\n` : ""}</header>\n\n<slot />\n\n<nav class="app-tabbar">\n${navMarkup}\n</nav>`
352
+ : `<header class="app-nav${shellMode === "menu_bar" ? " menu-bar" : ""}">\n <a class="brand" href="/">${brandName}</a>\n <nav class="app-nav-links">\n${navMarkup}\n </nav>\n${hasCommandPalette ? ` <button class="command-palette-button" type="button">Command Palette</button>\n` : ""}</header>\n\n<slot />`;
353
+ files["src/routes/+layout.svelte"] = `<script${useTypescript ? ' lang="ts"' : ""}>\n import "../app.css";\n</script>\n\n<div class="app-shell" data-shell="${shellMode}" data-windowing="${windowingMode}" data-navigation-patterns="${navigationPatterns}">\n ${shellLayout}\n${footerEnabled ? `\n <footer class="app-footer">\n <span>Generated from Topogram</span>\n </footer>` : ""}\n</div>\n`;
354
+ files["src/routes/+page.svelte"] = webRenderers.renderHomePage({
355
+ useTypescript,
356
+ demoPrimaryEnvVar: demoTaskEnvVar,
357
+ screens: contract.screens.map((screen) => ({
358
+ id: screen.id,
359
+ title: screen.title || screen.id,
360
+ route: screen.route,
361
+ navigable: Boolean(screen.route) && !screen.route.includes(":")
362
+ })),
363
+ projectionName: contract.projection.name,
364
+ homeDescription,
365
+ webReference
366
+ });
367
+ files["src/lib/topogram/api-contracts.json"] = `${JSON.stringify(apiContracts, null, 2)}\n`;
368
+ files["src/lib/auth/visibility.ts"] = buildSvelteVisibilityModule();
369
+ files["src/lib/api/client.ts"] = buildSvelteKitClientModule(webReference, `http://localhost:${runtimeReference?.ports?.server || 3000}`);
370
+ files["src/lib/api/lookups.ts"] = buildSvelteKitLookupModule(`http://localhost:${runtimeReference?.ports?.server || 3000}`);
371
+
372
+ for (const screen of contract.screens || []) {
373
+ if (!screen.route || screen.route === "/") continue;
374
+ Object.assign(files, buildGenericSvelteKitScreenFiles(screen, contract, useTypescript));
375
+ }
376
+
377
+ const taskList = contract.screens.find((screen) => screen.id === webScreenReference.listScreenId);
378
+ const taskDetail = contract.screens.find((screen) => screen.id === webScreenReference.detailScreenId);
379
+ const taskCreate = contract.screens.find((screen) => screen.id === webScreenReference.createScreenId);
380
+ const taskEdit = contract.screens.find((screen) => screen.id === webScreenReference.editScreenId);
381
+ const taskExports = webScreenReference.exportsScreenId
382
+ ? contract.screens.find((screen) => screen.id === webScreenReference.exportsScreenId)
383
+ : null;
384
+ const taskListLookups = Object.fromEntries((taskList?.lookups || []).map((lookup) => [lookup.field, lookupDescriptor(lookup)]));
385
+ const taskCreateLookups = Object.fromEntries((taskCreate?.lookups || []).map((lookup) => [lookup.field, lookupDescriptor(lookup)]));
386
+ const taskEditLookups = Object.fromEntries((taskEdit?.lookups || []).map((lookup) => [lookup.field, lookupDescriptor(lookup)]));
387
+ const routePageScreenIds = new Map(
388
+ (contract.screens || [])
389
+ .filter((screen) => screen.route && screen.route !== "/")
390
+ .map((screen) => [screenRoutePagePath(screen), screen.id])
391
+ );
392
+ const implementationScreenIds = new Set();
393
+
394
+ if (taskList?.route && taskDetail?.route && taskCreate?.route && taskEdit?.route) {
395
+ for (const [relativePath, contents] of Object.entries(webRenderers.renderRoutes({
396
+ useTypescript,
397
+ contract,
398
+ taskList,
399
+ taskDetail,
400
+ taskCreate,
401
+ taskEdit,
402
+ taskExports,
403
+ taskListLookups,
404
+ taskCreateLookups,
405
+ taskEditLookups,
406
+ projectEnvVar,
407
+ ownerEnvVar,
408
+ webReference,
409
+ prettyScreenKind
410
+ }))) {
411
+ const filePath = `src/routes/${relativePath}`;
412
+ files[filePath] = contents;
413
+ const screenId = routePageScreenIds.get(filePath);
414
+ if (screenId) implementationScreenIds.add(screenId);
415
+ }
416
+ }
417
+
418
+ files["src/lib/topogram/generation-coverage.json"] = `${JSON.stringify(buildSvelteKitGenerationCoverage(contract, files, implementationScreenIds), null, 2)}\n`;
419
+ files["src/lib/topogram/ui-web-contract.json"] = `${JSON.stringify(contract, null, 2)}\n`;
420
+ return files;
421
+ }
422
+
423
+ export function generateSvelteKitApp(graph, options = {}) {
424
+ const realization = buildWebRealization(graph, options);
425
+ return buildSvelteKitScaffold(realization.contract, realization.apiContracts, options);
426
+ }
@@ -0,0 +1,65 @@
1
+ import { buildWebRealization } from "../../../realization/ui/index.js";
2
+
3
+ export function generateUiWebContract(graph, options = {}) {
4
+ if (!options.projectionId) {
5
+ const output = {};
6
+ for (const projection of (graph.byKind.projection || []).filter((entry) => entry.platform === "ui_web")) {
7
+ output[projection.id] = buildWebRealization(graph, { ...options, projectionId: projection.id }).contract;
8
+ }
9
+ return output;
10
+ }
11
+
12
+ return buildWebRealization(graph, options).contract;
13
+ }
14
+
15
+ export function generateUiWebDebug(graph, options = {}) {
16
+ const contracts = options.projectionId ? [generateUiWebContract(graph, options)] : Object.values(generateUiWebContract(graph, options));
17
+ const lines = [];
18
+
19
+ lines.push("# UI Web Debug");
20
+ lines.push("");
21
+ lines.push(`Generated from \`${graph.root}\``);
22
+ lines.push("");
23
+
24
+ for (const contract of contracts) {
25
+ lines.push(`## \`${contract.projection.id}\` - ${contract.projection.name}`);
26
+ lines.push("");
27
+ if (contract.sharedProjection?.id) {
28
+ lines.push(`Shared projection: \`${contract.sharedProjection.id}\``);
29
+ }
30
+ lines.push(
31
+ `Generator defaults: ${
32
+ Object.keys(contract.generatorDefaults).length > 0
33
+ ? Object.entries(contract.generatorDefaults).map(([key, value]) => `\`${key}=${value}\``).join(", ")
34
+ : "_none_"
35
+ }`
36
+ );
37
+ if (contract.appShell) {
38
+ lines.push(`App shell: \`${contract.appShell.shell}\` brand \`${contract.appShell.brand}\``);
39
+ }
40
+ if (contract.navigation) {
41
+ const visibleItems = (contract.navigation.items || []).filter((item) => item.visible);
42
+ lines.push(`Navigation: ${visibleItems.length > 0 ? visibleItems.map((item) => `\`${item.label}\``).join(", ") : "_none_"}`);
43
+ }
44
+ lines.push("");
45
+
46
+ for (const screen of contract.screens) {
47
+ lines.push(`### \`${screen.id}\``);
48
+ lines.push("");
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_"}`);
51
+ lines.push("");
52
+ }
53
+
54
+ if ((contract.sitemap || []).length > 0) {
55
+ lines.push("### Sitemap");
56
+ lines.push("");
57
+ for (const entry of contract.sitemap) {
58
+ lines.push(`- ${entry.include ? "include" : "exclude"} \`${entry.route}\` (${entry.label})`);
59
+ }
60
+ lines.push("");
61
+ }
62
+ }
63
+
64
+ return `${lines.join("\n").trimEnd()}\n`;
65
+ }