@topogram/cli 0.3.48 → 0.3.50

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.
@@ -0,0 +1,308 @@
1
+ // @ts-check
2
+
3
+ const DEFAULT_DESIGN_INTENT = Object.freeze({
4
+ density: "comfortable",
5
+ tone: "neutral",
6
+ radiusScale: "medium",
7
+ colorRoles: Object.freeze({
8
+ primary: "accent"
9
+ }),
10
+ typographyRoles: Object.freeze({
11
+ body: "readable",
12
+ heading: "prominent"
13
+ }),
14
+ actionRoles: Object.freeze({
15
+ primary: "prominent"
16
+ }),
17
+ accessibility: Object.freeze({
18
+ contrast: "aa",
19
+ focus: "visible"
20
+ })
21
+ });
22
+
23
+ const DENSITY_VALUES = {
24
+ compact: {
25
+ spaceUnit: "0.75rem",
26
+ pagePadding: "1.5rem 1rem 3rem",
27
+ controlPadding: "0.55rem 0.75rem"
28
+ },
29
+ comfortable: {
30
+ spaceUnit: "1rem",
31
+ pagePadding: "2rem 1.25rem 4rem",
32
+ controlPadding: "0.7rem 1rem"
33
+ },
34
+ spacious: {
35
+ spaceUnit: "1.25rem",
36
+ pagePadding: "2.5rem 1.5rem 5rem",
37
+ controlPadding: "0.85rem 1.15rem"
38
+ }
39
+ };
40
+
41
+ const RADIUS_VALUES = {
42
+ none: {
43
+ card: "0",
44
+ control: "0",
45
+ pill: "0"
46
+ },
47
+ small: {
48
+ card: "8px",
49
+ control: "8px",
50
+ pill: "999px"
51
+ },
52
+ medium: {
53
+ card: "14px",
54
+ control: "12px",
55
+ pill: "999px"
56
+ },
57
+ large: {
58
+ card: "18px",
59
+ control: "16px",
60
+ pill: "999px"
61
+ }
62
+ };
63
+
64
+ const COLOR_VALUES = {
65
+ accent: "#0f5cc0",
66
+ critical: "#b42318",
67
+ danger: "#b42318",
68
+ success: "#027a48",
69
+ warning: "#b54708",
70
+ neutral: "#516173",
71
+ muted: "#607284"
72
+ };
73
+
74
+ const TONE_VALUES = {
75
+ neutral: {
76
+ text: "#182026",
77
+ muted: "#607284",
78
+ background: "linear-gradient(180deg, #f5f7fb 0%, #edf2f7 100%)",
79
+ surface: "#ffffff",
80
+ surfaceSubtle: "#fbfcfe",
81
+ border: "#d7e1ec"
82
+ },
83
+ operational: {
84
+ text: "#182026",
85
+ muted: "#607284",
86
+ background: "linear-gradient(180deg, #f5f7fb 0%, #edf2f7 100%)",
87
+ surface: "#ffffff",
88
+ surfaceSubtle: "#fbfcfe",
89
+ border: "#d7e1ec"
90
+ },
91
+ editorial: {
92
+ text: "#1f2933",
93
+ muted: "#5c6670",
94
+ background: "linear-gradient(180deg, #f8fafc 0%, #eef2f7 100%)",
95
+ surface: "#ffffff",
96
+ surfaceSubtle: "#f8fafc",
97
+ border: "#d8dee8"
98
+ },
99
+ playful: {
100
+ text: "#1f2937",
101
+ muted: "#5b6472",
102
+ background: "linear-gradient(180deg, #f7fbff 0%, #eef6ff 100%)",
103
+ surface: "#ffffff",
104
+ surfaceSubtle: "#f7fbff",
105
+ border: "#d6e4f5"
106
+ }
107
+ };
108
+
109
+ /**
110
+ * @param {string|null|undefined} value
111
+ * @returns {string}
112
+ */
113
+ function cssToken(value) {
114
+ return String(value || "default").replace(/[^A-Za-z0-9_-]/g, "_");
115
+ }
116
+
117
+ /**
118
+ * @param {Record<string, string>|null|undefined} source
119
+ * @param {Record<string, string>} fallback
120
+ * @returns {Record<string, string>}
121
+ */
122
+ function mergeStringMap(source, fallback) {
123
+ return {
124
+ ...fallback,
125
+ ...(source && typeof source === "object" ? source : {})
126
+ };
127
+ }
128
+
129
+ /**
130
+ * @param {any} design
131
+ * @returns {{
132
+ * density: string,
133
+ * tone: string,
134
+ * radiusScale: string,
135
+ * colorRoles: Record<string, string>,
136
+ * typographyRoles: Record<string, string>,
137
+ * actionRoles: Record<string, string>,
138
+ * accessibility: Record<string, string>
139
+ * }}
140
+ */
141
+ export function normalizeDesignIntent(design) {
142
+ const value = design && typeof design === "object" ? design : {};
143
+ return {
144
+ density: typeof value.density === "string" ? value.density : DEFAULT_DESIGN_INTENT.density,
145
+ tone: typeof value.tone === "string" ? value.tone : DEFAULT_DESIGN_INTENT.tone,
146
+ radiusScale: typeof value.radiusScale === "string" ? value.radiusScale : DEFAULT_DESIGN_INTENT.radiusScale,
147
+ colorRoles: mergeStringMap(value.colorRoles, DEFAULT_DESIGN_INTENT.colorRoles),
148
+ typographyRoles: mergeStringMap(value.typographyRoles, DEFAULT_DESIGN_INTENT.typographyRoles),
149
+ actionRoles: mergeStringMap(value.actionRoles, DEFAULT_DESIGN_INTENT.actionRoles),
150
+ accessibility: mergeStringMap(value.accessibility, DEFAULT_DESIGN_INTENT.accessibility)
151
+ };
152
+ }
153
+
154
+ /**
155
+ * @param {Record<string, string>} map
156
+ * @param {string} prefix
157
+ * @returns {string[]}
158
+ */
159
+ function tokenMapLines(map, prefix) {
160
+ return Object.entries(map)
161
+ .sort(([left], [right]) => left.localeCompare(right))
162
+ .map(([role, value]) => ` --topogram-design-${prefix}-${cssToken(role)}: ${cssToken(value)};`);
163
+ }
164
+
165
+ /**
166
+ * @param {any} design
167
+ * @returns {string}
168
+ */
169
+ export function renderDesignIntentCss(design) {
170
+ const normalized = normalizeDesignIntent(design);
171
+ const tone = TONE_VALUES[normalized.tone] || TONE_VALUES.neutral;
172
+ const density = DENSITY_VALUES[normalized.density] || DENSITY_VALUES.comfortable;
173
+ const radius = RADIUS_VALUES[normalized.radiusScale] || RADIUS_VALUES.medium;
174
+ const primaryColor = COLOR_VALUES[normalized.colorRoles.primary] || COLOR_VALUES.accent;
175
+ const dangerColor = COLOR_VALUES[normalized.colorRoles.danger] || COLOR_VALUES.critical;
176
+ const focusColor = primaryColor;
177
+
178
+ return `/* Topogram semantic design intent. Generators map normalized UI tokens to stack CSS here. */
179
+ :root {
180
+ --topogram-design-density: ${cssToken(normalized.density)};
181
+ --topogram-design-tone: ${cssToken(normalized.tone)};
182
+ --topogram-design-radius-scale: ${cssToken(normalized.radiusScale)};
183
+ ${tokenMapLines(normalized.colorRoles, "color").join("\n")}
184
+ ${tokenMapLines(normalized.typographyRoles, "typography").join("\n")}
185
+ ${tokenMapLines(normalized.actionRoles, "action").join("\n")}
186
+ ${tokenMapLines(normalized.accessibility, "accessibility").join("\n")}
187
+ --topogram-space-unit: ${density.spaceUnit};
188
+ --topogram-page-padding: ${density.pagePadding};
189
+ --topogram-control-padding: ${density.controlPadding};
190
+ --topogram-radius-card: ${radius.card};
191
+ --topogram-radius-control: ${radius.control};
192
+ --topogram-radius-pill: ${radius.pill};
193
+ --topogram-text-color: ${tone.text};
194
+ --topogram-muted-color: ${tone.muted};
195
+ --topogram-surface-background: ${tone.background};
196
+ --topogram-surface-card: ${tone.surface};
197
+ --topogram-surface-subtle: ${tone.surfaceSubtle};
198
+ --topogram-border-color: ${tone.border};
199
+ --topogram-action-primary-background: ${primaryColor};
200
+ --topogram-action-primary-color: #ffffff;
201
+ --topogram-action-danger-background: ${dangerColor};
202
+ --topogram-focus-outline: 3px solid ${focusColor};
203
+ }
204
+ `;
205
+ }
206
+
207
+ /**
208
+ * @param {ReturnType<typeof normalizeDesignIntent>} design
209
+ * @returns {Array<{ category: string, role: string|null, value: string, marker: string }>}
210
+ */
211
+ function requiredDesignMarkers(design) {
212
+ return [
213
+ {
214
+ category: "density",
215
+ role: null,
216
+ value: design.density,
217
+ marker: "--topogram-design-density"
218
+ },
219
+ {
220
+ category: "tone",
221
+ role: null,
222
+ value: design.tone,
223
+ marker: "--topogram-design-tone"
224
+ },
225
+ {
226
+ category: "radius_scale",
227
+ role: null,
228
+ value: design.radiusScale,
229
+ marker: "--topogram-design-radius-scale"
230
+ },
231
+ ...Object.entries(design.colorRoles).map(([role, value]) => ({
232
+ category: "color_roles",
233
+ role,
234
+ value,
235
+ marker: `--topogram-design-color-${cssToken(role)}`
236
+ })),
237
+ ...Object.entries(design.typographyRoles).map(([role, value]) => ({
238
+ category: "typography_roles",
239
+ role,
240
+ value,
241
+ marker: `--topogram-design-typography-${cssToken(role)}`
242
+ })),
243
+ ...Object.entries(design.actionRoles).map(([role, value]) => ({
244
+ category: "action_roles",
245
+ role,
246
+ value,
247
+ marker: `--topogram-design-action-${cssToken(role)}`
248
+ })),
249
+ ...Object.entries(design.accessibility).map(([role, value]) => ({
250
+ category: "accessibility",
251
+ role,
252
+ value,
253
+ marker: `--topogram-design-accessibility-${cssToken(role)}`
254
+ }))
255
+ ];
256
+ }
257
+
258
+ /**
259
+ * @param {any} contract
260
+ * @param {Record<string, string>} files
261
+ * @param {string} cssPath
262
+ * @returns {{ coverage: any, diagnostics: any[] }}
263
+ */
264
+ export function buildDesignIntentCoverage(contract, files, cssPath) {
265
+ const design = normalizeDesignIntent(contract?.design);
266
+ const css = files[cssPath] || "";
267
+ const markers = requiredDesignMarkers(design);
268
+ const mapped = markers.filter((item) => css.includes(item.marker));
269
+ const missing = markers.filter((item) => !css.includes(item.marker));
270
+ const coverage = {
271
+ status: missing.length === 0 ? "mapped" : "unmapped",
272
+ css_path: cssPath,
273
+ tokens: {
274
+ density: design.density,
275
+ tone: design.tone,
276
+ radius_scale: design.radiusScale,
277
+ color_roles: design.colorRoles,
278
+ typography_roles: design.typographyRoles,
279
+ action_roles: design.actionRoles,
280
+ accessibility: design.accessibility
281
+ },
282
+ mapped: mapped.map((item) => ({
283
+ category: item.category,
284
+ role: item.role,
285
+ value: item.value,
286
+ marker: item.marker
287
+ })),
288
+ missing: missing.map((item) => ({
289
+ category: item.category,
290
+ role: item.role,
291
+ value: item.value,
292
+ marker: item.marker
293
+ }))
294
+ };
295
+ return {
296
+ coverage,
297
+ diagnostics: missing.map((item) => ({
298
+ code: "design_intent_not_mapped",
299
+ severity: "error",
300
+ category: item.category,
301
+ role: item.role,
302
+ value: item.value,
303
+ marker: item.marker,
304
+ message: `UI design intent token '${item.category}${item.role ? `.${item.role}` : ""}' was not mapped into ${cssPath}.`,
305
+ suggested_fix: "Render Topogram semantic design variables with renderDesignIntentCss before writing the web stylesheet."
306
+ }))
307
+ };
308
+ }
@@ -1,5 +1,7 @@
1
1
  // @ts-check
