@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,305 @@
1
+ import { getProjection, uiProjectionCandidates } from "../../generator/surfaces/shared.js";
2
+ import { buildComponentBehaviorRealizations } from "../../component-behavior.js";
3
+
4
+ function toBooleanFlag(value, fallback = false) {
5
+ if (value === "true") return true;
6
+ if (value === "false") return false;
7
+ return fallback;
8
+ }
9
+
10
+ function buildAppShellContract(projection) {
11
+ const values = Object.fromEntries((projection.uiAppShell || []).map((entry) => [entry.key, entry.value]));
12
+ return {
13
+ brand: values.brand || projection.name || projection.id,
14
+ shell: values.shell || "topbar",
15
+ primaryNav: values.primary_nav || "primary",
16
+ secondaryNav: values.secondary_nav || "secondary",
17
+ utilityNav: values.utility_nav || "utility",
18
+ footer: values.footer || "none",
19
+ globalSearch: toBooleanFlag(values.global_search, false),
20
+ notifications: toBooleanFlag(values.notifications, false),
21
+ accountMenu: toBooleanFlag(values.account_menu, false),
22
+ workspaceSwitcher: toBooleanFlag(values.workspace_switcher, false),
23
+ windowing: values.windowing || "single_window"
24
+ };
25
+ }
26
+
27
+ function toSortEntry(entry) {
28
+ return {
29
+ field: entry.field,
30
+ direction: entry.direction
31
+ };
32
+ }
33
+
34
+ function ownershipFieldByCapability(graph) {
35
+ const output = new Map();
36
+
37
+ for (const projection of graph.byKind.projection || []) {
38
+ for (const entry of projection.httpAuthz || []) {
39
+ const capabilityId = entry.capability?.id;
40
+ if (!capabilityId || !entry.ownershipField || output.has(capabilityId)) {
41
+ continue;
42
+ }
43
+ output.set(capabilityId, entry.ownershipField);
44
+ }
45
+ }
46
+
47
+ return output;
48
+ }
49
+
50
+ function deriveDefaultPattern(screen, collectionEntries) {
51
+ if (screen.kind === "detail") return "detail_panel";
52
+ if (screen.kind === "form") return "edit_form";
53
+ if (screen.kind === "board") return "board_view";
54
+ if (screen.kind === "calendar") return "calendar_view";
55
+ if (screen.kind === "dashboard" || screen.kind === "analytics" || screen.kind === "report") return "summary_stats";
56
+ if (screen.kind === "feed" || screen.kind === "inbox") return "activity_feed";
57
+ const view = collectionEntries.find((entry) => entry.operation === "view")?.value;
58
+ if (view === "data_grid") return "data_grid_view";
59
+ if (view === "table") return "resource_table";
60
+ if (view === "cards" || view === "gallery") return "resource_cards";
61
+ if (screen.kind === "list") return "resource_table";
62
+ return null;
63
+ }
64
+
65
+ function componentById(graph, componentId) {
66
+ return (graph.byKind.component || []).find((component) => component.id === componentId) || null;
67
+ }
68
+
69
+ function componentContractFor(graph, componentId) {
70
+ const component = componentById(graph, componentId);
71
+ return component?.componentContract || null;
72
+ }
73
+
74
+ function summarizeComponentRef(graph, componentId) {
75
+ const component = componentById(graph, componentId);
76
+ if (!component) {
77
+ return { id: componentId, name: componentId, category: null, version: null };
78
+ }
79
+ return {
80
+ id: component.id,
81
+ name: component.name || component.id,
82
+ category: component.category || null,
83
+ version: component.version || null
84
+ };
85
+ }
86
+
87
+ export function buildComponentUsageContract(graph, entry) {
88
+ const componentId = entry.component?.id || null;
89
+ const contract = componentId ? componentContractFor(graph, componentId) : null;
90
+ return {
91
+ type: "ui_component_usage",
92
+ region: entry.region || null,
93
+ component: componentId ? summarizeComponentRef(graph, componentId) : null,
94
+ dataBindings: (entry.dataBindings || []).map((binding) => ({
95
+ prop: binding.prop || null,
96
+ source: binding.source || null
97
+ })),
98
+ eventBindings: (entry.eventBindings || []).map((binding) => ({
99
+ event: binding.event || null,
100
+ action: binding.action || null,
101
+ target: binding.target || null
102
+ })),
103
+ behaviorRealizations: buildComponentBehaviorRealizations(contract, entry)
104
+ };
105
+ }
106
+
107
+ export function buildComponentContractMap(graph, componentUsages) {
108
+ return Object.fromEntries(
109
+ [...new Set(componentUsages.map((entry) => entry.component?.id).filter(Boolean))]
110
+ .sort()
111
+ .map((componentId) => [componentId, componentContractFor(graph, componentId)])
112
+ .filter(([, contract]) => contract)
113
+ );
114
+ }
115
+
116
+ function buildNavigationContract(projection, screens) {
117
+ const routeMap = new Map((projection.uiRoutes || []).map((entry) => [entry.screenId, entry]));
118
+ const screenEntries = (projection.uiNavigation || []).filter((entry) => entry.targetKind === "screen");
119
+ const groupEntries = (projection.uiNavigation || []).filter((entry) => entry.targetKind === "group");
120
+ const groups = groupEntries.map((entry) => ({
121
+ id: entry.targetId,
122
+ label: entry.directives.label || entry.targetId,
123
+ placement: entry.directives.placement || "primary",
124
+ pattern: entry.directives.pattern || null,
125
+ icon: entry.directives.icon || null,
126
+ order: entry.directives.order || null
127
+ }));
128
+ const byScreen = new Map(screenEntries.map((entry) => [entry.targetId, entry]));
129
+
130
+ const items = screens.map((screen) => {
131
+ const entry = byScreen.get(screen.id);
132
+ const directives = entry?.directives || {};
133
+ const route = routeMap.get(screen.id)?.path || null;
134
+ const derivedVisible = route ? !route.includes(":") && !["detail"].includes(screen.kind) : false;
135
+ return {
136
+ screenId: screen.id,
137
+ route,
138
+ label: directives.label || screen.title || screen.id,
139
+ groupId: directives.group || null,
140
+ placement: directives.placement || "primary",
141
+ pattern: directives.pattern || null,
142
+ visible: directives.visible ? directives.visible === "true" : derivedVisible,
143
+ default: directives.default === "true",
144
+ breadcrumb: directives.breadcrumb || null,
145
+ sitemap: directives.sitemap || "include",
146
+ order: directives.order || null
147
+ };
148
+ });
149
+
150
+ return {
151
+ groups,
152
+ items,
153
+ patterns: [
154
+ ...new Set([
155
+ ...groups.map((group) => group.pattern).filter(Boolean),
156
+ ...items.map((item) => item.pattern).filter(Boolean)
157
+ ])
158
+ ].sort(),
159
+ defaultScreenId: items.find((item) => item.default)?.screenId || items.find((item) => item.visible)?.screenId || null
160
+ };
161
+ }
162
+
163
+ function buildUiScreenContract(graph, projection, screen, ownershipFields) {
164
+ const collectionEntries = (projection.uiCollections || []).filter((entry) => entry.screenId === screen.id);
165
+ const actionEntries = (projection.uiActions || []).filter((entry) => entry.screenId === screen.id);
166
+ const lookupEntries = (projection.uiLookups || []).filter((entry) => entry.screenId === screen.id);
167
+ const regionEntries = (projection.uiScreenRegions || []).filter((entry) => entry.screenId === screen.id);
168
+ const componentEntries = (projection.uiComponents || []).filter((entry) => entry.screenId === screen.id);
169
+ const screenActionIds = new Set(
170
+ [
171
+ screen.primaryAction?.id,
172
+ screen.secondaryAction?.id,
173
+ screen.destructiveAction?.id,
174
+ screen.terminalAction?.id,
175
+ ...actionEntries.map((entry) => entry.capability?.id)
176
+ ].filter(Boolean)
177
+ );
178
+ const visibilityEntries = (projection.uiVisibility || []).filter((entry) => screenActionIds.has(entry.capability?.id));
179
+ const patterns = new Set(regionEntries.map((entry) => entry.pattern).filter(Boolean));
180
+ const derivedDefaultPattern = deriveDefaultPattern(screen, collectionEntries);
181
+ if (derivedDefaultPattern) {
182
+ patterns.add(derivedDefaultPattern);
183
+ }
184
+
185
+ return {
186
+ type: "ui_screen_contract",
187
+ id: screen.id,
188
+ kind: screen.kind,
189
+ title: screen.title,
190
+ loadCapability: screen.load,
191
+ submitCapability: screen.submit,
192
+ detailCapability: screen.detailCapability,
193
+ inputShape: screen.inputShape,
194
+ viewShape: screen.viewShape,
195
+ itemShape: screen.itemShape,
196
+ emptyState:
197
+ screen.emptyTitle || screen.emptyBody
198
+ ? {
199
+ title: screen.emptyTitle,
200
+ body: screen.emptyBody
201
+ }
202
+ : null,
203
+ navigation: {
204
+ successNavigate: screen.successNavigate,
205
+ successRefresh: screen.successRefresh
206
+ },
207
+ states: {
208
+ loading: screen.loadingState || "auto",
209
+ empty: screen.emptyTitle || screen.emptyBody ? "empty_state_panel" : "auto",
210
+ error: screen.errorState || "auto",
211
+ unauthorized: screen.unauthorizedState || "auto",
212
+ notFound: screen.notFoundState || "auto",
213
+ success: screen.successState || "auto"
214
+ },
215
+ actions: {
216
+ primary: screen.primaryAction,
217
+ secondary: screen.secondaryAction,
218
+ destructive: screen.destructiveAction,
219
+ terminal: screen.terminalAction,
220
+ screen: actionEntries.map((entry) => ({
221
+ capability: entry.capability,
222
+ prominence: entry.prominence,
223
+ placement: entry.placement || null
224
+ }))
225
+ },
226
+ collection: {
227
+ filters: collectionEntries.filter((entry) => entry.operation === "filter").map((entry) => entry.field),
228
+ search: collectionEntries.filter((entry) => entry.operation === "search").map((entry) => entry.field),
229
+ pagination: collectionEntries.find((entry) => entry.operation === "pagination")?.field || null,
230
+ views: collectionEntries.filter((entry) => entry.operation === "view").map((entry) => entry.value),
231
+ refresh: collectionEntries.find((entry) => entry.operation === "refresh")?.value || "manual",
232
+ groupBy: collectionEntries.filter((entry) => entry.operation === "group").map((entry) => entry.field),
233
+ sort: collectionEntries
234
+ .filter((entry) => entry.operation === "sort")
235
+ .map(toSortEntry)
236
+ },
237
+ visibility: visibilityEntries.map((entry) => ({
238
+ capability: entry.capability,
239
+ predicate: entry.predicate,
240
+ value: entry.value,
241
+ claimValue: entry.claimValue || null,
242
+ ownershipField: entry.predicate === "ownership" ? ownershipFields.get(entry.capability?.id || "") || null : null
243
+ })),
244
+ lookups: lookupEntries.map((entry) => ({
245
+ field: entry.field,
246
+ entity: entry.entity,
247
+ labelField: entry.labelField,
248
+ emptyLabel: entry.emptyLabel || null
249
+ })),
250
+ regions: regionEntries.map((entry) => ({
251
+ region: entry.region,
252
+ pattern: entry.pattern || null,
253
+ placement: entry.placement || null,
254
+ title: entry.title || null,
255
+ state: entry.state || null,
256
+ variant: entry.variant || null
257
+ })),
258
+ components: componentEntries.map((entry) => buildComponentUsageContract(graph, entry)),
259
+ patterns: [...patterns]
260
+ };
261
+ }
262
+
263
+ export function buildUiSharedRealization(graph, options = {}) {
264
+ const projections = options.projectionId ? [getProjection(graph, options.projectionId)] : uiProjectionCandidates(graph);
265
+ const ownershipFields = ownershipFieldByCapability(graph);
266
+
267
+ if (options.projectionId) {
268
+ const projection = projections[0];
269
+ const componentUsages = projection.uiComponents || [];
270
+ const screens = (projection.uiScreens || []).map((screen) => buildUiScreenContract(graph, projection, screen, ownershipFields));
271
+ return {
272
+ projection: {
273
+ id: projection.id,
274
+ name: projection.name || projection.id,
275
+ platform: projection.platform
276
+ },
277
+ realizes: projection.realizes,
278
+ outputs: projection.outputs,
279
+ components: buildComponentContractMap(graph, componentUsages),
280
+ appShell: buildAppShellContract(projection),
281
+ navigation: buildNavigationContract(projection, screens),
282
+ screens
283
+ };
284
+ }
285
+
286
+ const output = {};
287
+ for (const projection of projections) {
288
+ const componentUsages = projection.uiComponents || [];
289
+ const screens = (projection.uiScreens || []).map((screen) => buildUiScreenContract(graph, projection, screen, ownershipFields));
290
+ output[projection.id] = {
291
+ projection: {
292
+ id: projection.id,
293
+ name: projection.name || projection.id,
294
+ platform: projection.platform
295
+ },
296
+ realizes: projection.realizes,
297
+ outputs: projection.outputs,
298
+ components: buildComponentContractMap(graph, componentUsages),
299
+ appShell: buildAppShellContract(projection),
300
+ navigation: buildNavigationContract(projection, screens),
301
+ screens
302
+ };
303
+ }
304
+ return output;
305
+ }
@@ -0,0 +1,189 @@
1
+ import { buildApiRealization } from "../api/index.js";
2
+ import { generatorDefaultsMap, getProjection, sharedUiProjectionForWeb } from "../../generator/surfaces/shared.js";
3
+ import {
4
+ buildComponentContractMap,
5
+ buildComponentUsageContract,
6
+ buildUiSharedRealization
7
+ } from "./build-ui-shared-realization.js";
8
+
9
+ function collectCapabilityIds(contract) {
10
+ const capabilityIds = new Set();
11
+ for (const screen of contract.screens || []) {
12
+ if (screen.loadCapability?.id) capabilityIds.add(screen.loadCapability.id);
13
+ if (screen.submitCapability?.id) capabilityIds.add(screen.submitCapability.id);
14
+ if (screen.actions?.primary?.id) capabilityIds.add(screen.actions.primary.id);
15
+ if (screen.actions?.secondary?.id) capabilityIds.add(screen.actions.secondary.id);
16
+ if (screen.actions?.destructive?.id) capabilityIds.add(screen.actions.destructive.id);
17
+ if (screen.actions?.terminal?.id) capabilityIds.add(screen.actions.terminal.id);
18
+ for (const action of screen.actions?.screen || []) {
19
+ capabilityIds.add(action.capability.id);
20
+ }
21
+ }
22
+ return [...capabilityIds].sort();
23
+ }
24
+
25
+ export function buildWebRealization(graph, options = {}) {
26
+ if (!options.projectionId) {
27
+ throw new Error("Routed UI realization requires --projection <id>");
28
+ }
29
+
30
+ const projection = getProjection(graph, options.projectionId);
31
+ const surfaceHints =
32
+ projection.platform === "ui_ios" ? projection.uiIos || [] : projection.uiWeb || [];
33
+ const sharedProjection = sharedUiProjectionForWeb(graph, projection);
34
+ const sharedContract = sharedProjection
35
+ ? buildUiSharedRealization(graph, { projectionId: sharedProjection.id })
36
+ : {
37
+ projection: null,
38
+ realizes: [],
39
+ outputs: [],
40
+ components: {},
41
+ screens: []
42
+ };
43
+ const concreteContract = buildUiSharedRealization(graph, { projectionId: projection.id });
44
+
45
+ const routeMap = new Map((projection.uiRoutes || []).map((entry) => [entry.screenId, entry]));
46
+ const uiWebByScreen = new Map();
47
+ const uiWebByAction = new Map();
48
+
49
+ for (const entry of surfaceHints) {
50
+ if (entry.targetKind === "screen") {
51
+ if (!uiWebByScreen.has(entry.targetId)) {
52
+ uiWebByScreen.set(entry.targetId, []);
53
+ }
54
+ uiWebByScreen.get(entry.targetId).push(entry);
55
+ } else if (entry.targetKind === "action") {
56
+ if (!uiWebByAction.has(entry.targetId)) {
57
+ uiWebByAction.set(entry.targetId, []);
58
+ }
59
+ uiWebByAction.get(entry.targetId).push(entry);
60
+ }
61
+ }
62
+
63
+ const screenMap = new Map((sharedContract.screens || []).map((screen) => [screen.id, { ...screen, components: [...(screen.components || [])] }]));
64
+ for (const screen of concreteContract.screens || []) {
65
+ if (!screenMap.has(screen.id)) {
66
+ screenMap.set(screen.id, { ...screen, components: [...(screen.components || [])] });
67
+ continue;
68
+ }
69
+ const existing = screenMap.get(screen.id);
70
+ screenMap.set(screen.id, {
71
+ ...existing,
72
+ ...screen,
73
+ components: [...(existing.components || []), ...(screen.components || [])],
74
+ regions: mergeByKey(existing.regions || [], screen.regions || [], (entry) => entry.region),
75
+ patterns: [...new Set([...(existing.patterns || []), ...(screen.patterns || [])])]
76
+ });
77
+ }
78
+ for (const entry of projection.uiComponents || []) {
79
+ if (!screenMap.has(entry.screenId)) {
80
+ continue;
81
+ }
82
+ const screen = screenMap.get(entry.screenId);
83
+ if ((screen.components || []).some((usage) => componentUsageFingerprint(usage) === componentUsageFingerprintFromEntry(entry))) {
84
+ continue;
85
+ }
86
+ screen.components = [...(screen.components || []), buildComponentUsageContract(graph, entry)];
87
+ }
88
+
89
+ const appShell = projection.uiAppShell?.length || !sharedProjection ? concreteContract.appShell : sharedContract.appShell;
90
+ const navigation = projection.uiNavigation?.length || !sharedProjection ? concreteContract.navigation : sharedContract.navigation;
91
+ const componentContracts = {
92
+ ...(sharedContract.components || {}),
93
+ ...buildComponentContractMap(graph, projection.uiComponents || [])
94
+ };
95
+
96
+ const contract = {
97
+ projection: {
98
+ id: projection.id,
99
+ name: projection.name || projection.id,
100
+ platform: projection.platform
101
+ },
102
+ sharedProjection: sharedProjection
103
+ ? {
104
+ id: sharedProjection.id,
105
+ name: sharedProjection.name || sharedProjection.id
106
+ }
107
+ : null,
108
+ generatorDefaults: generatorDefaultsMap(projection),
109
+ outputs: projection.outputs,
110
+ components: componentContracts,
111
+ appShell: appShell || null,
112
+ navigation: {
113
+ groups: navigation?.groups || [],
114
+ patterns: navigation?.patterns || [],
115
+ defaultScreenId: navigation?.defaultScreenId || null,
116
+ items: (navigation?.items || []).map((item) => ({
117
+ ...item,
118
+ route: routeMap.get(item.screenId)?.path || item.route || null
119
+ }))
120
+ },
121
+ screens: [...screenMap.values()].map((screen) => ({
122
+ ...screen,
123
+ route: routeMap.get(screen.id)?.path || null,
124
+ web: Object.fromEntries((uiWebByScreen.get(screen.id) || []).map((entry) => [entry.directive, entry.value])),
125
+ actionWeb: Object.fromEntries(
126
+ [...screen.actions.screen, screen.actions.primary, screen.actions.secondary, screen.actions.destructive, screen.actions.terminal]
127
+ .filter(Boolean)
128
+ .map((action) => {
129
+ const actionId = action.capability?.id || action.id;
130
+ const entries = uiWebByAction.get(actionId) || [];
131
+ return [actionId, Object.fromEntries(entries.map((entry) => [entry.directive, entry.value]))];
132
+ })
133
+ )
134
+ })),
135
+ sitemap: (navigation?.items || [])
136
+ .map((item) => ({
137
+ screenId: item.screenId,
138
+ label: item.label,
139
+ route: routeMap.get(item.screenId)?.path || item.route || null,
140
+ include: item.sitemap !== "exclude"
141
+ }))
142
+ .filter((entry) => entry.route)
143
+ };
144
+ const capabilityIds = collectCapabilityIds(contract);
145
+ const apiContracts = {};
146
+ for (const capabilityId of capabilityIds) {
147
+ apiContracts[capabilityId] = buildApiRealization(graph, { capabilityId });
148
+ }
149
+
150
+ const isNativeUi = projection.platform === "ui_ios";
151
+
152
+ return {
153
+ type: isNativeUi ? "native_ui_realization" : "web_app_realization",
154
+ app: {
155
+ id: contract.projection.id,
156
+ family: isNativeUi ? "native" : "web",
157
+ target: contract.generatorDefaults.profile || (isNativeUi ? "swiftui" : "sveltekit"),
158
+ name: contract.projection.name
159
+ },
160
+ contract,
161
+ capabilityIds,
162
+ apiContracts
163
+ };
164
+ }
165
+
166
+ function mergeByKey(left, right, keyFn) {
167
+ const output = new Map();
168
+ for (const entry of left) output.set(keyFn(entry), entry);
169
+ for (const entry of right) output.set(keyFn(entry), { ...(output.get(keyFn(entry)) || {}), ...entry });
170
+ return [...output.values()];
171
+ }
172
+
173
+ function componentUsageFingerprint(usage) {
174
+ return [
175
+ usage?.region || "",
176
+ usage?.component?.id || "",
177
+ ...(usage?.dataBindings || []).map((binding) => `data:${binding.prop}:${binding.source?.id || ""}`),
178
+ ...(usage?.eventBindings || []).map((binding) => `event:${binding.event}:${binding.action}:${binding.target?.id || ""}`)
179
+ ].join("|");
180
+ }
181
+
182
+ function componentUsageFingerprintFromEntry(entry) {
183
+ return [
184
+ entry?.region || "",
185
+ entry?.component?.id || "",
186
+ ...(entry?.dataBindings || []).map((binding) => `data:${binding.prop}:${binding.source?.id || ""}`),
187
+ ...(entry?.eventBindings || []).map((binding) => `event:${binding.event}:${binding.action}:${binding.target?.id || ""}`)
188
+ ].join("|");
189
+ }
@@ -0,0 +1,2 @@
1
+ export { buildUiSharedRealization } from "./build-ui-shared-realization.js";
2
+ export { buildWebRealization } from "./build-web-realization.js";