/** * Shell HTML renderer. * * Wraps module content in the shared rSpace layout: header with app/space * switchers + identity,
with module content, shell script + styles. * * In standalone mode, modules call renderStandaloneShell() which omits the * app/space switchers and only includes identity. */ import type { ModuleInfo } from "../shared/module"; export interface ShellOptions { /** Page */ title: string; /** Current module ID (highlighted in app switcher) */ moduleId: string; /** Current space slug */ spaceSlug: string; /** Space display name */ spaceName?: string; /** Module HTML content to inject into <main> */ body: string; /** Additional <script type="module"> tags for module-specific JS */ scripts?: string; /** Additional <link>/<style> tags for module-specific CSS */ styles?: string; /** List of available modules (for app switcher) */ modules: ModuleInfo[]; /** Theme for the header: 'dark' or 'light' */ theme?: "dark" | "light"; /** Extra <head> content (meta tags, preloads, etc.) */ head?: string; } export function renderShell(opts: ShellOptions): string { const { title, moduleId, spaceSlug, spaceName, body, scripts = "", styles = "", modules, theme = "light", head = "", } = opts; const moduleListJSON = JSON.stringify(modules); return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🌌</text></svg>"> <title>${escapeHtml(title)} ${styles} ${head}
${body}
${scripts} `; } /** Minimal shell for standalone module deployments (no app/space switcher) */ export function renderStandaloneShell(opts: { title: string; body: string; scripts?: string; styles?: string; theme?: "dark" | "light"; head?: string; }): string { const { title, body, scripts = "", styles = "", theme = "light", head = "" } = opts; return ` ${escapeHtml(title)} ${styles} ${head}
${body}
${scripts} `; } function escapeHtml(s: string): string { return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } function escapeAttr(s: string): string { return s.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); }