@topogram/cli 0.3.63 → 0.3.64

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 (121) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan.d.ts +6 -0
  3. package/src/adoption/reporting.d.ts +10 -0
  4. package/src/adoption/review-groups.d.ts +6 -0
  5. package/src/agent-brief.d.ts +3 -0
  6. package/src/agent-brief.js +495 -0
  7. package/src/agent-ops/query-builders.d.ts +26 -0
  8. package/src/archive/archive.d.ts +2 -0
  9. package/src/archive/compact.d.ts +1 -0
  10. package/src/archive/unarchive.d.ts +1 -0
  11. package/src/catalog.d.ts +10 -0
  12. package/src/catalog.js +62 -66
  13. package/src/cli/catalog-alias.d.ts +1 -0
  14. package/src/cli/command-parser.js +38 -0
  15. package/src/cli/command-parsers/core.js +102 -0
  16. package/src/cli/command-parsers/generator.js +39 -0
  17. package/src/cli/command-parsers/import.js +44 -0
  18. package/src/cli/command-parsers/legacy-workflow.js +21 -0
  19. package/src/cli/command-parsers/project.js +47 -0
  20. package/src/cli/command-parsers/sdlc.js +47 -0
  21. package/src/cli/command-parsers/shared.js +51 -0
  22. package/src/cli/command-parsers/template.js +48 -0
  23. package/src/cli/commands/agent.js +47 -0
  24. package/src/cli/commands/catalog.js +617 -0
  25. package/src/cli/commands/check.js +268 -0
  26. package/src/cli/commands/doctor.js +268 -0
  27. package/src/cli/commands/emit.js +149 -0
  28. package/src/cli/commands/generate.js +96 -0
  29. package/src/cli/commands/generator-policy.js +785 -0
  30. package/src/cli/commands/generator.js +443 -0
  31. package/src/cli/commands/import-runner.js +157 -0
  32. package/src/cli/commands/import.js +1734 -0
  33. package/src/cli/commands/inspect.js +55 -0
  34. package/src/cli/commands/new.js +94 -0
  35. package/src/cli/commands/package.js +815 -0
  36. package/src/cli/commands/query.js +1302 -0
  37. package/src/cli/commands/release-rollout.js +257 -0
  38. package/src/cli/commands/release-shared.js +528 -0
  39. package/src/cli/commands/release-status.js +429 -0
  40. package/src/cli/commands/release.js +107 -0
  41. package/src/cli/commands/sdlc.js +168 -0
  42. package/src/cli/commands/setup.js +76 -0
  43. package/src/cli/commands/source.js +291 -0
  44. package/src/cli/commands/template-runner.js +198 -0
  45. package/src/cli/commands/template.js +2145 -0
  46. package/src/cli/commands/trust.js +219 -0
  47. package/src/cli/commands/version.js +40 -0
  48. package/src/cli/commands/widget.js +168 -0
  49. package/src/cli/commands/workflow.js +63 -0
  50. package/src/cli/dispatcher.js +392 -0
  51. package/src/cli/help-dispatch.js +188 -0
  52. package/src/cli/help.js +296 -0
  53. package/src/cli/migration-guidance.js +59 -0
  54. package/src/cli/options.js +96 -0
  55. package/src/cli/output-safety.js +107 -0
  56. package/src/cli/path-normalization.js +29 -0
  57. package/src/cli.js +47 -11711
  58. package/src/example-implementation.d.ts +2 -0
  59. package/src/format.d.ts +1 -0
  60. package/src/generator/check.d.ts +1 -0
  61. package/src/generator/context/bundle.d.ts +1 -0
  62. package/src/generator/context/shared.d.ts +2 -0
  63. package/src/generator/native/parity-bundle.js +2 -1
  64. package/src/generator/surfaces/web/html-escape.js +22 -0
  65. package/src/generator/surfaces/web/react.js +10 -8
  66. package/src/generator/surfaces/web/sveltekit.js +7 -5
  67. package/src/generator/surfaces/web/vanilla.js +8 -4
  68. package/src/generator.d.ts +2 -0
  69. package/src/github-client.js +520 -0
  70. package/src/import/core/shared.js +20 -62
  71. package/src/import/extractors/api/flutter-dio.js +4 -8
  72. package/src/import/extractors/api/react-native-repository.js +4 -8
  73. package/src/import/index.d.ts +4 -0
  74. package/src/import/provenance.d.ts +4 -0
  75. package/src/new-project.js +100 -11
  76. package/src/npm-safety.js +79 -0
  77. package/src/parser.d.ts +1 -0
  78. package/src/path-helpers.d.ts +1 -0
  79. package/src/path-helpers.js +20 -0
  80. package/src/project-config.js +1 -0
  81. package/src/reconcile/docs.d.ts +8 -0
  82. package/src/reconcile/journeys.d.ts +1 -0
  83. package/src/resolver.d.ts +1 -0
  84. package/src/runtime-support.js +29 -0
  85. package/src/sdlc/adopt.d.ts +1 -0
  86. package/src/sdlc/check.d.ts +1 -0
  87. package/src/sdlc/explain.d.ts +1 -0
  88. package/src/sdlc/release.d.ts +1 -0
  89. package/src/sdlc/scaffold.d.ts +1 -0
  90. package/src/sdlc/transition.d.ts +1 -0
  91. package/src/text-helpers.d.ts +6 -0
  92. package/src/text-helpers.js +245 -0
  93. package/src/topogram-config.js +306 -0
  94. package/src/validator.d.ts +2 -0
  95. package/src/workflows/adoption/index.js +26 -0
  96. package/src/workflows/docs-generate.js +262 -0
  97. package/src/workflows/docs-scan.js +703 -0
  98. package/src/workflows/docs.js +15 -0
  99. package/src/workflows/import-app/api.js +799 -0
  100. package/src/workflows/import-app/db.js +538 -0
  101. package/src/workflows/import-app/index.js +30 -0
  102. package/src/workflows/import-app/shared.js +218 -0
  103. package/src/workflows/import-app/ui.js +443 -0
  104. package/src/workflows/import-app/workflow.js +159 -0
  105. package/src/workflows/reconcile/adoption-plan.js +742 -0
  106. package/src/workflows/reconcile/auth.js +692 -0
  107. package/src/workflows/reconcile/bundle-core.js +600 -0
  108. package/src/workflows/reconcile/bundle-shared.js +75 -0
  109. package/src/workflows/reconcile/candidate-model.js +477 -0
  110. package/src/workflows/reconcile/canonical-surface.js +264 -0
  111. package/src/workflows/reconcile/gap-report.js +333 -0
  112. package/src/workflows/reconcile/ids.js +6 -0
  113. package/src/workflows/reconcile/impacts.js +625 -0
  114. package/src/workflows/reconcile/index.js +7 -0
  115. package/src/workflows/reconcile/renderers.js +461 -0
  116. package/src/workflows/reconcile/summary.js +90 -0
  117. package/src/workflows/reconcile/workflow.js +309 -0
  118. package/src/workflows/shared.js +189 -0
  119. package/src/workflows/types.d.ts +93 -0
  120. package/src/workflows.d.ts +1 -0
  121. package/src/workflows.js +10 -7652
