@topogram/cli 0.3.51 → 0.3.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +4 -4
- package/CHANGELOG.md +11 -11
- package/package.json +1 -1
- package/src/adoption/plan.js +2 -2
- package/src/agent-ops/query-builders.js +42 -33
- package/src/cli.js +174 -129
- package/src/generator/adapters.d.ts +1 -0
- package/src/generator/adapters.js +64 -39
- package/src/generator/check.js +19 -12
- package/src/generator/context/diff.js +9 -9
- package/src/generator/context/domain-coverage.js +11 -10
- package/src/generator/context/domain-page.js +6 -6
- package/src/generator/context/shared.js +37 -21
- package/src/generator/context/slice.js +70 -65
- package/src/generator/index.js +12 -12
- package/src/generator/output.js +21 -20
- package/src/generator/registry.js +71 -49
- package/src/generator/runtime/app-bundle.js +15 -15
- package/src/generator/runtime/compile-check.js +7 -7
- package/src/generator/runtime/deployment.js +9 -9
- package/src/generator/runtime/environment.js +39 -39
- package/src/generator/runtime/runtime-check.js +5 -5
- package/src/generator/runtime/shared.js +40 -38
- package/src/generator/runtime/smoke.js +5 -5
- package/src/generator/surfaces/databases/contract.js +1 -1
- package/src/generator/surfaces/databases/lifecycle-shared.js +6 -5
- package/src/generator/surfaces/databases/postgres/drizzle.js +3 -2
- package/src/generator/surfaces/databases/postgres/prisma.js +3 -2
- package/src/generator/surfaces/databases/shared.js +3 -2
- package/src/generator/surfaces/databases/snapshot.js +1 -1
- package/src/generator/surfaces/databases/sqlite/prisma.js +3 -2
- package/src/generator/surfaces/native/swiftui-app.js +3 -3
- package/src/generator/surfaces/native/swiftui-templates/Package.swift.txt +1 -1
- package/src/generator/surfaces/native/swiftui-templates/README.generated.md +3 -3
- package/src/generator/surfaces/native/swiftui-templates/runtime/DynamicScreens.swift +3 -3
- package/src/generator/surfaces/services/persistence-wiring.js +3 -2
- package/src/generator/surfaces/services/server-contract.js +4 -4
- package/src/generator/surfaces/shared.js +2 -2
- package/src/generator/surfaces/web/design-intent.js +1 -1
- package/src/generator/surfaces/web/index.js +7 -7
- package/src/generator/surfaces/web/{react-components.js → react-widgets.js} +53 -53
- package/src/generator/surfaces/web/react.js +36 -36
- package/src/generator/surfaces/web/{sveltekit-components.js → sveltekit-widgets.js} +53 -53
- package/src/generator/surfaces/web/sveltekit.js +34 -34
- package/src/generator/surfaces/web/{ui-web-contract.js → ui-surface-contract.js} +8 -8
- package/src/generator/surfaces/web/vanilla.js +6 -6
- package/src/generator/{component-conformance.js → widget-conformance.js} +129 -128
- package/src/generator/widgets.js +40 -0
- package/src/generator-policy.js +10 -12
- package/src/import/core/runner.js +34 -34
- package/src/import/core/shared.js +1 -1
- package/src/import/extractors/ui/android-compose.js +1 -1
- package/src/import/extractors/ui/blazor.js +1 -1
- package/src/import/extractors/ui/razor-pages.js +1 -1
- package/src/import/extractors/ui/react-router.js +4 -4
- package/src/import/extractors/ui/sveltekit.js +4 -4
- package/src/import/extractors/ui/swiftui.js +1 -1
- package/src/import/extractors/ui/uikit.js +1 -1
- package/src/new-project.js +19 -18
- package/src/project-config.js +104 -44
- package/src/proofs/contract-audit.js +1 -1
- package/src/proofs/ios-parity.js +1 -1
- package/src/proofs/issues-parity.js +1 -1
- package/src/realization/backend/build-backend-runtime-realization.js +2 -2
- package/src/realization/ui/build-ui-shared-realization.js +33 -33
- package/src/realization/ui/build-web-realization.js +23 -20
- package/src/reconcile/journeys.js +1 -1
- package/src/resolver/index.js +148 -65
- package/src/validator/index.js +509 -423
- package/src/validator/kinds.js +36 -36
- package/src/validator/per-kind/{component.js → widget.js} +47 -47
- package/src/{component-behavior.js → widget-behavior.js} +3 -3
- package/src/workflows.js +39 -38
- package/template-helpers/react.js +4 -4
- package/template-helpers/sveltekit.js +4 -4
- package/src/generator/components.js +0 -39
- /package/src/resolver/enrich/{component.js → widget.js} +0 -0
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
import { UI_GENERATOR_RENDERED_COMPONENT_PATTERNS } from "../../../ui/taxonomy.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* @typedef {{ id?: string, name?: string }}
|
|
7
|
-
* @typedef {{
|
|
8
|
-
* @typedef {{ patterns?: string[] }}
|
|
9
|
-
* @typedef {Record<string,
|
|
10
|
-
* @typedef {{ itemsExpression?: string,
|
|
11
|
-
* @typedef {{
|
|
6
|
+
* @typedef {{ id?: string, name?: string }} WidgetReference
|
|
7
|
+
* @typedef {{ widget?: WidgetReference, region?: string, pattern?: string }} WidgetUsage
|
|
8
|
+
* @typedef {{ patterns?: string[] }} WidgetContract
|
|
9
|
+
* @typedef {Record<string, WidgetContract>} WidgetContractMap
|
|
10
|
+
* @typedef {{ itemsExpression?: string, widgetContracts?: WidgetContractMap, useTypescript?: boolean }} RenderOptions
|
|
11
|
+
* @typedef {{ widgets?: WidgetUsage[] }} ScreenContract
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -33,44 +33,44 @@ function escapeText(value) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* @param {
|
|
36
|
+
* @param {WidgetUsage} usage
|
|
37
37
|
* @returns {string}
|
|
38
38
|
*/
|
|
39
|
-
function
|
|
40
|
-
return usage?.
|
|
39
|
+
function widgetName(usage) {
|
|
40
|
+
return usage?.widget?.name || usage?.widget?.id || "Widget";
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* @param {
|
|
44
|
+
* @param {WidgetUsage} usage
|
|
45
45
|
* @returns {string}
|
|
46
46
|
*/
|
|
47
|
-
function
|
|
48
|
-
return usage?.
|
|
47
|
+
function widgetId(usage) {
|
|
48
|
+
return usage?.widget?.id || "widget";
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* @param {
|
|
53
|
-
* @param {
|
|
52
|
+
* @param {WidgetUsage} usage
|
|
53
|
+
* @param {WidgetContractMap | undefined} widgetContracts
|
|
54
54
|
* @returns {string[]}
|
|
55
55
|
*/
|
|
56
|
-
function
|
|
57
|
-
const id = usage?.
|
|
58
|
-
const contract = id ?
|
|
56
|
+
function widgetPatterns(usage, widgetContracts) {
|
|
57
|
+
const id = usage?.widget?.id;
|
|
58
|
+
const contract = id ? widgetContracts?.[id] : null;
|
|
59
59
|
return Array.isArray(contract?.patterns) ? contract.patterns : [];
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* @param {
|
|
64
|
-
* @param {
|
|
63
|
+
* @param {WidgetUsage} usage
|
|
64
|
+
* @param {WidgetContractMap | undefined} widgetContracts
|
|
65
65
|
* @param {string} pattern
|
|
66
66
|
* @returns {boolean}
|
|
67
67
|
*/
|
|
68
|
-
function usagePattern(usage,
|
|
69
|
-
return usage?.pattern ||
|
|
68
|
+
function usagePattern(usage, widgetContracts) {
|
|
69
|
+
return usage?.pattern || widgetPatterns(usage, widgetContracts)[0] || null;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
export function
|
|
73
|
-
const pattern = usagePattern(usage,
|
|
72
|
+
export function reactWidgetUsageSupport(usage, widgetContracts) {
|
|
73
|
+
const pattern = usagePattern(usage, widgetContracts);
|
|
74
74
|
return {
|
|
75
75
|
pattern,
|
|
76
76
|
supported: UI_GENERATOR_RENDERED_COMPONENT_PATTERNS.has(pattern || "")
|
|
@@ -78,16 +78,16 @@ export function reactComponentUsageSupport(usage, componentContracts) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
* @param {
|
|
81
|
+
* @param {WidgetUsage} usage
|
|
82
82
|
* @param {RenderOptions} options
|
|
83
83
|
* @returns {string}
|
|
84
84
|
*/
|
|
85
85
|
function renderSummaryStats(usage, options) {
|
|
86
86
|
const items = options.itemsExpression || "items";
|
|
87
|
-
return `<section className="
|
|
87
|
+
return `<section className="widget-card widget-summary" data-topogram-widget="${escapeAttribute(widgetId(usage))}">
|
|
88
88
|
<div>
|
|
89
|
-
<p className="
|
|
90
|
-
<h2>${escapeText(
|
|
89
|
+
<p className="widget-eyebrow">Widget</p>
|
|
90
|
+
<h2>${escapeText(widgetName(usage))}</h2>
|
|
91
91
|
</div>
|
|
92
92
|
<div className="summary-grid">
|
|
93
93
|
<div>
|
|
@@ -107,22 +107,22 @@ function renderSummaryStats(usage, options) {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
|
-
* @param {
|
|
110
|
+
* @param {WidgetUsage} usage
|
|
111
111
|
* @param {RenderOptions} options
|
|
112
112
|
* @returns {string}
|
|
113
113
|
*/
|
|
114
114
|
function renderCollectionTable(usage, options) {
|
|
115
115
|
const items = options.itemsExpression || "items";
|
|
116
116
|
const itemParam = options.useTypescript ? "(item: any)" : "(item)";
|
|
117
|
-
return `<div className="
|
|
118
|
-
<div className="
|
|
117
|
+
return `<div className="widget-card widget-table" data-topogram-widget="${escapeAttribute(widgetId(usage))}">
|
|
118
|
+
<div className="widget-header">
|
|
119
119
|
<div>
|
|
120
|
-
<p className="
|
|
121
|
-
<h2>${escapeText(
|
|
120
|
+
<p className="widget-eyebrow">Widget</p>
|
|
121
|
+
<h2>${escapeText(widgetName(usage))}</h2>
|
|
122
122
|
</div>
|
|
123
123
|
<span className="badge">{${items}.length} items</span>
|
|
124
124
|
</div>
|
|
125
|
-
<div className="table-wrap
|
|
125
|
+
<div className="table-wrap widget-table-wrap">
|
|
126
126
|
<table className="resource-table data-grid">
|
|
127
127
|
<thead>
|
|
128
128
|
<tr>
|
|
@@ -146,18 +146,18 @@ function renderCollectionTable(usage, options) {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/**
|
|
149
|
-
* @param {
|
|
149
|
+
* @param {WidgetUsage} usage
|
|
150
150
|
* @param {RenderOptions} options
|
|
151
151
|
* @returns {string}
|
|
152
152
|
*/
|
|
153
153
|
function renderBoard(usage, options) {
|
|
154
154
|
const items = options.itemsExpression || "items";
|
|
155
155
|
const itemParam = options.useTypescript ? "(item: any)" : "(item)";
|
|
156
|
-
return `<div className="
|
|
157
|
-
<div className="
|
|
156
|
+
return `<div className="widget-card widget-board" data-topogram-widget="${escapeAttribute(widgetId(usage))}">
|
|
157
|
+
<div className="widget-header">
|
|
158
158
|
<div>
|
|
159
|
-
<p className="
|
|
160
|
-
<h2>${escapeText(
|
|
159
|
+
<p className="widget-eyebrow">Widget</p>
|
|
160
|
+
<h2>${escapeText(widgetName(usage))}</h2>
|
|
161
161
|
</div>
|
|
162
162
|
</div>
|
|
163
163
|
<div className="board-grid">
|
|
@@ -176,18 +176,18 @@ function renderBoard(usage, options) {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
/**
|
|
179
|
-
* @param {
|
|
179
|
+
* @param {WidgetUsage} usage
|
|
180
180
|
* @param {RenderOptions} options
|
|
181
181
|
* @returns {string}
|
|
182
182
|
*/
|
|
183
183
|
function renderCalendar(usage, options) {
|
|
184
184
|
const items = options.itemsExpression || "items";
|
|
185
185
|
const itemParam = options.useTypescript ? "(item: any)" : "(item)";
|
|
186
|
-
return `<div className="
|
|
187
|
-
<div className="
|
|
186
|
+
return `<div className="widget-card widget-calendar" data-topogram-widget="${escapeAttribute(widgetId(usage))}">
|
|
187
|
+
<div className="widget-header">
|
|
188
188
|
<div>
|
|
189
|
-
<p className="
|
|
190
|
-
<h2>${escapeText(
|
|
189
|
+
<p className="widget-eyebrow">Widget</p>
|
|
190
|
+
<h2>${escapeText(widgetName(usage))}</h2>
|
|
191
191
|
</div>
|
|
192
192
|
</div>
|
|
193
193
|
<div className="calendar-list">
|
|
@@ -202,13 +202,13 @@ function renderCalendar(usage, options) {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
/**
|
|
205
|
-
* @param {
|
|
205
|
+
* @param {WidgetUsage} usage
|
|
206
206
|
* @param {RenderOptions} options
|
|
207
207
|
* @returns {string}
|
|
208
208
|
*/
|
|
209
209
|
function renderUsage(usage, options) {
|
|
210
|
-
const
|
|
211
|
-
const { pattern } =
|
|
210
|
+
const widgetContracts = options.widgetContracts || {};
|
|
211
|
+
const { pattern } = reactWidgetUsageSupport(usage, widgetContracts);
|
|
212
212
|
if (pattern === "summary_stats") {
|
|
213
213
|
return renderSummaryStats(usage, options);
|
|
214
214
|
}
|
|
@@ -227,10 +227,10 @@ function renderUsage(usage, options) {
|
|
|
227
227
|
/**
|
|
228
228
|
* @param {ScreenContract} screen
|
|
229
229
|
* @param {string} region
|
|
230
|
-
* @returns {
|
|
230
|
+
* @returns {WidgetUsage[]}
|
|
231
231
|
*/
|
|
232
|
-
export function
|
|
233
|
-
return (screen?.
|
|
232
|
+
export function reactWidgetUsagesForRegion(screen, region) {
|
|
233
|
+
return (screen?.widgets || []).filter((usage) => usage?.region === region);
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
/**
|
|
@@ -238,8 +238,8 @@ export function reactComponentUsagesForRegion(screen, region) {
|
|
|
238
238
|
* @param {string} region
|
|
239
239
|
* @returns {boolean}
|
|
240
240
|
*/
|
|
241
|
-
export function
|
|
242
|
-
return
|
|
241
|
+
export function hasReactWidgetRegion(screen, region) {
|
|
242
|
+
return reactWidgetUsagesForRegion(screen, region).length > 0;
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
/**
|
|
@@ -248,8 +248,8 @@ export function hasReactComponentRegion(screen, region) {
|
|
|
248
248
|
* @param {RenderOptions} [options]
|
|
249
249
|
* @returns {string}
|
|
250
250
|
*/
|
|
251
|
-
export function
|
|
252
|
-
const rendered =
|
|
251
|
+
export function renderReactWidgetRegion(screen, region, options = {}) {
|
|
252
|
+
const rendered = reactWidgetUsagesForRegion(screen, region)
|
|
253
253
|
.map((usage) => renderUsage(usage, options))
|
|
254
254
|
.filter(Boolean);
|
|
255
255
|
if (rendered.length === 0) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { buildWebRealization } from "../../../realization/ui/index.js";
|
|
2
2
|
import { getExampleImplementation } from "../../../example-implementation.js";
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "./react-
|
|
4
|
+
reactWidgetUsageSupport,
|
|
5
|
+
renderReactWidgetRegion
|
|
6
|
+
} from "./react-widgets.js";
|
|
7
7
|
import { buildDesignIntentCoverage, renderDesignIntentCss } from "./design-intent.js";
|
|
8
8
|
import { renderApiClientModule, renderLookupModule, renderVisibilityModule } from "./shared.js";
|
|
9
9
|
|
|
@@ -52,7 +52,7 @@ function screenRegions(screen) {
|
|
|
52
52
|
for (const region of screen?.regions || []) {
|
|
53
53
|
if (region?.name) names.add(region.name);
|
|
54
54
|
}
|
|
55
|
-
for (const usage of screen?.
|
|
55
|
+
for (const usage of screen?.widgets || []) {
|
|
56
56
|
if (usage?.region) names.add(usage.region);
|
|
57
57
|
}
|
|
58
58
|
return [...names];
|
|
@@ -76,7 +76,7 @@ function sampleItemsForScreen(screen) {
|
|
|
76
76
|
title: `${title} completed sample`,
|
|
77
77
|
name: `${title} completed sample`,
|
|
78
78
|
message: `${title} completed sample`,
|
|
79
|
-
description: "Second generated row for
|
|
79
|
+
description: "Second generated row for widget rendering checks.",
|
|
80
80
|
status: "completed",
|
|
81
81
|
priority: "low",
|
|
82
82
|
created_at: "2026-01-02"
|
|
@@ -137,8 +137,8 @@ function buildReactScreenPage(screen, contract) {
|
|
|
137
137
|
const sampleItems = sampleItemsForScreen(screen);
|
|
138
138
|
const renderedRegions = screenRegions(screen)
|
|
139
139
|
.map((region) => {
|
|
140
|
-
const rendered =
|
|
141
|
-
|
|
140
|
+
const rendered = renderReactWidgetRegion(screen, region, {
|
|
141
|
+
widgetContracts: contract.widgets,
|
|
142
142
|
itemsExpression: "items",
|
|
143
143
|
useTypescript: true
|
|
144
144
|
});
|
|
@@ -188,8 +188,8 @@ ${renderedRegions || ` ${defaultCollection}`}
|
|
|
188
188
|
`;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
function
|
|
192
|
-
return Array.isArray(screen?.
|
|
191
|
+
function screenWidgetUsages(screen) {
|
|
192
|
+
return Array.isArray(screen?.widgets) ? screen.widgets : [];
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
function screenPagePath(screen) {
|
|
@@ -217,38 +217,38 @@ function buildReactGenerationCoverage(contract, files, routeScreens) {
|
|
|
217
217
|
suggested_fix: "Check the React generator contract-complete route emission for this screen."
|
|
218
218
|
});
|
|
219
219
|
}
|
|
220
|
-
const
|
|
221
|
-
const
|
|
222
|
-
const marker =
|
|
223
|
-
const support =
|
|
220
|
+
const widgetUsages = screenWidgetUsages(screen).map((usage) => {
|
|
221
|
+
const widgetId = usage.widget?.id || null;
|
|
222
|
+
const marker = widgetId ? `data-topogram-widget="${widgetId}"` : null;
|
|
223
|
+
const support = reactWidgetUsageSupport(usage, contract.widgets);
|
|
224
224
|
const usageRendered = Boolean(marker && contents.includes(marker));
|
|
225
|
-
if (
|
|
225
|
+
if (widgetId && rendered && !support.supported) {
|
|
226
226
|
diagnostics.push({
|
|
227
|
-
code: "
|
|
227
|
+
code: "widget_pattern_not_supported",
|
|
228
228
|
severity: "error",
|
|
229
229
|
screen: screen.id,
|
|
230
230
|
route: screen.route,
|
|
231
231
|
region: usage.region || null,
|
|
232
232
|
pattern: support.pattern || null,
|
|
233
|
-
|
|
234
|
-
message: `Screen '${screen.id}' uses
|
|
235
|
-
suggested_fix: "Use a supported
|
|
233
|
+
widget: widgetId,
|
|
234
|
+
message: `Screen '${screen.id}' uses widget '${widgetId}' with unsupported React widget pattern '${support.pattern || "(missing)"}'.`,
|
|
235
|
+
suggested_fix: "Use a supported widget pattern for this generator or provide an implementation override."
|
|
236
236
|
});
|
|
237
237
|
}
|
|
238
|
-
if (
|
|
238
|
+
if (widgetId && rendered && !usageRendered) {
|
|
239
239
|
diagnostics.push({
|
|
240
|
-
code: "
|
|
240
|
+
code: "widget_usage_not_rendered",
|
|
241
241
|
severity: "warning",
|
|
242
242
|
screen: screen.id,
|
|
243
243
|
route: screen.route,
|
|
244
244
|
region: usage.region || null,
|
|
245
|
-
|
|
246
|
-
message: `Screen '${screen.id}' uses
|
|
247
|
-
suggested_fix: "Render the
|
|
245
|
+
widget: widgetId,
|
|
246
|
+
message: `Screen '${screen.id}' uses widget '${widgetId}' but the generated React page does not contain its widget marker.`,
|
|
247
|
+
suggested_fix: "Render the widget region with renderReactWidgetRegion or add a supported widget pattern."
|
|
248
248
|
});
|
|
249
249
|
}
|
|
250
250
|
return {
|
|
251
|
-
|
|
251
|
+
widget: widgetId,
|
|
252
252
|
region: usage.region || null,
|
|
253
253
|
pattern: support.pattern || null,
|
|
254
254
|
supported: support.supported,
|
|
@@ -262,7 +262,7 @@ function buildReactGenerationCoverage(contract, files, routeScreens) {
|
|
|
262
262
|
page: pagePath,
|
|
263
263
|
rendered,
|
|
264
264
|
renderer: rendered ? "generator" : "missing",
|
|
265
|
-
|
|
265
|
+
widget_usages: widgetUsages
|
|
266
266
|
};
|
|
267
267
|
});
|
|
268
268
|
|
|
@@ -273,16 +273,16 @@ function buildReactGenerationCoverage(contract, files, routeScreens) {
|
|
|
273
273
|
projection: {
|
|
274
274
|
id: contract.projection.id,
|
|
275
275
|
name: contract.projection.name,
|
|
276
|
-
|
|
276
|
+
type: contract.projection.type
|
|
277
277
|
},
|
|
278
278
|
summary: {
|
|
279
279
|
routed_screens: screens.length,
|
|
280
280
|
rendered_screens: screens.filter((screen) => screen.rendered).length,
|
|
281
281
|
implementation_screens: 0,
|
|
282
282
|
generator_screens: screens.filter((screen) => screen.renderer === "generator").length,
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
(total, screen) => total + screen.
|
|
283
|
+
widget_usages: screens.reduce((total, screen) => total + screen.widget_usages.length, 0),
|
|
284
|
+
rendered_widget_usages: screens.reduce(
|
|
285
|
+
(total, screen) => total + screen.widget_usages.filter((usage) => usage.rendered).length,
|
|
286
286
|
0
|
|
287
287
|
),
|
|
288
288
|
diagnostics: diagnostics.length,
|
|
@@ -481,7 +481,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
|
481
481
|
);
|
|
482
482
|
`;
|
|
483
483
|
files["src/vite-env.d.ts"] = `/// <reference types="vite/client" />\n`;
|
|
484
|
-
files["src/app.css"] = `${renderDesignIntentCss(contract.
|
|
484
|
+
files["src/app.css"] = `${renderDesignIntentCss(contract.designTokens)}
|
|
485
485
|
|
|
486
486
|
:root {
|
|
487
487
|
font-family: system-ui, sans-serif;
|
|
@@ -536,11 +536,11 @@ button:focus-visible, .button-link:focus-visible, a:focus-visible, input:focus-v
|
|
|
536
536
|
.muted { color: var(--topogram-muted-color); }
|
|
537
537
|
.empty-state { padding: 1rem 0; }
|
|
538
538
|
.error-text { color: #b42318; }
|
|
539
|
-
.
|
|
540
|
-
.
|
|
541
|
-
.
|
|
542
|
-
.
|
|
543
|
-
.
|
|
539
|
+
.widget-card { border: 1px solid var(--topogram-border-color); border-radius: var(--topogram-radius-card); background: var(--topogram-surface-subtle); padding: 1rem; margin-top: 1rem; }
|
|
540
|
+
.widget-header { display: flex; align-items: center; justify-content: space-between; gap: var(--topogram-space-unit); flex-wrap: wrap; }
|
|
541
|
+
.widget-eyebrow { margin: 0 0 0.25rem; color: var(--topogram-muted-color); font-size: 0.75rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
|
|
542
|
+
.widget-card h2, .widget-card h3 { margin: 0; }
|
|
543
|
+
.widget-table-wrap { margin-top: 1rem; }
|
|
544
544
|
.summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); gap: 0.75rem; }
|
|
545
545
|
.summary-grid div, .board-column { border: 1px solid #e0e8f1; border-radius: var(--topogram-radius-control); background: white; padding: 0.85rem; }
|
|
546
546
|
.summary-grid strong { display: block; font-size: 1.5rem; }
|
|
@@ -553,7 +553,7 @@ button:focus-visible, .button-link:focus-visible, a:focus-visible, input:focus-v
|
|
|
553
553
|
`;
|
|
554
554
|
files["src/App.tsx"] = buildAppTsx(contract, webReferenceWithDefaults);
|
|
555
555
|
files["src/lib/topogram/api-contracts.json"] = `${JSON.stringify(realization.apiContracts, null, 2)}\n`;
|
|
556
|
-
files["src/lib/topogram/ui-
|
|
556
|
+
files["src/lib/topogram/ui-surface-contract.json"] = `${JSON.stringify(contract, null, 2)}\n`;
|
|
557
557
|
files["src/lib/auth/visibility.ts"] = buildReactVisibilityModule();
|
|
558
558
|
files["src/lib/api/client.ts"] = buildReactClientModule(webReferenceWithDefaults);
|
|
559
559
|
files["src/lib/api/lookups.ts"] = buildLookupModule(webReferenceWithDefaults);
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
import { UI_GENERATOR_RENDERED_COMPONENT_PATTERNS } from "../../../ui/taxonomy.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* @typedef {{ id?: string, name?: string }}
|
|
7
|
-
* @typedef {{
|
|
8
|
-
* @typedef {{ patterns?: string[] }}
|
|
9
|
-
* @typedef {Record<string,
|
|
10
|
-
* @typedef {{ itemsExpression?: string,
|
|
11
|
-
* @typedef {{
|
|
6
|
+
* @typedef {{ id?: string, name?: string }} WidgetReference
|
|
7
|
+
* @typedef {{ widget?: WidgetReference, region?: string, pattern?: string }} WidgetUsage
|
|
8
|
+
* @typedef {{ patterns?: string[] }} WidgetContract
|
|
9
|
+
* @typedef {Record<string, WidgetContract>} WidgetContractMap
|
|
10
|
+
* @typedef {{ itemsExpression?: string, widgetContracts?: WidgetContractMap, useTypescript?: boolean }} RenderOptions
|
|
11
|
+
* @typedef {{ widgets?: WidgetUsage[] }} ScreenContract
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -24,44 +24,44 @@ function escapeHtml(value) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* @param {
|
|
27
|
+
* @param {WidgetUsage} usage
|
|
28
28
|
* @returns {string}
|
|
29
29
|
*/
|
|
30
|
-
function
|
|
31
|
-
return usage?.
|
|
30
|
+
function widgetName(usage) {
|
|
31
|
+
return usage?.widget?.name || usage?.widget?.id || "Widget";
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* @param {
|
|
35
|
+
* @param {WidgetUsage} usage
|
|
36
36
|
* @returns {string}
|
|
37
37
|
*/
|
|
38
|
-
function
|
|
39
|
-
return usage?.
|
|
38
|
+
function widgetId(usage) {
|
|
39
|
+
return usage?.widget?.id || "widget";
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* @param {
|
|
44
|
-
* @param {
|
|
43
|
+
* @param {WidgetUsage} usage
|
|
44
|
+
* @param {WidgetContractMap | undefined} widgetContracts
|
|
45
45
|
* @returns {string[]}
|
|
46
46
|
*/
|
|
47
|
-
function
|
|
48
|
-
const id = usage?.
|
|
49
|
-
const contract = id ?
|
|
47
|
+
function widgetPatterns(usage, widgetContracts) {
|
|
48
|
+
const id = usage?.widget?.id;
|
|
49
|
+
const contract = id ? widgetContracts?.[id] : null;
|
|
50
50
|
return Array.isArray(contract?.patterns) ? contract.patterns : [];
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
* @param {
|
|
55
|
-
* @param {
|
|
54
|
+
* @param {WidgetUsage} usage
|
|
55
|
+
* @param {WidgetContractMap | undefined} widgetContracts
|
|
56
56
|
* @param {string} pattern
|
|
57
57
|
* @returns {boolean}
|
|
58
58
|
*/
|
|
59
|
-
function usagePattern(usage,
|
|
60
|
-
return usage?.pattern ||
|
|
59
|
+
function usagePattern(usage, widgetContracts) {
|
|
60
|
+
return usage?.pattern || widgetPatterns(usage, widgetContracts)[0] || null;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
export function
|
|
64
|
-
const pattern = usagePattern(usage,
|
|
63
|
+
export function svelteKitWidgetUsageSupport(usage, widgetContracts) {
|
|
64
|
+
const pattern = usagePattern(usage, widgetContracts);
|
|
65
65
|
return {
|
|
66
66
|
pattern,
|
|
67
67
|
supported: UI_GENERATOR_RENDERED_COMPONENT_PATTERNS.has(pattern || "")
|
|
@@ -69,16 +69,16 @@ export function svelteKitComponentUsageSupport(usage, componentContracts) {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* @param {
|
|
72
|
+
* @param {WidgetUsage} usage
|
|
73
73
|
* @param {RenderOptions} options
|
|
74
74
|
* @returns {string}
|
|
75
75
|
*/
|
|
76
76
|
function renderSummaryStats(usage, options) {
|
|
77
77
|
const items = options.itemsExpression || "data.result.items";
|
|
78
|
-
return `<section class="
|
|
78
|
+
return `<section class="widget-card widget-summary" data-topogram-widget="${escapeHtml(widgetId(usage))}">
|
|
79
79
|
<div>
|
|
80
|
-
<p class="
|
|
81
|
-
<h2>${escapeHtml(
|
|
80
|
+
<p class="widget-eyebrow">Widget</p>
|
|
81
|
+
<h2>${escapeHtml(widgetName(usage))}</h2>
|
|
82
82
|
</div>
|
|
83
83
|
<div class="summary-grid">
|
|
84
84
|
<div>
|
|
@@ -98,21 +98,21 @@ function renderSummaryStats(usage, options) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
/**
|
|
101
|
-
* @param {
|
|
101
|
+
* @param {WidgetUsage} usage
|
|
102
102
|
* @param {RenderOptions} options
|
|
103
103
|
* @returns {string}
|
|
104
104
|
*/
|
|
105
105
|
function renderCollectionTable(usage, options) {
|
|
106
106
|
const items = options.itemsExpression || "data.result.items";
|
|
107
|
-
return `<div class="
|
|
108
|
-
<div class="
|
|
107
|
+
return `<div class="widget-card widget-table" data-topogram-widget="${escapeHtml(widgetId(usage))}">
|
|
108
|
+
<div class="widget-header">
|
|
109
109
|
<div>
|
|
110
|
-
<p class="
|
|
111
|
-
<h2>${escapeHtml(
|
|
110
|
+
<p class="widget-eyebrow">Widget</p>
|
|
111
|
+
<h2>${escapeHtml(widgetName(usage))}</h2>
|
|
112
112
|
</div>
|
|
113
113
|
<span class="badge">{${items}.length} items</span>
|
|
114
114
|
</div>
|
|
115
|
-
<div class="table-wrap
|
|
115
|
+
<div class="table-wrap widget-table-wrap">
|
|
116
116
|
<table class="resource-table data-grid">
|
|
117
117
|
<thead>
|
|
118
118
|
<tr>
|
|
@@ -136,17 +136,17 @@ function renderCollectionTable(usage, options) {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
/**
|
|
139
|
-
* @param {
|
|
139
|
+
* @param {WidgetUsage} usage
|
|
140
140
|
* @param {RenderOptions} options
|
|
141
141
|
* @returns {string}
|
|
142
142
|
*/
|
|
143
143
|
function renderBoard(usage, options) {
|
|
144
144
|
const items = options.itemsExpression || "data.result.items";
|
|
145
|
-
return `<div class="
|
|
146
|
-
<div class="
|
|
145
|
+
return `<div class="widget-card widget-board" data-topogram-widget="${escapeHtml(widgetId(usage))}">
|
|
146
|
+
<div class="widget-header">
|
|
147
147
|
<div>
|
|
148
|
-
<p class="
|
|
149
|
-
<h2>${escapeHtml(
|
|
148
|
+
<p class="widget-eyebrow">Widget</p>
|
|
149
|
+
<h2>${escapeHtml(widgetName(usage))}</h2>
|
|
150
150
|
</div>
|
|
151
151
|
</div>
|
|
152
152
|
<div class="board-grid">
|
|
@@ -163,17 +163,17 @@ function renderBoard(usage, options) {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
/**
|
|
166
|
-
* @param {
|
|
166
|
+
* @param {WidgetUsage} usage
|
|
167
167
|
* @param {RenderOptions} options
|
|
168
168
|
* @returns {string}
|
|
169
169
|
*/
|
|
170
170
|
function renderCalendar(usage, options) {
|
|
171
171
|
const items = options.itemsExpression || "data.result.items";
|
|
172
|
-
return `<div class="
|
|
173
|
-
<div class="
|
|
172
|
+
return `<div class="widget-card widget-calendar" data-topogram-widget="${escapeHtml(widgetId(usage))}">
|
|
173
|
+
<div class="widget-header">
|
|
174
174
|
<div>
|
|
175
|
-
<p class="
|
|
176
|
-
<h2>${escapeHtml(
|
|
175
|
+
<p class="widget-eyebrow">Widget</p>
|
|
176
|
+
<h2>${escapeHtml(widgetName(usage))}</h2>
|
|
177
177
|
</div>
|
|
178
178
|
</div>
|
|
179
179
|
<div class="calendar-list">
|
|
@@ -188,13 +188,13 @@ function renderCalendar(usage, options) {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
/**
|
|
191
|
-
* @param {
|
|
191
|
+
* @param {WidgetUsage} usage
|
|
192
192
|
* @param {RenderOptions} options
|
|
193
193
|
* @returns {string}
|
|
194
194
|
*/
|
|
195
195
|
function renderUsage(usage, options) {
|
|
196
|
-
const
|
|
197
|
-
const { pattern } =
|
|
196
|
+
const widgetContracts = options.widgetContracts || {};
|
|
197
|
+
const { pattern } = svelteKitWidgetUsageSupport(usage, widgetContracts);
|
|
198
198
|
if (pattern === "summary_stats") {
|
|
199
199
|
return renderSummaryStats(usage, options);
|
|
200
200
|
}
|
|
@@ -213,10 +213,10 @@ function renderUsage(usage, options) {
|
|
|
213
213
|
/**
|
|
214
214
|
* @param {ScreenContract} screen
|
|
215
215
|
* @param {string} region
|
|
216
|
-
* @returns {
|
|
216
|
+
* @returns {WidgetUsage[]}
|
|
217
217
|
*/
|
|
218
|
-
export function
|
|
219
|
-
return (screen?.
|
|
218
|
+
export function svelteKitWidgetUsagesForRegion(screen, region) {
|
|
219
|
+
return (screen?.widgets || []).filter((usage) => usage?.region === region);
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
/**
|
|
@@ -224,8 +224,8 @@ export function svelteKitComponentUsagesForRegion(screen, region) {
|
|
|
224
224
|
* @param {string} region
|
|
225
225
|
* @returns {boolean}
|
|
226
226
|
*/
|
|
227
|
-
export function
|
|
228
|
-
return
|
|
227
|
+
export function hasSvelteKitWidgetRegion(screen, region) {
|
|
228
|
+
return svelteKitWidgetUsagesForRegion(screen, region).length > 0;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
/**
|
|
@@ -234,8 +234,8 @@ export function hasSvelteKitComponentRegion(screen, region) {
|
|
|
234
234
|
* @param {RenderOptions} [options]
|
|
235
235
|
* @returns {string}
|
|
236
236
|
*/
|
|
237
|
-
export function
|
|
238
|
-
const rendered =
|
|
237
|
+
export function renderSvelteKitWidgetRegion(screen, region, options = {}) {
|
|
238
|
+
const rendered = svelteKitWidgetUsagesForRegion(screen, region)
|
|
239
239
|
.map((usage) => renderUsage(usage, options))
|
|
240
240
|
.filter(Boolean);
|
|
241
241
|
if (rendered.length === 0) {
|