2
2
 
3
+ import { UI_GENERATOR_RENDERED_COMPONENT_PATTERNS } from "../../../ui/taxonomy.js";
4
+
3
5
  /**
4
6
  * @typedef {{ id?: string, name?: string }} ComponentReference
5
7
  * @typedef {{ component?: ComponentReference, region?: string, pattern?: string }} ComponentUsage
@@ -71,13 +73,7 @@ export function reactComponentUsageSupport(usage, componentContracts) {
71
73
  const pattern = usagePattern(usage, componentContracts);
72
74
  return {
73
75
  pattern,
74
- supported: [
75
- "summary_stats",
76
- "board_view",
77
- "calendar_view",
78
- "resource_table",
79
- "data_grid_view"
80
- ].includes(pattern || "")
76
+ supported: UI_GENERATOR_RENDERED_COMPONENT_PATTERNS.has(pattern || "")
81
77
  };
82
78
  }
83
79
 
@@ -4,6 +4,7 @@ import {
4
4
  reactComponentUsageSupport,
5
5
  renderReactComponentRegion
6
6
  } from "./react-components.js";
7
+ import { buildDesignIntentCoverage, renderDesignIntentCss } from "./design-intent.js";
7
8
  import { renderApiClientModule, renderLookupModule, renderVisibilityModule } from "./shared.js";
8
9
 
9
10
  function componentNameForScreen(screenId) {
@@ -197,6 +198,8 @@ function screenPagePath(screen) {
197
198
 
198
199
  function buildReactGenerationCoverage(contract, files, routeScreens) {
199
200
  const diagnostics = [];
201
+ const designIntent = buildDesignIntentCoverage(contract, files, "src/app.css");
202
+ diagnostics.push(...designIntent.diagnostics);
200
203
  const routeScreenIds = new Set(routeScreens.map((screen) => screen.id));
201
204
  const screens = (contract.screens || [])
202
205
  .filter((screen) => routeScreenIds.has(screen.id))
@@ -286,6 +289,7 @@ function buildReactGenerationCoverage(contract, files, routeScreens) {
286
289
  errors: diagnostics.filter((diagnostic) => diagnostic.severity === "error").length,
287
290
  warnings: diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length
288
291
  },
292
+ design_intent: designIntent.coverage,
289
293
  screens,
290
294
  diagnostics
291
295
  };
@@ -477,42 +481,45 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
477
481
  );
478
482
  `;
479
483
  files["src/vite-env.d.ts"] = `/// <reference types="vite/client" />\n`;
480
- files["src/app.css"] = `:root {
484
+ files["src/app.css"] = `${renderDesignIntentCss(contract.design)}
485
+
486
+ :root {
481
487
  font-family: system-ui, sans-serif;
482
- color: #182026;
483
- background: linear-gradient(180deg, #f5f7fb 0%, #edf2f7 100%);
488
+ color: var(--topogram-text-color);
489
+ background: var(--topogram-surface-background);
484
490
  }
485
491
  body { margin: 0; }
486
- a { color: #0f5cc0; text-decoration: none; }
492
+ a { color: var(--topogram-action-primary-background); text-decoration: none; }
487
493
  a:hover { text-decoration: underline; }
488
- main { max-width: 72rem; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
494
+ main { max-width: 72rem; margin: 0 auto; padding: var(--topogram-page-padding); }
489
495
  .app-shell { min-height: 100vh; }
490
496
  .app-workspace { display: grid; grid-template-columns: 18rem minmax(0, 1fr); min-height: 100vh; }
491
497
  .app-main-shell { min-width: 0; }
492
- .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); }
493
- .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); }
498
+ .app-sidebar { position: sticky; top: 0; align-self: start; min-height: 100vh; display: grid; align-content: start; gap: var(--topogram-space-unit); 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); }
499
+ .app-nav { position: sticky; top: 0; z-index: 10; display: flex; align-items: center; justify-content: space-between; gap: var(--topogram-space-unit); 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); }
494
500
  .app-nav-links, .app-nav nav, .app-tabbar { display: flex; gap: 0.75rem; flex-wrap: wrap; }
495
501
  .app-nav.menu-bar { border-bottom-style: dashed; }
496
502
  .app-nav.compact { justify-content: flex-end; }
497
503
  .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); }
498
504
  .brand { font-weight: 700; letter-spacing: 0.01em; }
499
- .brand-mark { font-weight: 700; color: #607284; }
500
- .command-palette-button { background: #182026; color: white; border: none; border-radius: 999px; padding: 0.6rem 0.9rem; font: inherit; cursor: pointer; }
501
- .app-footer { max-width: 72rem; margin: 0 auto; padding: 0 1.25rem 2rem; color: #607284; }
502
- .card { background: white; border-radius: 16px; padding: 1.25rem; box-shadow: 0 12px 30px rgba(24, 32, 38, 0.08); }
503
- .hero, .stack, .grid, .filters, .task-meta, .resource-meta, .definition-list { display: grid; gap: 1rem; }
505
+ .brand-mark { font-weight: 700; color: var(--topogram-muted-color); }
506
+ .command-palette-button { background: var(--topogram-text-color); color: white; border: none; border-radius: var(--topogram-radius-pill); padding: var(--topogram-control-padding); font: inherit; cursor: pointer; }
507
+ .app-footer { max-width: 72rem; margin: 0 auto; padding: 0 1.25rem 2rem; color: var(--topogram-muted-color); }
508
+ .card { background: var(--topogram-surface-card); border-radius: var(--topogram-radius-card); padding: 1.25rem; box-shadow: 0 12px 30px rgba(24, 32, 38, 0.08); }
509
+ .hero, .stack, .grid, .filters, .resource-meta, .definition-list { display: grid; gap: var(--topogram-space-unit); }
504
510
  .grid.two { grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
505
511
  .filters { grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); margin: 1rem 0 1.25rem; }
506
512
  label { display: grid; gap: 0.35rem; font-size: 0.95rem; }
507
513
  input, textarea, button, select { font: inherit; }
508
- input, textarea, select { width: 100%; box-sizing: border-box; border: 1px solid #c9d4e2; border-radius: 12px; padding: 0.7rem 0.85rem; background: white; }
514
+ input, textarea, select { width: 100%; box-sizing: border-box; border: 1px solid #c9d4e2; border-radius: var(--topogram-radius-control); padding: var(--topogram-control-padding); background: white; }
509
515
  textarea { min-height: 8rem; resize: vertical; }
510
- 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; }
511
- .button-link.secondary { background: #e9eef6; color: #182026; }
516
+ button, .button-link { display: inline-flex; align-items: center; justify-content: center; gap: 0.35rem; border: none; border-radius: var(--topogram-radius-pill); padding: var(--topogram-control-padding); background: var(--topogram-action-primary-background); color: var(--topogram-action-primary-color); font-weight: 600; cursor: pointer; }
517
+ button:focus-visible, .button-link:focus-visible, a:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-visible { outline: var(--topogram-focus-outline); outline-offset: 2px; }
518
+ .button-link.secondary { background: #e9eef6; color: var(--topogram-text-color); }
512
519
  .button-row { display: flex; gap: 0.75rem; flex-wrap: wrap; align-items: center; }
513
- .task-list, .resource-list { list-style: none; padding: 0; margin: 1rem 0 0; display: grid; gap: 0.75rem; }
514
- .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; }
515
- .table-wrap { margin-top: 1rem; overflow-x: auto; border: 1px solid #d7e1ec; border-radius: 14px; background: white; }
520
+ .resource-list { list-style: none; padding: 0; margin: 1rem 0 0; display: grid; gap: 0.75rem; }
521
+ .resource-list li { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--topogram-space-unit); padding: 1rem; border: 1px solid #e0e8f1; border-radius: var(--topogram-radius-card); background: var(--topogram-surface-subtle); }
522
+ .table-wrap { margin-top: 1rem; overflow-x: auto; border: 1px solid var(--topogram-border-color); border-radius: var(--topogram-radius-card); background: white; }
516
523
  .resource-table { width: 100%; border-collapse: collapse; min-width: 42rem; }
517
524
  .resource-table th, .resource-table td { padding: 0.85rem 1rem; text-align: left; border-bottom: 1px solid #e7edf5; vertical-align: top; }
518
525
  .resource-table th { font-size: 0.85rem; letter-spacing: 0.04em; text-transform: uppercase; color: #516173; background: #f8fbff; }
@@ -521,28 +528,28 @@ button, .button-link { display: inline-flex; align-items: center; justify-conten
521
528
  .data-grid thead th { position: sticky; top: 0; z-index: 1; background: #eef5ff; }
522
529
  .data-grid-shell { box-shadow: inset 0 0 0 1px rgba(15, 92, 192, 0.04); }
523
530
  .cell-stack { display: grid; gap: 0.35rem; }
524
- .cell-secondary { color: #607284; font-size: 0.92rem; }
531
+ .cell-secondary { color: var(--topogram-muted-color); font-size: 0.92rem; }
525
532
  .definition-list { grid-template-columns: minmax(8rem, 12rem) 1fr; align-items: start; }
526
533
  .definition-list dt { font-weight: 600; color: #516173; }
527
534
  .definition-list dd { margin: 0; }
528
- .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; }
529
- .muted { color: #607284; }
535
+ .badge { display: inline-flex; align-items: center; padding: 0.25rem 0.6rem; border-radius: var(--topogram-radius-pill); background: #eef4ff; color: var(--topogram-action-primary-background); font-size: 0.85rem; font-weight: 600; }
536
+ .muted { color: var(--topogram-muted-color); }
530
537
  .empty-state { padding: 1rem 0; }
531
538
  .error-text { color: #b42318; }
532
- .component-card { border: 1px solid #d7e1ec; border-radius: 14px; background: #fbfcfe; padding: 1rem; margin-top: 1rem; }
533
- .component-header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
534
- .component-eyebrow { margin: 0 0 0.25rem; color: #607284; font-size: 0.75rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
539
+ .component-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
+ .component-header { display: flex; align-items: center; justify-content: space-between; gap: var(--topogram-space-unit); flex-wrap: wrap; }
541
+ .component-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; }
535
542
  .component-card h2, .component-card h3 { margin: 0; }
536
543
  .component-table-wrap { margin-top: 1rem; }
537
544
  .summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); gap: 0.75rem; }
538
- .summary-grid div, .board-column { border: 1px solid #e0e8f1; border-radius: 12px; background: white; padding: 0.85rem; }
545
+ .summary-grid div, .board-column { border: 1px solid #e0e8f1; border-radius: var(--topogram-radius-control); background: white; padding: 0.85rem; }
539
546
  .summary-grid strong { display: block; font-size: 1.5rem; }
540
- .summary-grid span, .calendar-list span { color: #607284; font-size: 0.9rem; }
547
+ .summary-grid span, .calendar-list span { color: var(--topogram-muted-color); font-size: 0.9rem; }
541
548
  .board-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: 0.75rem; margin-top: 1rem; }
542
- .board-card, .calendar-card { display: grid; gap: 0.25rem; border: 1px solid #e0e8f1; border-radius: 10px; background: #f8fbff; padding: 0.75rem; }
549
+ .board-card, .calendar-card { display: grid; gap: 0.25rem; border: 1px solid #e0e8f1; border-radius: var(--topogram-radius-control); background: #f8fbff; padding: 0.75rem; }
543
550
  .calendar-list { display: grid; gap: 0.75rem; margin-top: 1rem; }
544
551
  @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); } }
545
- @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; } }
552
+ @media (max-width: 640px) { .definition-list { grid-template-columns: 1fr; } .resource-list li { flex-direction: column; } .resource-table { min-width: 36rem; } .app-nav { flex-wrap: wrap; } }
546
553
  `;
547
554
  files["src/App.tsx"] = buildAppTsx(contract, webReferenceWithDefaults);
548
555
  files["src/lib/topogram/api-contracts.json"] = `${JSON.stringify(realization.apiContracts, null, 2)}\n`;
@@ -1,5 +1,7 @@
1
1
  // @ts-check
2
2
 
3
+ import { UI_GENERATOR_RENDERED_COMPONENT_PATTERNS } from "../../../ui/taxonomy.js";
4
+
3
5
  /**
4
6
  * @typedef {{ id?: string, name?: string }} ComponentReference
5
7
  * @typedef {{ component?: ComponentReference, region?: string, pattern?: string }} ComponentUsage
@@ -62,13 +64,7 @@ export function svelteKitComponentUsageSupport(usage, componentContracts) {
62
64
  const pattern = usagePattern(usage, componentContracts);
63
65
  return {
64
66
  pattern,
65
- supported: [
66
- "summary_stats",
67
- "board_view",
68
- "calendar_view",
69
- "resource_table",
70
- "data_grid_view"
71
- ].includes(pattern || "")
67
+ supported: UI_GENERATOR_RENDERED_COMPONENT_PATTERNS.has(pattern || "")
72
68
  };
73
69
  }
74
70
 
@@ -2,6 +2,7 @@ import { buildWebRealization } from "../../../realization/ui/index.js";
2
2
  import { lookupRouteSegment } from "../services/runtime-helpers.js";
3
3
  import { getExampleImplementation } from "../../../example-implementation.js";
4
4
  import { renderApiClientModule, renderLookupModule, renderVisibilityModule } from "./shared.js";
5
+ import { buildDesignIntentCoverage, renderDesignIntentCss } from "./design-intent.js";
5
6
  import {
6
7
  renderSvelteKitComponentRegion,
7
8
  svelteKitComponentUsageSupport
@@ -165,6 +166,8 @@ ${renderedRegions || ` ${defaultCollection}`}
165
166
 
166
167
  function buildSvelteKitGenerationCoverage(contract, files, implementationScreenIds) {
167
168
  const diagnostics = [];
169
+ const designIntent = buildDesignIntentCoverage(contract, files, "src/app.css");
170
+ diagnostics.push(...designIntent.diagnostics);
168
171
  const screens = (contract.screens || [])
169
172
  .filter((screen) => Boolean(screen.route) && screen.route !== "/")
170
173
  .map((screen) => {
@@ -258,6 +261,7 @@ function buildSvelteKitGenerationCoverage(contract, files, implementationScreenI
258
261
  errors: diagnostics.filter((diagnostic) => diagnostic.severity === "error").length,
259
262
  warnings: diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length
260
263
  },
264
+ design_intent: designIntent.coverage,
261
265
  screens,
262
266
  diagnostics
263
267
  };
@@ -319,9 +323,9 @@ function buildSvelteKitScaffold(contract, apiContracts, options = {}) {
319
323
  const navigationPatterns = (contract.navigation?.patterns || []).join(" ");
320
324
  const hasCommandPalette = (contract.navigation?.patterns || []).includes("command_palette");
321
325
  const homeDescription = webReference.home.heroDescriptionTemplate.replace("PROFILE", `\`${profile}\``);
322
- const demoTaskEnvVar = webReference.home.demoPrimaryEnvVar;
323
- const ownerEnvVar = webReference.createPrimary.defaultAssigneeEnvVar;
324
- const projectEnvVar = webReference.createPrimary.defaultContainerEnvVar;
326
+ const demoPrimaryEnvVar = webReference.home.demoPrimaryEnvVar;
327
+ const ownerEnvVar = webReference.createPrimary.defaultOwnerEnvVar || webReference.createPrimary.defaultAssigneeEnvVar;
328
+ const containerEnvVar = webReference.createPrimary.defaultContainerEnvVar;
325
329
  files["package.json"] = JSON.stringify(
326
330
  {
327
331
  name: contract.projection.id,
@@ -370,7 +374,8 @@ function buildSvelteKitScaffold(contract, apiContracts, options = {}) {
370
374
  files["src/app.html"] =
371
375
  "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n %sveltekit.head%\n </head>\n <body data-sveltekit-preload-data=\"hover\">\n <div style=\"display: contents\">%sveltekit.body%</div>\n </body>\n</html>\n";
372
376
  files["src/app.css"] =
373
- ":root {\n font-family: system-ui, sans-serif;\n color: #182026;\n background: linear-gradient(180deg, #f5f7fb 0%, #edf2f7 100%);\n}\nbody {\n margin: 0;\n}\na {\n color: #0f5cc0;\n text-decoration: none;\n}\na:hover {\n text-decoration: underline;\n}\nmain {\n max-width: 72rem;\n margin: 0 auto;\n padding: 2rem 1.25rem 4rem;\n}\n.app-shell {\n min-height: 100vh;\n}\n.app-workspace {\n display: grid;\n grid-template-columns: 18rem minmax(0, 1fr);\n min-height: 100vh;\n}\n.app-main-shell {\n min-width: 0;\n}\n.app-sidebar {\n position: sticky;\n top: 0;\n align-self: start;\n min-height: 100vh;\n display: grid;\n align-content: start;\n gap: 1rem;\n padding: 1.25rem 1rem;\n border-right: 1px solid rgba(24, 32, 38, 0.08);\n background: rgba(255, 255, 255, 0.86);\n backdrop-filter: blur(12px);\n}\n.app-nav {\n position: sticky;\n top: 0;\n z-index: 10;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n padding: 1rem 1.25rem;\n border-bottom: 1px solid rgba(24, 32, 38, 0.08);\n background: rgba(255, 255, 255, 0.9);\n backdrop-filter: blur(12px);\n}\n.app-nav-links,\n.app-nav nav,\n.app-tabbar {\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n}\n.app-nav.menu-bar {\n border-bottom-style: dashed;\n}\n.app-nav.compact {\n justify-content: flex-end;\n}\n.app-tabbar {\n position: sticky;\n bottom: 0;\n z-index: 10;\n justify-content: space-around;\n padding: 0.85rem 1rem calc(0.85rem + env(safe-area-inset-bottom, 0px));\n border-top: 1px solid rgba(24, 32, 38, 0.08);\n background: rgba(255, 255, 255, 0.92);\n backdrop-filter: blur(12px);\n}\n.brand {\n font-weight: 700;\n letter-spacing: 0.01em;\n}\n.brand-mark {\n font-weight: 700;\n color: #607284;\n}\n.command-palette-button {\n background: #182026;\n color: white;\n border: none;\n border-radius: 999px;\n padding: 0.6rem 0.9rem;\n font: inherit;\n cursor: pointer;\n}\n.app-footer {\n max-width: 72rem;\n margin: 0 auto;\n padding: 0 1.25rem 2rem;\n color: #607284;\n}\n.card {\n background: white;\n border-radius: 16px;\n padding: 1.25rem;\n box-shadow: 0 12px 30px rgba(24, 32, 38, 0.08);\n}\n.hero {\n display: grid;\n gap: 1rem;\n}\n.grid {\n display: grid;\n gap: 1rem;\n}\n.grid.two {\n grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));\n}\n.filters {\n display: grid;\n gap: 0.75rem;\n grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));\n margin: 1rem 0 1.25rem;\n}\nlabel {\n display: grid;\n gap: 0.35rem;\n font-size: 0.95rem;\n}\ninput,\ntextarea,\nbutton,\nselect {\n font: inherit;\n}\ninput,\ntextarea,\nselect {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #c9d4e2;\n border-radius: 12px;\n padding: 0.7rem 0.85rem;\n background: white;\n}\ntextarea {\n min-height: 8rem;\n resize: vertical;\n}\nbutton,\n.button-link {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.35rem;\n border: none;\n border-radius: 999px;\n padding: 0.7rem 1rem;\n background: #0f5cc0;\n color: white;\n font-weight: 600;\n cursor: pointer;\n}\n.button-link.secondary {\n background: #e9eef6;\n color: #182026;\n}\n.button-row {\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n align-items: center;\n}\n.stack {\n display: grid;\n gap: 1rem;\n}\n.task-list,\n.resource-list {\n list-style: none;\n padding: 0;\n margin: 1rem 0 0;\n display: grid;\n gap: 0.75rem;\n}\n.task-list li,\n.resource-list li {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: 1rem;\n padding: 1rem;\n border: 1px solid #e0e8f1;\n border-radius: 14px;\n background: #fbfcfe;\n}\n.table-wrap {\n margin-top: 1rem;\n overflow-x: auto;\n border: 1px solid #d7e1ec;\n border-radius: 14px;\n background: white;\n}\n.resource-table {\n width: 100%;\n border-collapse: collapse;\n min-width: 42rem;\n}\n.resource-table th,\n.resource-table td {\n padding: 0.85rem 1rem;\n text-align: left;\n border-bottom: 1px solid #e7edf5;\n vertical-align: top;\n}\n.resource-table th {\n font-size: 0.85rem;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: #516173;\n background: #f8fbff;\n}\n.resource-table tbody tr:hover {\n background: #fbfdff;\n}\n.data-grid {\n min-width: 64rem;\n font-size: 0.95rem;\n}\n.data-grid thead th {\n position: sticky;\n top: 0;\n z-index: 1;\n background: #eef5ff;\n}\n.data-grid-shell {\n box-shadow: inset 0 0 0 1px rgba(15, 92, 192, 0.04);\n}\n.cell-stack,\n.task-meta,\n.definition-list {\n display: grid;\n gap: 0.5rem;\n}\n.cell-secondary {\n color: #607284;\n font-size: 0.92rem;\n}\n.definition-list {\n grid-template-columns: minmax(8rem, 12rem) 1fr;\n align-items: start;\n}\n.definition-list dt {\n font-weight: 600;\n color: #516173;\n}\n.definition-list dd {\n margin: 0;\n}\n.badge {\n display: inline-flex;\n align-items: center;\n padding: 0.25rem 0.6rem;\n border-radius: 999px;\n background: #eef4ff;\n color: #0f5cc0;\n font-size: 0.85rem;\n font-weight: 600;\n}\n.muted {\n color: #607284;\n}\n.empty-state {\n padding: 1rem 0;\n}\n.component-card {\n border: 1px solid #d7e1ec;\n border-radius: 14px;\n background: #fbfcfe;\n padding: 1rem;\n margin-top: 1rem;\n}\n.component-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n flex-wrap: wrap;\n}\n.component-eyebrow {\n margin: 0 0 0.25rem;\n color: #607284;\n font-size: 0.75rem;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n.component-card h2,\n.component-card h3 {\n margin: 0;\n}\n.component-table-wrap {\n margin-top: 1rem;\n}\n.summary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));\n gap: 0.75rem;\n}\n.summary-grid div,\n.board-column {\n border: 1px solid #e0e8f1;\n border-radius: 12px;\n background: white;\n padding: 0.85rem;\n}\n.summary-grid strong {\n display: block;\n font-size: 1.5rem;\n}\n.summary-grid span,\n.calendar-list span {\n color: #607284;\n font-size: 0.9rem;\n}\n.board-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));\n gap: 0.75rem;\n margin-top: 1rem;\n}\n.board-card,\n.calendar-card {\n display: grid;\n gap: 0.25rem;\n border: 1px solid #e0e8f1;\n border-radius: 10px;\n background: #f8fbff;\n padding: 0.75rem;\n}\n.calendar-list {\n display: grid;\n gap: 0.75rem;\n margin-top: 1rem;\n}\nsmall.route-hint {\n display: block;\n color: #607284;\n margin-top: 0.25rem;\n}\n@media (max-width: 900px) {\n .app-workspace {\n grid-template-columns: 1fr;\n }\n .app-sidebar {\n position: static;\n min-height: auto;\n border-right: none;\n border-bottom: 1px solid rgba(24, 32, 38, 0.08);\n }\n}\n@media (max-width: 640px) {\n .definition-list {\n grid-template-columns: 1fr;\n }\n .task-list li,\n .resource-list li {\n flex-direction: column;\n }\n .resource-table {\n min-width: 36rem;\n }\n .app-nav {\n flex-wrap: wrap;\n }\n}\n";
377
+ `${renderDesignIntentCss(contract.design)}\n` +
378
+ ":root {\n font-family: system-ui, sans-serif;\n color: var(--topogram-text-color);\n background: var(--topogram-surface-background);\n}\nbody {\n margin: 0;\n}\na {\n color: var(--topogram-action-primary-background);\n text-decoration: none;\n}\na:hover {\n text-decoration: underline;\n}\nmain {\n max-width: 72rem;\n margin: 0 auto;\n padding: var(--topogram-page-padding);\n}\n.app-shell {\n min-height: 100vh;\n}\n.app-workspace {\n display: grid;\n grid-template-columns: 18rem minmax(0, 1fr);\n min-height: 100vh;\n}\n.app-main-shell {\n min-width: 0;\n}\n.app-sidebar {\n position: sticky;\n top: 0;\n align-self: start;\n min-height: 100vh;\n display: grid;\n align-content: start;\n gap: var(--topogram-space-unit);\n padding: 1.25rem 1rem;\n border-right: 1px solid rgba(24, 32, 38, 0.08);\n background: rgba(255, 255, 255, 0.86);\n backdrop-filter: blur(12px);\n}\n.app-nav {\n position: sticky;\n top: 0;\n z-index: 10;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--topogram-space-unit);\n padding: 1rem 1.25rem;\n border-bottom: 1px solid rgba(24, 32, 38, 0.08);\n background: rgba(255, 255, 255, 0.9);\n backdrop-filter: blur(12px);\n}\n.app-nav-links,\n.app-nav nav,\n.app-tabbar {\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n}\n.app-nav.menu-bar {\n border-bottom-style: dashed;\n}\n.app-nav.compact {\n justify-content: flex-end;\n}\n.app-tabbar {\n position: sticky;\n bottom: 0;\n z-index: 10;\n justify-content: space-around;\n padding: 0.85rem 1rem calc(0.85rem + env(safe-area-inset-bottom, 0px));\n border-top: 1px solid rgba(24, 32, 38, 0.08);\n background: rgba(255, 255, 255, 0.92);\n backdrop-filter: blur(12px);\n}\n.brand {\n font-weight: 700;\n letter-spacing: 0.01em;\n}\n.brand-mark {\n font-weight: 700;\n color: var(--topogram-muted-color);\n}\n.command-palette-button {\n background: var(--topogram-text-color);\n color: white;\n border: none;\n border-radius: var(--topogram-radius-pill);\n padding: var(--topogram-control-padding);\n font: inherit;\n cursor: pointer;\n}\n.app-footer {\n max-width: 72rem;\n margin: 0 auto;\n padding: 0 1.25rem 2rem;\n color: var(--topogram-muted-color);\n}\n.card {\n background: var(--topogram-surface-card);\n border-radius: var(--topogram-radius-card);\n padding: 1.25rem;\n box-shadow: 0 12px 30px rgba(24, 32, 38, 0.08);\n}\n.hero {\n display: grid;\n gap: var(--topogram-space-unit);\n}\n.grid {\n display: grid;\n gap: var(--topogram-space-unit);\n}\n.grid.two {\n grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));\n}\n.filters {\n display: grid;\n gap: 0.75rem;\n grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));\n margin: 1rem 0 1.25rem;\n}\nlabel {\n display: grid;\n gap: 0.35rem;\n font-size: 0.95rem;\n}\ninput,\ntextarea,\nbutton,\nselect {\n font: inherit;\n}\ninput,\ntextarea,\nselect {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #c9d4e2;\n border-radius: var(--topogram-radius-control);\n padding: var(--topogram-control-padding);\n background: white;\n}\ntextarea {\n min-height: 8rem;\n resize: vertical;\n}\nbutton,\n.button-link {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.35rem;\n border: none;\n border-radius: var(--topogram-radius-pill);\n padding: var(--topogram-control-padding);\n background: var(--topogram-action-primary-background);\n color: var(--topogram-action-primary-color);\n font-weight: 600;\n cursor: pointer;\n}\nbutton:focus-visible,\n.button-link:focus-visible,\na:focus-visible,\ninput:focus-visible,\ntextarea:focus-visible,\nselect:focus-visible {\n outline: var(--topogram-focus-outline);\n outline-offset: 2px;\n}\n.button-link.secondary {\n background: #e9eef6;\n color: var(--topogram-text-color);\n}\n.button-row {\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n align-items: center;\n}\n.stack {\n display: grid;\n gap: var(--topogram-space-unit);\n}\n\n.resource-list {\n list-style: none;\n padding: 0;\n margin: 1rem 0 0;\n display: grid;\n gap: 0.75rem;\n}\n\n.resource-list li {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: var(--topogram-space-unit);\n padding: 1rem;\n border: 1px solid #e0e8f1;\n border-radius: var(--topogram-radius-card);\n background: var(--topogram-surface-subtle);\n}\n.table-wrap {\n margin-top: 1rem;\n overflow-x: auto;\n border: 1px solid var(--topogram-border-color);\n border-radius: var(--topogram-radius-card);\n background: white;\n}\n.resource-table {\n width: 100%;\n border-collapse: collapse;\n min-width: 42rem;\n}\n.resource-table th,\n.resource-table td {\n padding: 0.85rem 1rem;\n text-align: left;\n border-bottom: 1px solid #e7edf5;\n vertical-align: top;\n}\n.resource-table th {\n font-size: 0.85rem;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: #516173;\n background: #f8fbff;\n}\n.resource-table tbody tr:hover {\n background: #fbfdff;\n}\n.data-grid {\n min-width: 64rem;\n font-size: 0.95rem;\n}\n.data-grid thead th {\n position: sticky;\n top: 0;\n z-index: 1;\n background: #eef5ff;\n}\n.data-grid-shell {\n box-shadow: inset 0 0 0 1px rgba(15, 92, 192, 0.04);\n}\n.cell-stack,\n.resource-meta,\n.definition-list {\n display: grid;\n gap: 0.5rem;\n}\n.cell-secondary {\n color: var(--topogram-muted-color);\n font-size: 0.92rem;\n}\n.definition-list {\n grid-template-columns: minmax(8rem, 12rem) 1fr;\n align-items: start;\n}\n.definition-list dt {\n font-weight: 600;\n color: #516173;\n}\n.definition-list dd {\n margin: 0;\n}\n.badge {\n display: inline-flex;\n align-items: center;\n padding: 0.25rem 0.6rem;\n border-radius: var(--topogram-radius-pill);\n background: #eef4ff;\n color: var(--topogram-action-primary-background);\n font-size: 0.85rem;\n font-weight: 600;\n}\n.muted {\n color: var(--topogram-muted-color);\n}\n.empty-state {\n padding: 1rem 0;\n}\n.component-card {\n border: 1px solid var(--topogram-border-color);\n border-radius: var(--topogram-radius-card);\n background: var(--topogram-surface-subtle);\n padding: 1rem;\n margin-top: 1rem;\n}\n.component-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--topogram-space-unit);\n flex-wrap: wrap;\n}\n.component-eyebrow {\n margin: 0 0 0.25rem;\n color: var(--topogram-muted-color);\n font-size: 0.75rem;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n.component-card h2,\n.component-card h3 {\n margin: 0;\n}\n.component-table-wrap {\n margin-top: 1rem;\n}\n.summary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));\n gap: 0.75rem;\n}\n.summary-grid div,\n.board-column {\n border: 1px solid #e0e8f1;\n border-radius: var(--topogram-radius-control);\n background: white;\n padding: 0.85rem;\n}\n.summary-grid strong {\n display: block;\n font-size: 1.5rem;\n}\n.summary-grid span,\n.calendar-list span {\n color: var(--topogram-muted-color);\n font-size: 0.9rem;\n}\n.board-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));\n gap: 0.75rem;\n margin-top: 1rem;\n}\n.board-card,\n.calendar-card {\n display: grid;\n gap: 0.25rem;\n border: 1px solid #e0e8f1;\n border-radius: var(--topogram-radius-control);\n background: #f8fbff;\n padding: 0.75rem;\n}\n.calendar-list {\n display: grid;\n gap: 0.75rem;\n margin-top: 1rem;\n}\nsmall.route-hint {\n display: block;\n color: var(--topogram-muted-color);\n margin-top: 0.25rem;\n}\n@media (max-width: 900px) {\n .app-workspace {\n grid-template-columns: 1fr;\n }\n .app-sidebar {\n position: static;\n min-height: auto;\n border-right: none;\n border-bottom: 1px solid rgba(24, 32, 38, 0.08);\n }\n}\n@media (max-width: 640px) {\n .definition-list {\n grid-template-columns: 1fr;\n }\n .resource-list li {\n flex-direction: column;\n }\n .resource-table {\n min-width: 36rem;\n }\n .app-nav {\n flex-wrap: wrap;\n }\n}\n";
374
379
  const navMarkup = navLinks.map((link) => ` <a href="${link.route}">${link.label}</a>`).join("\n");
375
380
  const shellLayout =
376
381
  shellMode === "split_view"
@@ -381,7 +386,7 @@ function buildSvelteKitScaffold(contract, apiContracts, options = {}) {
381
386
  files["src/routes/+layout.svelte"] = `<script${useTypescript ? ' lang="ts"' : ""}>\n import "../app.css";\n</script>\n\n<div class="app-shell" data-shell="${shellMode}" data-windowing="${windowingMode}" data-navigation-patterns="${navigationPatterns}">\n ${shellLayout}\n${footerEnabled ? `\n <footer class="app-footer">\n <span>Generated from Topogram</span>\n </footer>` : ""}\n</div>\n`;
382
387
  files["src/routes/+page.svelte"] = webRenderers.renderHomePage({
383
388
  useTypescript,
384
- demoPrimaryEnvVar: demoTaskEnvVar,
389
+ demoPrimaryEnvVar,
385
390
  screens: contract.screens.map((screen) => ({
386
391
  id: screen.id,
387
392
  title: screen.title || screen.id,
@@ -402,16 +407,16 @@ function buildSvelteKitScaffold(contract, apiContracts, options = {}) {
402
407
  Object.assign(files, buildGenericSvelteKitScreenFiles(screen, contract, useTypescript));
403
408
  }
404
409
 
405
- const taskList = contract.screens.find((screen) => screen.id === webScreenReference.listScreenId);
406
- const taskDetail = contract.screens.find((screen) => screen.id === webScreenReference.detailScreenId);
407
- const taskCreate = contract.screens.find((screen) => screen.id === webScreenReference.createScreenId);
408
- const taskEdit = contract.screens.find((screen) => screen.id === webScreenReference.editScreenId);
409
- const taskExports = webScreenReference.exportsScreenId
410
+ const primaryList = contract.screens.find((screen) => screen.id === webScreenReference.listScreenId);
411
+ const primaryDetail = contract.screens.find((screen) => screen.id === webScreenReference.detailScreenId);
412
+ const primaryCreate = contract.screens.find((screen) => screen.id === webScreenReference.createScreenId);
413
+ const primaryEdit = contract.screens.find((screen) => screen.id === webScreenReference.editScreenId);
414
+ const primaryExports = webScreenReference.exportsScreenId
410
415
  ? contract.screens.find((screen) => screen.id === webScreenReference.exportsScreenId)
411
416
  : null;
412
- const taskListLookups = Object.fromEntries((taskList?.lookups || []).map((lookup) => [lookup.field, lookupDescriptor(lookup)]));
413
- const taskCreateLookups = Object.fromEntries((taskCreate?.lookups || []).map((lookup) => [lookup.field, lookupDescriptor(lookup)]));
414
- const taskEditLookups = Object.fromEntries((taskEdit?.lookups || []).map((lookup) => [lookup.field, lookupDescriptor(lookup)]));
417
+ const primaryListLookups = Object.fromEntries((primaryList?.lookups || []).map((lookup) => [lookup.field, lookupDescriptor(lookup)]));
418
+ const primaryCreateLookups = Object.fromEntries((primaryCreate?.lookups || []).map((lookup) => [lookup.field, lookupDescriptor(lookup)]));
419
+ const primaryEditLookups = Object.fromEntries((primaryEdit?.lookups || []).map((lookup) => [lookup.field, lookupDescriptor(lookup)]));
415
420
  const routePageScreenIds = new Map(
416
421
  (contract.screens || [])
417
422
  .filter((screen) => screen.route && screen.route !== "/")
@@ -419,19 +424,19 @@ function buildSvelteKitScaffold(contract, apiContracts, options = {}) {
419
424
  );
420
425
  const implementationScreenIds = new Set();
421
426
 
422
- if (taskList?.route && taskDetail?.route && taskCreate?.route && taskEdit?.route) {
427
+ if (primaryList?.route && primaryDetail?.route && primaryCreate?.route && primaryEdit?.route) {
423
428
  for (const [relativePath, contents] of Object.entries(webRenderers.renderRoutes({
424
429
  useTypescript,
425
430
  contract,
426
- taskList,
427
- taskDetail,
428
- taskCreate,
429
- taskEdit,
430
- taskExports,
431
- taskListLookups,
432
- taskCreateLookups,
433
- taskEditLookups,
434
- projectEnvVar,
431
+ primaryList,
432
+ primaryDetail,
433
+ primaryCreate,
434
+ primaryEdit,
435
+ primaryExports,
436
+ primaryListLookups,
437
+ primaryCreateLookups,
438
+ primaryEditLookups,
439
+ containerEnvVar,
435
440
  ownerEnvVar,
436
441
  webReference,
437
442
  prettyScreenKind