@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,538 @@
1
+ import { buildWebRealization } from "../../../realization/ui/index.js";
2
+ import { getExampleImplementation } from "../../../example-implementation.js";
3
+ import { renderReactComponentRegion } from "./react-components.js";
4
+ import { renderApiClientModule, renderLookupModule, renderVisibilityModule } from "./shared.js";
5
+
6
+ function componentNameForScreen(screenId) {
7
+ return screenId
8
+ .split(/[_\-\s]+/)
9
+ .filter(Boolean)
10
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
11
+ .join("") + "Page";
12
+ }
13
+
14
+ function buildReactClientModule(webReference) {
15
+ return renderApiClientModule("react", webReference);
16
+ }
17
+
18
+ function buildLookupModule(webReference) {
19
+ return renderLookupModule("react", webReference.defaultApiBaseUrl || "http://localhost:3000");
20
+ }
21
+
22
+ function buildReactVisibilityModule() {
23
+ return renderVisibilityModule("react");
24
+ }
25
+
26
+ function resolveNavLinks(contract, webReference) {
27
+ const contractLinks = (contract.navigation?.items || [])
28
+ .filter((item) => item.visible && item.route && item.placement === "primary")
29
+ .sort((a, b) => String(a.order || "").localeCompare(String(b.order || "")) || a.label.localeCompare(b.label))
30
+ .map((item) => ({ label: item.label, route: item.route }));
31
+ if (contractLinks.length > 0) {
32
+ return contractLinks;
33
+ }
34
+ return Array.isArray(webReference.nav?.links) && webReference.nav.links.length > 0
35
+ ? webReference.nav.links
36
+ : [
37
+ { label: webReference.nav.browseLabel, route: webReference.nav.browseRoute },
38
+ { label: webReference.nav.createLabel, route: webReference.nav.createRoute }
39
+ ];
40
+ }
41
+
42
+ function routeSamplePath(route) {
43
+ return String(route || "/").replace(/:([A-Za-z0-9_]+)/g, "sample-$1");
44
+ }
45
+
46
+ function screenRegions(screen) {
47
+ const names = new Set();
48
+ for (const region of screen?.regions || []) {
49
+ if (region?.name) names.add(region.name);
50
+ }
51
+ for (const usage of screen?.components || []) {
52
+ if (usage?.region) names.add(usage.region);
53
+ }
54
+ return [...names];
55
+ }
56
+
57
+ function sampleItemsForScreen(screen) {
58
+ const title = screen?.title || screen?.id || "Resource";
59
+ return [
60
+ {
61
+ id: "sample-active",
62
+ title: `${title} sample`,
63
+ name: `${title} sample`,
64
+ message: `${title} sample`,
65
+ description: "Generated from Topogram UI contract metadata.",
66
+ status: "active",
67
+ priority: "medium",
68
+ created_at: "2026-01-01"
69
+ },
70
+ {
71
+ id: "sample-completed",
72
+ title: `${title} completed sample`,
73
+ name: `${title} completed sample`,
74
+ message: `${title} completed sample`,
75
+ description: "Second generated row for component rendering checks.",
76
+ status: "completed",
77
+ priority: "low",
78
+ created_at: "2026-01-02"
79
+ }
80
+ ];
81
+ }
82
+
83
+ function buildReactHomePage(contract, webReference) {
84
+ const screens = contract.screens.map((screen) => ({
85
+ id: screen.id,
86
+ title: screen.title || screen.id,
87
+ route: screen.route,
88
+ sampleRoute: routeSamplePath(screen.route),
89
+ navigable: Boolean(screen.route)
90
+ }));
91
+ const homeDescription = webReference.home.heroDescriptionTemplate.replace("PROFILE", "`react`");
92
+ return `import { Link } from "react-router-dom";
93
+
94
+ const screens = ${JSON.stringify(screens, null, 2)};
95
+
96
+ export function HomePage() {
97
+ return (
98
+ <main>
99
+ <div className="stack">
100
+ <section className="card hero">
101
+ <div>
102
+ <p className="muted">Generated starter</p>
103
+ <h1>${contract.projection.name}</h1>
104
+ <p>${homeDescription}</p>
105
+ </div>
106
+ <div className="button-row">
107
+ {screens.filter((screen) => screen.navigable).slice(0, 2).map((screen) => (
108
+ <Link className="button-link" to={screen.sampleRoute} key={screen.id}>{screen.title}</Link>
109
+ ))}
110
+ </div>
111
+ </section>
112
+
113
+ <section className="grid two">
114
+ {screens.map((screen) => (
115
+ <article className="card" key={screen.id}>
116
+ <h2>{screen.title}</h2>
117
+ {screen.navigable ? (
118
+ <p><Link to={screen.sampleRoute}>Open screen</Link></p>
119
+ ) : (
120
+ <p className="muted">Contract-only screen</p>
121
+ )}
122
+ </article>
123
+ ))}
124
+ </section>
125
+ </div>
126
+ </main>
127
+ );
128
+ }
129
+ `;
130
+ }
131
+
132
+ function buildReactScreenPage(screen, contract) {
133
+ const sampleItems = sampleItemsForScreen(screen);
134
+ const renderedRegions = screenRegions(screen)
135
+ .map((region) => {
136
+ const rendered = renderReactComponentRegion(screen, region, {
137
+ componentContracts: contract.components,
138
+ itemsExpression: "items",
139
+ useTypescript: true
140
+ });
141
+ if (!rendered) return "";
142
+ return ` <section className="stack" data-topogram-region="${region}">
143
+ ${rendered}
144
+ </section>`;
145
+ })
146
+ .filter(Boolean)
147
+ .join("\n\n");
148
+ const defaultCollection = `<section className="card">
149
+ <h2>Sample rows</h2>
150
+ <ul className="resource-list">
151
+ {items.map((item: any) => (
152
+ <li key={item.id}>
153
+ <div className="resource-meta">
154
+ <strong>{item.title}</strong>
155
+ <span className="muted">{item.description}</span>
156
+ </div>
157
+ <span className="badge">{item.status}</span>
158
+ </li>
159
+ ))}
160
+ </ul>
161
+ </section>`;
162
+
163
+ return `const items: any[] = ${JSON.stringify(sampleItems, null, 2)};
164
+
165
+ export function ${componentNameForScreen(screen.id)}() {
166
+ return (
167
+ <main>
168
+ <div className="stack">
169
+ <section className="card">
170
+ <div className="button-row" style={{ justifyContent: "space-between" }}>
171
+ <div>
172
+ <p className="muted">${screen.kind || "screen"}</p>
173
+ <h1>${screen.title || screen.id}</h1>
174
+ <p>This React page was generated from <code>${screen.id}</code>.</p>
175
+ </div>
176
+ </div>
177
+ </section>
178
+
179
+ ${renderedRegions || ` ${defaultCollection}`}
180
+ </div>
181
+ </main>
182
+ );
183
+ }
184
+ `;
185
+ }
186
+
187
+ function screenComponentUsages(screen) {
188
+ return Array.isArray(screen?.components) ? screen.components : [];
189
+ }
190
+
191
+ function screenPagePath(screen) {
192
+ return `src/pages/${componentNameForScreen(screen.id)}.tsx`;
193
+ }
194
+
195
+ function buildReactGenerationCoverage(contract, files, routeScreens) {
196
+ const diagnostics = [];
197
+ const routeScreenIds = new Set(routeScreens.map((screen) => screen.id));
198
+ const screens = (contract.screens || [])
199
+ .filter((screen) => routeScreenIds.has(screen.id))
200
+ .map((screen) => {
201
+ const pagePath = screenPagePath(screen);
202
+ const contents = files[pagePath] || "";
203
+ const rendered = Boolean(contents);
204
+ if (!rendered) {
205
+ diagnostics.push({
206
+ code: "screen_route_not_rendered",
207
+ severity: "error",
208
+ screen: screen.id,
209
+ route: screen.route,
210
+ message: `Screen '${screen.id}' has route '${screen.route}' but no React page was generated.`,
211
+ suggested_fix: "Check the React generator contract-complete route emission for this screen."
212
+ });
213
+ }
214
+ const componentUsages = screenComponentUsages(screen).map((usage) => {
215
+ const componentId = usage.component?.id || null;
216
+ const marker = componentId ? `data-topogram-component="${componentId}"` : null;
217
+ const usageRendered = Boolean(marker && contents.includes(marker));
218
+ if (componentId && rendered && !usageRendered) {
219
+ diagnostics.push({
220
+ code: "component_usage_not_rendered",
221
+ severity: "warning",
222
+ screen: screen.id,
223
+ route: screen.route,
224
+ region: usage.region || null,
225
+ component: componentId,
226
+ message: `Screen '${screen.id}' uses component '${componentId}' but the generated React page does not contain its component marker.`,
227
+ suggested_fix: "Render the component region with renderReactComponentRegion or add a supported component pattern."
228
+ });
229
+ }
230
+ return {
231
+ component: componentId,
232
+ region: usage.region || null,
233
+ rendered: usageRendered,
234
+ marker
235
+ };
236
+ });
237
+ return {
238
+ id: screen.id,
239
+ route: screen.route,
240
+ page: pagePath,
241
+ rendered,
242
+ renderer: rendered ? "generator" : "missing",
243
+ component_usages: componentUsages
244
+ };
245
+ });
246
+
247
+ return {
248
+ type: "generation_coverage",
249
+ surface: "web",
250
+ generator: "topogram/react",
251
+ projection: {
252
+ id: contract.projection.id,
253
+ name: contract.projection.name,
254
+ platform: contract.projection.platform
255
+ },
256
+ summary: {
257
+ routed_screens: screens.length,
258
+ rendered_screens: screens.filter((screen) => screen.rendered).length,
259
+ implementation_screens: 0,
260
+ generator_screens: screens.filter((screen) => screen.renderer === "generator").length,
261
+ component_usages: screens.reduce((total, screen) => total + screen.component_usages.length, 0),
262
+ rendered_component_usages: screens.reduce(
263
+ (total, screen) => total + screen.component_usages.filter((usage) => usage.rendered).length,
264
+ 0
265
+ ),
266
+ diagnostics: diagnostics.length,
267
+ errors: diagnostics.filter((diagnostic) => diagnostic.severity === "error").length,
268
+ warnings: diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length
269
+ },
270
+ screens,
271
+ diagnostics
272
+ };
273
+ }
274
+
275
+ function buildAppTsx(contract, webReference) {
276
+ const navLinks = resolveNavLinks(contract, webReference);
277
+ const brandName = contract.appShell?.brand || webReference.brandName;
278
+ const footerEnabled = contract.appShell?.footer && contract.appShell.footer !== "none";
279
+ const shellMode = contract.appShell?.shell || "topbar";
280
+ const windowingMode = contract.appShell?.windowing || "single_window";
281
+ const navigationPatterns = (contract.navigation?.patterns || []).join(" ");
282
+ const hasCommandPalette = (contract.navigation?.patterns || []).includes("command_palette");
283
+ const navItems = navLinks.map((link) => ` <Link to="${link.route}">${link.label}</Link>`).join("\n");
284
+ const routeScreens = contract.screens.filter((screen) => screen.route && componentNameForScreen(screen.id) !== "EditorialSettingsPage");
285
+ const importLines = routeScreens
286
+ .map((screen) => `import { ${componentNameForScreen(screen.id)} } from "./pages/${componentNameForScreen(screen.id)}";`)
287
+ .join("\n");
288
+ const routeLines = routeScreens
289
+ .map((screen) => ` <Route path="${screen.route}" element={<${componentNameForScreen(screen.id)} />} />`)
290
+ .join("\n");
291
+
292
+ const shellFrame =
293
+ shellMode === "split_view"
294
+ ? ` <div className="app-workspace">
295
+ <aside className="app-sidebar">
296
+ <Link className="brand" to="/">${brandName}</Link>
297
+ <nav className="app-nav-links">
298
+ ${navItems}
299
+ </nav>
300
+ ${hasCommandPalette ? ` <button className="command-palette-button" type="button">Command Palette</button>` : ""}
301
+ </aside>
302
+ <div className="app-main-shell">
303
+ <header className="app-nav compact">
304
+ <div className="brand-mark">${brandName}</div>
305
+ ${hasCommandPalette ? ` <button className="command-palette-button" type="button">Command Palette</button>` : ""}
306
+ </header>
307
+ <main>
308
+ <Routes>
309
+ <Route path="/" element={<HomePage />} />
310
+ ${routeLines}
311
+ </Routes>
312
+ </main>
313
+ </div>
314
+ </div>`
315
+ : shellMode === "bottom_tabs"
316
+ ? ` <header className="app-nav">
317
+ <Link className="brand" to="/">${brandName}</Link>
318
+ ${hasCommandPalette ? ` <button className="command-palette-button" type="button">Command Palette</button>` : ""}
319
+ </header>
320
+ <main>
321
+ <Routes>
322
+ <Route path="/" element={<HomePage />} />
323
+ ${routeLines}
324
+ </Routes>
325
+ </main>
326
+ <nav className="app-tabbar">
327
+ ${navItems}
328
+ </nav>`
329
+ : ` <header className="app-nav${shellMode === "menu_bar" ? " menu-bar" : ""}">
330
+ <Link className="brand" to="/">${brandName}</Link>
331
+ <nav className="app-nav-links">
332
+ ${navItems}
333
+ </nav>
334
+ ${hasCommandPalette ? ` <button className="command-palette-button" type="button">Command Palette</button>` : ""}
335
+ </header>
336
+ <main>
337
+ <Routes>
338
+ <Route path="/" element={<HomePage />} />
339
+ ${routeLines}
340
+ </Routes>
341
+ </main>`;
342
+
343
+ return `import { BrowserRouter, Link, Route, Routes } from "react-router-dom";
344
+ import { HomePage } from "./pages/HomePage";
345
+ ${importLines}
346
+
347
+ export default function App() {
348
+ return (
349
+ <BrowserRouter>
350
+ <div className="app-shell" data-shell="${shellMode}" data-windowing="${windowingMode}" data-navigation-patterns="${navigationPatterns}">
351
+ ${shellFrame}
352
+ ${footerEnabled ? ` <footer className="app-footer">
353
+ <span>Generated from Topogram</span>
354
+ </footer>
355
+ ` : ""}
356
+ </div>
357
+ </BrowserRouter>
358
+ );
359
+ }
360
+ `;
361
+ }
362
+
363
+ function buildReactScaffold(realization, graph, options = {}) {
364
+ const implementation = getExampleImplementation(graph, options);
365
+ const webReference = implementation.web.reference;
366
+ const runtimeReference = implementation.runtime.reference;
367
+ const webReferenceWithDefaults = {
368
+ ...webReference,
369
+ defaultApiBaseUrl: `http://localhost:${runtimeReference?.ports?.server || 3000}`
370
+ };
371
+ const contract = realization.contract;
372
+ const routeScreens = contract.screens.filter((screen) => screen.route && componentNameForScreen(screen.id) !== "EditorialSettingsPage");
373
+ const files = {};
374
+
375
+ files["package.json"] = `${JSON.stringify({
376
+ name: contract.projection.id,
377
+ private: true,
378
+ version: "0.1.0",
379
+ type: "module",
380
+ scripts: {
381
+ dev: "vite dev",
382
+ build: "vite build",
383
+ preview: "vite preview",
384
+ check: "tsc --noEmit"
385
+ },
386
+ dependencies: {
387
+ react: "^18.3.1",
388
+ "react-dom": "^18.3.1",
389
+ "react-router-dom": "^6.30.1"
390
+ },
391
+ devDependencies: {
392
+ "@types/react": "^18.3.3",
393
+ "@types/react-dom": "^18.3.0",
394
+ "@vitejs/plugin-react": "^4.7.0",
395
+ typescript: "^5.6.3",
396
+ vite: "^7.1.11"
397
+ }
398
+ }, null, 2)}\n`;
399
+ files["tsconfig.json"] = `${JSON.stringify({
400
+ compilerOptions: {
401
+ target: "ES2020",
402
+ useDefineForClassFields: true,
403
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
404
+ allowJs: false,
405
+ skipLibCheck: true,
406
+ esModuleInterop: true,
407
+ allowSyntheticDefaultImports: true,
408
+ strict: true,
409
+ forceConsistentCasingInFileNames: true,
410
+ module: "ESNext",
411
+ moduleResolution: "Node",
412
+ resolveJsonModule: true,
413
+ isolatedModules: true,
414
+ noEmit: true,
415
+ jsx: "react-jsx"
416
+ },
417
+ include: ["src"]
418
+ }, null, 2)}\n`;
419
+ files["vite.config.ts"] = `import { defineConfig } from "vite";
420
+ import react from "@vitejs/plugin-react";
421
+
422
+ export default defineConfig({
423
+ plugins: [react()],
424
+ envPrefix: ["VITE_", "PUBLIC_TOPOGRAM_", "TOPOGRAM_"]
425
+ });
426
+ `;
427
+ files["index.html"] = `<!doctype html>
428
+ <html lang="en">
429
+ <head>
430
+ <meta charset="UTF-8" />
431
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
432
+ <title>${webReference.brandName}</title>
433
+ </head>
434
+ <body>
435
+ <div id="root"></div>
436
+ <script type="module" src="/src/main.tsx"></script>
437
+ </body>
438
+ </html>
439
+ `;
440
+ files["src/main.tsx"] = `import React from "react";
441
+ import ReactDOM from "react-dom/client";
442
+ import App from "./App";
443
+ import "./app.css";
444
+
445
+ ReactDOM.createRoot(document.getElementById("root")!).render(
446
+ <React.StrictMode>
447
+ <App />
448
+ </React.StrictMode>
449
+ );
450
+ `;
451
+ files["src/vite-env.d.ts"] = `/// <reference types="vite/client" />\n`;
452
+ files["src/app.css"] = `:root {
453
+ font-family: system-ui, sans-serif;
454
+ color: #182026;
455
+ background: linear-gradient(180deg, #f5f7fb 0%, #edf2f7 100%);
456
+ }
457
+ body { margin: 0; }
458
+ a { color: #0f5cc0; text-decoration: none; }
459
+ a:hover { text-decoration: underline; }
460
+ main { max-width: 72rem; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
461
+ .app-shell { min-height: 100vh; }
462
+ .app-workspace { display: grid; grid-template-columns: 18rem minmax(0, 1fr); min-height: 100vh; }
463
+ .app-main-shell { min-width: 0; }
464
+ .app-sidebar { position: sticky; top: 0; align-self: start; min-height: 100vh; display: grid; align-content: start; gap: 1rem; padding: 1.25rem 1rem; border-right: 1px solid rgba(24, 32, 38, 0.08); background: rgba(255, 255, 255, 0.86); backdrop-filter: blur(12px); }
465
+ .app-nav { position: sticky; top: 0; z-index: 10; display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 1rem 1.25rem; border-bottom: 1px solid rgba(24, 32, 38, 0.08); background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(12px); }
466
+ .app-nav-links, .app-nav nav, .app-tabbar { display: flex; gap: 0.75rem; flex-wrap: wrap; }
467
+ .app-nav.menu-bar { border-bottom-style: dashed; }
468
+ .app-nav.compact { justify-content: flex-end; }
469
+ .app-tabbar { position: sticky; bottom: 0; z-index: 10; justify-content: space-around; padding: 0.85rem 1rem calc(0.85rem + env(safe-area-inset-bottom, 0px)); border-top: 1px solid rgba(24, 32, 38, 0.08); background: rgba(255, 255, 255, 0.92); backdrop-filter: blur(12px); }
470
+ .brand { font-weight: 700; letter-spacing: 0.01em; }
471
+ .brand-mark { font-weight: 700; color: #607284; }
472
+ .command-palette-button { background: #182026; color: white; border: none; border-radius: 999px; padding: 0.6rem 0.9rem; font: inherit; cursor: pointer; }
473
+ .app-footer { max-width: 72rem; margin: 0 auto; padding: 0 1.25rem 2rem; color: #607284; }
474
+ .card { background: white; border-radius: 16px; padding: 1.25rem; box-shadow: 0 12px 30px rgba(24, 32, 38, 0.08); }
475
+ .hero, .stack, .grid, .filters, .task-meta, .resource-meta, .definition-list { display: grid; gap: 1rem; }
476
+ .grid.two { grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
477
+ .filters { grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); margin: 1rem 0 1.25rem; }
478
+ label { display: grid; gap: 0.35rem; font-size: 0.95rem; }
479
+ input, textarea, button, select { font: inherit; }
480
+ input, textarea, select { width: 100%; box-sizing: border-box; border: 1px solid #c9d4e2; border-radius: 12px; padding: 0.7rem 0.85rem; background: white; }
481
+ textarea { min-height: 8rem; resize: vertical; }
482
+ button, .button-link { display: inline-flex; align-items: center; justify-content: center; gap: 0.35rem; border: none; border-radius: 999px; padding: 0.7rem 1rem; background: #0f5cc0; color: white; font-weight: 600; cursor: pointer; }
483
+ .button-link.secondary { background: #e9eef6; color: #182026; }
484
+ .button-row { display: flex; gap: 0.75rem; flex-wrap: wrap; align-items: center; }
485
+ .task-list, .resource-list { list-style: none; padding: 0; margin: 1rem 0 0; display: grid; gap: 0.75rem; }
486
+ .task-list li, .resource-list li { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; padding: 1rem; border: 1px solid #e0e8f1; border-radius: 14px; background: #fbfcfe; }
487
+ .table-wrap { margin-top: 1rem; overflow-x: auto; border: 1px solid #d7e1ec; border-radius: 14px; background: white; }
488
+ .resource-table { width: 100%; border-collapse: collapse; min-width: 42rem; }
489
+ .resource-table th, .resource-table td { padding: 0.85rem 1rem; text-align: left; border-bottom: 1px solid #e7edf5; vertical-align: top; }
490
+ .resource-table th { font-size: 0.85rem; letter-spacing: 0.04em; text-transform: uppercase; color: #516173; background: #f8fbff; }
491
+ .resource-table tbody tr:hover { background: #fbfdff; }
492
+ .data-grid { min-width: 64rem; font-size: 0.95rem; }
493
+ .data-grid thead th { position: sticky; top: 0; z-index: 1; background: #eef5ff; }
494
+ .data-grid-shell { box-shadow: inset 0 0 0 1px rgba(15, 92, 192, 0.04); }
495
+ .cell-stack { display: grid; gap: 0.35rem; }
496
+ .cell-secondary { color: #607284; font-size: 0.92rem; }
497
+ .definition-list { grid-template-columns: minmax(8rem, 12rem) 1fr; align-items: start; }
498
+ .definition-list dt { font-weight: 600; color: #516173; }
499
+ .definition-list dd { margin: 0; }
500
+ .badge { display: inline-flex; align-items: center; padding: 0.25rem 0.6rem; border-radius: 999px; background: #eef4ff; color: #0f5cc0; font-size: 0.85rem; font-weight: 600; }
501
+ .muted { color: #607284; }
502
+ .empty-state { padding: 1rem 0; }
503
+ .error-text { color: #b42318; }
504
+ .component-card { border: 1px solid #d7e1ec; border-radius: 14px; background: #fbfcfe; padding: 1rem; margin-top: 1rem; }
505
+ .component-header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
506
+ .component-eyebrow { margin: 0 0 0.25rem; color: #607284; font-size: 0.75rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
507
+ .component-card h2, .component-card h3 { margin: 0; }
508
+ .component-table-wrap { margin-top: 1rem; }
509
+ .summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); gap: 0.75rem; }
510
+ .summary-grid div, .board-column { border: 1px solid #e0e8f1; border-radius: 12px; background: white; padding: 0.85rem; }
511
+ .summary-grid strong { display: block; font-size: 1.5rem; }
512
+ .summary-grid span, .calendar-list span { color: #607284; font-size: 0.9rem; }
513
+ .board-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: 0.75rem; margin-top: 1rem; }
514
+ .board-card, .calendar-card { display: grid; gap: 0.25rem; border: 1px solid #e0e8f1; border-radius: 10px; background: #f8fbff; padding: 0.75rem; }
515
+ .calendar-list { display: grid; gap: 0.75rem; margin-top: 1rem; }
516
+ @media (max-width: 900px) { .app-workspace { grid-template-columns: 1fr; } .app-sidebar { position: static; min-height: auto; border-right: none; border-bottom: 1px solid rgba(24, 32, 38, 0.08); } }
517
+ @media (max-width: 640px) { .definition-list { grid-template-columns: 1fr; } .task-list li, .resource-list li { flex-direction: column; } .resource-table { min-width: 36rem; } .app-nav { flex-wrap: wrap; } }
518
+ `;
519
+ files["src/App.tsx"] = buildAppTsx(contract, webReferenceWithDefaults);
520
+ files["src/lib/topogram/api-contracts.json"] = `${JSON.stringify(realization.apiContracts, null, 2)}\n`;
521
+ files["src/lib/topogram/ui-web-contract.json"] = `${JSON.stringify(contract, null, 2)}\n`;
522
+ files["src/lib/auth/visibility.ts"] = buildReactVisibilityModule();
523
+ files["src/lib/api/client.ts"] = buildReactClientModule(webReferenceWithDefaults);
524
+ files["src/lib/api/lookups.ts"] = buildLookupModule(webReferenceWithDefaults);
525
+ files["src/pages/HomePage.tsx"] = buildReactHomePage(contract, webReferenceWithDefaults);
526
+
527
+ for (const screen of routeScreens) {
528
+ files[screenPagePath(screen)] = buildReactScreenPage(screen, contract);
529
+ }
530
+
531
+ files["src/lib/topogram/generation-coverage.json"] = `${JSON.stringify(buildReactGenerationCoverage(contract, files, routeScreens), null, 2)}\n`;
532
+ return files;
533
+ }
534
+
535
+ export function generateReactApp(graph, options = {}) {
536
+ const realization = buildWebRealization(graph, options);
537
+ return buildReactScaffold(realization, graph, options);
538
+ }
@@ -0,0 +1 @@
1
+ export { generateSvelteKitApp as generateWebRoutes } from "./sveltekit.js";
@@ -0,0 +1 @@
1
+ export { generateSvelteKitApp as generateWebScreens } from "./sveltekit.js";