@@ -0,0 +1,2 @@
1
+ export function loadImplementationProvider(root: string): Promise<any>;
2
+ export function getExampleImplementation(options?: any): any;
@@ -0,0 +1 @@
1
+ export function stableStringify(value: any): string;
@@ -0,0 +1 @@
1
+ export function checkGeneratorPack(spec: string, options?: { cwd?: string }): any;
@@ -0,0 +1 @@
1
+ export function generateContextBundle(...args: any[]): any;
@@ -0,0 +1,2 @@
1
+ export function recommendedVerificationTargets(...args: any[]): any;
2
+ export function buildLocalMaintainedBoundaryArtifact(...args: any[]): any;
@@ -1,4 +1,5 @@
1
1
  import { getExampleImplementation } from "../../example-implementation.js";
2
+ import { githubRepoSlug } from "../../topogram-config.js";
2
3
  import { getDefaultEnvironmentProjections, resolveRuntimeTopology, runtimeUrls } from "../runtime/shared.js";
3
4
 
4
5
  /** Pinned toolchains for reproducible native parity stubs (document in README). */
@@ -62,7 +63,7 @@ function buildNativeParityPlan(graph, options = {}) {
62
63
 
63
64
  function renderRootReadme(plan, urls) {
64
65
  const demoOpsUrl =
65
- "https://github.com/attebury/topogram/blob/main/docs/README.md";
66
+ `https://github.com/${githubRepoSlug(null)}/blob/main/docs/README.md`;
66
67
  return `# Native parity bundle
67
68
 
68
69
  Minimal **Android (Gradle/Kotlin)** and **iOS (Swift Package / SwiftUI)** stubs wired to the same runtime URL metadata as other Topogram bundles.
@@ -0,0 +1,22 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @param {unknown} value
5
+ * @returns {string}
6
+ */
7
+ export function escapeHtml(value) {
8
+ return String(value ?? "")
9
+ .replace(/&/g, "&amp;")
10
+ .replace(/</g, "&lt;")
11
+ .replace(/>/g, "&gt;");
12
+ }
13
+
14
+ /**
15
+ * @param {unknown} value
16
+ * @returns {string}
17
+ */
18
+ export function escapeAttr(value) {
19
+ return escapeHtml(value)
20
+ .replace(/"/g, "&quot;")
21
+ .replace(/'/g, "&#39;");
22
+ }
@@ -5,6 +5,7 @@ import {
5
5
  renderReactWidgetRegion
6
6
  } from "./react-widgets.js";
7
7
  import { buildDesignIntentCoverage, renderDesignIntentCss } from "./design-intent.js";
8
+ import { escapeAttr, escapeHtml } from "./html-escape.js";
8
9
  import { renderApiClientModule, renderLookupModule, renderVisibilityModule } from "./shared.js";
9
10
 
10
11
  function componentNameForScreen(screenId) {
@@ -309,25 +310,26 @@ function assertGenerationCoverage(coverage) {
309
310
  function buildAppTsx(contract, webReference) {
310
311
  const navLinks = resolveNavLinks(contract, webReference);
311
312
  const brandName = contract.appShell?.brand || webReference.brandName;
313
+ const safeBrandName = escapeHtml(brandName);
312
314
  const footerEnabled = contract.appShell?.footer && contract.appShell.footer !== "none";
313
315
  const shellMode = contract.appShell?.shell || "topbar";
314
316
  const windowingMode = contract.appShell?.windowing || "single_window";
315
317
  const navigationPatterns = (contract.navigation?.patterns || []).join(" ");
316
318
  const hasCommandPalette = (contract.navigation?.patterns || []).includes("command_palette");
317
- const navItems = navLinks.map((link) => ` <Link to="${link.route}">${link.label}</Link>`).join("\n");
319
+ const navItems = navLinks.map((link) => ` <Link to="${escapeAttr(link.route)}">${escapeHtml(link.label)}</Link>`).join("\n");
318
320
  const routeScreens = contract.screens.filter((screen) => screen.route && componentNameForScreen(screen.id) !== "EditorialSettingsPage");
319
321
  const importLines = routeScreens
320
322
  .map((screen) => `import { ${componentNameForScreen(screen.id)} } from "./pages/${componentNameForScreen(screen.id)}";`)
321
323
  .join("\n");
322
324
  const routeLines = routeScreens
323
- .map((screen) => ` <Route path="${screen.route}" element={<${componentNameForScreen(screen.id)} />} />`)
325
+ .map((screen) => ` <Route path="${escapeAttr(screen.route)}" element={<${componentNameForScreen(screen.id)} />} />`)
324
326
  .join("\n");
325
327
 
326
328
  const shellFrame =
327
329
  shellMode === "split_view"
328
330
  ? ` <div className="app-workspace">
329
331
  <aside className="app-sidebar">
330
- <Link className="brand" to="/">${brandName}</Link>
332
+ <Link className="brand" to="/">${safeBrandName}</Link>
331
333
  <nav className="app-nav-links">
332
334
  ${navItems}
333
335
  </nav>
@@ -335,7 +337,7 @@ ${hasCommandPalette ? ` <button className="command-palette-button" ty
335
337
  </aside>
336
338
  <div className="app-main-shell">
337
339
  <header className="app-nav compact">
338
- <div className="brand-mark">${brandName}</div>
340
+ <div className="brand-mark">${safeBrandName}</div>
339
341
  ${hasCommandPalette ? ` <button className="command-palette-button" type="button">Command Palette</button>` : ""}
340
342
  </header>
341
343
  <main>
@@ -348,7 +350,7 @@ ${routeLines}
348
350
  </div>`
349
351
  : shellMode === "bottom_tabs"
350
352
  ? ` <header className="app-nav">
351
- <Link className="brand" to="/">${brandName}</Link>
353
+ <Link className="brand" to="/">${safeBrandName}</Link>
352
354
  ${hasCommandPalette ? ` <button className="command-palette-button" type="button">Command Palette</button>` : ""}
353
355
  </header>
354
356
  <main>
@@ -361,7 +363,7 @@ ${routeLines}
361
363
  ${navItems}
362
364
  </nav>`
363
365
  : ` <header className="app-nav${shellMode === "menu_bar" ? " menu-bar" : ""}">
364
- <Link className="brand" to="/">${brandName}</Link>
366
+ <Link className="brand" to="/">${safeBrandName}</Link>
365
367
  <nav className="app-nav-links">
366
368
  ${navItems}
367
369
  </nav>
@@ -381,7 +383,7 @@ ${importLines}
381
383
  export default function App() {
382
384
  return (
383
385
  <BrowserRouter>
384
- <div className="app-shell" data-shell="${shellMode}" data-windowing="${windowingMode}" data-navigation-patterns="${navigationPatterns}">
386
+ <div className="app-shell" data-shell="${escapeAttr(shellMode)}" data-windowing="${escapeAttr(windowingMode)}" data-navigation-patterns="${escapeAttr(navigationPatterns)}">
385
387
  ${shellFrame}
386
388
  ${footerEnabled ? ` <footer className="app-footer">
387
389
  <span>Generated from Topogram</span>
@@ -463,7 +465,7 @@ export default defineConfig({
463
465
  <head>
464
466
  <meta charset="UTF-8" />
465
467
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
466
- <title>${webReference.brandName}</title>
468
+ <title>${escapeHtml(webReference.brandName)}</title>
467
469
  </head>
468
470
  <body>
469
471
  <div id="root"></div>
@@ -3,6 +3,7 @@ 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
5
  import { buildDesignIntentCoverage, renderDesignIntentCss } from "./design-intent.js";
6
+ import { escapeAttr, escapeHtml } from "./html-escape.js";
6
7
  import {
7
8
  renderSvelteKitWidgetRegion,
8
9
  svelteKitWidgetUsageSupport
@@ -324,6 +325,7 @@ function buildSvelteKitScaffold(contract, apiContracts, options = {}) {
324
325
  const files = {};
325
326
 
326
327
  const brandName = contract.appShell?.brand || webReference.brandName;
328
+ const safeBrandName = escapeHtml(brandName);
327
329
  const navLinks = resolveNavLinks(contract, webReference);
328
330
  const footerEnabled = contract.appShell?.footer && contract.appShell.footer !== "none";
329
331
  const shellMode = contract.appShell?.shell || "topbar";
@@ -384,14 +386,14 @@ function buildSvelteKitScaffold(contract, apiContracts, options = {}) {
384
386
  files["src/app.css"] =
385
387
  `${renderDesignIntentCss(contract.designTokens)}\n` +
386
388
  ":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.widget-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.widget-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.widget-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.widget-card h2,\n.widget-card h3 {\n margin: 0;\n}\n.widget-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";
387
- const navMarkup = navLinks.map((link) => ` <a href="${link.route}">${link.label}</a>`).join("\n");
389
+ const navMarkup = navLinks.map((link) => ` <a href="${escapeAttr(link.route)}">${escapeHtml(link.label)}</a>`).join("\n");
388
390
  const shellLayout =
389
391
  shellMode === "split_view"
390
- ? `<div class="app-workspace">\n <aside class="app-sidebar">\n <a class="brand" href="/">${brandName}</a>\n <nav class="app-nav-links">\n${navMarkup}\n </nav>\n${hasCommandPalette ? ` <button class="command-palette-button" type="button">Command Palette</button>\n` : ""} </aside>\n <div class="app-main-shell">\n <header class="app-nav compact">\n <div class="brand-mark">${brandName}</div>\n${hasCommandPalette ? ` <button class="command-palette-button" type="button">Command Palette</button>\n` : ""} </header>\n\n <slot />\n </div>\n</div>`
392
+ ? `<div class="app-workspace">\n <aside class="app-sidebar">\n <a class="brand" href="/">${safeBrandName}</a>\n <nav class="app-nav-links">\n${navMarkup}\n </nav>\n${hasCommandPalette ? ` <button class="command-palette-button" type="button">Command Palette</button>\n` : ""} </aside>\n <div class="app-main-shell">\n <header class="app-nav compact">\n <div class="brand-mark">${safeBrandName}</div>\n${hasCommandPalette ? ` <button class="command-palette-button" type="button">Command Palette</button>\n` : ""} </header>\n\n <slot />\n </div>\n</div>`
391
393
  : shellMode === "bottom_tabs"
392
- ? `<header class="app-nav">\n <a class="brand" href="/">${brandName}</a>\n${hasCommandPalette ? ` <button class="command-palette-button" type="button">Command Palette</button>\n` : ""}</header>\n\n<slot />\n\n<nav class="app-tabbar">\n${navMarkup}\n</nav>`
393
- : `<header class="app-nav${shellMode === "menu_bar" ? " menu-bar" : ""}">\n <a class="brand" href="/">${brandName}</a>\n <nav class="app-nav-links">\n${navMarkup}\n </nav>\n${hasCommandPalette ? ` <button class="command-palette-button" type="button">Command Palette</button>\n` : ""}</header>\n\n<slot />`;
394
- 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`;
394
+ ? `<header class="app-nav">\n <a class="brand" href="/">${safeBrandName}</a>\n${hasCommandPalette ? ` <button class="command-palette-button" type="button">Command Palette</button>\n` : ""}</header>\n\n<slot />\n\n<nav class="app-tabbar">\n${navMarkup}\n</nav>`
395
+ : `<header class="app-nav${shellMode === "menu_bar" ? " menu-bar" : ""}">\n <a class="brand" href="/">${safeBrandName}</a>\n <nav class="app-nav-links">\n${navMarkup}\n </nav>\n${hasCommandPalette ? ` <button class="command-palette-button" type="button">Command Palette</button>\n` : ""}</header>\n\n<slot />`;
396
+ files["src/routes/+layout.svelte"] = `<script${useTypescript ? ' lang="ts"' : ""}>\n import "../app.css";\n</script>\n\n<div class="app-shell" data-shell="${escapeAttr(shellMode)}" data-windowing="${escapeAttr(windowingMode)}" data-navigation-patterns="${escapeAttr(navigationPatterns)}">\n ${shellLayout}\n${footerEnabled ? `\n <footer class="app-footer">\n <span>Generated from Topogram</span>\n </footer>` : ""}\n</div>\n`;
395
397
  files["src/routes/+page.svelte"] = webRenderers.renderHomePage({
396
398
  useTypescript,
397
399
  demoPrimaryEnvVar,
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { buildWebRealization } from "../../../realization/ui/index.js";
4
4
  import { buildDesignIntentCoverage, renderDesignIntentCss } from "./design-intent.js";
5
+ import { escapeAttr, escapeHtml } from "./html-escape.js";
5
6
 
6
7
  function slugify(value) {
7
8
  return String(value || "page")
@@ -26,19 +27,20 @@ function routeFileName(routePath) {
26
27
  }
27
28
 
28
29
  function renderHtml({ title, nav, body }) {
30
+ const safeTitle = escapeHtml(title);
29
31
  return `<!doctype html>
30
32
  <html lang="en">
31
33
  <head>
32
34
  <meta charset="utf-8" />
33
35
  <meta name="viewport" content="width=device-width, initial-scale=1" />
34
- <title>${title}</title>
36
+ <title>${safeTitle}</title>
35
37
  <link rel="stylesheet" href="./styles.css" />
36
38
  </head>
37
39
  <body>
38
40
  <header class="app-header">
39
41
  <a class="brand" href="./index.html">Topogram Hello</a>
40
42
  <nav>
41
- ${nav.map((item) => ` <a href="./${item.file}">${item.title}</a>`).join("\n")}
43
+ ${nav.map((item) => ` <a href="./${escapeAttr(item.file)}">${escapeHtml(item.title)}</a>`).join("\n")}
42
44
  </nav>
43
45
  </header>
44
46
  <main>
@@ -285,13 +287,15 @@ export function generateVanillaWebApp(graph, options = {}) {
285
287
  };
286
288
 
287
289
  routes.forEach((route, index) => {
290
+ const safeTitle = escapeHtml(route.title);
291
+ const safeProjectionId = escapeHtml(contract.projection.id);
288
292
  files[route.file] = renderHtml({
289
293
  title: route.title,
290
294
  nav,
291
295
  body: ` <section class="panel">
292
296
  <p class="muted">Page ${index + 1} of ${routes.length}</p>
293
- <h1>${route.title}</h1>
294
- <p>This page was generated from the <code>${contract.projection.id}</code> Topogram web projection.</p>
297
+ <h1>${safeTitle}</h1>
298
+ <p>This page was generated from the <code>${safeProjectionId}</code> Topogram web projection.</p>
295
299
  <p class="muted">Generated timestamp: <span data-generated-at>pending</span></p>
296
300
  </section>`
297
301
  });
@@ -0,0 +1,2 @@
1
+ export function buildOutputFiles(result: any, options?: any): any;
2
+ export function generateWorkspace(ast: any, options?: any): any;