@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.
- package/package.json +1 -1
- package/src/adoption/plan.d.ts +6 -0
- package/src/adoption/reporting.d.ts +10 -0
- package/src/adoption/review-groups.d.ts +6 -0
- package/src/agent-brief.d.ts +3 -0
- package/src/agent-brief.js +495 -0
- package/src/agent-ops/query-builders.d.ts +26 -0
- package/src/archive/archive.d.ts +2 -0
- package/src/archive/compact.d.ts +1 -0
- package/src/archive/unarchive.d.ts +1 -0
- package/src/catalog.d.ts +10 -0
- package/src/catalog.js +62 -66
- package/src/cli/catalog-alias.d.ts +1 -0
- package/src/cli/command-parser.js +38 -0
- package/src/cli/command-parsers/core.js +102 -0
- package/src/cli/command-parsers/generator.js +39 -0
- package/src/cli/command-parsers/import.js +44 -0
- package/src/cli/command-parsers/legacy-workflow.js +21 -0
- package/src/cli/command-parsers/project.js +47 -0
- package/src/cli/command-parsers/sdlc.js +47 -0
- package/src/cli/command-parsers/shared.js +51 -0
- package/src/cli/command-parsers/template.js +48 -0
- package/src/cli/commands/agent.js +47 -0
- package/src/cli/commands/catalog.js +617 -0
- package/src/cli/commands/check.js +268 -0
- package/src/cli/commands/doctor.js +268 -0
- package/src/cli/commands/emit.js +149 -0
- package/src/cli/commands/generate.js +96 -0
- package/src/cli/commands/generator-policy.js +785 -0
- package/src/cli/commands/generator.js +443 -0
- package/src/cli/commands/import-runner.js +157 -0
- package/src/cli/commands/import.js +1734 -0
- package/src/cli/commands/inspect.js +55 -0
- package/src/cli/commands/new.js +94 -0
- package/src/cli/commands/package.js +815 -0
- package/src/cli/commands/query.js +1302 -0
- package/src/cli/commands/release-rollout.js +257 -0
- package/src/cli/commands/release-shared.js +528 -0
- package/src/cli/commands/release-status.js +429 -0
- package/src/cli/commands/release.js +107 -0
- package/src/cli/commands/sdlc.js +168 -0
- package/src/cli/commands/setup.js +76 -0
- package/src/cli/commands/source.js +291 -0
- package/src/cli/commands/template-runner.js +198 -0
- package/src/cli/commands/template.js +2145 -0
- package/src/cli/commands/trust.js +219 -0
- package/src/cli/commands/version.js +40 -0
- package/src/cli/commands/widget.js +168 -0
- package/src/cli/commands/workflow.js +63 -0
- package/src/cli/dispatcher.js +392 -0
- package/src/cli/help-dispatch.js +188 -0
- package/src/cli/help.js +296 -0
- package/src/cli/migration-guidance.js +59 -0
- package/src/cli/options.js +96 -0
- package/src/cli/output-safety.js +107 -0
- package/src/cli/path-normalization.js +29 -0
- package/src/cli.js +47 -11711
- package/src/example-implementation.d.ts +2 -0
- package/src/format.d.ts +1 -0
- package/src/generator/check.d.ts +1 -0
- package/src/generator/context/bundle.d.ts +1 -0
- package/src/generator/context/shared.d.ts +2 -0
- package/src/generator/native/parity-bundle.js +2 -1
- package/src/generator/surfaces/web/html-escape.js +22 -0
- package/src/generator/surfaces/web/react.js +10 -8
- package/src/generator/surfaces/web/sveltekit.js +7 -5
- package/src/generator/surfaces/web/vanilla.js +8 -4
- package/src/generator.d.ts +2 -0
- package/src/github-client.js +520 -0
- package/src/import/core/shared.js +20 -62
- package/src/import/extractors/api/flutter-dio.js +4 -8
- package/src/import/extractors/api/react-native-repository.js +4 -8
- package/src/import/index.d.ts +4 -0
- package/src/import/provenance.d.ts +4 -0
- package/src/new-project.js +100 -11
- package/src/npm-safety.js +79 -0
- package/src/parser.d.ts +1 -0
- package/src/path-helpers.d.ts +1 -0
- package/src/path-helpers.js +20 -0
- package/src/project-config.js +1 -0
- package/src/reconcile/docs.d.ts +8 -0
- package/src/reconcile/journeys.d.ts +1 -0
- package/src/resolver.d.ts +1 -0
- package/src/runtime-support.js +29 -0
- package/src/sdlc/adopt.d.ts +1 -0
- package/src/sdlc/check.d.ts +1 -0
- package/src/sdlc/explain.d.ts +1 -0
- package/src/sdlc/release.d.ts +1 -0
- package/src/sdlc/scaffold.d.ts +1 -0
- package/src/sdlc/transition.d.ts +1 -0
- package/src/text-helpers.d.ts +6 -0
- package/src/text-helpers.js +245 -0
- package/src/topogram-config.js +306 -0
- package/src/validator.d.ts +2 -0
- package/src/workflows/adoption/index.js +26 -0
- package/src/workflows/docs-generate.js +262 -0
- package/src/workflows/docs-scan.js +703 -0
- package/src/workflows/docs.js +15 -0
- package/src/workflows/import-app/api.js +799 -0
- package/src/workflows/import-app/db.js +538 -0
- package/src/workflows/import-app/index.js +30 -0
- package/src/workflows/import-app/shared.js +218 -0
- package/src/workflows/import-app/ui.js +443 -0
- package/src/workflows/import-app/workflow.js +159 -0
- package/src/workflows/reconcile/adoption-plan.js +742 -0
- package/src/workflows/reconcile/auth.js +692 -0
- package/src/workflows/reconcile/bundle-core.js +600 -0
- package/src/workflows/reconcile/bundle-shared.js +75 -0
- package/src/workflows/reconcile/candidate-model.js +477 -0
- package/src/workflows/reconcile/canonical-surface.js +264 -0
- package/src/workflows/reconcile/gap-report.js +333 -0
- package/src/workflows/reconcile/ids.js +6 -0
- package/src/workflows/reconcile/impacts.js +625 -0
- package/src/workflows/reconcile/index.js +7 -0
- package/src/workflows/reconcile/renderers.js +461 -0
- package/src/workflows/reconcile/summary.js +90 -0
- package/src/workflows/reconcile/workflow.js +309 -0
- package/src/workflows/shared.js +189 -0
- package/src/workflows/types.d.ts +93 -0
- package/src/workflows.d.ts +1 -0
- package/src/workflows.js +10 -7652
package/src/format.d.ts
ADDED
|
@@ -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;
|
|
@@ -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
|
-
|
|
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, "&")
|
|
10
|
+
.replace(/</g, "<")
|
|
11
|
+
.replace(/>/g, ">");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {unknown} value
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
export function escapeAttr(value) {
|
|
19
|
+
return escapeHtml(value)
|
|
20
|
+
.replace(/"/g, """)
|
|
21
|
+
.replace(/'/g, "'");
|
|
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="/">${
|
|
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">${
|
|
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="/">${
|
|
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="/">${
|
|
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="/">${
|
|
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="/">${
|
|
393
|
-
: `<header class="app-nav${shellMode === "menu_bar" ? " menu-bar" : ""}">\n <a class="brand" href="/">${
|
|
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>${
|
|
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>${
|
|
294
|
-
<p>This page was generated from the <code>${
|
|
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
|
});
|