@runcontext/site 0.3.5 → 0.4.1
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/dist/index.cjs +1495 -649
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -8
- package/dist/index.d.ts +10 -8
- package/dist/index.mjs +1494 -648
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -3
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/generator.ts","../src/templates/shared.ts","../src/templates/index.ts","../src/templates/model.ts","../src/templates/schema.ts","../src/templates/rules.ts","../src/templates/glossary.ts","../src/templates/owner.ts","../src/templates/search.ts","../src/search/build-index.ts"],"sourcesContent":["/**\n * Site generator for ContextKit.\n *\n * `generateSite` accepts a Manifest and returns a Map of relative file path\n * to HTML content. The caller is responsible for writing files to disk.\n *\n * `buildSite` is a convenience function used by the CLI that compiles context,\n * generates the site, and writes files to the output directory.\n */\n\nimport ejs from 'ejs';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { Manifest, SiteConfig, ContextKitConfig } from '@runcontext/core';\nimport {\n indexTemplate,\n modelTemplate,\n schemaTemplate,\n rulesTemplate,\n glossaryTemplate,\n ownerTemplate,\n searchTemplate,\n} from './templates.js';\nimport { buildSearchIndex } from './search/build-index.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface GenerateSiteOptions {\n manifest: Manifest;\n config?: SiteConfig;\n}\n\n// ---------------------------------------------------------------------------\n// generateSite — pure function returning Map<path, html>\n// ---------------------------------------------------------------------------\n\n/**\n * Generate all HTML pages for the documentation site.\n *\n * @param manifest - The compiled ContextKit manifest\n * @param config - Optional site configuration (title, base_path)\n * @returns Map of relative file paths to HTML content\n */\nexport function generateSite(\n manifest: Manifest,\n config?: SiteConfig,\n): Map<string, string> {\n const files = new Map<string, string>();\n const siteTitle = config?.title ?? 'ContextKit';\n const basePath = (config?.base_path ?? '').replace(/\\/+$/, '') || '.';\n\n const commonData = {\n siteTitle,\n basePath,\n };\n\n // --- Index page ---\n files.set(\n 'index.html',\n ejs.render(indexTemplate, {\n ...commonData,\n pageTitle: 'Home',\n models: manifest.models,\n governance: manifest.governance,\n tiers: manifest.tiers,\n owners: manifest.owners,\n terms: manifest.terms,\n }),\n );\n\n // --- Model pages ---\n for (const [name, model] of Object.entries(manifest.models)) {\n const gov = manifest.governance[name] ?? null;\n const tier = manifest.tiers[name] ?? null;\n const rules = manifest.rules[name] ?? null;\n const lineage = manifest.lineage[name] ?? null;\n\n // Main model page\n files.set(\n `models/${name}.html`,\n ejs.render(modelTemplate, {\n ...commonData,\n pageTitle: name,\n model,\n gov,\n tier,\n rules,\n lineage,\n }),\n );\n\n // Schema browser page\n files.set(\n `models/${name}/schema.html`,\n ejs.render(schemaTemplate, {\n ...commonData,\n pageTitle: `${name} — Schema`,\n model,\n gov,\n tier,\n }),\n );\n\n // Rules page\n files.set(\n `models/${name}/rules.html`,\n ejs.render(rulesTemplate, {\n ...commonData,\n pageTitle: `${name} — Rules`,\n modelName: name,\n rules,\n }),\n );\n }\n\n // --- Glossary page ---\n files.set(\n 'glossary.html',\n ejs.render(glossaryTemplate, {\n ...commonData,\n pageTitle: 'Glossary',\n terms: manifest.terms,\n }),\n );\n\n // --- Owner pages ---\n for (const [oid, owner] of Object.entries(manifest.owners)) {\n // Find models governed by this owner\n const governedModels: Array<{ name: string; tier: string | null }> = [];\n for (const [modelName, gov] of Object.entries(manifest.governance)) {\n if (gov.owner === oid) {\n const tierScore = manifest.tiers[modelName];\n governedModels.push({\n name: modelName,\n tier: tierScore?.tier ?? null,\n });\n }\n }\n\n files.set(\n `owners/${oid}.html`,\n ejs.render(ownerTemplate, {\n ...commonData,\n pageTitle: owner.display_name,\n owner,\n governedModels,\n }),\n );\n }\n\n // --- Search page ---\n const searchIndex = buildSearchIndex(manifest, basePath);\n files.set(\n 'search.html',\n ejs.render(searchTemplate, {\n ...commonData,\n pageTitle: 'Search',\n searchIndexJson: JSON.stringify(searchIndex),\n }),\n );\n\n // --- Search index JSON (for programmatic access) ---\n files.set('search-index.json', JSON.stringify(searchIndex, null, 2));\n\n return files;\n}\n\n// ---------------------------------------------------------------------------\n// buildSite — convenience function used by CLI\n// ---------------------------------------------------------------------------\n\n/**\n * Build the documentation site and write files to disk.\n *\n * Called by the CLI `site` command. Accepts a manifest, config, and output\n * directory, then generates and writes all site files.\n *\n * @param manifest - The compiled ContextKit manifest\n * @param config - The full ContextKit configuration\n * @param outputDir - Directory to write site files to\n */\nexport async function buildSite(\n manifest: Manifest,\n config: ContextKitConfig,\n outputDir: string,\n): Promise<void> {\n const siteConfig = config.site;\n const files = generateSite(manifest, siteConfig);\n\n for (const [filePath, content] of files) {\n const fullPath = path.join(outputDir, filePath);\n const dir = path.dirname(fullPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(fullPath, content, 'utf-8');\n }\n}\n","/**\n * Shared layout constants for the ContextKit dark editorial site theme.\n *\n * Exports string fragments used by all page templates:\n * HEAD, NAV, FOOTER, TIER_BADGE, SCRIPTS\n */\n\n// ---------------------------------------------------------------------------\n// HEAD — doctype, meta, fonts, full CSS design system\n// ---------------------------------------------------------------------------\n\nexport const HEAD = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> — <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@300;400;500;600;700&family=DM+Sans:wght@300;400;500;600&family=JetBrains+Mono:wght@300;400;500&display=swap\" rel=\"stylesheet\">\n <style>\n :root {\n --gold: #D4A855;\n --gold-light: #E8C878;\n --gold-dark: #A07D3A;\n --gold-glow: rgba(212, 168, 85, 0.15);\n --bg: #0A0A0C;\n --bg-card: #111114;\n --bg-elevated: #18181C;\n --text: #E8E6E1;\n --text-muted: #8A8880;\n --text-dim: #5A5850;\n --border: #2A2A2E;\n --green: #4ADE80;\n --green-dim: rgba(74, 222, 128, 0.12);\n --blue: #60A5FA;\n --red: #F87171;\n --serif: 'Cormorant Garamond', Georgia, serif;\n --sans: 'DM Sans', system-ui, sans-serif;\n --mono: 'JetBrains Mono', monospace;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n overflow-x: hidden;\n }\n a { color: var(--gold); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === GRAIN OVERLAY === */\n body::before {\n content: '';\n position: fixed;\n inset: 0;\n background-image: url(\"data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E\");\n pointer-events: none;\n z-index: 9999;\n }\n\n /* === LAYOUT === */\n .page { max-width: 1200px; margin: 0 auto; padding: 3rem 2rem 6rem; }\n .section { margin-bottom: 3rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold-dark);\n margin-bottom: 0.75rem;\n }\n .section h2, h2.section-title {\n font-family: var(--serif);\n font-weight: 400;\n font-size: clamp(1.6rem, 3vw, 2.4rem);\n letter-spacing: -0.01em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n .section-intro {\n color: var(--text-muted);\n font-size: 1rem;\n max-width: 600px;\n margin-bottom: 2rem;\n font-weight: 300;\n }\n .divider {\n width: 100%;\n height: 1px;\n background: var(--border);\n margin: 0 auto;\n }\n\n /* === HERO (index page) === */\n .hero {\n text-align: center;\n padding: 5rem 2rem 4rem;\n position: relative;\n }\n .hero::before {\n content: '';\n position: absolute;\n top: -30%;\n left: 50%;\n transform: translateX(-50%);\n width: 700px;\n height: 700px;\n background: radial-gradient(circle, var(--gold-glow) 0%, transparent 70%);\n pointer-events: none;\n }\n .hero-eyebrow {\n font-family: var(--mono);\n font-size: 0.7rem;\n letter-spacing: 0.3em;\n text-transform: uppercase;\n color: var(--gold);\n margin-bottom: 1.5rem;\n position: relative;\n }\n .hero h1 {\n font-family: var(--serif);\n font-weight: 300;\n font-size: clamp(2.5rem, 6vw, 5rem);\n line-height: 1.1;\n letter-spacing: -0.02em;\n color: var(--text);\n position: relative;\n }\n .hero h1 em { font-style: italic; color: var(--gold); }\n .hero-sub {\n font-size: 1.05rem;\n color: var(--text-muted);\n max-width: 520px;\n margin: 1.5rem auto 0;\n font-weight: 300;\n position: relative;\n }\n\n /* === STATS === */\n .stats {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 3rem;\n }\n .stat {\n background: var(--bg-card);\n padding: 1.5rem;\n text-align: center;\n }\n .stat-value {\n font-family: var(--serif);\n font-size: 2.4rem;\n font-weight: 300;\n color: var(--gold);\n line-height: 1;\n }\n .stat-label {\n font-size: 0.7rem;\n color: var(--text-muted);\n margin-top: 0.4rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s, box-shadow 0.3s;\n }\n .card:hover {\n border-color: var(--gold-dark);\n box-shadow: 0 0 40px rgba(212,168,85,0.06);\n }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1.5rem; }\n .card-grid-sm { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-block;\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n padding: 0.2rem 0.55rem;\n border-radius: 4px;\n background: var(--bg);\n border: 1px solid var(--border);\n color: var(--text-muted);\n }\n .tag-gold { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .tag-silver { color: #C0C0C0; border-color: rgba(192,192,192,0.3); background: rgba(192,192,192,0.08); }\n .tag-bronze { color: #CD7F32; border-color: rgba(205,127,50,0.3); background: rgba(205,127,50,0.08); }\n .tag-none { color: var(--text-dim); border-color: var(--border); }\n .tag-green { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .tag-red { color: var(--red); border-color: rgba(248,113,113,0.3); background: rgba(248,113,113,0.08); }\n .tag-blue { color: var(--blue); border-color: rgba(96,165,250,0.3); background: rgba(96,165,250,0.08); }\n .tag-nav {\n padding: 0.35rem 0.75rem;\n font-size: 0.65rem;\n border-radius: 6px;\n text-decoration: none;\n transition: border-color 0.2s;\n }\n .tag-nav:hover { border-color: var(--gold); text-decoration: none; }\n\n /* === TABLES (dark) === */\n .table-dark { width: 100%; border-collapse: collapse; }\n .table-dark th {\n font-family: var(--mono);\n font-size: 0.65rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.75rem 1rem;\n border-bottom: 1px solid var(--border);\n }\n .table-dark td {\n padding: 0.75rem 1rem;\n border-bottom: 1px solid rgba(42,42,46,0.5);\n font-size: 0.85rem;\n vertical-align: top;\n }\n .table-dark tr:hover td { background: rgba(212,168,85,0.02); }\n .mono { font-family: var(--mono); font-size: 0.82rem; }\n\n /* === SEMANTIC ROLE PILLS === */\n .role-identifier { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }\n .role-metric { color: #06B6D4; border-color: rgba(6,182,212,0.3); background: rgba(6,182,212,0.08); }\n .role-dimension { color: #818CF8; border-color: rgba(129,140,248,0.3); background: rgba(129,140,248,0.08); }\n .role-date { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }\n .role-attribute { color: var(--text-muted); border-color: var(--border); }\n\n /* === DS TYPE PILLS === */\n .ds-type {\n font-family: var(--mono);\n font-size: 0.65rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n padding: 0.15rem 0.45rem;\n border-radius: 4px;\n display: inline-block;\n }\n .ds-type-fact { color: #818CF8; background: rgba(129,140,248,0.1); }\n .ds-type-dimension { color: #34D399; background: rgba(52,211,153,0.1); }\n .ds-type-event { color: #FB923C; background: rgba(251,146,60,0.1); }\n .ds-type-view { color: #A78BFA; background: rgba(167,139,250,0.1); }\n\n /* === METRIC CARDS === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--gold-light);\n margin-bottom: 0.5rem;\n }\n .metric-desc {\n color: var(--text-muted);\n font-size: 0.85rem;\n line-height: 1.6;\n margin-bottom: 1rem;\n }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.72rem;\n color: var(--text-dim);\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.8rem;\n line-height: 1.5;\n overflow-x: auto;\n }\n\n /* === QUERY CARDS === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 1.5rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .query-card:hover { border-color: var(--gold-dark); }\n .query-question {\n padding: 1.25rem 1.5rem;\n font-family: var(--serif);\n font-size: 1.1rem;\n font-weight: 400;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.6rem;\n }\n .query-question::before {\n content: 'Q';\n font-family: var(--mono);\n font-size: 0.6rem;\n font-style: normal;\n letter-spacing: 0.1em;\n color: var(--gold);\n background: rgba(212,168,85,0.1);\n border: 1px solid rgba(212,168,85,0.2);\n padding: 0.15rem 0.35rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.3rem;\n }\n .query-sql {\n padding: 1rem 1.5rem;\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text-muted);\n line-height: 1.6;\n background: var(--bg);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.6rem 1.5rem;\n display: flex;\n gap: 1rem;\n font-size: 0.7rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAILS === */\n .guardrail {\n border: 1px solid rgba(248, 113, 113, 0.2);\n border-radius: 12px;\n padding: 1.5rem;\n background: rgba(248, 113, 113, 0.03);\n margin-bottom: 1rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.82rem;\n color: var(--red);\n margin-bottom: 0.4rem;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n background: var(--bg);\n padding: 0.4rem 0.65rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.6rem;\n }\n .guardrail-reason {\n font-size: 0.82rem;\n color: var(--text-muted);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1.5rem 0;\n }\n .lineage-col {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n min-width: 220px;\n }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.25rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.75rem 1rem;\n background: var(--bg-card);\n transition: border-color 0.3s;\n }\n .lineage-node:hover { border-color: var(--gold-dark); }\n .lineage-node-name {\n font-family: var(--mono);\n font-size: 0.78rem;\n color: var(--text);\n margin-bottom: 0.15rem;\n }\n .lineage-node-detail {\n font-size: 0.7rem;\n color: var(--text-dim);\n }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 50px;\n color: var(--gold-dark);\n font-size: 1.3rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 12px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .scorecard-tier {\n background: var(--bg-card);\n padding: 1.5rem;\n }\n .scorecard-tier-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 1rem;\n }\n .scorecard-tier-name {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n text-transform: capitalize;\n }\n .scorecard-tier-name.bronze { color: #CD7F32; }\n .scorecard-tier-name.silver { color: #C0C0C0; }\n .scorecard-tier-name.gold { color: var(--gold); }\n .scorecard-pass {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n padding: 0.15rem 0.5rem;\n border-radius: 4px;\n }\n .scorecard-pass.passed { color: var(--green); background: var(--green-dim); }\n .scorecard-pass.failed { color: var(--red); background: rgba(248,113,113,0.1); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.4rem;\n padding: 0.3rem 0;\n font-size: 0.75rem;\n color: var(--text-muted);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.8rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n gap: 1.5rem;\n }\n .glossary-card {\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 1.5rem;\n background: var(--bg-card);\n position: relative;\n overflow: hidden;\n transition: border-color 0.3s;\n }\n .glossary-card:hover { border-color: var(--gold-dark); }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--gold-dark), transparent);\n }\n .glossary-term {\n font-family: var(--serif);\n font-size: 1.3rem;\n font-weight: 500;\n margin-bottom: 0.5rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.85rem;\n color: var(--text-muted);\n line-height: 1.7;\n font-weight: 300;\n }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.9rem;\n color: var(--text-dim);\n transition: transform 0.2s;\n width: 20px;\n text-align: center;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.3s ease;\n }\n\n /* === SEARCH === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 0.8rem 1.2rem;\n font-family: var(--sans);\n font-size: 1rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.2s, box-shadow 0.2s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--gold-dark);\n box-shadow: 0 0 0 3px var(--gold-glow);\n }\n\n /* === BACK LINK === */\n .back-link {\n font-size: 0.85rem;\n color: var(--text-muted);\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n margin-bottom: 1.5rem;\n text-decoration: none;\n }\n .back-link:hover { color: var(--gold); text-decoration: none; }\n\n /* === GOVERNANCE GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-item {\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.3rem;\n }\n .gov-value {\n font-size: 0.9rem;\n color: var(--text);\n }\n\n /* === ANIMATIONS === */\n @keyframes fadeUp {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n }\n .reveal {\n opacity: 0;\n transform: translateY(25px);\n transition: opacity 0.6s ease, transform 0.6s ease;\n }\n .reveal.visible {\n opacity: 1;\n transform: translateY(0);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: #818CF8; }\n .sql-fn { color: #34D399; }\n .sql-str { color: #FCD34D; }\n .sql-num { color: #FB923C; }\n .sql-cm { color: var(--text-dim); font-style: italic; }\n </style>\n</head>`;\n\n// ---------------------------------------------------------------------------\n// NAV — sticky dark navigation\n// ---------------------------------------------------------------------------\n\nexport const NAV = `<nav style=\"position:sticky;top:0;z-index:100;background:var(--bg-card);border-bottom:1px solid var(--border);padding:0.75rem 2rem;display:flex;align-items:center;gap:2rem;\">\n <a href=\"<%= basePath %>/index.html\" style=\"font-family:var(--serif);font-size:1.25rem;font-weight:600;color:var(--gold);text-decoration:none;\"><%- siteTitle %></a>\n <div style=\"display:flex;gap:1.5rem;font-size:0.82rem;\">\n <a href=\"<%= basePath %>/index.html\" style=\"color:var(--text-muted);text-decoration:none;\">Models</a>\n <a href=\"<%= basePath %>/glossary.html\" style=\"color:var(--text-muted);text-decoration:none;\">Glossary</a>\n <a href=\"<%= basePath %>/search.html\" style=\"color:var(--text-muted);text-decoration:none;\">Search</a>\n </div>\n</nav>`;\n\n// ---------------------------------------------------------------------------\n// FOOTER\n// ---------------------------------------------------------------------------\n\nexport const FOOTER = `<footer style=\"text-align:center;padding:3rem 2rem;color:var(--text-dim);font-size:0.78rem;border-top:1px solid var(--border);margin-top:4rem;\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\" style=\"color:var(--gold-dark);\">ContextKit</a>\n</footer>`;\n\n// ---------------------------------------------------------------------------\n// TIER_BADGE — EJS helper function (injected into templates that need it)\n// ---------------------------------------------------------------------------\n\nexport const TIER_BADGE = `<% function tierBadge(tier) {\n var cls = { none: 'tag-none', bronze: 'tag-bronze', silver: 'tag-silver', gold: 'tag-gold' };\n var c = cls[tier] || cls.none;\n return '<span class=\"tag ' + c + '\">' + tier + '</span>';\n} %>`;\n\n// ---------------------------------------------------------------------------\n// SCRIPTS — shared JavaScript for interactivity\n// ---------------------------------------------------------------------------\n\nexport const SCRIPTS = `<script>\n(function() {\n // Scroll-reveal observer\n var reveals = document.querySelectorAll('.reveal');\n if (reveals.length > 0 && 'IntersectionObserver' in window) {\n var observer = new IntersectionObserver(function(entries) {\n entries.forEach(function(e) {\n if (e.isIntersecting) {\n e.target.classList.add('visible');\n observer.unobserve(e.target);\n }\n });\n }, { threshold: 0.1 });\n reveals.forEach(function(el) { observer.observe(el); });\n } else {\n reveals.forEach(function(el) { el.classList.add('visible'); });\n }\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\\\u2212';\n }\n };\n\n // Simple SQL syntax highlighter — operates on trusted template-rendered content\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n var kw = /\\\\b(SELECT|FROM|WHERE|JOIN|LEFT|RIGHT|INNER|OUTER|CROSS|ON|AND|OR|NOT|IN|AS|GROUP|BY|ORDER|HAVING|LIMIT|OFFSET|UNION|ALL|DISTINCT|CASE|WHEN|THEN|ELSE|END|IS|NULL|BETWEEN|LIKE|EXISTS|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|SET|VALUES|INTO|WITH|DESC|ASC|OVER|PARTITION|WINDOW|FILTER|LATERAL|UNNEST|TRUE|FALSE)\\\\b/gi;\n var fn = /\\\\b(SUM|COUNT|AVG|MIN|MAX|ROUND|COALESCE|CAST|NULLIF|ABS|UPPER|LOWER|LENGTH|TRIM|SUBSTRING|CONCAT|ROW_NUMBER|RANK|DENSE_RANK|LAG|LEAD|FIRST_VALUE|LAST_VALUE|NTILE|PERCENTILE_CONT|STRING_AGG|ARRAY_AGG|LIST|STRUCT_PACK)\\\\b/gi;\n var str = /('(?:[^'\\\\\\\\]|\\\\\\\\.)*')/g;\n var num = /\\\\b(\\\\d+\\\\.?\\\\d*)\\\\b/g;\n\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n text = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>`;\n","import { HEAD, NAV, FOOTER, TIER_BADGE, SCRIPTS } from './shared.js';\n\nexport const indexTemplate = `${HEAD}\n<body>\n${NAV}\n${TIER_BADGE}\n\n<section class=\"hero\">\n <div class=\"hero-eyebrow\"><%- siteTitle %></div>\n <h1>Metadata<br><em>Catalog</em></h1>\n <p class=\"hero-sub\">Explore semantic models, governed datasets, business glossary, and data quality tiers.</p>\n</section>\n\n<div class=\"divider\"></div>\n\n<main class=\"page\">\n <%\n var modelNames = Object.keys(models);\n var totalDatasets = 0;\n var totalFields = 0;\n for (var mn of modelNames) {\n var m = models[mn];\n if (m.datasets) {\n totalDatasets += m.datasets.length;\n for (var ds of m.datasets) {\n if (ds.fields) totalFields += ds.fields.length;\n }\n }\n }\n var totalTerms = Object.keys(terms).length;\n var totalOwners = Object.keys(owners).length;\n %>\n\n <div class=\"stats reveal\">\n <div class=\"stat\">\n <div class=\"stat-value\"><%= modelNames.length %></div>\n <div class=\"stat-label\">Models</div>\n </div>\n <div class=\"stat\">\n <div class=\"stat-value\"><%= totalDatasets %></div>\n <div class=\"stat-label\">Datasets</div>\n </div>\n <div class=\"stat\">\n <div class=\"stat-value\"><%= totalFields %></div>\n <div class=\"stat-label\">Fields</div>\n </div>\n <div class=\"stat\">\n <div class=\"stat-value\"><%= totalTerms %></div>\n <div class=\"stat-label\">Terms</div>\n </div>\n <div class=\"stat\">\n <div class=\"stat-value\"><%= totalOwners %></div>\n <div class=\"stat-label\">Owners</div>\n </div>\n </div>\n\n <div class=\"section reveal\">\n <div class=\"section-label\">Semantic Models</div>\n <h2 class=\"section-title\">Models</h2>\n <% if (modelNames.length === 0) { %>\n <p style=\"color:var(--text-muted);\">No models found.</p>\n <% } else { %>\n <div class=\"card-grid\">\n <% for (var name of modelNames) { %>\n <div class=\"card\">\n <div style=\"display:flex;align-items:center;gap:0.6rem;margin-bottom:0.6rem;\">\n <a href=\"<%= basePath %>/models/<%= name %>.html\" style=\"font-family:var(--mono);font-size:0.95rem;color:var(--gold-light);text-decoration:none;\">\n <%= name %>\n </a>\n <% if (tiers[name]) { %><%- tierBadge(tiers[name].tier) %><% } %>\n </div>\n <% if (models[name].description) { %>\n <p style=\"color:var(--text-muted);font-size:0.85rem;margin-bottom:0.75rem;font-weight:300;\"><%= models[name].description %></p>\n <% } %>\n <% if (governance[name]) { %>\n <div style=\"display:flex;gap:0.5rem;flex-wrap:wrap;align-items:center;\">\n <% if (governance[name].owner) { %>\n <a href=\"<%= basePath %>/owners/<%= governance[name].owner %>.html\" class=\"tag tag-nav\" style=\"text-decoration:none;\"><%= governance[name].owner %></a>\n <% } %>\n <% if (governance[name].trust) { %>\n <span class=\"tag tag-green\"><%= governance[name].trust %></span>\n <% } %>\n <% if (governance[name].tags) { for (var t of governance[name].tags) { %>\n <span class=\"tag\"><%= t %></span>\n <% } } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n\n <% if (Object.keys(owners).length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Data Stewardship</div>\n <h2 class=\"section-title\">Owners</h2>\n <div class=\"card-grid-sm\">\n <% for (var oid of Object.keys(owners)) { %>\n <a href=\"<%= basePath %>/owners/<%= oid %>.html\" class=\"card\" style=\"text-decoration:none;\">\n <div style=\"font-size:1rem;font-weight:500;color:var(--text);margin-bottom:0.25rem;\"><%= owners[oid].display_name %></div>\n <% if (owners[oid].team) { %>\n <div style=\"font-size:0.78rem;color:var(--text-dim);\"><%= owners[oid].team %></div>\n <% } %>\n </a>\n <% } %>\n </div>\n </div>\n <% } %>\n</main>\n\n${FOOTER}\n${SCRIPTS}\n</body>\n</html>`;\n","import { HEAD, NAV, FOOTER, TIER_BADGE, SCRIPTS } from './shared.js';\n\nexport const modelTemplate = `${HEAD}\n<body>\n${NAV}\n${TIER_BADGE}\n\n<main class=\"page\">\n <a href=\"<%= basePath %>/\" class=\"back-link\">← All Models</a>\n\n <div style=\"display:flex;align-items:center;gap:0.75rem;margin-bottom:0.5rem;\">\n <h1 style=\"font-family:var(--serif);font-weight:300;font-size:clamp(2rem,5vw,3.5rem);color:var(--text);\"><%= model.name %></h1>\n <% if (tier) { %><%- tierBadge(tier.tier) %><% } %>\n </div>\n <% if (model.description) { %>\n <p style=\"color:var(--text-muted);font-size:1rem;margin-bottom:1.5rem;max-width:700px;font-weight:300;\"><%= model.description %></p>\n <% } %>\n\n <div style=\"display:flex;gap:0.6rem;margin-bottom:2.5rem;\">\n <a href=\"<%= basePath %>/models/<%= model.name %>/schema.html\" class=\"tag tag-nav tag-blue\">Schema Browser</a>\n <a href=\"<%= basePath %>/models/<%= model.name %>/rules.html\" class=\"tag tag-nav tag-gold\">Rules & Queries</a>\n </div>\n\n <% if (gov) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Governance</div>\n <div class=\"gov-grid\">\n <div class=\"gov-item\">\n <div class=\"gov-label\">Owner</div>\n <div class=\"gov-value\"><a href=\"<%= basePath %>/owners/<%= gov.owner %>.html\"><%= gov.owner %></a></div>\n </div>\n <% if (gov.trust) { %>\n <div class=\"gov-item\">\n <div class=\"gov-label\">Trust</div>\n <div class=\"gov-value\"><%= gov.trust %></div>\n </div>\n <% } %>\n <% if (gov.security) { %>\n <div class=\"gov-item\">\n <div class=\"gov-label\">Security</div>\n <div class=\"gov-value\"><%= gov.security %></div>\n </div>\n <% } %>\n <% if (gov.tags && gov.tags.length > 0) { %>\n <div class=\"gov-item\">\n <div class=\"gov-label\">Tags</div>\n <div class=\"gov-value\" style=\"display:flex;gap:0.4rem;flex-wrap:wrap;\">\n <% for (var t of gov.tags) { %><span class=\"tag\"><%= t %></span><% } %>\n </div>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (model.datasets && model.datasets.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Data Explorer</div>\n <h2 class=\"section-title\">Datasets</h2>\n <p style=\"color:var(--text-muted);font-size:0.85rem;margin-bottom:1.5rem;font-weight:300;\">Click a dataset to explore its fields, governance, and metadata.</p>\n <div style=\"display:flex;flex-direction:column;gap:1rem;\">\n <% for (var i = 0; i < model.datasets.length; i++) { var ds = model.datasets[i]; %>\n <% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>\n <div class=\"card\">\n <div class=\"expandable-header\" onclick=\"toggleExpand('ds-<%= i %>')\">\n <div>\n <div style=\"display:flex;align-items:center;gap:0.6rem;margin-bottom:0.25rem;\">\n <span class=\"mono\" style=\"color:var(--text);\"><%= ds.name %></span>\n <% if (dsGov && dsGov.table_type) { %>\n <span class=\"ds-type ds-type-<%= dsGov.table_type %>\"><%= dsGov.table_type %></span>\n <% } %>\n <% if (ds.fields) { %><span class=\"tag\"><%= ds.fields.length %> fields</span><% } %>\n </div>\n <div style=\"font-size:0.75rem;color:var(--text-dim);font-family:var(--mono);\">\n <%= ds.source %>\n <% if (dsGov && dsGov.grain) { %> · <span style=\"color:var(--text-muted);font-family:var(--sans);\"><%= dsGov.grain %></span><% } %>\n <% if (dsGov && dsGov.refresh) { %> · <span style=\"color:var(--text-muted);font-family:var(--sans);\"><%= dsGov.refresh %></span><% } %>\n </div>\n <% if (ds.description) { %>\n <p style=\"font-size:0.82rem;color:var(--text-muted);margin-top:0.4rem;font-weight:300;\"><%= ds.description %></p>\n <% } %>\n </div>\n <span class=\"expand-icon\" id=\"ds-<%= i %>-icon\">+</span>\n </div>\n <div class=\"expandable-content\" id=\"ds-<%= i %>\">\n <% if (ds.fields && ds.fields.length > 0) { %>\n <table class=\"table-dark\" style=\"margin-top:1rem;\">\n <thead>\n <tr>\n <th>Field</th>\n <th>Description</th>\n <th>Role</th>\n <th>Aggregation</th>\n </tr>\n </thead>\n <tbody>\n <% for (var field of ds.fields) { %>\n <% var fKey = ds.name + '.' + field.name; %>\n <% var fGov = gov && gov.fields && gov.fields[fKey]; %>\n <% var role = fGov && fGov.semantic_role ? fGov.semantic_role : ''; %>\n <tr>\n <td class=\"mono\" style=\"font-size:0.78rem;\"><%= field.name %></td>\n <td style=\"color:var(--text-muted);font-size:0.82rem;\"><%= field.description || '' %></td>\n <td><% if (role) { %><span class=\"tag role-<%= role %>\"><%= role %></span><% } %></td>\n <td style=\"font-family:var(--mono);font-size:0.78rem;color:var(--text-dim);\"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %></td>\n </tr>\n <% } %>\n </tbody>\n </table>\n <% } %>\n </div>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (model.relationships && model.relationships.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Data Model</div>\n <h2 class=\"section-title\">Relationships</h2>\n <table class=\"table-dark\">\n <thead>\n <tr><th>Name</th><th>From</th><th></th><th>To</th></tr>\n </thead>\n <tbody>\n <% for (var rel of model.relationships) { %>\n <tr>\n <td style=\"color:var(--text-muted);font-size:0.82rem;\"><%= rel.name %></td>\n <td class=\"mono\" style=\"font-size:0.78rem;\"><%= rel.from %></td>\n <td style=\"color:var(--gold-dark);text-align:center;\">→</td>\n <td class=\"mono\" style=\"font-size:0.78rem;\"><%= rel.to %></td>\n </tr>\n <% } %>\n </tbody>\n </table>\n </div>\n <% } %>\n\n <% if (model.metrics && model.metrics.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Computed Metrics</div>\n <h2 class=\"section-title\">Metrics</h2>\n <div class=\"card-grid\">\n <% for (var metric of model.metrics) { %>\n <div class=\"card\">\n <div class=\"metric-name\"><%= metric.name %></div>\n <% if (metric.description) { %><div class=\"metric-desc\"><%= metric.description %></div><% } %>\n <% if (metric.expression && metric.expression.dialects && metric.expression.dialects.length > 0) { %>\n <div class=\"metric-formula\"><%= metric.expression.dialects[0].expression %></div>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (lineage && (lineage.upstream && lineage.upstream.length > 0 || lineage.downstream && lineage.downstream.length > 0)) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Data Lineage</div>\n <h2 class=\"section-title\">Lineage</h2>\n <div class=\"lineage-flow\">\n <% if (lineage.upstream && lineage.upstream.length > 0) { %>\n <div class=\"lineage-col\">\n <div class=\"lineage-col-label\">Upstream</div>\n <% for (var u of lineage.upstream) { %>\n <div class=\"lineage-node\">\n <div class=\"lineage-node-name\"><%= u.source %></div>\n <div class=\"lineage-node-detail\"><%= u.type || '' %><% if (u.tool) { %> via <%= u.tool %><% } %></div>\n </div>\n <% } %>\n </div>\n <div class=\"lineage-arrow\">→</div>\n <% } %>\n <div class=\"lineage-col\">\n <div class=\"lineage-col-label\">This Model</div>\n <div class=\"lineage-node\" style=\"border-color:var(--gold-dark);\">\n <div class=\"lineage-node-name\" style=\"color:var(--gold);\"><%= model.name %></div>\n </div>\n </div>\n <% if (lineage.downstream && lineage.downstream.length > 0) { %>\n <div class=\"lineage-arrow\">→</div>\n <div class=\"lineage-col\">\n <div class=\"lineage-col-label\">Downstream</div>\n <% for (var d of lineage.downstream) { %>\n <div class=\"lineage-node\">\n <div class=\"lineage-node-name\"><%= d.target %></div>\n <div class=\"lineage-node-detail\"><%= d.type || '' %><% if (d.tool) { %> via <%= d.tool %><% } %></div>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (tier) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Data Quality</div>\n <h2 class=\"section-title\">Tier Scorecard</h2>\n <div class=\"scorecard\">\n <% var tierLevels = ['bronze', 'silver', 'gold']; %>\n <% for (var lvl of tierLevels) { %>\n <div class=\"scorecard-tier\">\n <div class=\"scorecard-tier-header\">\n <span class=\"scorecard-tier-name <%= lvl %>\"><%= lvl %></span>\n <% if (tier[lvl].passed) { %>\n <span class=\"scorecard-pass passed\">Passed</span>\n <% } else { %>\n <span class=\"scorecard-pass failed\">Not passed</span>\n <% } %>\n </div>\n <% if (tier[lvl].checks && tier[lvl].checks.length > 0) { %>\n <ul class=\"check-list\">\n <% for (var chk of tier[lvl].checks) { %>\n <li class=\"check-item\">\n <span class=\"check-icon <%= chk.passed ? 'pass' : 'fail' %>\"><%= chk.passed ? '\\\\u2713' : '\\\\u2717' %></span>\n <span><%= chk.label %><% if (chk.detail) { %> — <span style=\"color:var(--text-dim);\"><%= chk.detail %></span><% } %></span>\n </li>\n <% } %>\n </ul>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n</main>\n\n${FOOTER}\n${SCRIPTS}\n</body>\n</html>`;\n","import { HEAD, NAV, FOOTER, TIER_BADGE, SCRIPTS } from './shared.js';\n\nexport const schemaTemplate = `${HEAD}\n<body>\n${NAV}\n${TIER_BADGE}\n\n<main class=\"page\">\n <a href=\"<%= basePath %>/models/<%= model.name %>.html\" class=\"back-link\">← Back to <%= model.name %></a>\n\n <div style=\"display:flex;align-items:center;gap:0.75rem;margin-bottom:2rem;\">\n <h1 style=\"font-family:var(--serif);font-weight:300;font-size:clamp(1.8rem,4vw,2.8rem);color:var(--text);\"><%= model.name %></h1>\n <% if (tier) { %><%- tierBadge(tier.tier) %><% } %>\n <span style=\"font-size:0.85rem;color:var(--text-dim);\">Schema Browser</span>\n </div>\n\n <% if (model.datasets && model.datasets.length > 0) { %>\n <% for (var i = 0; i < model.datasets.length; i++) { var ds = model.datasets[i]; %>\n <% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>\n <div class=\"section reveal\">\n <div class=\"card\" style=\"padding:0;overflow:hidden;\">\n <div style=\"padding:1.25rem 1.5rem;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.5rem;\">\n <div style=\"display:flex;align-items:center;gap:0.6rem;\">\n <span class=\"mono\" style=\"font-size:1rem;color:var(--text);\"><%= ds.name %></span>\n <% if (dsGov && dsGov.table_type) { %>\n <span class=\"ds-type ds-type-<%= dsGov.table_type %>\"><%= dsGov.table_type %></span>\n <% } %>\n </div>\n <div style=\"display:flex;gap:0.75rem;flex-wrap:wrap;\">\n <% if (dsGov && dsGov.grain) { %><span class=\"tag\"><%= dsGov.grain %></span><% } %>\n <% if (dsGov && dsGov.refresh) { %><span class=\"tag\"><%= dsGov.refresh %></span><% } %>\n <% if (dsGov && dsGov.security) { %><span class=\"tag\"><%= dsGov.security %></span><% } %>\n </div>\n </div>\n <div style=\"padding:0 1.5rem 0.5rem;font-size:0.75rem;color:var(--text-dim);font-family:var(--mono);padding-top:0.75rem;\">\n Source: <%= ds.source %>\n </div>\n <% if (ds.description) { %>\n <div style=\"padding:0 1.5rem 1rem;font-size:0.85rem;color:var(--text-muted);font-weight:300;\"><%= ds.description %></div>\n <% } %>\n\n <% if (ds.fields && ds.fields.length > 0) { %>\n <table class=\"table-dark\">\n <thead>\n <tr>\n <th>Field</th>\n <th>Description</th>\n <th>Semantic Role</th>\n <th>Aggregation</th>\n </tr>\n </thead>\n <tbody>\n <% for (var field of ds.fields) { %>\n <% var fieldKey = ds.name + '.' + field.name; %>\n <% var fGov = gov && gov.fields && gov.fields[fieldKey]; %>\n <% var role = fGov && fGov.semantic_role ? fGov.semantic_role : ''; %>\n <tr>\n <td class=\"mono\" style=\"font-size:0.78rem;\"><%= field.name %></td>\n <td style=\"color:var(--text-muted);font-size:0.82rem;\"><%= field.description || '' %></td>\n <td><% if (role) { %><span class=\"tag role-<%= role %>\"><%= role %></span><% } %></td>\n <td style=\"font-family:var(--mono);font-size:0.78rem;color:var(--text-dim);\"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %></td>\n </tr>\n <% } %>\n </tbody>\n </table>\n <% } else { %>\n <p style=\"padding:1.5rem;color:var(--text-dim);font-size:0.85rem;\">No fields defined.</p>\n <% } %>\n </div>\n </div>\n <% } %>\n <% } else { %>\n <p style=\"color:var(--text-muted);\">No datasets found.</p>\n <% } %>\n</main>\n\n${FOOTER}\n${SCRIPTS}\n</body>\n</html>`;\n","import { HEAD, NAV, FOOTER, SCRIPTS } from './shared.js';\n\nexport const rulesTemplate = `${HEAD}\n<body>\n${NAV}\n\n<main class=\"page\">\n <a href=\"<%= basePath %>/models/<%= modelName %>.html\" class=\"back-link\">← Back to <%= modelName %></a>\n\n <h1 style=\"font-family:var(--serif);font-weight:300;font-size:clamp(1.8rem,4vw,2.8rem);color:var(--text);margin-bottom:2rem;\">\n <%= modelName %> <span style=\"color:var(--text-dim);font-weight:300;\">— Rules & Queries</span>\n </h1>\n\n <% if (rules && rules.golden_queries && rules.golden_queries.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Golden Queries</div>\n <h2 class=\"section-title\">Pre-validated questions</h2>\n <% for (var gq of rules.golden_queries) { %>\n <div class=\"query-card\">\n <div class=\"query-question\"><%= gq.question %></div>\n <div class=\"query-sql sql-highlight\"><%= gq.sql %></div>\n <% if (gq.dialect || (gq.tags && gq.tags.length > 0)) { %>\n <div class=\"query-meta\">\n <% if (gq.dialect) { %><span>Dialect: <%= gq.dialect %></span><% } %>\n <% if (gq.tags && gq.tags.length > 0) { %><span>Tags: <%= gq.tags.join(', ') %></span><% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n\n <% if (rules && rules.business_rules && rules.business_rules.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Business Rules</div>\n <h2 class=\"section-title\">Business Rules</h2>\n <div style=\"display:flex;flex-direction:column;gap:1rem;\">\n <% for (var br of rules.business_rules) { %>\n <div class=\"card\">\n <div style=\"font-family:var(--mono);font-size:0.88rem;color:var(--gold-light);margin-bottom:0.4rem;\"><%= br.name %></div>\n <p style=\"font-size:0.85rem;color:var(--text-muted);line-height:1.6;font-weight:300;\"><%= br.definition %></p>\n <% if (br.enforcement && br.enforcement.length > 0) { %>\n <div style=\"display:flex;gap:0.4rem;margin-top:0.6rem;flex-wrap:wrap;\">\n <% for (var e of br.enforcement) { %><span class=\"tag tag-green\"><%= e %></span><% } %>\n </div>\n <% } %>\n <% if (br.avoid && br.avoid.length > 0) { %>\n <div style=\"display:flex;gap:0.4rem;margin-top:0.4rem;flex-wrap:wrap;\">\n <% for (var a of br.avoid) { %><span class=\"tag tag-red\"><%= a %></span><% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (rules && rules.guardrail_filters && rules.guardrail_filters.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Safety</div>\n <h2 class=\"section-title\">Guardrail Filters</h2>\n <% for (var gf of rules.guardrail_filters) { %>\n <div class=\"guardrail\">\n <div class=\"guardrail-name\"><%= gf.name %></div>\n <div class=\"guardrail-filter\"><%= gf.filter %></div>\n <div class=\"guardrail-reason\"><%= gf.reason %></div>\n <% if (gf.tables && gf.tables.length > 0) { %>\n <div style=\"display:flex;gap:0.4rem;margin-top:0.6rem;\">\n <% for (var tb of gf.tables) { %><span class=\"tag\"><%= tb %></span><% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n\n <% if (rules && rules.hierarchies && rules.hierarchies.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Drill Paths</div>\n <h2 class=\"section-title\">Hierarchies</h2>\n <div style=\"display:flex;flex-direction:column;gap:1rem;\">\n <% for (var h of rules.hierarchies) { %>\n <div class=\"card\">\n <div style=\"font-family:var(--mono);font-size:0.88rem;color:var(--gold-light);margin-bottom:0.4rem;\"><%= h.name %></div>\n <div style=\"font-size:0.82rem;color:var(--text-muted);margin-bottom:0.4rem;\">Dataset: <span class=\"mono\"><%= h.dataset %></span></div>\n <div style=\"display:flex;align-items:center;gap:0.5rem;flex-wrap:wrap;\">\n <% for (var li = 0; li < h.levels.length; li++) { %>\n <span class=\"tag tag-blue\"><%= h.levels[li] %></span>\n <% if (li < h.levels.length - 1) { %><span style=\"color:var(--gold-dark);\">→</span><% } %>\n <% } %>\n </div>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (!rules || ((!rules.golden_queries || rules.golden_queries.length === 0) && (!rules.business_rules || rules.business_rules.length === 0) && (!rules.guardrail_filters || rules.guardrail_filters.length === 0) && (!rules.hierarchies || rules.hierarchies.length === 0))) { %>\n <p style=\"color:var(--text-muted);\">No rules or queries defined for this model.</p>\n <% } %>\n</main>\n\n${FOOTER}\n${SCRIPTS}\n</body>\n</html>`;\n","import { HEAD, NAV, FOOTER, SCRIPTS } from './shared.js';\n\nexport const glossaryTemplate = `${HEAD}\n<body>\n${NAV}\n\n<main class=\"page\">\n <h1 style=\"font-family:var(--serif);font-weight:300;font-size:clamp(2rem,5vw,3.5rem);color:var(--text);margin-bottom:0.5rem;\">Glossary</h1>\n <p style=\"color:var(--text-muted);font-size:1rem;margin-bottom:2rem;font-weight:300;\">Business term definitions and mappings.</p>\n\n <% var termIds = Object.keys(terms).sort(); %>\n <% if (termIds.length === 0) { %>\n <p style=\"color:var(--text-muted);\">No terms defined.</p>\n <% } else { %>\n <input type=\"text\" id=\"glossary-filter\"\n placeholder=\"Filter terms...\"\n class=\"search-input\"\n style=\"margin-bottom:2rem;max-width:400px;\" />\n\n <div class=\"glossary-grid\" id=\"glossary-grid\">\n <% for (var tid of termIds) { %>\n <% var term = terms[tid]; %>\n <div class=\"glossary-card\" id=\"term-<%= tid %>\" data-term=\"<%= tid %>\">\n <div class=\"glossary-term\"><%= tid %></div>\n <div class=\"glossary-def\"><%= term.definition %></div>\n <% if (term.synonyms && term.synonyms.length > 0) { %>\n <div style=\"display:flex;gap:0.4rem;margin-top:0.75rem;flex-wrap:wrap;\">\n <% for (var s of term.synonyms) { %><span class=\"tag\"><%= s %></span><% } %>\n </div>\n <% } %>\n <% if (term.maps_to && term.maps_to.length > 0) { %>\n <div style=\"display:flex;gap:0.4rem;margin-top:0.5rem;flex-wrap:wrap;\">\n <% for (var m of term.maps_to) { %><span class=\"tag tag-blue\"><%= m %></span><% } %>\n </div>\n <% } %>\n <% if (term.owner) { %>\n <div style=\"font-size:0.78rem;color:var(--text-dim);margin-top:0.6rem;\">\n Owner: <a href=\"<%= basePath %>/owners/<%= term.owner %>.html\"><%= term.owner %></a>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n</main>\n\n${FOOTER}\n${SCRIPTS}\n<script>\n(function() {\n var input = document.getElementById('glossary-filter');\n if (!input) return;\n var cards = document.querySelectorAll('.glossary-card');\n input.addEventListener('input', function() {\n var q = input.value.toLowerCase();\n cards.forEach(function(card) {\n var text = card.textContent.toLowerCase();\n card.style.display = text.indexOf(q) !== -1 ? '' : 'none';\n });\n });\n})();\n</script>\n</body>\n</html>`;\n","import { HEAD, NAV, FOOTER, TIER_BADGE, SCRIPTS } from './shared.js';\n\nexport const ownerTemplate = `${HEAD}\n<body>\n${NAV}\n${TIER_BADGE}\n\n<main class=\"page\">\n <a href=\"<%= basePath %>/\" class=\"back-link\">← All Models</a>\n\n <h1 style=\"font-family:var(--serif);font-weight:300;font-size:clamp(2rem,5vw,3.5rem);color:var(--text);margin-bottom:0.5rem;\"><%= owner.display_name %></h1>\n\n <div style=\"display:flex;gap:1.5rem;font-size:0.85rem;color:var(--text-dim);margin-bottom:1.5rem;\">\n <% if (owner.email) { %><span>Email: <span style=\"color:var(--text-muted);\"><%= owner.email %></span></span><% } %>\n <% if (owner.team) { %><span>Team: <span style=\"color:var(--text-muted);\"><%= owner.team %></span></span><% } %>\n </div>\n\n <% if (owner.description) { %>\n <p style=\"color:var(--text-muted);font-size:0.95rem;margin-bottom:2rem;font-weight:300;max-width:600px;\"><%= owner.description %></p>\n <% } %>\n\n <% if (governedModels.length > 0) { %>\n <div class=\"section reveal\">\n <div class=\"section-label\">Stewardship</div>\n <h2 class=\"section-title\">Governed Models</h2>\n <div class=\"card-grid-sm\">\n <% for (var gm of governedModels) { %>\n <a href=\"<%= basePath %>/models/<%= gm.name %>.html\" class=\"card\" style=\"text-decoration:none;\">\n <div style=\"display:flex;align-items:center;gap:0.5rem;\">\n <span class=\"mono\" style=\"color:var(--gold-light);\"><%= gm.name %></span>\n <% if (gm.tier) { %><%- tierBadge(gm.tier) %><% } %>\n </div>\n </a>\n <% } %>\n </div>\n </div>\n <% } else { %>\n <p style=\"color:var(--text-muted);\">No models governed by this owner.</p>\n <% } %>\n</main>\n\n${FOOTER}\n${SCRIPTS}\n</body>\n</html>`;\n","import { HEAD, NAV, FOOTER, SCRIPTS } from './shared.js';\n\nexport const searchTemplate = `${HEAD}\n<body>\n${NAV}\n\n<main class=\"page\">\n <h1 style=\"font-family:var(--serif);font-weight:300;font-size:clamp(2rem,5vw,3.5rem);color:var(--text);margin-bottom:2rem;\">Search</h1>\n\n <input type=\"text\" id=\"search-input\"\n placeholder=\"Search models, datasets, terms...\"\n class=\"search-input\"\n style=\"margin-bottom:2rem;\" />\n\n <div id=\"search-results\" style=\"display:flex;flex-direction:column;gap:0.75rem;\"></div>\n</main>\n\n${FOOTER}\n${SCRIPTS}\n\n<script src=\"https://cdn.jsdelivr.net/npm/minisearch@7.1.0/dist/umd/index.min.js\"></script>\n<script>\n(function() {\n var indexData = <%- searchIndexJson %>;\n var miniSearch = MiniSearch.loadJSON(JSON.stringify(indexData.index), indexData.options);\n\n var input = document.getElementById('search-input');\n var resultsContainer = document.getElementById('search-results');\n var docs = indexData.documents;\n\n var typeColors = {\n model: 'tag-gold',\n dataset: 'tag-blue',\n term: 'tag-green',\n owner: 'tag-bronze'\n };\n\n function clearResults() {\n while (resultsContainer.firstChild) {\n resultsContainer.removeChild(resultsContainer.firstChild);\n }\n }\n\n function createResultElement(doc) {\n var wrapper = document.createElement('div');\n wrapper.className = 'card';\n wrapper.style.padding = '1rem 1.25rem';\n\n var top = document.createElement('div');\n top.style.display = 'flex';\n top.style.alignItems = 'center';\n top.style.gap = '0.5rem';\n\n var link = document.createElement('a');\n link.href = doc.url;\n link.style.fontFamily = 'var(--mono)';\n link.style.fontSize = '0.88rem';\n link.style.color = 'var(--gold-light)';\n link.textContent = doc.title;\n top.appendChild(link);\n\n var badge = document.createElement('span');\n badge.className = 'tag ' + (typeColors[doc.type] || '');\n badge.textContent = doc.type;\n top.appendChild(badge);\n\n wrapper.appendChild(top);\n\n if (doc.description) {\n var desc = document.createElement('p');\n desc.style.fontSize = '0.82rem';\n desc.style.color = 'var(--text-muted)';\n desc.style.marginTop = '0.3rem';\n desc.style.fontWeight = '300';\n desc.textContent = doc.description;\n wrapper.appendChild(desc);\n }\n\n return wrapper;\n }\n\n input.addEventListener('input', function() {\n var query = input.value.trim();\n clearResults();\n if (!query) return;\n var hits = miniSearch.search(query, { prefix: true, fuzzy: 0.2 });\n if (hits.length === 0) {\n var noResults = document.createElement('p');\n noResults.style.color = 'var(--text-muted)';\n noResults.textContent = 'No results found.';\n resultsContainer.appendChild(noResults);\n return;\n }\n hits.slice(0, 20).forEach(function(hit) {\n var doc = docs[hit.id];\n if (doc) {\n resultsContainer.appendChild(createResultElement(doc));\n }\n });\n });\n})();\n</script>\n</body>\n</html>`;\n","/**\n * Builds a MiniSearch index from a Manifest for client-side search.\n *\n * The index is serialized to JSON so it can be embedded in the search page\n * and loaded by MiniSearch on the client side.\n */\n\nimport MiniSearch from 'minisearch';\nimport type { Manifest } from '@runcontext/core';\n\nexport interface SearchDocument {\n id: string;\n type: string;\n title: string;\n description: string;\n url: string;\n}\n\nexport interface SearchIndex {\n /** Serialized MiniSearch index (JSON-parsed object). */\n index: unknown;\n /** MiniSearch constructor options needed to reload the index. */\n options: {\n fields: string[];\n storeFields: string[];\n idField: string;\n };\n /** Map of document ID to document metadata for rendering results. */\n documents: Record<string, SearchDocument>;\n}\n\nconst MINISEARCH_OPTIONS = {\n fields: ['title', 'description', 'type'],\n storeFields: ['title', 'description', 'type', 'url'],\n idField: 'id',\n};\n\n/**\n * Build a search index from a manifest.\n *\n * @param manifest - The compiled ContextKit manifest\n * @param basePath - The base URL path for links (e.g. '' or '/docs')\n * @returns A SearchIndex object ready for JSON serialization\n */\nexport function buildSearchIndex(manifest: Manifest, basePath: string): SearchIndex {\n const docs: SearchDocument[] = [];\n let idCounter = 0;\n\n // Index models\n for (const [name, model] of Object.entries(manifest.models)) {\n docs.push({\n id: String(idCounter++),\n type: 'model',\n title: name,\n description: model.description ?? '',\n url: `${basePath}/models/${name}.html`,\n });\n\n // Index datasets within each model\n if (model.datasets) {\n for (const ds of model.datasets) {\n docs.push({\n id: String(idCounter++),\n type: 'dataset',\n title: `${name} / ${ds.name}`,\n description: ds.description ?? '',\n url: `${basePath}/models/${name}/schema.html`,\n });\n }\n }\n }\n\n // Index glossary terms\n for (const [termId, term] of Object.entries(manifest.terms)) {\n docs.push({\n id: String(idCounter++),\n type: 'term',\n title: termId,\n description: term.definition,\n url: `${basePath}/glossary.html#term-${termId}`,\n });\n }\n\n // Index owners\n for (const [oid, owner] of Object.entries(manifest.owners)) {\n docs.push({\n id: String(idCounter++),\n type: 'owner',\n title: owner.display_name,\n description: owner.description ?? '',\n url: `${basePath}/owners/${oid}.html`,\n });\n }\n\n // Build MiniSearch index\n const miniSearch = new MiniSearch(MINISEARCH_OPTIONS);\n miniSearch.addAll(docs);\n\n // Create document lookup map by id\n const documentsMap: Record<string, SearchDocument> = {};\n for (const doc of docs) {\n documentsMap[doc.id] = doc;\n }\n\n return {\n index: JSON.parse(JSON.stringify(miniSearch)),\n options: MINISEARCH_OPTIONS,\n documents: documentsMap,\n };\n}\n"],"mappings":";AAUA,OAAO,SAAS;AAChB,YAAY,QAAQ;AACpB,YAAY,UAAU;;;ACDf,IAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/generator.ts","../src/templates/shared.ts","../src/templates/index.ts","../src/templates/model.ts","../src/templates/schema.ts","../src/templates/rules.ts","../src/templates/glossary.ts","../src/templates/owner.ts","../src/templates/search.ts","../src/search/build-index.ts"],"sourcesContent":["/**\n * Site generator for ContextKit.\n *\n * `generateSite` accepts a Manifest and returns a Map of relative file path\n * to HTML content. The caller is responsible for writing files to disk.\n *\n * `buildSite` is a convenience function used by the CLI that compiles context,\n * generates the site, and writes files to the output directory.\n */\n\nimport ejs from 'ejs';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { Manifest, SiteConfig, ContextKitConfig } from '@runcontext/core';\nimport {\n indexTemplate,\n modelTemplate,\n schemaTemplate,\n rulesTemplate,\n glossaryTemplate,\n ownerTemplate,\n searchTemplate,\n} from './templates.js';\nimport { buildSearchIndex } from './search/build-index.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface GenerateSiteOptions {\n manifest: Manifest;\n config?: SiteConfig;\n}\n\n// ---------------------------------------------------------------------------\n// generateSite — pure function returning Map<path, html>\n// ---------------------------------------------------------------------------\n\n/**\n * Generate all HTML pages for the documentation site.\n *\n * @param manifest - The compiled ContextKit manifest\n * @param config - Optional site configuration (title, base_path)\n * @returns Map of relative file paths to HTML content\n */\nexport function generateSite(\n manifest: Manifest,\n config?: SiteConfig,\n options?: { studioMode?: boolean },\n): Map<string, string> {\n const files = new Map<string, string>();\n const siteTitle = config?.title ?? 'ContextKit';\n const basePath = (config?.base_path ?? '').replace(/\\/+$/, '') || '.';\n const studioMode = options?.studioMode ?? false;\n\n // Common data shared across all pages (includes sidebar data)\n const commonData = {\n siteTitle,\n basePath,\n studioMode,\n models: manifest.models,\n tiers: manifest.tiers,\n };\n\n // --- Index page ---\n files.set(\n 'index.html',\n ejs.render(indexTemplate, {\n ...commonData,\n pageTitle: 'Home',\n governance: manifest.governance,\n owners: manifest.owners,\n terms: manifest.terms,\n }),\n );\n\n // --- Model pages ---\n for (const [name, model] of Object.entries(manifest.models)) {\n const gov = manifest.governance[name] ?? null;\n const tier = manifest.tiers[name] ?? null;\n const rules = manifest.rules[name] ?? null;\n const lineage = manifest.lineage[name] ?? null;\n\n // Main model page\n files.set(\n `models/${name}.html`,\n ejs.render(modelTemplate, {\n ...commonData,\n pageTitle: name,\n model,\n gov,\n tier,\n rules,\n lineage,\n }),\n );\n\n // Schema browser page\n files.set(\n `models/${name}/schema.html`,\n ejs.render(schemaTemplate, {\n ...commonData,\n pageTitle: `${name} — Schema`,\n model,\n gov,\n tier,\n }),\n );\n\n // Rules page\n files.set(\n `models/${name}/rules.html`,\n ejs.render(rulesTemplate, {\n ...commonData,\n pageTitle: `${name} — Rules`,\n modelName: name,\n rules,\n }),\n );\n }\n\n // --- Glossary page ---\n files.set(\n 'glossary.html',\n ejs.render(glossaryTemplate, {\n ...commonData,\n pageTitle: 'Glossary',\n terms: manifest.terms,\n }),\n );\n\n // --- Owner pages ---\n for (const [oid, owner] of Object.entries(manifest.owners)) {\n // Find models governed by this owner\n const governedModels: Array<{ name: string; tier: string | null }> = [];\n for (const [modelName, gov] of Object.entries(manifest.governance)) {\n if (gov.owner === oid) {\n const tierScore = manifest.tiers[modelName];\n governedModels.push({\n name: modelName,\n tier: tierScore?.tier ?? null,\n });\n }\n }\n\n files.set(\n `owners/${oid}.html`,\n ejs.render(ownerTemplate, {\n ...commonData,\n pageTitle: owner.display_name,\n owner,\n governedModels,\n }),\n );\n }\n\n // --- Search page ---\n const searchIndex = buildSearchIndex(manifest, basePath);\n files.set(\n 'search.html',\n ejs.render(searchTemplate, {\n ...commonData,\n pageTitle: 'Search',\n searchIndexJson: JSON.stringify(searchIndex),\n }),\n );\n\n // --- Search index JSON (for programmatic access) ---\n files.set('search-index.json', JSON.stringify(searchIndex, null, 2));\n\n return files;\n}\n\n// ---------------------------------------------------------------------------\n// buildSite — convenience function used by CLI\n// ---------------------------------------------------------------------------\n\n/**\n * Build the documentation site and write files to disk.\n *\n * Called by the CLI `site` command. Accepts a manifest, config, and output\n * directory, then generates and writes all site files.\n *\n * @param manifest - The compiled ContextKit manifest\n * @param config - The full ContextKit configuration\n * @param outputDir - Directory to write site files to\n */\nexport async function buildSite(\n manifest: Manifest,\n config: ContextKitConfig,\n outputDir: string,\n): Promise<void> {\n const siteConfig = config.site;\n const files = generateSite(manifest, siteConfig);\n\n for (const [filePath, content] of files) {\n const fullPath = path.join(outputDir, filePath);\n const dir = path.dirname(fullPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(fullPath, content, 'utf-8');\n }\n}\n","/**\n * Shared layout for the ContextKit metadata catalog site.\n *\n * Redesigned with sidebar navigation, top bar with docs link,\n * and a clean dark theme matching the Starlight docs site.\n */\n\n// ---------------------------------------------------------------------------\n// HEAD — doctype, meta, fonts, full CSS design system\n// ---------------------------------------------------------------------------\n\nexport const HEAD = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><%= pageTitle %> — <%= siteTitle %></title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Geist+Mono:wght@300;400;500;600&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">\n <style>\n :root {\n --accent: #c9a55a;\n --accent-light: #e0be6a;\n --accent-dim: rgba(201, 165, 90, 0.12);\n --accent-border: rgba(201, 165, 90, 0.25);\n --bg: #0a0908;\n --bg-sidebar: #0f0e0d;\n --bg-card: #141312;\n --bg-hover: #1a1918;\n --bg-code: #111010;\n --text: #e8e6e1;\n --text-secondary: #a09d94;\n --text-dim: #6a675e;\n --border: #252320;\n --border-light: #302d28;\n --green: #4aba6a;\n --green-dim: rgba(74, 186, 106, 0.1);\n --red: #d45050;\n --red-dim: rgba(212, 80, 80, 0.08);\n --blue: #5b9cf5;\n --blue-dim: rgba(91, 156, 245, 0.1);\n --purple: #a78bfa;\n --cyan: #22d3ee;\n --orange: #f59e0b;\n --sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;\n --mono: 'Geist Mono', 'SF Mono', 'Consolas', monospace;\n --sidebar-w: 260px;\n --topbar-h: 48px;\n }\n\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n\n body {\n font-family: var(--sans);\n background: var(--bg);\n color: var(--text);\n line-height: 1.6;\n }\n\n a { color: var(--accent); text-decoration: none; }\n a:hover { text-decoration: underline; }\n\n /* === TOPBAR === */\n .topbar {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: var(--topbar-h);\n background: var(--bg-sidebar);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 1.25rem;\n z-index: 200;\n }\n .topbar-left {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n }\n .topbar-logo {\n font-family: var(--sans);\n font-size: 0.9rem;\n font-weight: 700;\n color: var(--accent);\n text-decoration: none;\n letter-spacing: -0.02em;\n }\n .topbar-logo:hover { text-decoration: none; }\n .topbar-sep {\n color: var(--border-light);\n font-size: 1.1rem;\n font-weight: 300;\n }\n .topbar-page {\n font-size: 0.8rem;\n color: var(--text-secondary);\n }\n .topbar-right {\n display: flex;\n align-items: center;\n gap: 1rem;\n }\n .topbar-link {\n font-size: 0.78rem;\n color: var(--text-dim);\n text-decoration: none;\n transition: color 0.15s;\n }\n .topbar-link:hover { color: var(--text); text-decoration: none; }\n .topbar-docs {\n font-size: 0.72rem;\n font-weight: 500;\n color: var(--accent);\n border: 1px solid var(--accent-border);\n padding: 0.3rem 0.65rem;\n border-radius: 5px;\n text-decoration: none;\n transition: background 0.15s;\n }\n .topbar-docs:hover { background: var(--accent-dim); text-decoration: none; }\n\n .menu-toggle {\n display: none;\n background: none;\n border: none;\n color: var(--text-secondary);\n font-size: 1.3rem;\n cursor: pointer;\n padding: 0.25rem;\n }\n\n /* === SIDEBAR === */\n .sidebar {\n position: fixed;\n top: var(--topbar-h);\n left: 0;\n bottom: 0;\n width: var(--sidebar-w);\n background: var(--bg-sidebar);\n border-right: 1px solid var(--border);\n overflow-y: auto;\n padding: 1rem 0;\n z-index: 100;\n }\n .sidebar::-webkit-scrollbar { width: 4px; }\n .sidebar::-webkit-scrollbar-track { background: transparent; }\n .sidebar::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 2px; }\n\n .sidebar-section {\n padding: 0 0.75rem;\n margin-bottom: 1.25rem;\n }\n .sidebar-heading {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n padding: 0 0.5rem;\n margin-bottom: 0.4rem;\n }\n .sidebar-item {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.35rem 0.5rem;\n border-radius: 5px;\n font-size: 0.82rem;\n color: var(--text-secondary);\n text-decoration: none;\n transition: background 0.12s, color 0.12s;\n }\n .sidebar-item:hover {\n background: var(--bg-hover);\n color: var(--text);\n text-decoration: none;\n }\n .sidebar-item.active {\n background: var(--accent-dim);\n color: var(--accent-light);\n }\n .sidebar-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.1rem 0.35rem;\n border-radius: 3px;\n margin-left: auto;\n }\n .sidebar-badge-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.12); }\n .sidebar-badge-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.1); }\n .sidebar-badge-gold { color: var(--accent); background: var(--accent-dim); }\n .sidebar-badge-none { color: var(--text-dim); background: rgba(106, 103, 94, 0.1); }\n\n /* === MAIN CONTENT === */\n .main {\n margin-left: var(--sidebar-w);\n margin-top: var(--topbar-h);\n min-height: calc(100vh - var(--topbar-h));\n padding: 2rem 2.5rem 4rem;\n max-width: calc(900px + var(--sidebar-w));\n }\n\n /* === BREADCRUMB === */\n .breadcrumb {\n display: flex;\n align-items: center;\n gap: 0.35rem;\n font-size: 0.78rem;\n color: var(--text-dim);\n margin-bottom: 1.5rem;\n }\n .breadcrumb a {\n color: var(--text-secondary);\n text-decoration: none;\n }\n .breadcrumb a:hover { color: var(--text); text-decoration: none; }\n .breadcrumb-sep { color: var(--border-light); }\n\n /* === PAGE HEADER === */\n .page-header { margin-bottom: 2rem; }\n .page-header h1 {\n font-size: clamp(1.5rem, 3vw, 2rem);\n font-weight: 700;\n letter-spacing: -0.025em;\n color: var(--text);\n line-height: 1.2;\n }\n .page-header .subtitle {\n font-size: 0.95rem;\n color: var(--text-secondary);\n margin-top: 0.4rem;\n font-weight: 300;\n max-width: 600px;\n }\n\n /* === STATS ROW === */\n .stats-row {\n display: flex;\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n margin-bottom: 2rem;\n }\n .stat-item {\n flex: 1;\n background: var(--bg-card);\n padding: 1rem 1.25rem;\n text-align: center;\n min-width: 80px;\n }\n .stat-val {\n font-family: var(--mono);\n font-size: 1.5rem;\n font-weight: 600;\n color: var(--accent);\n line-height: 1;\n }\n .stat-lbl {\n font-size: 0.65rem;\n color: var(--text-dim);\n margin-top: 0.3rem;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n }\n\n /* === SECTION === */\n .section { margin-bottom: 2.5rem; }\n .section-label {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.2em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.5rem;\n }\n .section-title {\n font-size: 1.15rem;\n font-weight: 600;\n letter-spacing: -0.015em;\n margin-bottom: 1rem;\n color: var(--text);\n }\n\n /* === CARDS === */\n .card {\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--bg-card);\n transition: border-color 0.15s;\n }\n .card:hover { border-color: var(--border-light); }\n .card-link {\n text-decoration: none;\n display: block;\n }\n .card-link:hover { text-decoration: none; }\n .card-link:hover .card { border-color: var(--accent-border); }\n .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }\n\n /* === TAGS === */\n .tag {\n display: inline-flex;\n align-items: center;\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 500;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n padding: 0.15rem 0.45rem;\n border-radius: 3px;\n background: rgba(106, 103, 94, 0.1);\n color: var(--text-dim);\n border: 1px solid transparent;\n }\n .tag-gold { color: var(--accent); background: var(--accent-dim); }\n .tag-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.08); }\n .tag-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.1); }\n .tag-green { color: var(--green); background: var(--green-dim); }\n .tag-red { color: var(--red); background: var(--red-dim); }\n .tag-blue { color: var(--blue); background: var(--blue-dim); }\n .tag-purple { color: var(--purple); background: rgba(167, 139, 250, 0.1); }\n\n /* === TABLES === */\n .data-table { width: 100%; border-collapse: collapse; }\n .data-table th {\n font-family: var(--mono);\n font-size: 0.6rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n text-align: left;\n padding: 0.6rem 0.75rem;\n border-bottom: 1px solid var(--border);\n }\n .data-table td {\n padding: 0.55rem 0.75rem;\n border-bottom: 1px solid rgba(37, 35, 32, 0.6);\n font-size: 0.82rem;\n vertical-align: top;\n }\n .data-table tr:hover td { background: rgba(201, 165, 90, 0.02); }\n .mono { font-family: var(--mono); }\n\n /* === SEMANTIC ROLE TAGS === */\n .role-identifier { color: var(--accent); background: var(--accent-dim); }\n .role-metric { color: var(--cyan); background: rgba(34, 211, 238, 0.08); }\n .role-dimension { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .role-date { color: var(--green); background: var(--green-dim); }\n .role-attribute { color: var(--text-dim); background: rgba(106, 103, 94, 0.08); }\n\n /* === DS TYPE TAGS === */\n .ds-fact { color: var(--purple); background: rgba(167, 139, 250, 0.08); }\n .ds-dimension { color: var(--green); background: var(--green-dim); }\n .ds-event { color: var(--orange); background: rgba(245, 158, 11, 0.08); }\n .ds-view { color: var(--blue); background: var(--blue-dim); }\n\n /* === GOV GRID === */\n .gov-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n .gov-cell {\n background: var(--bg-card);\n padding: 0.75rem 1rem;\n }\n .gov-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.2rem;\n }\n .gov-value { font-size: 0.85rem; color: var(--text); }\n\n /* === EXPANDABLE === */\n .expandable-header {\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n user-select: none;\n }\n .expand-icon {\n font-family: var(--mono);\n font-size: 0.85rem;\n color: var(--text-dim);\n width: 18px;\n text-align: center;\n transition: transform 0.15s;\n }\n .expandable-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.25s ease;\n }\n\n /* === QUERY CARD === */\n .query-card {\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n background: var(--bg-card);\n margin-bottom: 1rem;\n transition: border-color 0.15s;\n }\n .query-card:hover { border-color: var(--border-light); }\n .query-q {\n padding: 1rem 1.25rem;\n font-size: 0.95rem;\n font-weight: 500;\n font-style: italic;\n color: var(--text);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n }\n .query-q-badge {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-style: normal;\n font-weight: 600;\n color: var(--accent);\n background: var(--accent-dim);\n border: 1px solid var(--accent-border);\n padding: 0.1rem 0.3rem;\n border-radius: 3px;\n flex-shrink: 0;\n margin-top: 0.2rem;\n }\n .query-sql {\n padding: 0.85rem 1.25rem;\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text-secondary);\n line-height: 1.7;\n background: var(--bg-code);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n .query-meta {\n padding: 0.5rem 1.25rem;\n display: flex;\n gap: 0.75rem;\n font-size: 0.65rem;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n }\n\n /* === GUARDRAIL === */\n .guardrail {\n border: 1px solid rgba(212, 80, 80, 0.15);\n border-radius: 8px;\n padding: 1rem 1.25rem;\n background: var(--red-dim);\n margin-bottom: 0.75rem;\n }\n .guardrail-name {\n font-family: var(--mono);\n font-size: 0.8rem;\n color: var(--red);\n margin-bottom: 0.35rem;\n font-weight: 500;\n }\n .guardrail-filter {\n font-family: var(--mono);\n font-size: 0.75rem;\n color: var(--text);\n background: var(--bg-code);\n padding: 0.35rem 0.6rem;\n border-radius: 4px;\n border: 1px solid var(--border);\n display: inline-block;\n margin-bottom: 0.4rem;\n }\n .guardrail-reason {\n font-size: 0.8rem;\n color: var(--text-secondary);\n font-weight: 300;\n }\n\n /* === LINEAGE === */\n .lineage-flow {\n display: flex;\n align-items: stretch;\n gap: 0;\n overflow-x: auto;\n padding: 1rem 0;\n }\n .lineage-col { display: flex; flex-direction: column; gap: 0.5rem; min-width: 200px; }\n .lineage-col-label {\n font-family: var(--mono);\n font-size: 0.55rem;\n letter-spacing: 0.15em;\n text-transform: uppercase;\n color: var(--text-dim);\n margin-bottom: 0.15rem;\n padding-left: 0.5rem;\n }\n .lineage-node {\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 0.85rem;\n background: var(--bg-card);\n }\n .lineage-node-name { font-family: var(--mono); font-size: 0.75rem; color: var(--text); }\n .lineage-node-detail { font-size: 0.65rem; color: var(--text-dim); margin-top: 0.1rem; }\n .lineage-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 40px;\n color: var(--text-dim);\n font-size: 1rem;\n }\n\n /* === SCORECARD === */\n .scorecard {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n @media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }\n .sc-tier { background: var(--bg-card); padding: 1.25rem; }\n .sc-tier-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 0.75rem;\n }\n .sc-tier-name { font-size: 0.9rem; font-weight: 600; text-transform: capitalize; }\n .sc-tier-name.bronze { color: #b87a4a; }\n .sc-tier-name.silver { color: #a0a8b8; }\n .sc-tier-name.gold { color: var(--accent); }\n .sc-status {\n font-family: var(--mono);\n font-size: 0.55rem;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 0.12rem 0.4rem;\n border-radius: 3px;\n }\n .sc-status.pass { color: var(--green); background: var(--green-dim); }\n .sc-status.fail { color: var(--red); background: var(--red-dim); }\n .check-list { list-style: none; }\n .check-item {\n display: flex;\n align-items: flex-start;\n gap: 0.35rem;\n padding: 0.2rem 0;\n font-size: 0.72rem;\n color: var(--text-secondary);\n line-height: 1.4;\n }\n .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.75rem; }\n .check-icon.pass { color: var(--green); }\n .check-icon.fail { color: var(--red); }\n\n /* === GLOSSARY === */\n .glossary-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 0.75rem;\n }\n .glossary-card { position: relative; overflow: hidden; }\n .glossary-card::before {\n content: '';\n position: absolute;\n top: 0; left: 0; right: 0;\n height: 2px;\n background: linear-gradient(90deg, var(--accent-border), transparent);\n }\n .glossary-term {\n font-size: 1rem;\n font-weight: 600;\n margin-bottom: 0.35rem;\n color: var(--text);\n }\n .glossary-def {\n font-size: 0.82rem;\n color: var(--text-secondary);\n line-height: 1.6;\n font-weight: 300;\n }\n\n /* === SEARCH INPUT === */\n .search-input {\n width: 100%;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 0.6rem 1rem;\n font-family: var(--sans);\n font-size: 0.9rem;\n color: var(--text);\n outline: none;\n transition: border-color 0.15s, box-shadow 0.15s;\n }\n .search-input::placeholder { color: var(--text-dim); }\n .search-input:focus {\n border-color: var(--accent-border);\n box-shadow: 0 0 0 3px var(--accent-dim);\n }\n\n /* === SQL HIGHLIGHT === */\n .sql-kw { color: var(--purple); }\n .sql-fn { color: var(--green); }\n .sql-str { color: var(--orange); }\n .sql-num { color: var(--orange); }\n\n /* === METRIC === */\n .metric-name {\n font-family: var(--mono);\n font-size: 0.85rem;\n font-weight: 500;\n color: var(--accent-light);\n margin-bottom: 0.35rem;\n }\n .metric-desc { color: var(--text-secondary); font-size: 0.82rem; line-height: 1.6; margin-bottom: 0.75rem; }\n .metric-formula {\n font-family: var(--mono);\n font-size: 0.7rem;\n color: var(--text-dim);\n background: var(--bg-code);\n border: 1px solid var(--border);\n border-radius: 5px;\n padding: 0.5rem 0.7rem;\n overflow-x: auto;\n }\n\n /* === FOOTER === */\n .site-footer {\n margin-left: var(--sidebar-w);\n text-align: center;\n padding: 2rem 1.5rem;\n color: var(--text-dim);\n font-size: 0.72rem;\n border-top: 1px solid var(--border);\n }\n .site-footer a { color: var(--text-dim); }\n .site-footer a:hover { color: var(--accent); }\n\n /* === RESPONSIVE === */\n @media (max-width: 860px) {\n .sidebar {\n transform: translateX(-100%);\n transition: transform 0.25s ease;\n z-index: 300;\n }\n .sidebar.open { transform: translateX(0); }\n .main { margin-left: 0; padding: 1.5rem 1.25rem 3rem; }\n .site-footer { margin-left: 0; }\n .menu-toggle { display: block; }\n .stats-row { flex-wrap: wrap; }\n .stat-item { min-width: 60px; }\n .overlay {\n position: fixed;\n inset: 0;\n background: rgba(0,0,0,0.5);\n z-index: 250;\n display: none;\n }\n .overlay.open { display: block; }\n }\n </style>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <style>\n .edit-btn { background: none; border: 1px solid #c9a55a; color: #c9a55a; border-radius: 4px; padding: 2px 8px; font-size: 12px; cursor: pointer; margin-left: 8px; opacity: 0.6; transition: opacity 0.2s; }\n .edit-btn:hover { opacity: 1; }\n .editable { cursor: text; border-bottom: 1px dashed #c9a55a40; transition: border-color 0.2s; }\n .editable:hover { border-bottom-color: #c9a55a; }\n .editable:focus { outline: none; border-bottom: 2px solid #c9a55a; background: #c9a55a10; }\n .staged-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #1a1a2e; border-top: 2px solid #c9a55a; padding: 12px 24px; display: flex; align-items: center; justify-content: space-between; z-index: 1000; box-shadow: 0 -4px 12px rgba(0,0,0,0.3); }\n .staged-btn { border: none; border-radius: 6px; padding: 8px 20px; font-size: 14px; cursor: pointer; font-weight: 500; }\n .staged-btn.primary { background: #c9a55a; color: #0a0a0f; }\n .staged-btn.primary:hover { background: #d4b06a; }\n .staged-btn.secondary { background: transparent; border: 1px solid #666; color: #999; }\n .staged-btn.secondary:hover { border-color: #999; color: #fff; }\n .diff-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 2000; display: flex; align-items: center; justify-content: center; }\n .diff-modal-content { background: #1a1a2e; border: 1px solid #333; border-radius: 12px; padding: 24px; max-width: 800px; width: 90%; max-height: 80vh; overflow-y: auto; }\n .diff-modal-content h2 { margin: 0 0 16px; color: #c9a55a; }\n .diff-file { margin: 12px 0; padding: 12px; background: #0a0a0f; border-radius: 8px; font-family: monospace; font-size: 13px; white-space: pre-wrap; }\n .diff-file-name { color: #888; font-size: 12px; margin-bottom: 8px; }\n .diff-add { color: #4ade80; }\n .diff-del { color: #f87171; }\n .diff-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 16px; }\n .toast { position: fixed; top: 20px; right: 20px; background: #1a1a2e; border: 1px solid #c9a55a; color: #e0e0e0; padding: 12px 20px; border-radius: 8px; z-index: 3000; animation: toast-in 0.3s ease-out; }\n @keyframes toast-in { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }\n .studio-add-btn { background: none; border: 1px dashed #c9a55a60; color: #c9a55a; border-radius: 8px; padding: 12px; width: 100%; cursor: pointer; font-size: 14px; transition: all 0.2s; }\n .studio-add-btn:hover { border-color: #c9a55a; background: #c9a55a10; }\n </style>\n<% } %>\n</head>`;\n\n// ---------------------------------------------------------------------------\n// SIDEBAR_DATA — EJS logic to build sidebar model list\n// ---------------------------------------------------------------------------\n\nexport const SIDEBAR_DATA = `<%\n var _sidebarModels = typeof models !== 'undefined' ? Object.keys(models) : (typeof model !== 'undefined' ? [model.name] : []);\n var _sidebarTiers = typeof tiers !== 'undefined' ? tiers : (typeof tier !== 'undefined' && typeof model !== 'undefined' ? (function(){ var t = {}; t[model.name] = tier; return t; })() : {});\n%>`;\n\n// ---------------------------------------------------------------------------\n// TOPBAR\n// ---------------------------------------------------------------------------\n\nexport const TOPBAR = `<div class=\"topbar\">\n <div class=\"topbar-left\">\n <button class=\"menu-toggle\" onclick=\"toggleSidebar()\" aria-label=\"Toggle menu\">☰</button>\n <a href=\"<%= basePath %>/index.html\" class=\"topbar-logo\"><%- siteTitle %></a>\n <span class=\"topbar-sep\">/</span>\n <span class=\"topbar-page\"><%= pageTitle %></span>\n </div>\n <div class=\"topbar-right\">\n <a href=\"<%= basePath %>/search.html\" class=\"topbar-link\">Search</a>\n <a href=\"https://contextkit.dev\" class=\"topbar-docs\" target=\"_blank\" rel=\"noopener\">Docs ↗</a>\n </div>\n</div>`;\n\n// ---------------------------------------------------------------------------\n// SIDEBAR\n// ---------------------------------------------------------------------------\n\nexport const SIDEBAR = `<div class=\"overlay\" id=\"sidebar-overlay\" onclick=\"toggleSidebar()\"></div>\n<nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Navigation</div>\n <a href=\"<%= basePath %>/index.html\" class=\"sidebar-item<%= pageTitle === 'Home' ? ' active' : '' %>\">\n <span>Home</span>\n </a>\n <a href=\"<%= basePath %>/glossary.html\" class=\"sidebar-item<%= pageTitle === 'Glossary' ? ' active' : '' %>\">\n <span>Glossary</span>\n </a>\n <a href=\"<%= basePath %>/search.html\" class=\"sidebar-item<%= pageTitle === 'Search' ? ' active' : '' %>\">\n <span>Search</span>\n </a>\n </div>\n <% if (_sidebarModels.length > 0) { %>\n <div class=\"sidebar-section\">\n <div class=\"sidebar-heading\">Models</div>\n <% for (var _sm of _sidebarModels) { %>\n <a href=\"<%= basePath %>/models/<%= _sm %>.html\" class=\"sidebar-item<%= (typeof model !== 'undefined' && model.name === _sm) ? ' active' : '' %>\">\n <span class=\"mono\" style=\"font-size:0.78rem;\"><%= _sm %></span>\n <% if (_sidebarTiers[_sm]) { %>\n <span class=\"sidebar-badge sidebar-badge-<%= _sidebarTiers[_sm].tier || 'none' %>\"><%= _sidebarTiers[_sm].tier || '\\\\u2014' %></span>\n <% } %>\n </a>\n <% } %>\n </div>\n <% } %>\n</nav>`;\n\n// ---------------------------------------------------------------------------\n// FOOTER\n// ---------------------------------------------------------------------------\n\nexport const FOOTER = `<footer class=\"site-footer\">\n Generated by <a href=\"https://github.com/erickittelson/ContextKit\">ContextKit</a>\n · \n <a href=\"https://contextkit.dev\" target=\"_blank\" rel=\"noopener\">Documentation</a>\n</footer>`;\n\n// ---------------------------------------------------------------------------\n// TIER_BADGE\n// ---------------------------------------------------------------------------\n\nexport const TIER_BADGE = `<% function tierBadge(tier) {\n var cls = { none: '', bronze: 'tag-bronze', silver: 'tag-silver', gold: 'tag-gold' };\n var c = cls[tier] || '';\n return '<span class=\"tag ' + c + '\">' + (tier || 'none') + '</span>';\n} %>`;\n\n// ---------------------------------------------------------------------------\n// SCRIPTS\n// ---------------------------------------------------------------------------\n\nexport const SCRIPTS = `<script>\n(function() {\n // Sidebar toggle (mobile)\n window.toggleSidebar = function() {\n document.getElementById('sidebar').classList.toggle('open');\n document.getElementById('sidebar-overlay').classList.toggle('open');\n };\n\n // Expandable sections\n window.toggleExpand = function(id) {\n var el = document.getElementById(id);\n var icon = document.getElementById(id + '-icon');\n if (!el) return;\n if (el.style.maxHeight && el.style.maxHeight !== '0px') {\n el.style.maxHeight = '0px';\n if (icon) icon.textContent = '+';\n } else {\n el.style.maxHeight = el.scrollHeight + 'px';\n if (icon) icon.textContent = '\\\\u2212';\n }\n };\n\n // SQL syntax highlighter — operates on trusted, template-rendered content only.\n // The SQL blocks contain server-rendered code snippets from the user's own\n // golden queries / business rules, not arbitrary user input.\n window.highlightSQL = function() {\n var blocks = document.querySelectorAll('.sql-highlight');\n var kw = /\\\\b(SELECT|FROM|WHERE|JOIN|LEFT|RIGHT|INNER|OUTER|CROSS|ON|AND|OR|NOT|IN|AS|GROUP|BY|ORDER|HAVING|LIMIT|OFFSET|UNION|ALL|DISTINCT|CASE|WHEN|THEN|ELSE|END|IS|NULL|BETWEEN|LIKE|EXISTS|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|SET|VALUES|INTO|WITH|DESC|ASC|OVER|PARTITION|WINDOW|FILTER|LATERAL|UNNEST|TRUE|FALSE)\\\\b/gi;\n var fn = /\\\\b(SUM|COUNT|AVG|MIN|MAX|ROUND|COALESCE|CAST|NULLIF|ABS|UPPER|LOWER|LENGTH|TRIM|SUBSTRING|CONCAT|ROW_NUMBER|RANK|DENSE_RANK|LAG|LEAD|FIRST_VALUE|LAST_VALUE|NTILE|PERCENTILE_CONT|STRING_AGG|ARRAY_AGG|LIST|STRUCT_PACK)\\\\b/gi;\n var str = /('(?:[^'\\\\\\\\]|\\\\\\\\.)*')/g;\n blocks.forEach(function(block) {\n var text = block.textContent || '';\n // Escape HTML entities first to prevent injection\n var div = document.createElement('div');\n div.appendChild(document.createTextNode(text));\n text = div.textContent || '';\n text = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n text = text.replace(str, '<span class=\"sql-str\">$1</span>');\n text = text.replace(kw, '<span class=\"sql-kw\">$&</span>');\n text = text.replace(fn, '<span class=\"sql-fn\">$&</span>');\n // Safe: content is from trusted template-rendered golden queries\n block.innerHTML = text;\n });\n };\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', window.highlightSQL);\n } else {\n window.highlightSQL();\n }\n})();\n</script>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n<div class=\"staged-bar\" id=\"staged-bar\" style=\"display:none;\">\n <span id=\"staged-count\" style=\"color:#c9a55a;font-weight:500;\">0 changes staged</span>\n <div>\n <button onclick=\"previewAndSave()\" class=\"staged-btn primary\">Preview & Save</button>\n <button onclick=\"discardEdits()\" class=\"staged-btn secondary\" style=\"margin-left:8px;\">Discard</button>\n </div>\n</div>\n<div class=\"diff-modal\" id=\"diff-modal\" style=\"display:none;\">\n <div class=\"diff-modal-content\">\n <h2>Review Changes</h2>\n <div id=\"diff-container\"></div>\n <div class=\"diff-actions\">\n <button onclick=\"confirmSave()\" class=\"staged-btn primary\">Save All</button>\n <button onclick=\"closeDiffModal()\" class=\"staged-btn secondary\">Cancel</button>\n </div>\n </div>\n</div>\n<script>\nwindow.studioState = { edits: [] };\n\nfunction stageEdit(file, path, value, label) {\n window.studioState.edits = window.studioState.edits.filter(\n e => !(e.file === file && e.path === path)\n );\n window.studioState.edits.push({ file, path, value, label });\n updateStagedBar();\n}\n\nfunction updateStagedBar() {\n const bar = document.getElementById('staged-bar');\n const count = document.getElementById('staged-count');\n if (!bar || !count) return;\n const n = window.studioState.edits.length;\n bar.style.display = n > 0 ? 'flex' : 'none';\n count.textContent = n + ' change' + (n !== 1 ? 's' : '') + ' staged';\n}\n\nfunction discardEdits() {\n window.studioState.edits = [];\n updateStagedBar();\n document.querySelectorAll('.editable[contenteditable=\"true\"]').forEach(el => {\n el.contentEditable = 'false';\n if (el.dataset.original) el.textContent = el.dataset.original;\n });\n}\n\nasync function previewAndSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n const fileGroups = {};\n for (const edit of edits) {\n if (!fileGroups[edit.file]) fileGroups[edit.file] = [];\n fileGroups[edit.file].push(edit);\n }\n\n const previews = [];\n for (const [file, fileEdits] of Object.entries(fileGroups)) {\n try {\n const res = await fetch('/api/preview', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ file: fileEdits[0].file, path: fileEdits[0].path, value: fileEdits[0].value }),\n });\n const data = await res.json();\n previews.push({ file, edits: fileEdits, ...data });\n } catch (err) {\n previews.push({ file, edits: fileEdits, error: err.message });\n }\n }\n\n showDiffModal(previews);\n}\n\nfunction showDiffModal(previews) {\n const modal = document.getElementById('diff-modal');\n const container = document.getElementById('diff-container');\n if (!modal || !container) return;\n\n container.innerHTML = previews.map(p => {\n if (p.error) {\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div><span class=\"diff-del\">Error: ' + p.error + '</span></div>';\n }\n const editList = p.edits.map(e => '<div class=\"diff-add\"> ' + e.label + ': ' + JSON.stringify(e.value) + '</div>').join('');\n return '<div class=\"diff-file\"><div class=\"diff-file-name\">' + p.file + '</div>' + editList + '</div>';\n }).join('');\n\n modal.style.display = 'flex';\n}\n\nfunction closeDiffModal() {\n const modal = document.getElementById('diff-modal');\n if (modal) modal.style.display = 'none';\n}\n\nasync function confirmSave() {\n const edits = window.studioState.edits;\n if (edits.length === 0) return;\n\n try {\n const res = await fetch('/api/save', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ edits }),\n });\n const data = await res.json();\n const ok = data.results.filter(r => r.ok).length;\n showToast(ok + ' file(s) saved');\n window.studioState.edits = [];\n updateStagedBar();\n closeDiffModal();\n } catch (err) {\n showToast('Save failed: ' + err.message);\n }\n}\n\nfunction showToast(msg) {\n const existing = document.querySelector('.toast');\n if (existing) existing.remove();\n const toast = document.createElement('div');\n toast.className = 'toast';\n toast.textContent = msg;\n document.body.appendChild(toast);\n setTimeout(() => toast.remove(), 3000);\n}\n\nfunction initSSE() {\n const es = new EventSource('/api/events');\n es.addEventListener('update', function(e) {\n try {\n const data = JSON.parse(e.data);\n showToast('Recompiled — ' + data.diagnosticCount + ' diagnostics');\n setTimeout(() => location.reload(), 500);\n } catch {}\n });\n}\n\nfunction makeEditable(el) {\n el.addEventListener('click', function() {\n if (el.contentEditable === 'true') return;\n el.dataset.original = el.textContent;\n el.contentEditable = 'true';\n el.focus();\n });\n el.addEventListener('blur', function() {\n el.contentEditable = 'false';\n const newVal = el.textContent.trim();\n if (newVal !== el.dataset.original) {\n stageEdit(el.dataset.file, el.dataset.path, newVal, el.dataset.label || el.dataset.path);\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n }\n });\n el.addEventListener('keydown', function(e) {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n el.blur();\n }\n if (e.key === 'Escape') {\n el.textContent = el.dataset.original;\n el.contentEditable = 'false';\n }\n });\n}\n\nfunction makeDropdown(el) {\n el.addEventListener('click', function(e) {\n document.querySelectorAll('.studio-dropdown').forEach(d => d.remove());\n\n const options = (el.dataset.options || '').split(',');\n const dropdown = document.createElement('div');\n dropdown.className = 'studio-dropdown';\n dropdown.style.cssText = 'position:absolute;background:#1a1a2e;border:1px solid #c9a55a;border-radius:6px;padding:4px 0;z-index:500;min-width:120px;box-shadow:0 4px 12px rgba(0,0,0,0.4);';\n\n options.forEach(opt => {\n const item = document.createElement('div');\n item.textContent = opt.trim();\n item.style.cssText = 'padding:6px 16px;cursor:pointer;color:#e0e0e0;font-size:13px;';\n item.addEventListener('mouseenter', () => { item.style.background = '#c9a55a20'; });\n item.addEventListener('mouseleave', () => { item.style.background = 'none'; });\n item.addEventListener('click', (ev) => {\n ev.stopPropagation();\n const val = opt.trim();\n el.textContent = val;\n stageEdit(el.dataset.file, el.dataset.path, val, el.dataset.label || el.dataset.path);\n dropdown.remove();\n el.style.borderBottomColor = '#4ade80';\n setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);\n });\n dropdown.appendChild(item);\n });\n\n const rect = el.getBoundingClientRect();\n dropdown.style.top = (rect.bottom + window.scrollY + 4) + 'px';\n dropdown.style.left = (rect.left + window.scrollX) + 'px';\n document.body.appendChild(dropdown);\n\n setTimeout(() => {\n document.addEventListener('click', function closer() {\n dropdown.remove();\n document.removeEventListener('click', closer);\n }, { once: true });\n }, 0);\n });\n}\n\ndocument.addEventListener('DOMContentLoaded', function() {\n document.querySelectorAll('.editable').forEach(makeEditable);\n document.querySelectorAll('.dropdown-editable').forEach(makeDropdown);\n initSSE();\n});\n</script>\n<% } %>`;\n\n// ---------------------------------------------------------------------------\n// Studio mode constants — exported for use in page-specific templates\n// ---------------------------------------------------------------------------\n\nexport const STUDIO_CSS = `\n.edit-btn { background: none; border: 1px solid #c9a55a; color: #c9a55a; border-radius: 4px; padding: 2px 8px; font-size: 12px; cursor: pointer; margin-left: 8px; opacity: 0.6; transition: opacity 0.2s; }\n.edit-btn:hover { opacity: 1; }\n.editable { cursor: text; border-bottom: 1px dashed #c9a55a40; transition: border-color 0.2s; }\n.editable:hover { border-bottom-color: #c9a55a; }\n.editable:focus { outline: none; border-bottom: 2px solid #c9a55a; background: #c9a55a10; }\n.staged-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #1a1a2e; border-top: 2px solid #c9a55a; padding: 12px 24px; display: flex; align-items: center; justify-content: space-between; z-index: 1000; box-shadow: 0 -4px 12px rgba(0,0,0,0.3); }\n.staged-btn { border: none; border-radius: 6px; padding: 8px 20px; font-size: 14px; cursor: pointer; font-weight: 500; }\n.staged-btn.primary { background: #c9a55a; color: #0a0a0f; }\n.staged-btn.primary:hover { background: #d4b06a; }\n.staged-btn.secondary { background: transparent; border: 1px solid #666; color: #999; }\n.staged-btn.secondary:hover { border-color: #999; color: #fff; }\n.diff-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 2000; display: flex; align-items: center; justify-content: center; }\n.diff-modal-content { background: #1a1a2e; border: 1px solid #333; border-radius: 12px; padding: 24px; max-width: 800px; width: 90%; max-height: 80vh; overflow-y: auto; }\n.diff-modal-content h2 { margin: 0 0 16px; color: #c9a55a; }\n.diff-file { margin: 12px 0; padding: 12px; background: #0a0a0f; border-radius: 8px; font-family: monospace; font-size: 13px; white-space: pre-wrap; }\n.diff-file-name { color: #888; font-size: 12px; margin-bottom: 8px; }\n.diff-add { color: #4ade80; }\n.diff-del { color: #f87171; }\n.diff-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 16px; }\n.toast { position: fixed; top: 20px; right: 20px; background: #1a1a2e; border: 1px solid #c9a55a; color: #e0e0e0; padding: 12px 20px; border-radius: 8px; z-index: 3000; animation: toast-in 0.3s ease-out; }\n@keyframes toast-in { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }\n.studio-add-btn { background: none; border: 1px dashed #c9a55a60; color: #c9a55a; border-radius: 8px; padding: 12px; width: 100%; cursor: pointer; font-size: 14px; transition: all 0.2s; }\n.studio-add-btn:hover { border-color: #c9a55a; background: #c9a55a10; }\n`;\n\nexport const STAGED_BAR = `<div class=\"staged-bar\" id=\"staged-bar\" style=\"display:none;\">\n <span id=\"staged-count\" style=\"color:#c9a55a;font-weight:500;\">0 changes staged</span>\n <div>\n <button onclick=\"previewAndSave()\" class=\"staged-btn primary\">Preview & Save</button>\n <button onclick=\"discardEdits()\" class=\"staged-btn secondary\" style=\"margin-left:8px;\">Discard</button>\n </div>\n</div>`;\n\nexport const DIFF_MODAL = `<div class=\"diff-modal\" id=\"diff-modal\" style=\"display:none;\">\n <div class=\"diff-modal-content\">\n <h2>Review Changes</h2>\n <div id=\"diff-container\"></div>\n <div class=\"diff-actions\">\n <button onclick=\"confirmSave()\" class=\"staged-btn primary\">Save All</button>\n <button onclick=\"closeDiffModal()\" class=\"staged-btn secondary\">Cancel</button>\n </div>\n </div>\n</div>`;\n","import { HEAD, TOPBAR, SIDEBAR, SIDEBAR_DATA, FOOTER, TIER_BADGE, SCRIPTS } from './shared.js';\n\nexport const indexTemplate = `${HEAD}\n<body>\n${TOPBAR}\n${SIDEBAR_DATA}\n${SIDEBAR}\n${TIER_BADGE}\n\n<div class=\"main\">\n <div class=\"page-header\">\n <h1>Metadata Catalog</h1>\n <p class=\"subtitle\">Explore semantic models, governed datasets, business glossary, and data quality tiers.</p>\n </div>\n\n <%\n var modelNames = Object.keys(models);\n var totalDatasets = 0;\n var totalFields = 0;\n for (var mn of modelNames) {\n var m = models[mn];\n if (m.datasets) {\n totalDatasets += m.datasets.length;\n for (var ds of m.datasets) {\n if (ds.fields) totalFields += ds.fields.length;\n }\n }\n }\n var totalTerms = Object.keys(terms).length;\n var totalOwners = Object.keys(owners).length;\n %>\n\n <div class=\"stats-row\">\n <div class=\"stat-item\">\n <div class=\"stat-val\"><%= modelNames.length %></div>\n <div class=\"stat-lbl\">Models</div>\n </div>\n <div class=\"stat-item\">\n <div class=\"stat-val\"><%= totalDatasets %></div>\n <div class=\"stat-lbl\">Datasets</div>\n </div>\n <div class=\"stat-item\">\n <div class=\"stat-val\"><%= totalFields %></div>\n <div class=\"stat-lbl\">Fields</div>\n </div>\n <div class=\"stat-item\">\n <div class=\"stat-val\"><%= totalTerms %></div>\n <div class=\"stat-lbl\">Terms</div>\n </div>\n <div class=\"stat-item\">\n <div class=\"stat-val\"><%= totalOwners %></div>\n <div class=\"stat-lbl\">Owners</div>\n </div>\n </div>\n\n <div class=\"section\">\n <div class=\"section-label\">Semantic Models</div>\n <h2 class=\"section-title\">Models</h2>\n <% if (modelNames.length === 0) { %>\n <p style=\"color:var(--text-secondary);\">No models found. Run <code style=\"font-family:var(--mono);background:var(--bg-card);padding:0.15rem 0.4rem;border-radius:3px;\">context introspect</code> to get started.</p>\n <% } else { %>\n <div class=\"card-grid\">\n <% for (var name of modelNames) { %>\n <a href=\"<%= basePath %>/models/<%= name %>.html\" class=\"card-link\">\n <div class=\"card\">\n <div style=\"display:flex;align-items:center;gap:0.5rem;margin-bottom:0.4rem;\">\n <span class=\"mono\" style=\"font-size:0.88rem;color:var(--accent-light);\"><%= name %></span>\n <% if (tiers[name]) { %><%- tierBadge(tiers[name].tier) %><% } %>\n </div>\n <% if (models[name].description) { %>\n <p style=\"color:var(--text-secondary);font-size:0.82rem;margin-bottom:0.5rem;font-weight:300;\"><%= models[name].description %></p>\n <% } %>\n <% if (governance[name]) { %>\n <div style=\"display:flex;gap:0.35rem;flex-wrap:wrap;align-items:center;\">\n <% if (governance[name].owner) { %>\n <span class=\"tag\"><%= governance[name].owner %></span>\n <% } %>\n <% if (governance[name].trust) { %>\n <span class=\"tag tag-green\"><%= governance[name].trust %></span>\n <% } %>\n <% if (governance[name].tags) { for (var t of governance[name].tags) { %>\n <span class=\"tag\"><%= t %></span>\n <% } } %>\n </div>\n <% } %>\n </div>\n </a>\n <% } %>\n </div>\n <% } %>\n </div>\n\n <% if (Object.keys(owners).length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Data Stewardship</div>\n <h2 class=\"section-title\">Owners</h2>\n <div class=\"card-grid\">\n <% for (var oid of Object.keys(owners)) { %>\n <a href=\"<%= basePath %>/owners/<%= oid %>.html\" class=\"card-link\">\n <div class=\"card\">\n <div style=\"font-size:0.9rem;font-weight:500;color:var(--text);margin-bottom:0.15rem;\"><%= owners[oid].display_name %></div>\n <% if (owners[oid].team) { %>\n <div style=\"font-size:0.75rem;color:var(--text-dim);\"><%= owners[oid].team %></div>\n <% } %>\n </div>\n </a>\n <% } %>\n </div>\n </div>\n <% } %>\n</div>\n\n${FOOTER}\n${SCRIPTS}\n</body>\n</html>`;\n","import { HEAD, TOPBAR, SIDEBAR, SIDEBAR_DATA, FOOTER, TIER_BADGE, SCRIPTS } from './shared.js';\n\nexport const modelTemplate = `${HEAD}\n<body>\n${TOPBAR}\n${SIDEBAR_DATA}\n${SIDEBAR}\n${TIER_BADGE}\n\n<div class=\"main\">\n <div class=\"breadcrumb\">\n <a href=\"<%= basePath %>/index.html\">Home</a>\n <span class=\"breadcrumb-sep\">/</span>\n <span><%= model.name %></span>\n </div>\n\n <div class=\"page-header\">\n <div style=\"display:flex;align-items:center;gap:0.6rem;\">\n <h1><%= model.name %></h1>\n <% if (tier) { %><%- tierBadge(tier.tier) %><% } %>\n </div>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <p class=\"subtitle\"><span class=\"editable\" data-file=\"context/models/<%= model.name %>.osi.yaml\" data-path=\"semantic_model.0.description\" data-label=\"Model description\"><%= model.description || 'Add description...' %></span></p>\n <% } else { %>\n <% if (model.description) { %>\n <p class=\"subtitle\"><%= model.description %></p>\n <% } %>\n <% } %>\n </div>\n\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <div class=\"section\">\n <div class=\"section-label\">AI Context</div>\n <div class=\"card\" style=\"margin-bottom:1rem;\">\n <span class=\"editable\" data-file=\"context/models/<%= model.name %>.osi.yaml\" data-path=\"semantic_model.0.ai_context\" data-label=\"AI context\"><%= (typeof model.ai_context === 'string' ? model.ai_context : (model.ai_context ? JSON.stringify(model.ai_context) : 'Add AI context...')) %></span>\n </div>\n </div>\n <% } else { %>\n <% if (model.ai_context) { %>\n <div class=\"section\">\n <div class=\"section-label\">AI Context</div>\n <div class=\"card\" style=\"margin-bottom:1rem;\">\n <p style=\"font-size:0.85rem;color:var(--text-secondary);font-weight:300;line-height:1.6;\"><%= typeof model.ai_context === 'string' ? model.ai_context : JSON.stringify(model.ai_context) %></p>\n </div>\n </div>\n <% } %>\n <% } %>\n\n <div style=\"display:flex;gap:0.5rem;margin-bottom:2rem;\">\n <a href=\"<%= basePath %>/models/<%= model.name %>/schema.html\" class=\"tag tag-blue\" style=\"padding:0.3rem 0.6rem;font-size:0.65rem;text-decoration:none;\">Schema Browser</a>\n <a href=\"<%= basePath %>/models/<%= model.name %>/rules.html\" class=\"tag tag-gold\" style=\"padding:0.3rem 0.6rem;font-size:0.65rem;text-decoration:none;\">Rules & Queries</a>\n </div>\n\n <% if (gov) { %>\n <div class=\"section\">\n <div class=\"section-label\">Governance</div>\n <div class=\"gov-grid\">\n <div class=\"gov-cell\">\n <div class=\"gov-label\">Owner</div>\n <div class=\"gov-value\"><a href=\"<%= basePath %>/owners/<%= gov.owner %>.html\"><%= gov.owner %></a></div>\n </div>\n <div class=\"gov-cell\">\n <div class=\"gov-label\">Trust</div>\n <div class=\"gov-value\">\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span class=\"dropdown-editable\" data-file=\"context/governance/<%= model.name %>.governance.yaml\" data-path=\"trust\" data-label=\"Trust\" data-options=\"draft,reviewed,endorsed,certified\"><%= gov.trust || 'Select...' %></span>\n <% } else { %>\n <%= gov.trust || '' %>\n <% } %>\n </div>\n </div>\n <% if (gov.security) { %>\n <div class=\"gov-cell\">\n <div class=\"gov-label\">Security</div>\n <div class=\"gov-value\"><%= gov.security %></div>\n </div>\n <% } %>\n <div class=\"gov-cell\">\n <div class=\"gov-label\">Refresh Cadence</div>\n <div class=\"gov-value\">\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span class=\"editable\" data-file=\"context/governance/<%= model.name %>.governance.yaml\" data-path=\"refresh\" data-label=\"Refresh cadence\"><%= gov.refresh || 'Add refresh cadence...' %></span>\n <% } else { %>\n <%= gov.refresh || '' %>\n <% } %>\n </div>\n </div>\n <% if (gov.tags && gov.tags.length > 0) { %>\n <div class=\"gov-cell\">\n <div class=\"gov-label\">Tags</div>\n <div class=\"gov-value\" style=\"display:flex;gap:0.3rem;flex-wrap:wrap;\">\n <% for (var t of gov.tags) { %><span class=\"tag\"><%= t %></span><% } %>\n </div>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (model.datasets && model.datasets.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Data Explorer</div>\n <h2 class=\"section-title\">Datasets</h2>\n <div style=\"display:flex;flex-direction:column;gap:0.75rem;\">\n <% for (var i = 0; i < model.datasets.length; i++) { var ds = model.datasets[i]; %>\n <% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>\n <div class=\"card\">\n <div class=\"expandable-header\" onclick=\"toggleExpand('ds-<%= i %>')\">\n <div>\n <div style=\"display:flex;align-items:center;gap:0.5rem;margin-bottom:0.15rem;\">\n <span class=\"mono\" style=\"font-size:0.85rem;color:var(--text);\"><%= ds.name %></span>\n <% if (dsGov && dsGov.table_type) { %>\n <span class=\"tag ds-<%= dsGov.table_type %>\"><%= dsGov.table_type %></span>\n <% } %>\n <% if (ds.fields) { %><span class=\"tag\"><%= ds.fields.length %> fields</span><% } %>\n </div>\n <div style=\"font-size:0.72rem;color:var(--text-dim);font-family:var(--mono);\">\n <%= ds.source %>\n <% if (dsGov && dsGov.grain) { %> · <span style=\"font-family:var(--sans);color:var(--text-secondary);\"><%= dsGov.grain %></span><% } %>\n </div>\n <% if (ds.description) { %>\n <p style=\"font-size:0.8rem;color:var(--text-secondary);margin-top:0.3rem;font-weight:300;\"><%= ds.description %></p>\n <% } %>\n </div>\n <span class=\"expand-icon\" id=\"ds-<%= i %>-icon\">+</span>\n </div>\n <div class=\"expandable-content\" id=\"ds-<%= i %>\">\n <% if (ds.fields && ds.fields.length > 0) { %>\n <table class=\"data-table\" style=\"margin-top:0.75rem;\">\n <thead>\n <tr><th>Field</th><th>Description</th><th>Role</th><th>Aggregation</th></tr>\n </thead>\n <tbody>\n <% for (var field of ds.fields) { %>\n <% var fKey = ds.name + '.' + field.name; %>\n <% var fGov = gov && gov.fields && gov.fields[fKey]; %>\n <% var role = fGov && fGov.semantic_role ? fGov.semantic_role : ''; %>\n <tr>\n <td class=\"mono\" style=\"font-size:0.75rem;\"><%= field.name %></td>\n <td style=\"color:var(--text-secondary);font-size:0.8rem;\"><%= field.description || '' %></td>\n <td><% if (role) { %><span class=\"tag role-<%= role %>\"><%= role %></span><% } %></td>\n <td class=\"mono\" style=\"font-size:0.75rem;color:var(--text-dim);\"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %></td>\n </tr>\n <% } %>\n </tbody>\n </table>\n <% } %>\n </div>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (model.relationships && model.relationships.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Data Model</div>\n <h2 class=\"section-title\">Relationships</h2>\n <table class=\"data-table\">\n <thead>\n <tr><th>Name</th><th>From</th><th></th><th>To</th></tr>\n </thead>\n <tbody>\n <% for (var rel of model.relationships) { %>\n <tr>\n <td style=\"color:var(--text-secondary);font-size:0.8rem;\"><%= rel.name %></td>\n <td class=\"mono\" style=\"font-size:0.75rem;\"><%= rel.from %></td>\n <td style=\"color:var(--text-dim);text-align:center;\">→</td>\n <td class=\"mono\" style=\"font-size:0.75rem;\"><%= rel.to %></td>\n </tr>\n <% } %>\n </tbody>\n </table>\n </div>\n <% } %>\n\n <% if (model.metrics && model.metrics.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Computed Metrics</div>\n <h2 class=\"section-title\">Metrics</h2>\n <div class=\"card-grid\">\n <% for (var metric of model.metrics) { %>\n <div class=\"card\">\n <div class=\"metric-name\"><%= metric.name %></div>\n <% if (metric.description) { %><div class=\"metric-desc\"><%= metric.description %></div><% } %>\n <% if (metric.expression && metric.expression.dialects && metric.expression.dialects.length > 0) { %>\n <div class=\"metric-formula\"><%= metric.expression.dialects[0].expression %></div>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (rules && rules.golden_queries && rules.golden_queries.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Golden Queries</div>\n <h2 class=\"section-title\">Pre-validated Queries</h2>\n <% for (var gq of rules.golden_queries) { %>\n <div class=\"query-card\">\n <div class=\"query-q\">\n <span class=\"query-q-badge\">Q</span>\n <span><%= gq.question %></span>\n </div>\n <div class=\"query-sql sql-highlight\"><%= gq.sql %></div>\n <% if (gq.description) { %>\n <div class=\"query-meta\"><span><%= gq.description %></span></div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <div class=\"section\" id=\"golden-queries-studio\">\n <% if (!(rules && rules.golden_queries && rules.golden_queries.length > 0)) { %>\n <div class=\"section-label\">Golden Queries</div>\n <h2 class=\"section-title\">Pre-validated Queries</h2>\n <% } %>\n <div id=\"golden-query-forms\"></div>\n <button class=\"studio-add-btn\" onclick=\"addGoldenQuery('<%= model.name %>')\">+ Add Golden Query</button>\n </div>\n <script>\n function addGoldenQuery(modelName) {\n var container = document.getElementById('golden-query-forms');\n var card = document.createElement('div');\n card.className = 'card';\n card.style.marginBottom = '1rem';\n\n var questionLabel = document.createElement('label');\n questionLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n questionLabel.textContent = 'Question';\n var questionInput = document.createElement('input');\n questionInput.type = 'text';\n questionInput.className = 'search-input gq-question';\n questionInput.placeholder = 'e.g. What is the total revenue by region?';\n questionInput.style.fontSize = '0.85rem';\n var questionDiv = document.createElement('div');\n questionDiv.style.marginBottom = '0.75rem';\n questionDiv.appendChild(questionLabel);\n questionDiv.appendChild(questionInput);\n\n var sqlLabel = document.createElement('label');\n sqlLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n sqlLabel.textContent = 'SQL';\n var sqlInput = document.createElement('textarea');\n sqlInput.className = 'search-input gq-sql';\n sqlInput.rows = 4;\n sqlInput.placeholder = 'SELECT ...';\n sqlInput.style.cssText = 'font-family:var(--mono);font-size:0.8rem;resize:vertical;';\n var sqlDiv = document.createElement('div');\n sqlDiv.style.marginBottom = '0.75rem';\n sqlDiv.appendChild(sqlLabel);\n sqlDiv.appendChild(sqlInput);\n\n var descLabel = document.createElement('label');\n descLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n descLabel.textContent = 'Description';\n var descInput = document.createElement('input');\n descInput.type = 'text';\n descInput.className = 'search-input gq-desc';\n descInput.placeholder = 'Optional description';\n descInput.style.fontSize = '0.85rem';\n var descDiv = document.createElement('div');\n descDiv.style.marginBottom = '0.75rem';\n descDiv.appendChild(descLabel);\n descDiv.appendChild(descInput);\n\n var stageBtn = document.createElement('button');\n stageBtn.className = 'staged-btn primary';\n stageBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';\n stageBtn.textContent = 'Stage';\n stageBtn.addEventListener('click', function() { stageGoldenQuery(stageBtn, modelName); });\n\n var cancelBtn = document.createElement('button');\n cancelBtn.className = 'staged-btn secondary';\n cancelBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';\n cancelBtn.textContent = 'Cancel';\n cancelBtn.addEventListener('click', function() { card.remove(); });\n\n var btnDiv = document.createElement('div');\n btnDiv.style.cssText = 'display:flex;gap:0.5rem;';\n btnDiv.appendChild(stageBtn);\n btnDiv.appendChild(cancelBtn);\n\n card.appendChild(questionDiv);\n card.appendChild(sqlDiv);\n card.appendChild(descDiv);\n card.appendChild(btnDiv);\n container.appendChild(card);\n }\n\n function stageGoldenQuery(btn, modelName) {\n var card = btn.closest('.card');\n var question = card.querySelector('.gq-question').value.trim();\n var sql = card.querySelector('.gq-sql').value.trim();\n var desc = card.querySelector('.gq-desc').value.trim();\n if (!question || !sql) { showToast('Question and SQL are required'); return; }\n var entry = { question: question, sql: sql };\n if (desc) entry.description = desc;\n stageEdit('context/rules/' + modelName + '.rules.yaml', 'golden_queries.+', entry, 'Add golden query: ' + question);\n card.style.borderColor = '#4ade80';\n btn.textContent = 'Staged';\n btn.disabled = true;\n }\n </script>\n <% } %>\n\n <% if (rules && rules.guardrail_filters && rules.guardrail_filters.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Safety</div>\n <h2 class=\"section-title\">Guardrail Filters</h2>\n <% for (var gf of rules.guardrail_filters) { %>\n <div class=\"guardrail\">\n <div class=\"guardrail-name\"><%= gf.name %></div>\n <div class=\"guardrail-filter\"><%= gf.filter %></div>\n <div class=\"guardrail-reason\"><%= gf.reason %></div>\n </div>\n <% } %>\n </div>\n <% } %>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <div class=\"section\" id=\"guardrails-studio\">\n <% if (!(rules && rules.guardrail_filters && rules.guardrail_filters.length > 0)) { %>\n <div class=\"section-label\">Safety</div>\n <h2 class=\"section-title\">Guardrail Filters</h2>\n <% } %>\n <div id=\"guardrail-forms\"></div>\n <button class=\"studio-add-btn\" onclick=\"addGuardrail('<%= model.name %>')\">+ Add Guardrail</button>\n </div>\n <script>\n function addGuardrail(modelName) {\n var container = document.getElementById('guardrail-forms');\n var card = document.createElement('div');\n card.className = 'card';\n card.style.marginBottom = '1rem';\n\n var nameLabel = document.createElement('label');\n nameLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n nameLabel.textContent = 'Name';\n var nameInput = document.createElement('input');\n nameInput.type = 'text';\n nameInput.className = 'search-input gr-name';\n nameInput.placeholder = 'e.g. pii_filter';\n nameInput.style.fontSize = '0.85rem';\n var nameDiv = document.createElement('div');\n nameDiv.style.marginBottom = '0.75rem';\n nameDiv.appendChild(nameLabel);\n nameDiv.appendChild(nameInput);\n\n var filterLabel = document.createElement('label');\n filterLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n filterLabel.textContent = 'Filter Expression';\n var filterInput = document.createElement('input');\n filterInput.type = 'text';\n filterInput.className = 'search-input gr-filter';\n filterInput.placeholder = 'e.g. WHERE email IS NOT NULL';\n filterInput.style.cssText = 'font-family:var(--mono);font-size:0.85rem;';\n var filterDiv = document.createElement('div');\n filterDiv.style.marginBottom = '0.75rem';\n filterDiv.appendChild(filterLabel);\n filterDiv.appendChild(filterInput);\n\n var reasonLabel = document.createElement('label');\n reasonLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n reasonLabel.textContent = 'Reason';\n var reasonInput = document.createElement('input');\n reasonInput.type = 'text';\n reasonInput.className = 'search-input gr-reason';\n reasonInput.placeholder = 'Why this guardrail exists';\n reasonInput.style.fontSize = '0.85rem';\n var reasonDiv = document.createElement('div');\n reasonDiv.style.marginBottom = '0.75rem';\n reasonDiv.appendChild(reasonLabel);\n reasonDiv.appendChild(reasonInput);\n\n var stageBtn = document.createElement('button');\n stageBtn.className = 'staged-btn primary';\n stageBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';\n stageBtn.textContent = 'Stage';\n stageBtn.addEventListener('click', function() { stageGuardrail(stageBtn, modelName); });\n\n var cancelBtn = document.createElement('button');\n cancelBtn.className = 'staged-btn secondary';\n cancelBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';\n cancelBtn.textContent = 'Cancel';\n cancelBtn.addEventListener('click', function() { card.remove(); });\n\n var btnDiv = document.createElement('div');\n btnDiv.style.cssText = 'display:flex;gap:0.5rem;';\n btnDiv.appendChild(stageBtn);\n btnDiv.appendChild(cancelBtn);\n\n card.appendChild(nameDiv);\n card.appendChild(filterDiv);\n card.appendChild(reasonDiv);\n card.appendChild(btnDiv);\n container.appendChild(card);\n }\n\n function stageGuardrail(btn, modelName) {\n var card = btn.closest('.card');\n var name = card.querySelector('.gr-name').value.trim();\n var filter = card.querySelector('.gr-filter').value.trim();\n var reason = card.querySelector('.gr-reason').value.trim();\n if (!name || !filter) { showToast('Name and filter expression are required'); return; }\n var entry = { name: name, filter: filter };\n if (reason) entry.reason = reason;\n stageEdit('context/rules/' + modelName + '.rules.yaml', 'guardrail_filters.+', entry, 'Add guardrail: ' + name);\n card.style.borderColor = '#4ade80';\n btn.textContent = 'Staged';\n btn.disabled = true;\n }\n </script>\n <% } %>\n\n <% if (lineage && (lineage.upstream && lineage.upstream.length > 0 || lineage.downstream && lineage.downstream.length > 0)) { %>\n <div class=\"section\">\n <div class=\"section-label\">Data Lineage</div>\n <h2 class=\"section-title\">Lineage</h2>\n <div class=\"lineage-flow\">\n <% if (lineage.upstream && lineage.upstream.length > 0) { %>\n <div class=\"lineage-col\">\n <div class=\"lineage-col-label\">Upstream</div>\n <% for (var u of lineage.upstream) { %>\n <div class=\"lineage-node\">\n <div class=\"lineage-node-name\"><%= u.source %></div>\n <div class=\"lineage-node-detail\"><%= u.type || '' %><% if (u.tool) { %> via <%= u.tool %><% } %></div>\n </div>\n <% } %>\n </div>\n <div class=\"lineage-arrow\">→</div>\n <% } %>\n <div class=\"lineage-col\">\n <div class=\"lineage-col-label\">This Model</div>\n <div class=\"lineage-node\" style=\"border-color:var(--accent-border);\">\n <div class=\"lineage-node-name\" style=\"color:var(--accent);\"><%= model.name %></div>\n </div>\n </div>\n <% if (lineage.downstream && lineage.downstream.length > 0) { %>\n <div class=\"lineage-arrow\">→</div>\n <div class=\"lineage-col\">\n <div class=\"lineage-col-label\">Downstream</div>\n <% for (var d of lineage.downstream) { %>\n <div class=\"lineage-node\">\n <div class=\"lineage-node-name\"><%= d.target %></div>\n <div class=\"lineage-node-detail\"><%= d.type || '' %><% if (d.tool) { %> via <%= d.tool %><% } %></div>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <div class=\"section\" id=\"lineage-studio\">\n <% if (!(lineage && (lineage.upstream && lineage.upstream.length > 0 || lineage.downstream && lineage.downstream.length > 0))) { %>\n <div class=\"section-label\">Data Lineage</div>\n <h2 class=\"section-title\">Lineage</h2>\n <% } %>\n <div id=\"upstream-forms\"></div>\n <button class=\"studio-add-btn\" onclick=\"addUpstreamSource('<%= model.name %>')\" style=\"margin-bottom:0.75rem;\">+ Add Upstream Source</button>\n <div id=\"downstream-forms\"></div>\n <button class=\"studio-add-btn\" onclick=\"addDownstreamTarget('<%= model.name %>')\">+ Add Downstream Target</button>\n </div>\n <script>\n function createLineageForm(container, direction, modelName) {\n var card = document.createElement('div');\n card.className = 'card';\n card.style.marginBottom = '1rem';\n\n var nameLabel = document.createElement('label');\n nameLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n nameLabel.textContent = direction === 'upstream' ? 'Source Name' : 'Target Name';\n var nameInput = document.createElement('input');\n nameInput.type = 'text';\n nameInput.className = 'search-input lin-name';\n nameInput.placeholder = direction === 'upstream' ? 'e.g. raw_orders_db' : 'e.g. revenue_dashboard';\n nameInput.style.fontSize = '0.85rem';\n var nameDiv = document.createElement('div');\n nameDiv.style.marginBottom = '0.75rem';\n nameDiv.appendChild(nameLabel);\n nameDiv.appendChild(nameInput);\n\n var typeLabel = document.createElement('label');\n typeLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n typeLabel.textContent = 'Type';\n var typeSelect = document.createElement('select');\n typeSelect.className = 'search-input lin-type';\n typeSelect.style.fontSize = '0.85rem';\n ['pipeline', 'dashboard', 'api', 'file', 'derived'].forEach(function(opt) {\n var option = document.createElement('option');\n option.value = opt;\n option.textContent = opt;\n typeSelect.appendChild(option);\n });\n var typeDiv = document.createElement('div');\n typeDiv.style.marginBottom = '0.75rem';\n typeDiv.appendChild(typeLabel);\n typeDiv.appendChild(typeSelect);\n\n var notesLabel = document.createElement('label');\n notesLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';\n notesLabel.textContent = 'Notes';\n var notesInput = document.createElement('input');\n notesInput.type = 'text';\n notesInput.className = 'search-input lin-notes';\n notesInput.placeholder = 'Optional notes';\n notesInput.style.fontSize = '0.85rem';\n var notesDiv = document.createElement('div');\n notesDiv.style.marginBottom = '0.75rem';\n notesDiv.appendChild(notesLabel);\n notesDiv.appendChild(notesInput);\n\n var stageBtn = document.createElement('button');\n stageBtn.className = 'staged-btn primary';\n stageBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';\n stageBtn.textContent = 'Stage';\n stageBtn.addEventListener('click', function() {\n var entryName = card.querySelector('.lin-name').value.trim();\n var entryType = card.querySelector('.lin-type').value;\n var entryNotes = card.querySelector('.lin-notes').value.trim();\n if (!entryName) { showToast((direction === 'upstream' ? 'Source' : 'Target') + ' name is required'); return; }\n var entry = {};\n if (direction === 'upstream') { entry.source = entryName; } else { entry.target = entryName; }\n entry.type = entryType;\n if (entryNotes) entry.notes = entryNotes;\n stageEdit('context/lineage/' + modelName + '.lineage.yaml', direction + '.+', entry, 'Add ' + direction + ': ' + entryName);\n card.style.borderColor = '#4ade80';\n stageBtn.textContent = 'Staged';\n stageBtn.disabled = true;\n });\n\n var cancelBtn = document.createElement('button');\n cancelBtn.className = 'staged-btn secondary';\n cancelBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';\n cancelBtn.textContent = 'Cancel';\n cancelBtn.addEventListener('click', function() { card.remove(); });\n\n var btnDiv = document.createElement('div');\n btnDiv.style.cssText = 'display:flex;gap:0.5rem;';\n btnDiv.appendChild(stageBtn);\n btnDiv.appendChild(cancelBtn);\n\n card.appendChild(nameDiv);\n card.appendChild(typeDiv);\n card.appendChild(notesDiv);\n card.appendChild(btnDiv);\n container.appendChild(card);\n }\n\n function addUpstreamSource(modelName) {\n createLineageForm(document.getElementById('upstream-forms'), 'upstream', modelName);\n }\n\n function addDownstreamTarget(modelName) {\n createLineageForm(document.getElementById('downstream-forms'), 'downstream', modelName);\n }\n </script>\n <% } %>\n\n <% if (tier) { %>\n <div class=\"section\">\n <div class=\"section-label\">Data Quality</div>\n <h2 class=\"section-title\">Tier Scorecard</h2>\n <div class=\"scorecard\">\n <% var tierLevels = ['bronze', 'silver', 'gold']; %>\n <% for (var lvl of tierLevels) { %>\n <div class=\"sc-tier\">\n <div class=\"sc-tier-head\">\n <span class=\"sc-tier-name <%= lvl %>\"><%= lvl %></span>\n <% if (tier[lvl].passed) { %>\n <span class=\"sc-status pass\">Passed</span>\n <% } else { %>\n <span class=\"sc-status fail\">Not passed</span>\n <% } %>\n </div>\n <% if (tier[lvl].checks && tier[lvl].checks.length > 0) { %>\n <ul class=\"check-list\">\n <% for (var chk of tier[lvl].checks) { %>\n <li class=\"check-item\">\n <span class=\"check-icon <%= chk.passed ? 'pass' : 'fail' %>\"><%= chk.passed ? '\\\\u2713' : '\\\\u2717' %></span>\n <span><%= chk.label %><% if (chk.detail) { %> — <span style=\"color:var(--text-dim);\"><%= chk.detail %></span><% } %></span>\n </li>\n <% } %>\n </ul>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n</div>\n\n${FOOTER}\n${SCRIPTS}\n</body>\n</html>`;\n","import { HEAD, TOPBAR, SIDEBAR, SIDEBAR_DATA, FOOTER, TIER_BADGE, SCRIPTS } from './shared.js';\n\nexport const schemaTemplate = `${HEAD}\n<body>\n${TOPBAR}\n${SIDEBAR_DATA}\n${SIDEBAR}\n${TIER_BADGE}\n\n<div class=\"main\">\n <div class=\"breadcrumb\">\n <a href=\"<%= basePath %>/index.html\">Home</a>\n <span class=\"breadcrumb-sep\">/</span>\n <a href=\"<%= basePath %>/models/<%= model.name %>.html\"><%= model.name %></a>\n <span class=\"breadcrumb-sep\">/</span>\n <span>Schema</span>\n </div>\n\n <div class=\"page-header\">\n <div style=\"display:flex;align-items:center;gap:0.6rem;\">\n <h1><%= model.name %></h1>\n <% if (tier) { %><%- tierBadge(tier.tier) %><% } %>\n <span style=\"font-size:0.8rem;color:var(--text-dim);\">Schema Browser</span>\n </div>\n </div>\n\n <% if (model.datasets && model.datasets.length > 0) { %>\n <% for (var i = 0; i < model.datasets.length; i++) { var ds = model.datasets[i]; %>\n <% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>\n <div class=\"section\">\n <div class=\"card\" style=\"padding:0;overflow:hidden;\">\n <div style=\"padding:1rem 1.25rem;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.4rem;\">\n <div style=\"display:flex;align-items:center;gap:0.5rem;\">\n <span class=\"mono\" style=\"font-size:0.9rem;color:var(--text);\"><%= ds.name %></span>\n <% if (dsGov && dsGov.table_type) { %>\n <span class=\"tag ds-<%= dsGov.table_type %>\"><%= dsGov.table_type %></span>\n <% } %>\n </div>\n <div style=\"display:flex;gap:0.5rem;flex-wrap:wrap;\">\n <% if (dsGov && dsGov.grain) { %><span class=\"tag\"><%= dsGov.grain %></span><% } %>\n <% if (dsGov && dsGov.refresh) { %><span class=\"tag\"><%= dsGov.refresh %></span><% } %>\n <% if (dsGov && dsGov.security) { %><span class=\"tag\"><%= dsGov.security %></span><% } %>\n </div>\n </div>\n <div style=\"padding:0.5rem 1.25rem;font-size:0.72rem;color:var(--text-dim);font-family:var(--mono);\">\n Source: <%= ds.source %>\n </div>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <div style=\"padding:0 1.25rem 0.75rem;font-size:0.82rem;color:var(--text-secondary);font-weight:300;\"><span class=\"editable\" data-file=\"context/models/<%= model.name %>.osi.yaml\" data-path=\"semantic_model.0.datasets.<%= i %>.description\" data-label=\"<%= ds.name %> description\"><%= ds.description || 'Add description' %></span></div>\n <% } else if (ds.description) { %>\n <div style=\"padding:0 1.25rem 0.75rem;font-size:0.82rem;color:var(--text-secondary);font-weight:300;\"><%= ds.description %></div>\n <% } %>\n\n <% if (ds.fields && ds.fields.length > 0) { %>\n <table class=\"data-table\">\n <thead>\n <tr><th>Field</th><th>Description</th><th>Semantic Role</th><th>Aggregation</th></tr>\n </thead>\n <tbody>\n <% for (var j = 0; j < ds.fields.length; j++) { var field = ds.fields[j]; %>\n <% var fieldKey = ds.name + '.' + field.name; %>\n <% var fGov = gov && gov.fields && gov.fields[fieldKey]; %>\n <% var role = fGov && fGov.semantic_role ? fGov.semantic_role : ''; %>\n <tr>\n <td class=\"mono\" style=\"font-size:0.75rem;\"><%= field.name %></td>\n <td style=\"color:var(--text-secondary);font-size:0.8rem;\">\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span class=\"editable\" data-file=\"context/models/<%= model.name %>.osi.yaml\" data-path=\"semantic_model.0.datasets.<%= i %>.fields.<%= j %>.description\" data-label=\"<%= field.name %> description\"><%= field.description || 'Add description' %></span>\n <% } else { %>\n <%= field.description || '' %>\n <% } %>\n </td>\n <td>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span class=\"dropdown-editable\" data-file=\"context/governance/<%= model.name %>.governance.yaml\" data-path=\"datasets.<%= ds.name %>.fields.<%= field.name %>.semantic_role\" data-options=\"identifier,metric,dimension,date,attribute\" data-label=\"<%= field.name %> semantic role\"><%= role || 'Select role' %></span>\n <% } else { %>\n <% if (role) { %><span class=\"tag role-<%= role %>\"><%= role %></span><% } %>\n <% } %>\n </td>\n <td class=\"mono\" style=\"font-size:0.75rem;color:var(--text-dim);\">\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span class=\"dropdown-editable\" data-file=\"context/governance/<%= model.name %>.governance.yaml\" data-path=\"datasets.<%= ds.name %>.fields.<%= field.name %>.default_aggregation\" data-options=\"SUM,AVG,COUNT,MIN,MAX,NONE\" data-label=\"<%= field.name %> aggregation\"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : 'Select' %></span>\n <% } else { %>\n <%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %>\n <% } %>\n </td>\n </tr>\n <% } %>\n </tbody>\n </table>\n <% } else { %>\n <p style=\"padding:1.25rem;color:var(--text-dim);font-size:0.82rem;\">No fields defined.</p>\n <% } %>\n </div>\n </div>\n <% } %>\n <% } else { %>\n <p style=\"color:var(--text-secondary);\">No datasets found.</p>\n <% } %>\n</div>\n\n${FOOTER}\n${SCRIPTS}\n</body>\n</html>`;\n","import { HEAD, TOPBAR, SIDEBAR, SIDEBAR_DATA, FOOTER, SCRIPTS } from './shared.js';\n\nexport const rulesTemplate = `${HEAD}\n<body>\n${TOPBAR}\n${SIDEBAR_DATA}\n${SIDEBAR}\n\n<div class=\"main\">\n <div class=\"breadcrumb\">\n <a href=\"<%= basePath %>/index.html\">Home</a>\n <span class=\"breadcrumb-sep\">/</span>\n <a href=\"<%= basePath %>/models/<%= modelName %>.html\"><%= modelName %></a>\n <span class=\"breadcrumb-sep\">/</span>\n <span>Rules</span>\n </div>\n\n <div class=\"page-header\">\n <h1><%= modelName %> <span style=\"color:var(--text-dim);font-weight:300;\">— Rules & Queries</span></h1>\n </div>\n\n <% if (rules && rules.golden_queries && rules.golden_queries.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Golden Queries</div>\n <h2 class=\"section-title\">Pre-validated questions</h2>\n <% for (var gq of rules.golden_queries) { %>\n <div class=\"query-card\">\n <div class=\"query-q\">\n <span class=\"query-q-badge\">Q</span>\n <span><%= gq.question %></span>\n </div>\n <div class=\"query-sql sql-highlight\"><%= gq.sql %></div>\n <% if (gq.dialect || (gq.tags && gq.tags.length > 0)) { %>\n <div class=\"query-meta\">\n <% if (gq.dialect) { %><span>Dialect: <%= gq.dialect %></span><% } %>\n <% if (gq.tags && gq.tags.length > 0) { %><span>Tags: <%= gq.tags.join(', ') %></span><% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n\n <% if (rules && rules.business_rules && rules.business_rules.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Business Rules</div>\n <h2 class=\"section-title\">Business Rules</h2>\n <div style=\"display:flex;flex-direction:column;gap:0.75rem;\">\n <% for (var br of rules.business_rules) { %>\n <div class=\"card\">\n <div style=\"font-family:var(--mono);font-size:0.82rem;color:var(--accent-light);margin-bottom:0.3rem;font-weight:500;\"><%= br.name %></div>\n <p style=\"font-size:0.82rem;color:var(--text-secondary);line-height:1.6;font-weight:300;\"><%= br.definition %></p>\n <% if (br.enforcement && br.enforcement.length > 0) { %>\n <div style=\"display:flex;gap:0.3rem;margin-top:0.5rem;flex-wrap:wrap;\">\n <% for (var e of br.enforcement) { %><span class=\"tag tag-green\"><%= e %></span><% } %>\n </div>\n <% } %>\n <% if (br.avoid && br.avoid.length > 0) { %>\n <div style=\"display:flex;gap:0.3rem;margin-top:0.3rem;flex-wrap:wrap;\">\n <% for (var a of br.avoid) { %><span class=\"tag tag-red\"><%= a %></span><% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (rules && rules.guardrail_filters && rules.guardrail_filters.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Safety</div>\n <h2 class=\"section-title\">Guardrail Filters</h2>\n <% for (var gf of rules.guardrail_filters) { %>\n <div class=\"guardrail\">\n <div class=\"guardrail-name\"><%= gf.name %></div>\n <div class=\"guardrail-filter\"><%= gf.filter %></div>\n <div class=\"guardrail-reason\"><%= gf.reason %></div>\n <% if (gf.tables && gf.tables.length > 0) { %>\n <div style=\"display:flex;gap:0.3rem;margin-top:0.4rem;\">\n <% for (var tb of gf.tables) { %><span class=\"tag\"><%= tb %></span><% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n <% } %>\n\n <% if (rules && rules.hierarchies && rules.hierarchies.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Drill Paths</div>\n <h2 class=\"section-title\">Hierarchies</h2>\n <div style=\"display:flex;flex-direction:column;gap:0.75rem;\">\n <% for (var h of rules.hierarchies) { %>\n <div class=\"card\">\n <div style=\"font-family:var(--mono);font-size:0.82rem;color:var(--accent-light);margin-bottom:0.3rem;font-weight:500;\"><%= h.name %></div>\n <div style=\"font-size:0.78rem;color:var(--text-secondary);margin-bottom:0.3rem;\">Dataset: <span class=\"mono\"><%= h.dataset %></span></div>\n <div style=\"display:flex;align-items:center;gap:0.4rem;flex-wrap:wrap;\">\n <% for (var li = 0; li < h.levels.length; li++) { %>\n <span class=\"tag tag-blue\"><%= h.levels[li] %></span>\n <% if (li < h.levels.length - 1) { %><span style=\"color:var(--text-dim);\">→</span><% } %>\n <% } %>\n </div>\n </div>\n <% } %>\n </div>\n </div>\n <% } %>\n\n <% if (!rules || ((!rules.golden_queries || rules.golden_queries.length === 0) && (!rules.business_rules || rules.business_rules.length === 0) && (!rules.guardrail_filters || rules.guardrail_filters.length === 0) && (!rules.hierarchies || rules.hierarchies.length === 0))) { %>\n <p style=\"color:var(--text-secondary);\">No rules or queries defined for this model.</p>\n <% } %>\n</div>\n\n${FOOTER}\n${SCRIPTS}\n</body>\n</html>`;\n","import { HEAD, TOPBAR, SIDEBAR, SIDEBAR_DATA, FOOTER, SCRIPTS } from './shared.js';\n\nexport const glossaryTemplate = `${HEAD}\n<body>\n${TOPBAR}\n${SIDEBAR_DATA}\n${SIDEBAR}\n\n<div class=\"main\">\n <div class=\"page-header\">\n <h1>Glossary</h1>\n <p class=\"subtitle\">Business term definitions and mappings.</p>\n </div>\n\n <% var termIds = Object.keys(terms).sort(); %>\n <% if (termIds.length === 0) { %>\n <p style=\"color:var(--text-secondary);\">No terms defined.</p>\n <% } else { %>\n <input type=\"text\" id=\"glossary-filter\"\n placeholder=\"Filter terms...\"\n class=\"search-input\"\n style=\"margin-bottom:1.5rem;max-width:360px;\" />\n\n <div class=\"glossary-grid\" id=\"glossary-grid\">\n <% for (var tid of termIds) { %>\n <% var term = terms[tid]; %>\n <div class=\"glossary-card card\" id=\"term-<%= tid %>\" data-term=\"<%= tid %>\">\n <div class=\"glossary-term\"><%= tid %></div>\n <div class=\"glossary-def\">\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span class=\"editable\" data-file=\"context/glossary/<%= tid %>.term.yaml\" data-path=\"definition\" data-label=\"<%= tid %> definition\"><%= term.definition || 'Add definition' %></span>\n <% } else { %>\n <%= term.definition || '' %>\n <% } %>\n </div>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <div style=\"display:flex;gap:0.3rem;margin-top:0.5rem;flex-wrap:wrap;\">\n <span class=\"editable\" data-file=\"context/glossary/<%= tid %>.term.yaml\" data-path=\"synonyms\" data-label=\"<%= tid %> synonyms\" style=\"font-size:0.78rem;color:var(--text-dim);\"><%= (term.synonyms && term.synonyms.length > 0) ? term.synonyms.join(', ') : 'Add synonyms (comma-separated)' %></span>\n </div>\n <% } else if (term.synonyms && term.synonyms.length > 0) { %>\n <div style=\"display:flex;gap:0.3rem;margin-top:0.5rem;flex-wrap:wrap;\">\n <% for (var s of term.synonyms) { %><span class=\"tag\"><%= s %></span><% } %>\n </div>\n <% } %>\n <% if (term.maps_to && term.maps_to.length > 0) { %>\n <div style=\"display:flex;gap:0.3rem;margin-top:0.35rem;flex-wrap:wrap;\">\n <% for (var m of term.maps_to) { %><span class=\"tag tag-blue\"><%= m %></span><% } %>\n </div>\n <% } %>\n <% if (term.owner) { %>\n <div style=\"font-size:0.72rem;color:var(--text-dim);margin-top:0.4rem;\">\n Owner: <a href=\"<%= basePath %>/owners/<%= term.owner %>.html\"><%= term.owner %></a>\n </div>\n <% } %>\n </div>\n <% } %>\n </div>\n\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <div id=\"add-term-container\" style=\"margin-top:1.5rem;\">\n <button class=\"studio-add-btn\" id=\"add-term-btn\" onclick=\"document.getElementById('add-term-form').style.display='block';this.style.display='none';\">+ Add Term</button>\n <div id=\"add-term-form\" class=\"card\" style=\"display:none;padding:1.25rem;margin-top:0.5rem;\">\n <div style=\"font-size:0.85rem;color:#c9a55a;margin-bottom:1rem;font-weight:500;\">New Glossary Term</div>\n <div style=\"margin-bottom:0.75rem;\">\n <label style=\"display:block;font-size:0.75rem;color:var(--text-dim);margin-bottom:0.25rem;\">Term ID (slug)</label>\n <input type=\"text\" id=\"new-term-id\" placeholder=\"e.g. churn_rate\" style=\"width:100%;padding:6px 10px;background:var(--bg-card);border:1px solid #333;border-radius:6px;color:var(--text-primary);font-size:0.85rem;\" />\n </div>\n <div style=\"margin-bottom:0.75rem;\">\n <label style=\"display:block;font-size:0.75rem;color:var(--text-dim);margin-bottom:0.25rem;\">Definition</label>\n <input type=\"text\" id=\"new-term-def\" placeholder=\"What this term means\" style=\"width:100%;padding:6px 10px;background:var(--bg-card);border:1px solid #333;border-radius:6px;color:var(--text-primary);font-size:0.85rem;\" />\n </div>\n <div style=\"margin-bottom:0.75rem;\">\n <label style=\"display:block;font-size:0.75rem;color:var(--text-dim);margin-bottom:0.25rem;\">Synonyms (comma-separated)</label>\n <input type=\"text\" id=\"new-term-synonyms\" placeholder=\"e.g. attrition, turnover\" style=\"width:100%;padding:6px 10px;background:var(--bg-card);border:1px solid #333;border-radius:6px;color:var(--text-primary);font-size:0.85rem;\" />\n </div>\n <div style=\"margin-bottom:0.75rem;\">\n <label style=\"display:block;font-size:0.75rem;color:var(--text-dim);margin-bottom:0.25rem;\">Tags (comma-separated)</label>\n <input type=\"text\" id=\"new-term-tags\" placeholder=\"e.g. finance, metrics\" style=\"width:100%;padding:6px 10px;background:var(--bg-card);border:1px solid #333;border-radius:6px;color:var(--text-primary);font-size:0.85rem;\" />\n </div>\n <div style=\"display:flex;gap:0.5rem;justify-content:flex-end;\">\n <button onclick=\"document.getElementById('add-term-form').style.display='none';document.getElementById('add-term-btn').style.display='';\" style=\"background:none;border:1px solid #555;color:var(--text-secondary);border-radius:6px;padding:6px 14px;cursor:pointer;font-size:0.82rem;\">Cancel</button>\n <button onclick=\"submitNewTerm()\" style=\"background:#c9a55a;border:none;color:#000;border-radius:6px;padding:6px 14px;cursor:pointer;font-size:0.82rem;font-weight:500;\">Stage Term</button>\n </div>\n </div>\n </div>\n <% } %>\n <% } %>\n</div>\n\n${FOOTER}\n${SCRIPTS}\n<script>\n(function() {\n var input = document.getElementById('glossary-filter');\n if (!input) return;\n var cards = document.querySelectorAll('.glossary-card');\n input.addEventListener('input', function() {\n var q = input.value.toLowerCase();\n cards.forEach(function(card) {\n var text = card.textContent.toLowerCase();\n card.style.display = text.indexOf(q) !== -1 ? '' : 'none';\n });\n });\n})();\n</script>\n<% if (typeof studioMode !== 'undefined' && studioMode) { %>\n<script>\nfunction submitNewTerm() {\n var id = document.getElementById('new-term-id').value.trim();\n var def = document.getElementById('new-term-def').value.trim();\n var syns = document.getElementById('new-term-synonyms').value.trim();\n var tags = document.getElementById('new-term-tags').value.trim();\n if (!id) { alert('Term ID is required.'); return; }\n var file = 'context/glossary/' + id + '.term.yaml';\n if (def) stageEdit(file, 'definition', def, id + ' definition');\n if (syns) {\n var synList = syns.split(',').map(function(s){ return s.trim(); }).filter(Boolean);\n stageEdit(file, 'synonyms', synList, id + ' synonyms');\n }\n if (tags) {\n var tagList = tags.split(',').map(function(t){ return t.trim(); }).filter(Boolean);\n stageEdit(file, 'tags', tagList, id + ' tags');\n }\n document.getElementById('add-term-form').style.display = 'none';\n document.getElementById('add-term-btn').style.display = '';\n document.getElementById('new-term-id').value = '';\n document.getElementById('new-term-def').value = '';\n document.getElementById('new-term-synonyms').value = '';\n document.getElementById('new-term-tags').value = '';\n}\n</script>\n<% } %>\n</body>\n</html>`;\n","import { HEAD, TOPBAR, SIDEBAR, SIDEBAR_DATA, FOOTER, TIER_BADGE, SCRIPTS } from './shared.js';\n\nexport const ownerTemplate = `${HEAD}\n<body>\n${TOPBAR}\n${SIDEBAR_DATA}\n${SIDEBAR}\n${TIER_BADGE}\n\n<div class=\"main\">\n <div class=\"breadcrumb\">\n <a href=\"<%= basePath %>/index.html\">Home</a>\n <span class=\"breadcrumb-sep\">/</span>\n <span><%= owner.display_name %></span>\n </div>\n\n <div class=\"page-header\">\n <h1>\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span class=\"editable\" data-file=\"context/owners/<%= owner.id %>.owner.yaml\" data-path=\"display_name\" data-label=\"<%= owner.id %> display name\"><%= owner.display_name || 'Add display name' %></span>\n <% } else { %>\n <%= owner.display_name %>\n <% } %>\n </h1>\n </div>\n\n <div style=\"display:flex;gap:1.25rem;font-size:0.82rem;color:var(--text-dim);margin-bottom:1.5rem;\">\n <% if (typeof studioMode !== 'undefined' && studioMode) { %>\n <span>Email: <span class=\"editable\" data-file=\"context/owners/<%= owner.id %>.owner.yaml\" data-path=\"email\" data-label=\"<%= owner.id %> email\" style=\"color:var(--text-secondary);\"><%= owner.email || 'Add email' %></span></span>\n <span>Team: <span class=\"editable\" data-file=\"context/owners/<%= owner.id %>.owner.yaml\" data-path=\"team\" data-label=\"<%= owner.id %> team\" style=\"color:var(--text-secondary);\"><%= owner.team || 'Add team' %></span></span>\n <% } else { %>\n <% if (owner.email) { %><span>Email: <span style=\"color:var(--text-secondary);\"><%= owner.email %></span></span><% } %>\n <% if (owner.team) { %><span>Team: <span style=\"color:var(--text-secondary);\"><%= owner.team %></span></span><% } %>\n <% } %>\n </div>\n\n <% if (owner.description) { %>\n <p style=\"color:var(--text-secondary);font-size:0.9rem;margin-bottom:2rem;font-weight:300;max-width:560px;\"><%= owner.description %></p>\n <% } %>\n\n <% if (governedModels.length > 0) { %>\n <div class=\"section\">\n <div class=\"section-label\">Stewardship</div>\n <h2 class=\"section-title\">Governed Models</h2>\n <div class=\"card-grid\">\n <% for (var gm of governedModels) { %>\n <a href=\"<%= basePath %>/models/<%= gm.name %>.html\" class=\"card-link\">\n <div class=\"card\">\n <div style=\"display:flex;align-items:center;gap:0.4rem;\">\n <span class=\"mono\" style=\"font-size:0.85rem;color:var(--accent-light);\"><%= gm.name %></span>\n <% if (gm.tier) { %><%- tierBadge(gm.tier) %><% } %>\n </div>\n </div>\n </a>\n <% } %>\n </div>\n </div>\n <% } else { %>\n <p style=\"color:var(--text-secondary);\">No models governed by this owner.</p>\n <% } %>\n</div>\n\n${FOOTER}\n${SCRIPTS}\n</body>\n</html>`;\n","import { HEAD, TOPBAR, SIDEBAR, SIDEBAR_DATA, FOOTER, SCRIPTS } from './shared.js';\n\nexport const searchTemplate = `${HEAD}\n<body>\n${TOPBAR}\n${SIDEBAR_DATA}\n${SIDEBAR}\n\n<div class=\"main\">\n <div class=\"page-header\">\n <h1>Search</h1>\n </div>\n\n <input type=\"text\" id=\"search-input\"\n placeholder=\"Search models, datasets, fields, terms...\"\n class=\"search-input\"\n style=\"margin-bottom:1.5rem;\" />\n\n <div id=\"search-results\" style=\"display:flex;flex-direction:column;gap:0.5rem;\"></div>\n</div>\n\n${FOOTER}\n${SCRIPTS}\n\n<script src=\"https://cdn.jsdelivr.net/npm/minisearch@7.1.0/dist/umd/index.min.js\"></script>\n<script>\n(function() {\n var indexData = <%- searchIndexJson %>;\n var miniSearch = MiniSearch.loadJSON(JSON.stringify(indexData.index), indexData.options);\n\n var input = document.getElementById('search-input');\n var resultsContainer = document.getElementById('search-results');\n var docs = indexData.documents;\n\n var typeColors = {\n model: 'tag-gold',\n dataset: 'tag-blue',\n term: 'tag-green',\n owner: 'tag-bronze'\n };\n\n function clearResults() {\n while (resultsContainer.firstChild) {\n resultsContainer.removeChild(resultsContainer.firstChild);\n }\n }\n\n function createResultElement(doc) {\n var wrapper = document.createElement('div');\n wrapper.className = 'card';\n wrapper.style.padding = '0.75rem 1rem';\n\n var top = document.createElement('div');\n top.style.display = 'flex';\n top.style.alignItems = 'center';\n top.style.gap = '0.4rem';\n\n var link = document.createElement('a');\n link.href = doc.url;\n link.style.fontFamily = 'var(--mono)';\n link.style.fontSize = '0.82rem';\n link.style.color = 'var(--accent-light)';\n link.textContent = doc.title;\n top.appendChild(link);\n\n var badge = document.createElement('span');\n badge.className = 'tag ' + (typeColors[doc.type] || '');\n badge.textContent = doc.type;\n top.appendChild(badge);\n\n wrapper.appendChild(top);\n\n if (doc.description) {\n var desc = document.createElement('p');\n desc.style.fontSize = '0.78rem';\n desc.style.color = 'var(--text-secondary)';\n desc.style.marginTop = '0.2rem';\n desc.style.fontWeight = '300';\n desc.textContent = doc.description;\n wrapper.appendChild(desc);\n }\n\n return wrapper;\n }\n\n input.addEventListener('input', function() {\n var query = input.value.trim();\n clearResults();\n if (!query) return;\n var hits = miniSearch.search(query, { prefix: true, fuzzy: 0.2 });\n if (hits.length === 0) {\n var noResults = document.createElement('p');\n noResults.style.color = 'var(--text-secondary)';\n noResults.textContent = 'No results found.';\n resultsContainer.appendChild(noResults);\n return;\n }\n hits.slice(0, 20).forEach(function(hit) {\n var doc = docs[hit.id];\n if (doc) {\n resultsContainer.appendChild(createResultElement(doc));\n }\n });\n });\n})();\n</script>\n</body>\n</html>`;\n","/**\n * Builds a MiniSearch index from a Manifest for client-side search.\n *\n * The index is serialized to JSON so it can be embedded in the search page\n * and loaded by MiniSearch on the client side.\n */\n\nimport MiniSearch from 'minisearch';\nimport type { Manifest } from '@runcontext/core';\n\nexport interface SearchDocument {\n id: string;\n type: string;\n title: string;\n description: string;\n url: string;\n}\n\nexport interface SearchIndex {\n /** Serialized MiniSearch index (JSON-parsed object). */\n index: unknown;\n /** MiniSearch constructor options needed to reload the index. */\n options: {\n fields: string[];\n storeFields: string[];\n idField: string;\n };\n /** Map of document ID to document metadata for rendering results. */\n documents: Record<string, SearchDocument>;\n}\n\nconst MINISEARCH_OPTIONS = {\n fields: ['title', 'description', 'type'],\n storeFields: ['title', 'description', 'type', 'url'],\n idField: 'id',\n};\n\n/**\n * Build a search index from a manifest.\n *\n * @param manifest - The compiled ContextKit manifest\n * @param basePath - The base URL path for links (e.g. '' or '/docs')\n * @returns A SearchIndex object ready for JSON serialization\n */\nexport function buildSearchIndex(manifest: Manifest, basePath: string): SearchIndex {\n const docs: SearchDocument[] = [];\n let idCounter = 0;\n\n // Index models\n for (const [name, model] of Object.entries(manifest.models)) {\n docs.push({\n id: String(idCounter++),\n type: 'model',\n title: name,\n description: model.description ?? '',\n url: `${basePath}/models/${name}.html`,\n });\n\n // Index datasets within each model\n if (model.datasets) {\n for (const ds of model.datasets) {\n docs.push({\n id: String(idCounter++),\n type: 'dataset',\n title: `${name} / ${ds.name}`,\n description: ds.description ?? '',\n url: `${basePath}/models/${name}/schema.html`,\n });\n }\n }\n }\n\n // Index glossary terms\n for (const [termId, term] of Object.entries(manifest.terms)) {\n docs.push({\n id: String(idCounter++),\n type: 'term',\n title: termId,\n description: term.definition,\n url: `${basePath}/glossary.html#term-${termId}`,\n });\n }\n\n // Index owners\n for (const [oid, owner] of Object.entries(manifest.owners)) {\n docs.push({\n id: String(idCounter++),\n type: 'owner',\n title: owner.display_name,\n description: owner.description ?? '',\n url: `${basePath}/owners/${oid}.html`,\n });\n }\n\n // Build MiniSearch index\n const miniSearch = new MiniSearch(MINISEARCH_OPTIONS);\n miniSearch.addAll(docs);\n\n // Create document lookup map by id\n const documentsMap: Record<string, SearchDocument> = {};\n for (const doc of docs) {\n documentsMap[doc.id] = doc;\n }\n\n return {\n index: JSON.parse(JSON.stringify(miniSearch)),\n options: MINISEARCH_OPTIONS,\n documents: documentsMap,\n };\n}\n"],"mappings":";AAUA,OAAO,SAAS;AAChB,YAAY,QAAQ;AACpB,YAAY,UAAU;;;ACDf,IAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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O;AAAA,EACP,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runcontext/site",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Static documentation site generator for ContextKit",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Eric Kittelson",
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"url": "https://github.com/erickittelson/ContextKit.git",
|
|
11
11
|
"directory": "packages/site"
|
|
12
12
|
},
|
|
13
|
+
"bugs": "https://github.com/erickittelson/ContextKit/issues",
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18.0.0"
|
|
16
|
+
},
|
|
13
17
|
"keywords": [
|
|
14
18
|
"contextkit",
|
|
15
19
|
"site-generator",
|
|
@@ -36,9 +40,9 @@
|
|
|
36
40
|
"dist"
|
|
37
41
|
],
|
|
38
42
|
"dependencies": {
|
|
39
|
-
"@runcontext/core": "^0.3.5",
|
|
40
43
|
"ejs": "^3.1.10",
|
|
41
|
-
"minisearch": "^7.1.0"
|
|
44
|
+
"minisearch": "^7.1.0",
|
|
45
|
+
"@runcontext/core": "^0.4.1"
|
|
42
46
|
},
|
|
43
47
|
"devDependencies": {
|
|
44
48
|
"@types/ejs": "^3.1.5",
|