
"<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\n<!-- Shared deps for the .dc.html gallery entries (Templates / Building Blocks / Live screen).\n     Those files are Design Components: they need the DC runtime (support.js) and, for the T*\n     templates, their own design-system bundle. Iframing them by URL cannot work in a published\n     artifact — a blob URL has no directory, so their relative \"./support.js\" 404s and the page\n     renders raw {{ }} holes in serif. Declared ONCE here and spliced into each file's markup at\n     render time (buildDcSrcdoc), so 58 previews share one 64KB runtime instead of embedding it 58x. -->\n\n\n\n<!-- Gallery/screens/blocks HTML files referenced only as JS-built iframe src strings (never a static\n     attribute) — the publish bundler cannot discover those. Each live file gets its own\n     ext-resource-dependency meta below; resolveGallerySrc() (on Component) reads them back via\n     window.__resources, falling back to the relative assets/gallery/ path in dev/project mode. -->\n\n\n<!-- P4 (2026-09-06): the canvas's own index, and the context packs CM-D loads. Both were fetched on a\n     bare project-root path, which resolves against templates/canvas-dashboard/ and 404s in dev AND has\n     no resource id to resolve in a published build — so neither ever loaded. Declared here, read back\n     through window.__resources with a ../../ dev fallback, same as asset-index/toolset-default above. -->\n\n<!-- PT-8 (2026-09-07): the pitch/voice/video panes read their material at RUNTIME via fetch()\n     and JS-built image paths, so the publish bundler cannot discover any of it — the first stage-3\n     re-derive after those panes landed carried the panes but ZERO of their sources (verified: 0 of\n     281 bundled resources matched decks/vo_cues/objects), which offline is three panes rendering\n     error states. Same class as the gallery iframes above and the canvas-index fetch below; same\n     fix. Each pane reads these back through window.__resources with a ../../ dev fallback.\n     Do not delete one without deleting its reader. -->\n\n\n\n<!-- The deck DOCUMENTS themselves. Declared 2026-09-15 (D116): the animations and cue\n     files above were declared, the decks were not — so every pitch surface could count\n     slides but none could render one. deck-preview.js resolves these ids and falls back\n     to the relative path in dev. deck-stage.js is a sibling <x-import from=\"./\"> inside\n     each deck, invisible to the bundler for the same reason, so it is declared too. -->\n\n<!-- 2026-09-23: the rest of the decks on file (variant A, the domains catalogue) and the deck kit,\n     so their previews resolve offline. The kit's own siblings (deck-brand.js, image-slot.js,\n     the tenant pack) are declared for DeckPreview.doc's srcdoc rewrite. -->\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- The 300-background engine: nine plain <script src=\"backgroundsN.js\"> in the kit pages. -->\n\n\n\n\n\n\n\n\n\n<!-- 2026-09-23: the reels' JSX siblings. An animation DC loads them with a space-separated\n     <x-import from=\"./a.jsx ./b.jsx\">, invisible to the bundler, so every reel previewed as a\n     blank frame in the published shell. Keyed per unit (jsx-<unit>-<name>): each unit ships its\n     own animations-v3.jsx and they are not interchangeable. DeckPreview.doc rewrites them. -->\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- O6 · GENERATED from assets/index.json (222 entries). Paths carry LITERAL spaces:\n     the publish bundler resolves this content against disk without URL-decoding, so a %20 here\n     becomes a file it can never find (verified 2026-09-03: 138 %20-paths = 138 unresolved assets).\n     Do not hand-edit; regenerate. -->\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- D164: tenant logo files are set as JS-built img src, which the bundler cannot discover — declared here, read back via window.__resources. -->\n\n\n<script src=\"966fe5fd-e5f2-44f4-b8d6-c8b8dea71b90\"><\u002Fscript>\n<script src=\"f324c6f8-0920-49ab-98dd-dbe8a5535b1f\"><\u002Fscript>\n<script src=\"e8a6a1df-ad6d-4c8b-9c2a-f48877e04418\"><\u002Fscript>\n<script src=\"0f3a0fc1-d9a3-4b55-8578-e4f74f2d8e96\"><\u002Fscript>\n<!-- Loaded here for the same reason pitch-inventory.js is: three panes consume it in\n     renderVals(), and a pane's own <helmet><script src> is NOT guaranteed to have\n     executed before that pane's first render. pitch-inventory.js never raced because\n     the shell already loaded it (de-duped by URL, so the pane's tag is a no-op);\n     deck-preview.js had no shell-side load, and PanePitches — which uses it on its\n     FIRST render, with no fetch to hide behind — threw sc-logic-error on every clean\n     load. The panes still declare it (they must work opened standalone) and still\n     guard for its absence; this line removes the race in the shell. -->\n<script src=\"bfc5bcfb-fc58-4b53-918c-3fb9e5c25d0e\"><\u002Fscript>\n<script src=\"56bc19fe-0ed4-4fe3-ae0a-77dc0be6dc36\"><\u002Fscript>\n<script src=\"47a01519-f52a-4967-879f-9acdf1008950\"><\u002Fscript>\n<script src=\"8c8eee94-a5c3-4ac9-9eaa-cd1813c5d73e\"><\u002Fscript>\n<script src=\"8ebc0320-cbc0-4a68-814c-e80405a3ccac\"><\u002Fscript>\n<!-- inbox-threads.js and chart-paths.js are declared here (not just in PaneInbox/PaneQuality's own\n     helmets) because the publish bundler never scans inside dc-import'd sibling files — only the\n     shell's own <head>/helmet is discovered. Loading them here sets window.InboxThreads /\n     window.ChartPaths before any pane needs them; each pane's own duplicate tag is a harmless\n     no-op once the global already exists. -->\n<script src=\"3d92988b-4ef6-4c67-a18d-e776ca71f9e4\"><\u002Fscript>\n<script src=\"dc2dafeb-3dbe-4bda-b6d2-c7c260544ef5\"><\u002Fscript>\n<script src=\"405b2a2b-b88c-4807-b470-aee271eba87c\"><\u002Fscript>\n<script src=\"3bc23334-ce81-4722-a993-b011a29d33d0\"><\u002Fscript>\n<script src=\"df2c3897-1d4b-4f36-87ac-291111d66fc9\"><\u002Fscript>\n<!-- Client artefact fixtures (CANVAS-REPLY-14 §2 item B): PaneDelivery/PanePlatform and\n     PaneProposals/PanePackages each load theirs in their own helmet, but the publish bundler never\n     scans inside dc-import'd sibling files (see the inbox-threads.js note above), so the shell\n     must declare them too or a rebuilt Canvas Shell.html 404s both and those four panes never\n     leave their loading view. Same depth as brand-tokens.js; each pane's tag is then a no-op. -->\n<script src=\"ecd647a2-72f8-4874-9a34-44d3f99cf3df\"><\u002Fscript>\n<script src=\"4fe50761-93a8-439b-b911-cbf15521adaf\"><\u002Fscript>\n<script>\n/* Standalone build only (pipeline stage 2 — GENERATED, do not hand-edit; re-derive from\n   CanvasDashboard.dc.html after every shell edit, per CLAUDE.md). Child pane DCs each declare the\n   shared stylesheets/scripts in their own helmet. The bundler inlines the shell's head copies but\n   never scans inside dc-import'd siblings, so when a pane mounts, the runtime re-requests those\n   files by relative URL and they 404 offline. They are already loaded and applied from head by\n   then, so the duplicates are pure no-ops - this just stops them hitting the network at all.\n   context-store.js is additionally idempotent by construction (4.3), so even a duplicate that slips\n   through cannot replace the live store singleton. */\n(function () {\n  var DUP = [\"ds-colors-and-type.css\",\"ds-styles.css\",\"ds-boot.js\",\"state-layer.js\",\"pitch-inventory.js\",\"canvas-fullscreen.js\",\"fixtures.js\",\"inbox-threads.js\",\"agent-registry.js\",\"chart-paths.js\",\"canvas-artefacts.js\"];\n  function base(u) { try { return String(u).split('?')[0].split('/').pop(); } catch (e) { return ''; } }\n  function neutralise(n) {\n    var u = n.tagName === 'SCRIPT' ? n.getAttribute('src') : (n.tagName === 'LINK' ? n.getAttribute('href') : null);\n    if (!u || u.slice(0, 5) === 'data:' || u.slice(0, 5) === 'blob:') return;\n    if (DUP.indexOf(base(u)) === -1) return;\n    if (n.parentNode) n.parentNode.removeChild(n);   // already loaded from head - drop the duplicate\n  }\n  /* Pre-insertion guard. The four ./-relative head tags above are load-bearing —\n     the bundler inlines them to create its blob: copies, and removing them (tried\n     and measured 2026-09-22) drops the design-system bundle, InboxThreads and the\n     tenant packs entirely. But the bundler ALSO leaves the original tag beside the\n     blob, and ./ does not resolve at the bundle's own location, so each one costs a\n     failed request. There is no input-side fix: any relative form 404s at the root.\n     The observer below cannot help either — it fires after insertion, when the\n     fetch has already started. So the URL is stripped BEFORE the node lands.\n     ⚠ This is a RACE, not a guarantee: whether this patch installs before the\n     unpack injects those tags varies by load — it neutralised 4 of 4 on one load\n     and 0 of 4 on another. Treat the remaining failures as known, not fixed. */\n  function disarm(n) {\n    if (!n || n.nodeType !== 1) return;\n    var isScript = n.tagName === 'SCRIPT', isLink = n.tagName === 'LINK';\n    if (!isScript && !isLink) return;\n    var attr = isScript ? 'src' : 'href';\n    var u = n.getAttribute(attr);\n    if (!u || u.slice(0, 5) === 'data:' || u.slice(0, 5) === 'blob:') return;\n    if (DUP.indexOf(base(u)) === -1) return;\n    n.removeAttribute(attr);\n    n.setAttribute('data-dup-neutralised', base(u));\n  }\n  ['appendChild', 'insertBefore', 'replaceChild'].forEach(function (m) {\n    var orig = Node.prototype[m];\n    if (!orig || orig.__dcDupGuard) return;\n    var wrapped = function (a, b) { disarm(a); return orig.call(this, a, b); };\n    wrapped.__dcDupGuard = true;\n    Node.prototype[m] = wrapped;\n  });\n\n  new MutationObserver(function (recs) {\n    for (var i = 0; i < recs.length; i++) {\n      var a = recs[i].addedNodes;\n      for (var j = 0; j < a.length; j++) if (a[j].nodeType === 1) neutralise(a[j]);\n    }\n  }).observe(document.documentElement, { childList: true, subtree: true });\n})();\n<\u002Fscript>\n<script src=\"78100e1f-a3c3-4ac7-90e8-c17be807be5e\"><\u002Fscript>\n<\u002Fhead>\n<body>\n<template id=\"__bundler_thumbnail\" data-bg-color=\"#101012\">\n  <svg viewBox=\"0 0 400 400\" xmlns=\"http://www.w3.org/2000/svg\">\n    <rect width=\"400\" height=\"400\" style=\"fill:var(--app-bg, #101012)\"/>\n    <g transform=\"translate(200,200)\">\n      <circle r=\"78\" fill=\"none\" stroke-width=\"3\" opacity=\"0.9\" style=\"stroke:var(--app-accent, #1ed4e0)\"/>\n      <circle r=\"6\" style=\"fill:var(--app-accent, #1ed4e0)\"/>\n      <path d=\"M-46 -46 L46 46 M46 -46 L-46 46\" stroke-width=\"3\" stroke-linecap=\"round\" opacity=\"0.85\" style=\"stroke:var(--app-signal, #e8486a)\"/>\n    <\u002Fg>\n  <\u002Fsvg>\n<\u002Ftemplate>\n<x-dc>\n<!-- @template name=\"Canvas Shell\" description=\"The AaaS Canvas shell — one responsive codepath for desktop and phone. Heartbeat bar, ⌘K palette, app switcher, stream rail (docked ≥720px, drawer below), Tier-2 subnav, hero, KPI strip, roster plane, tasks tray, and the GTM outreach mail client. Fill one stream in the STREAMS map.\" -->\n<helmet>\n<!-- Design system: the two stylesheets are local, flattened copies with no nested or CDN @import,\n     so the publish bundler inlines them from these static hrefs. The bundle itself is declared as a\n     preload link (static href → inlined bytes, no execution) and appended in order by ds-boot.js;\n     see that file for why a plain async helmet script races support.js's React. -->\n<script src=\"966fe5fd-e5f2-44f4-b8d6-c8b8dea71b90\"><\u002Fscript>\n<script src=\"f324c6f8-0920-49ab-98dd-dbe8a5535b1f\"><\u002Fscript>\n<script src=\"e8a6a1df-ad6d-4c8b-9c2a-f48877e04418\"><\u002Fscript>\n<!-- The canvas-wide fullscreen viewer (FullscreenButton is its one control). Shell-loaded so a\n     pane's first click never races its own helmet. -->\n<script src=\"0f3a0fc1-d9a3-4b55-8578-e4f74f2d8e96\"><\u002Fscript>\n<!-- Loaded here for the same reason pitch-inventory.js is: three panes consume it in\n     renderVals(), and a pane's own <helmet><script src> is NOT guaranteed to have\n     executed before that pane's first render. pitch-inventory.js never raced because\n     the shell already loaded it (de-duped by URL, so the pane's tag is a no-op);\n     deck-preview.js had no shell-side load, and PanePitches — which uses it on its\n     FIRST render, with no fetch to hide behind — threw sc-logic-error on every clean\n     load. The panes still declare it (they must work opened standalone) and still\n     guard for its absence; this line removes the race in the shell. -->\n<script src=\"bfc5bcfb-fc58-4b53-918c-3fb9e5c25d0e\"><\u002Fscript>\n<script src=\"56bc19fe-0ed4-4fe3-ae0a-77dc0be6dc36\"><\u002Fscript>\n<script src=\"47a01519-f52a-4967-879f-9acdf1008950\"><\u002Fscript>\n<script src=\"8c8eee94-a5c3-4ac9-9eaa-cd1813c5d73e\"><\u002Fscript>\n<script src=\"8ebc0320-cbc0-4a68-814c-e80405a3ccac\"><\u002Fscript>\n<!-- inbox-threads.js and chart-paths.js are declared here (not just in PaneInbox/PaneQuality's own\n     helmets) because the publish bundler never scans inside dc-import'd sibling files — only the\n     shell's own <head>/helmet is discovered. Loading them here sets window.InboxThreads /\n     window.ChartPaths before any pane needs them; each pane's own duplicate tag is a harmless\n     no-op once the global already exists. -->\n<script src=\"3d92988b-4ef6-4c67-a18d-e776ca71f9e4\"><\u002Fscript>\n<script src=\"dc2dafeb-3dbe-4bda-b6d2-c7c260544ef5\"><\u002Fscript>\n<script src=\"405b2a2b-b88c-4807-b470-aee271eba87c\"><\u002Fscript>\n<script src=\"3bc23334-ce81-4722-a993-b011a29d33d0\"><\u002Fscript>\n<script src=\"df2c3897-1d4b-4f36-87ac-291111d66fc9\"><\u002Fscript>\n<!-- Client artefact fixtures (CANVAS-REPLY-14 §2 item B): PaneDelivery/PanePlatform and\n     PaneProposals/PanePackages each load theirs in their own helmet, but the publish bundler never\n     scans inside dc-import'd sibling files (see the inbox-threads.js note above), so the shell\n     must declare them too or a rebuilt Canvas Shell.html 404s both and those four panes never\n     leave their loading view. Same depth as brand-tokens.js; each pane's tag is then a no-op. -->\n<script src=\"ecd647a2-72f8-4874-9a34-44d3f99cf3df\"><\u002Fscript>\n<script src=\"4fe50761-93a8-439b-b911-cbf15521adaf\"><\u002Fscript>\n<style>\n  @keyframes aaasTicker{from{transform:translateX(0)}to{transform:translateX(-50%)}}\n  @keyframes inb-pulse{0%,100%{opacity:.35}50%{opacity:1}}\n  @media (prefers-reduced-motion: reduce){[data-ticker]{animation:none !important}}\n  html,body{margin:0;padding:0;height:100%}\n  *{box-sizing:border-box}\n  /* Narrow-pane reset (HANDOVER §2d). Flex/grid children default to min-width:auto, so one\n     unbreakable token (AP-Reconciliation, fuzzy_match, an email) sets a floor the child cannot\n     shrink below and the pane grows a scrollbar. :where() has zero specificity, so any inline\n     min-width still wins — this only lifts the auto floor from elements that never declared one.\n     NB overflow-wrap:anywhere must stay scoped: applied globally it zeroes min-content for every\n     element and forces intrinsic re-layout of every nested flex/grid tree, which wedges the page. */\n  :where(div,span,section,article,aside,header,footer,main,p,pre,button,a,label,li){min-width:0}\n  :where(.mono,.label-system,.label-nav,pre,code,.kpi-v){overflow-wrap:anywhere}\n  ::-webkit-scrollbar{width:10px;height:10px}\n  ::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.09);border-radius:8px}\n  ::-webkit-scrollbar-track{background:transparent}\n  @keyframes aaasPulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:0.45;transform:scale(0.72)}}\n  @keyframes aaasSkeleton{0%,100%{opacity:0.35}50%{opacity:0.7}}\n  @keyframes aaasSpin{to{transform:rotate(360deg)}}\n  /* Brand lockup indicator (ADR-036 §3) — the pulse that sits between mark and wordmark on every\n     domain surface; alternates circuit → red every 3s (DS \"Animation · logo dot pulse\"). */\n  @keyframes aaasLogoPulse{\n    0%,44%{background:rgb(var(--circuit-glow));box-shadow:0 0 7px rgb(var(--circuit-glow) / 0.85)}\n    50%,94%{background:rgb(var(--accent-red));box-shadow:0 0 7px rgb(var(--accent-red) / 0.85)}\n    22%,72%{opacity:0.35}\n    0%,44%,50%,94%,100%{opacity:1}\n  }\n  @media (prefers-reduced-motion: reduce){*{animation:none !important}}\n  /* Lazy dc-import mounts (pane switches after initial load) render a flat .sc-placeholder with\n     no shimmer — the runtime's own shine animation is scoped to html.sc-dc-streaming, which is only\n     true during the first paint. Without this the placeholder reads as a dead gray box instead of\n     \"loading\" on slower pane mounts. Unconditional so it always signals load-in-progress. */\n  .sc-placeholder{animation:aaasSkeleton 1.4s ease-in-out infinite}\n<\u002Fstyle>\n<\u002Fhelmet>\n\n<div data-theme=\"{{ theme }}\" style=\"{{ rootStyle }}\">\n  <sc-if value=\"{{ linkStale }}\" hint-placeholder-val=\"{{ false }}\">\n    <div role=\"alert\" style=\"position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:9999;display:flex;align-items:center;gap:16px;padding:12px 12px 12px 18px;max-width:calc(100vw - 32px);background:rgb(var(--basalt-surface, 26 26 28) / 0.85);backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px);border:1px solid rgb(var(--accent-red) / 0.35);border-radius:12px;box-shadow:0 8px 32px rgba(0,0,0,0.4)\">\n      <span style=\"width:8px;height:8px;border-radius:50%;background:rgb(var(--accent-red));flex:none\"><\u002Fspan>\n      <div style=\"display:flex;flex-direction:column;gap:2px;min-width:0\">\n        <span style=\"font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:500;letter-spacing:0.12em;text-transform:uppercase;color:rgb(var(--accent-red))\">SESSION EXPIRED<\u002Fspan>\n        <span style=\"font-family:Inter,sans-serif;font-size:13px;color:rgb(var(--text-primary, 236 236 232));text-wrap:pretty\">Panes can't load. Reload to continue.<\u002Fspan>\n      <\u002Fdiv>\n      <x-import component-from-global-scope=\"AaaSDesignSystem_2216a8.Button\" variant=\"bedrock\" size=\"sm\" on-click=\"{{ reloadPage }}\" hint-size=\"auto,32px\">Reload<\u002Fx-import>\n    <\u002Fdiv>\n  <\u002Fsc-if>\n  <div aria-hidden=\"true\" style=\"position:fixed;inset:0;pointer-events:none;background:radial-gradient(ellipse at 50% 32%, rgb(var(--basalt-bright)) 0%, rgb(var(--basalt-deep)) 62%);opacity:0.85\"><\u002Fdiv>\n  <!-- Film grain. A real <svg>, NOT a background-image data-URI. The data-URI form\n       produced two defects at once: the DC runtime's camelCase mapper rewrote\n       baseFrequency/numOctaves into sc-camel-* INSIDE the URI string, so feTurbulence\n       was inert and this layer painted nothing (the design system calls the grain\n       non-optional — \"Always layered… Never flat\"); and the bundler recorded the URI's\n       internal url() as an external asset, which the unpack resolver fetched as %23n —\n       the two 404s and the [error] {} on every load. Keep it as markup. -->\n  <svg aria-hidden=\"true\" focusable=\"false\" style=\"position:fixed;inset:0;width:100%;height:100%;pointer-events:none;opacity:var(--grain-opacity);mix-blend-mode:overlay;z-index:0\">\n    <filter id=\"om-grain-shell\"><feTurbulence type=\"fractalNoise\" baseFrequency=\"0.8\" numOctaves=\"2\" stitchTiles=\"stitch\"/><\u002Ffilter>\n    <rect width=\"100%\" height=\"100%\" filter=\"url(#om-grain-shell)\"><\u002Frect>\n  <\u002Fsvg>\n\n  <div style=\"position:relative;z-index:1;display:flex;flex-direction:column;height:100vh;overflow:hidden\">\n\n    <!-- L4 · Global heartbeat · static strip ≥ tablet, news-reader ticker on phones -->\n    <sc-if value=\"{{ hbStatic }}\" hint-placeholder-val=\"{{ true }}\">\n    <div style=\"display:flex;align-items:center;gap:16px;height:30px;padding:0 14px;border-bottom:1px solid var(--border);background:rgb(var(--basalt-deep));flex-shrink:0;overflow-x:auto;white-space:nowrap;scrollbar-width:none\">\n      <span style=\"display:inline-flex;align-items:center;gap:7px;flex-shrink:0\">\n        <span style=\"display:inline-flex\"><x-import component-from-global-scope=\"AaaSDesignSystem_2216a8.StatusDot\" variant=\"dual\" size=\"{{ n8 }}\" hint-size=\"8px,8px\"><\u002Fx-import><\u002Fspan>\n        <span class=\"label-system\" style=\"font-size:0.62rem;letter-spacing:0.14em;color:rgb(var(--lead))\">LIVE<\u002Fspan>\n      <\u002Fspan>\n      <sc-for list=\"{{ beats }}\" as=\"b\" hint-placeholder-count=\"5\">\n        <span style=\"display:inline-flex;gap:6px;align-items:baseline;flex-shrink:0\"><sc-if value=\"{{ b.isNeeds }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0;align-self:center\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><path d=\"M5 0.6 9.4 5 5 9.4 0.6 5z\" fill=\"rgb(var(--status-needs, 244 63 108))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ b.isStuck }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0;align-self:center\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><path d=\"M5 0.8 9.5 9H0.5z\" fill=\"rgb(var(--status-stuck, 248 217 116))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ b.isDone }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0;align-self:center\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"4.3\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\"><\u002Fcircle><path d=\"M3 5.1 4.4 6.5 7.1 3.7\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><\u002Fpath><\u002Fsvg><\u002Fsc-if>\n          <span class=\"label-system\" style=\"font-size:0.62rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ b.label }}<\u002Fspan>\n          <span class=\"label-system\" style=\"font-size:0.62rem;color:{{ b.color }}\">{{ b.value }}<\u002Fspan>\n        <\u002Fspan>\n      <\u002Fsc-for>\n      <span style=\"flex:1\"><\u002Fspan>\n      <span style=\"display:inline-flex;gap:6px;align-items:baseline;flex-shrink:0\">\n        <span class=\"label-system\" style=\"font-size:0.62rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">Last run<\u002Fspan>\n        <span class=\"label-system\" style=\"font-size:0.62rem;color:rgb(var(--text) / 0.85)\">{{ lastRun }}<\u002Fspan>\n      <\u002Fspan>\n    <\u002Fdiv>\n    <\u002Fsc-if>\n    <sc-if value=\"{{ hbTicker }}\" hint-placeholder-val=\"{{ false }}\">\n    <div style=\"display:flex;align-items:center;height:30px;border-bottom:1px solid var(--border);background:rgb(var(--basalt-deep));flex-shrink:0;overflow:hidden;white-space:nowrap\">\n      <span style=\"display:inline-flex;align-items:center;gap:7px;flex-shrink:0;height:100%;padding:0 12px 0 14px;border-right:1px solid var(--border);background:rgb(var(--basalt-deep));position:relative;z-index:1\">\n        <span style=\"display:inline-flex\"><x-import component-from-global-scope=\"AaaSDesignSystem_2216a8.StatusDot\" variant=\"dual\" size=\"{{ n8 }}\" hint-size=\"8px,8px\"><\u002Fx-import><\u002Fspan>\n        <span class=\"label-system\" style=\"font-size:0.62rem;letter-spacing:0.14em;color:rgb(var(--lead))\">LIVE<\u002Fspan>\n      <\u002Fspan>\n      <div style=\"flex:1;min-width:0;overflow:hidden;-webkit-mask-image:linear-gradient(90deg,transparent 0,#000 16px,#000 calc(100% - 24px),transparent 100%);mask-image:linear-gradient(90deg,transparent 0,#000 16px,#000 calc(100% - 24px),transparent 100%)\">\n        <div data-ticker=\"\" style=\"display:flex;width:max-content;animation:aaasTicker 32s linear infinite\">\n          <sc-for list=\"{{ tickerBeats }}\" as=\"b\" hint-placeholder-count=\"12\">\n            <span style=\"display:inline-flex;gap:6px;align-items:baseline;flex-shrink:0;padding-right:28px\"><sc-if value=\"{{ b.isNeeds }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0;align-self:center\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><path d=\"M5 0.6 9.4 5 5 9.4 0.6 5z\" fill=\"rgb(var(--status-needs, 244 63 108))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ b.isStuck }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0;align-self:center\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><path d=\"M5 0.8 9.5 9H0.5z\" fill=\"rgb(var(--status-stuck, 248 217 116))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ b.isDone }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0;align-self:center\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"4.3\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\"><\u002Fcircle><path d=\"M3 5.1 4.4 6.5 7.1 3.7\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><\u002Fpath><\u002Fsvg><\u002Fsc-if>\n              <span class=\"label-system\" style=\"font-size:0.62rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ b.label }}<\u002Fspan>\n              <span class=\"label-system\" style=\"font-size:0.62rem;color:{{ b.color }}\">{{ b.value }}<\u002Fspan>\n            <\u002Fspan>\n          <\u002Fsc-for>\n        <\u002Fdiv>\n      <\u002Fdiv>\n    <\u002Fdiv>\n    <\u002Fsc-if>\n\n    <!-- TopBar -->\n    <div style=\"display:flex;align-items:center;gap:10px;height:60px;padding:0 12px;border-bottom:1px solid var(--border);background:rgb(var(--basalt-deep) / 0.7);backdrop-filter:blur(12px);flex-shrink:0;position:relative;z-index:20\">\n      <div style=\"display:flex;align-items:center;gap:10px;flex-shrink:0\">\n        <button onClick=\"{{ toggleMega }}\" aria-label=\"Domains and streams\" title=\"Domains and streams\" style=\"width:38px;height:38px;min-width:{{ touchMin }};min-height:{{ touchMin }};border-radius:9px;border:1px solid var(--border);background:transparent;color:rgb(var(--text));display:inline-flex;align-items:center;justify-content:center;cursor:pointer\" style-hover=\"border-color:rgb(var(--lead) / 0.4);color:rgb(var(--lead))\">\n          <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"4\" y1=\"7\" x2=\"20\" y2=\"7\"><\u002Fline><line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\"><\u002Fline><line x1=\"4\" y1=\"17\" x2=\"20\" y2=\"17\"><\u002Fline><\u002Fsvg>\n        <\u002Fbutton>\n        <!-- Brand lockup · the design system's own logo file, then the domain written out.\n             tenants/aaas/brand/AaaS_Logo_{White,Black}.png — copied from the bound design system, whose\n             rule is explicit: the hex-orb lockup is the only branded icon, never recoloured or distorted.\n             So it ships as the asset, not as a trace. The PNG is the full lockup (glyph + AaaS + trailing\n             dot); the box clips the printed dot off the right edge and the live pulsing dot takes its\n             place at the same size and baseline, giving the blink without touching the artwork.\n             The mark and its dot are never gated — only the domain segment collapses on a narrow bar. -->\n        <button onClick=\"{{ cycleBrand }}\" aria-label=\"{{ brandLabelFull }}\" title=\"{{ brandCycleTitle }}\" style=\"display:flex;align-items:flex-end;gap:9px;flex-shrink:0;padding:0;margin:0;border:none;background:transparent;color:inherit;font:inherit;cursor:pointer\">\n          <sc-if value=\"{{ logoIsAaas }}\" hint-placeholder-val=\"{{ true }}\">\n          <span style=\"display:flex;align-items:flex-end;gap:4px;flex-shrink:0\">\n            <span style=\"display:block;width:83px;height:22px;overflow:hidden;flex-shrink:0\">\n              <sc-if value=\"{{ themeIsDark }}\" hint-placeholder-val=\"{{ true }}\">\n                <img src=\"c91aeb0c-ec31-422b-81f6-2fbaf87f0116\" alt=\"\" style=\"display:block;max-width:none;flex-shrink:0;width:88px;height:22px\"/>\n              <\u002Fsc-if>\n              <sc-if value=\"{{ themeIsLight }}\" hint-placeholder-val=\"{{ false }}\">\n                <img src=\"5b691cdd-31a1-4dfd-93fb-878bf0580420\" alt=\"\" style=\"display:block;max-width:none;flex-shrink:0;width:88px;height:22px\"/>\n              <\u002Fsc-if>\n            <\u002Fspan>\n            <span aria-hidden=\"true\" style=\"width:7px;height:7px;border-radius:50%;margin-bottom:1px;background:rgb(var(--circuit-glow));animation:aaasLogoPulse 3s steps(1,end) infinite;flex-shrink:0\"><\u002Fspan>\n          <\u002Fspan>\n          <\u002Fsc-if>\n          <sc-if value=\"{{ logoIsImg }}\" hint-placeholder-val=\"{{ false }}\">\n            <span style=\"display:inline-flex;flex-shrink:0\">{{ logoImgEl }}<\u002Fspan>\n          <\u002Fsc-if>\n          <sc-if value=\"{{ logoIsText }}\" hint-placeholder-val=\"{{ false }}\">\n            <span style=\"font-family:var(--font-inter);font-weight:900;font-size:1.2rem;line-height:22px;letter-spacing:-0.04em;color:rgb(var(--text));white-space:nowrap\">{{ logoText }}<\u002Fspan>\n          <\u002Fsc-if>\n          <sc-if value=\"{{ barFull }}\" hint-placeholder-val=\"{{ true }}\">\n          <span style=\"font-weight:700;letter-spacing:0.07em;font-size:0.82rem;line-height:1.35;text-transform:uppercase;white-space:nowrap;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ brandWordmark }}<\u002Fspan>\n          <\u002Fsc-if>\n        <\u002Fbutton>\n      <\u002Fdiv>\n\n      <button onClick=\"{{ openPalette }}\" aria-label=\"Search\" title=\"Search\" style=\"flex:{{ searchFlex }};min-width:{{ searchMin }};max-width:640px;display:flex;align-items:center;gap:9px;height:40px;min-height:{{ touchMin }};padding:0 13px;border-radius:var(--radius-btn);border:1px solid var(--border);background:rgb(var(--basalt-surface) / 0.5);color:var(--app-ink-soft, rgb(var(--text) / 0.7));cursor:pointer;font-family:var(--font-inter);font-size:0.9rem\" style-hover=\"border-color:rgb(var(--lead) / 0.4)\">\n        <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"flex-shrink:0\"><circle cx=\"11\" cy=\"11\" r=\"7\"><\u002Fcircle><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"><\u002Fline><\u002Fsvg>\n        <sc-if value=\"{{ barFull }}\" hint-placeholder-val=\"{{ true }}\">\n        <span style=\"overflow:hidden;text-overflow:ellipsis;white-space:nowrap\">Search<\u002Fspan>\n        <span style=\"margin-left:auto;font-family:var(--font-jetbrains);font-size:0.7rem;padding:2px 7px;border-radius:6px;border:1px solid var(--border);color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">⌘K<\u002Fspan>\n        <\u002Fsc-if>\n      <\u002Fbutton>\n\n      <sc-if value=\"{{ isWide }}\" hint-placeholder-val=\"{{ false }}\">\n        <div style=\"display:flex;align-items:center;gap:10px;flex-shrink:1;min-width:0;margin-left:auto\">\n          <!-- MVP · customer-facing canvas toggle (restored 2026-09-24, D164): the new user's first contact — delivery, context, onboarding. -->\n          <!-- 2026-10-02: the MVP/Client switch is retired — the Delivery role view is the first-contact canvas. -->\n          <sc-if value=\"{{ showOps }}\" hint-placeholder-val=\"{{ true }}\">\n          <div style=\"position:relative;display:inline-flex\">\n            <button onClick=\"{{ toggleRoleMenu }}\" aria-expanded=\"{{ roleMenuOpen }}\" title=\"Switch role\" style=\"display:inline-flex;align-items:center;gap:8px;height:34px;padding:0 12px;border-radius:9999px;border:1px solid rgb(var(--lead) / 0.35);background:rgb(var(--lead) / 0.08);color:rgb(var(--lead));cursor:pointer\">\n              <span class=\"label-nav\" style=\"font-size:0.64rem;white-space:nowrap\">{{ roleLabel }}<\u002Fspan>\n              <span aria-hidden=\"true\" style=\"display:inline-flex;transform:{{ roleCaret }};transition:transform .2s var(--liquid-ease)\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"><\u002Fpolyline><\u002Fsvg><\u002Fspan>\n            <\u002Fbutton>\n            <sc-if value=\"{{ roleMenuOpen }}\" hint-placeholder-val=\"{{ false }}\">\n              <div style=\"position:absolute;top:40px;left:0;z-index:70;min-width:184px;padding:6px;display:flex;flex-direction:column;gap:2px;border:1px solid var(--border);border-radius:var(--radius);background:rgb(var(--basalt-bright) / 0.95);backdrop-filter:blur(18px);box-shadow:var(--shadow-menu)\">\n                <span class=\"label-overline\" style=\"color:var(--app-ink-soft, rgb(var(--text) / 0.7));padding:6px 10px 4px\">Founder<\u002Fspan>\n                <button data-no-sports=\"1\" onClick=\"{{ toggleFounder }}\" role=\"switch\" aria-checked=\"{{ founderOn }}\" style=\"display:flex;align-items:center;gap:10px;width:100%;min-height:44px;padding:6px 10px;border:none;border-radius:8px;cursor:pointer;text-align:left;background:{{ founderBg }};color:rgb(var(--text))\" style-hover=\"background:rgb(var(--text) / 0.05)\">\n                  <span style=\"flex:1;display:flex;flex-direction:column;gap:2px\"><span style=\"font-family:var(--font-inter);font-size:0.86rem\">Sports founder<\u002Fspan><span class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ founderMeta }}<\u002Fspan><\u002Fspan>\n                  <span style=\"position:relative;display:inline-block;width:30px;height:16px;flex-shrink:0;border-radius:9999px;background:rgb(var(--basalt-deep));border:1px solid {{ founderTrackBorder }}\"><span style=\"position:absolute;top:2px;left:{{ founderKnobLeft }};width:10px;height:10px;border-radius:50%;background:{{ founderKnob }};transition:left .3s var(--liquid-ease)\"><\u002Fspan><\u002Fspan>\n                <\u002Fbutton>\n                <div style=\"height:1px;background:var(--border);margin:4px 6px\"><\u002Fdiv>\n                <span class=\"label-overline\" style=\"color:var(--app-ink-soft, rgb(var(--text) / 0.7));padding:6px 10px 4px\">Role view<\u002Fspan>\n                <sc-for list=\"{{ roles }}\" as=\"r2\" hint-placeholder-count=\"6\">\n                  <button onClick=\"{{ r2.onClick }}\" style=\"display:flex;align-items:center;gap:9px;width:100%;height:34px;padding:0 10px;border:none;border-radius:8px;cursor:pointer;text-align:left;font-family:var(--font-inter);font-size:0.86rem;background:{{ r2.bg }};color:{{ r2.color }}\" style-hover=\"background:rgb(var(--text) / 0.05)\">\n                    <span style=\"flex:1\">{{ r2.label }}<\u002Fspan>\n                    <sc-if value=\"{{ r2.active }}\" hint-placeholder-val=\"{{ false }}\">\n                      <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"5 12 10 17 20 6\"><\u002Fpolyline><\u002Fsvg>\n                    <\u002Fsc-if>\n                  <\u002Fbutton>\n                <\u002Fsc-for>\n              <\u002Fdiv>\n            <\u002Fsc-if>\n          <\u002Fdiv>\n          <\u002Fsc-if>\n          <button onClick=\"{{ toggleTheme }}\" aria-label=\"Toggle theme\" title=\"Toggle theme\" style=\"width:34px;height:34px;flex-shrink:0;border-radius:50%;border:1px solid rgb(var(--lead) / 0.4);background:transparent;color:rgb(var(--lead));display:inline-flex;align-items:center;justify-content:center;cursor:pointer\">{{ themeIcon }}<\u002Fbutton>\n          <div style=\"position:relative;display:inline-flex\">\n            <button onClick=\"{{ toggleHelpMenu }}\" aria-expanded=\"{{ helpMenuOpen }}\" aria-label=\"Help and walkthroughs\" title=\"Help &amp; walkthroughs\" style=\"width:34px;height:34px;flex-shrink:0;border-radius:50%;border:1px solid var(--border);background:transparent;color:rgb(var(--text));display:inline-flex;align-items:center;justify-content:center;cursor:pointer\" style-hover=\"border-color:rgb(var(--lead) / 0.4);color:rgb(var(--lead))\">{{ helpIconEl }}<\u002Fbutton>\n            <sc-if value=\"{{ helpMenuOpen }}\" hint-placeholder-val=\"{{ false }}\">\n              <div style=\"position:absolute;top:40px;right:0;z-index:70;min-width:290px;padding:6px;display:flex;flex-direction:column;gap:2px;border:1px solid var(--border);border-radius:var(--radius);background:rgb(var(--basalt-surface) / 0.96);backdrop-filter:blur(18px);box-shadow:0 18px 50px rgb(0 0 0 / 0.5)\">\n                <span class=\"label-overline\" style=\"color:var(--app-ink-soft, rgb(var(--text) / 0.7));padding:8px 10px 6px\">Help &amp; walkthroughs<\u002Fspan>\n                <button onClick=\"{{ helpStepPlanToggle }}\" style=\"display:flex;align-items:center;gap:10px;width:100%;padding:9px 10px;border:none;background:transparent;border-radius:8px;cursor:pointer;color:rgb(var(--text));font-size:0.82rem;text-align:left\" style-hover=\"background:rgb(var(--lead) / 0.08)\">\n                  <span style=\"width:7px;height:7px;border-radius:50%;flex-shrink:0;background:{{ helpStepPlanDot }}\"><\u002Fspan>\n                  <span style=\"flex:1\">{{ helpStepPlanLabel }}<\u002Fspan>\n                  <span class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ helpStepPlanMeta }}<\u002Fspan>\n                <\u002Fbutton>\n                <button onClick=\"{{ helpOpenOnboarding }}\" style=\"display:flex;align-items:center;gap:10px;width:100%;padding:9px 10px;border:none;background:transparent;border-radius:8px;cursor:pointer;color:rgb(var(--text));font-size:0.82rem;text-align:left\" style-hover=\"background:rgb(var(--lead) / 0.08)\">\n                  <span style=\"width:7px;height:7px;border-radius:50%;flex-shrink:0;background:var(--app-accent)\"><\u002Fspan>\n                  <span style=\"flex:1\">Onboarding · 7 steps<\u002Fspan>\n                  <span class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ helpOnbMeta }}<\u002Fspan>\n                <\u002Fbutton>\n                <button onClick=\"{{ toggleMvp }}\" style=\"display:flex;align-items:center;gap:10px;width:100%;padding:9px 10px;border:none;background:transparent;border-radius:8px;cursor:pointer;color:rgb(var(--text));font-size:0.82rem;text-align:left\" style-hover=\"background:rgb(var(--lead) / 0.08)\">\n                  <span style=\"width:7px;height:7px;border-radius:50%;flex-shrink:0;background:{{ firstContactDot }}\"><\u002Fspan>\n                  <span style=\"flex:1\">{{ firstContactLabel }}<\u002Fspan>\n                  <span class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">New user · offer link<\u002Fspan>\n                <\u002Fbutton>\n                <span style=\"height:1px;margin:4px 6px;background:var(--border)\"><\u002Fspan>\n                <span class=\"label-overline\" style=\"color:var(--app-ink-soft, rgb(var(--text) / 0.7));padding:6px 10px 4px\">Walkthroughs<\u002Fspan>\n                <sc-for list=\"{{ helpTracks }}\" as=\"ht\" hint-placeholder-count=\"8\">\n                  <button onClick=\"{{ ht.onClick }}\" style=\"display:flex;align-items:center;gap:10px;width:100%;padding:7px 10px;border:none;background:transparent;border-radius:8px;cursor:pointer;color:rgb(var(--text));font-size:0.8rem;text-align:left\" style-hover=\"background:rgb(var(--lead) / 0.08)\">\n                    <span style=\"width:6px;height:6px;border-radius:50%;flex-shrink:0;background:{{ ht.dot }}\"><\u002Fspan>\n                    <span style=\"flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis\">{{ ht.label }}<\u002Fspan>\n                    <span class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ ht.len }}<\u002Fspan>\n                  <\u002Fbutton>\n                <\u002Fsc-for>\n                <span style=\"height:1px;margin:4px 6px;background:var(--border)\"><\u002Fspan>\n                <!-- F-7a (D27) · the desktop reach for the same one switch (the Settings sheet carries it\n                     on mobile). Nothing else in the product exposes the four frontend states. -->\n                <button onClick=\"{{ toggleDevTools }}\" role=\"switch\" aria-checked=\"{{ devTools }}\" style=\"display:flex;align-items:center;gap:10px;width:100%;padding:9px 10px;border:none;background:transparent;border-radius:8px;cursor:pointer;color:rgb(var(--text));font-size:0.82rem;text-align:left\" style-hover=\"background:rgb(var(--lead) / 0.08)\">\n                  <span style=\"width:7px;height:7px;border-radius:50%;flex-shrink:0;background:{{ devToolsColor }}\"><\u002Fspan>\n                  <span style=\"flex:1\">Dev tools · force pane state<\u002Fspan>\n                  <span class=\"mono\" style=\"font-size:0.6rem;color:{{ devToolsColor }}\">{{ devToolsLabel }}<\u002Fspan>\n                <\u002Fbutton>\n              <\u002Fdiv>\n            <\u002Fsc-if>\n          <\u002Fdiv>\n          <button aria-label=\"Notifications\" title=\"Notifications\" style=\"position:relative;width:34px;height:34px;flex-shrink:0;border-radius:50%;border:1px solid var(--border);background:transparent;color:rgb(var(--text));display:inline-flex;align-items:center;justify-content:center;cursor:pointer\" style-hover=\"border-color:rgb(var(--lead) / 0.4);color:rgb(var(--lead))\">\n            <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"17\" height=\"17\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 9a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6z\"><\u002Fpath><path d=\"M10 20a2 2 0 0 0 4 0\"><\u002Fpath><\u002Fsvg>\n            <span style=\"position:absolute;top:6px;right:7px;width:7px;height:7px;border-radius:50%;background:rgb(var(--accent-red));box-shadow:0 0 8px rgb(var(--accent-red))\"><\u002Fspan>\n          <\u002Fbutton>\n          <button title=\"Sign in\" style=\"height:34px;padding:0 16px;border-radius:9999px;border:1px solid rgb(var(--lead) / 0.5);background:transparent;color:rgb(var(--lead));font-family:var(--font-jetbrains);font-size:0.7rem;letter-spacing:0.05em;cursor:pointer\" style-hover=\"background:rgb(var(--lead) / 0.08)\">LOGIN<\u002Fbutton>\n        <\u002Fdiv>\n      <\u002Fsc-if>\n      <sc-if value=\"{{ isMobile }}\" hint-placeholder-val=\"{{ true }}\">\n      <div style=\"display:flex;align-items:center;gap:8px;flex-shrink:0;margin-left:auto\">\n        <sc-if value=\"{{ isOps }}\" hint-placeholder-val=\"{{ true }}\">\n          <x-import component-from-global-scope=\"AaaSDesignSystem_2216a8.Badge\" variant=\"outline\" hint-size=\"auto,22px\">{{ roleLabel }}<\u002Fx-import>\n        <\u002Fsc-if>\n        <button onClick=\"{{ toggleSettings }}\" aria-label=\"Settings\" title=\"Settings\" style=\"position:relative;width:38px;height:38px;min-width:{{ touchMin }};min-height:{{ touchMin }};border-radius:50%;border:1px solid var(--border);background:transparent;color:rgb(var(--text));display:inline-flex;align-items:center;justify-content:center;cursor:pointer\" style-hover=\"border-color:rgb(var(--lead) / 0.4);color:rgb(var(--lead))\">\n          <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"><\u002Fcircle><path d=\"M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.9-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.9.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.9 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.9l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.9.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.9-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.9V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z\"><\u002Fpath><\u002Fsvg>\n          <span style=\"position:absolute;top:6px;right:7px;width:7px;height:7px;border-radius:50%;background:rgb(var(--accent-red));box-shadow:0 0 8px rgb(var(--accent-red))\"><\u002Fspan>\n        <\u002Fbutton>\n      <\u002Fdiv>\n      <\u002Fsc-if>\n\n      <!-- Settings sheet — everything the desktop top bar shows inline -->\n      <sc-if value=\"{{ settingsOpen }}\" hint-placeholder-val=\"{{ false }}\">\n        <div onClick=\"{{ closeSettings }}\" style=\"position:fixed;inset:0;z-index:58;background:rgb(0 0 0 / 0.72)\"><\u002Fdiv>\n        <div onClick=\"{{ stop }}\" style=\"position:absolute;top:54px;right:10px;left:10px;z-index:60;max-width:340px;margin-left:auto;padding:8px;display:flex;flex-direction:column;gap:2px;border:1px solid var(--border);border-radius:var(--radius);background:rgb(var(--basalt-bright));box-shadow:var(--shadow-menu)\">\n          <sc-if value=\"{{ showOps }}\" hint-placeholder-val=\"{{ true }}\">\n          <div style=\"padding:6px 12px 10px\">\n            <span class=\"label-overline\" style=\"color:var(--app-ink-soft, rgb(var(--text) / 0.7));display:block;margin-bottom:8px\">Founder<\u002Fspan>\n            <button data-no-sports=\"1\" onClick=\"{{ toggleFounder }}\" role=\"switch\" aria-checked=\"{{ founderOn }}\" style=\"display:flex;align-items:center;gap:10px;width:100%;min-height:44px;padding:6px 0;margin-bottom:10px;border:none;border-radius:8px;cursor:pointer;text-align:left;background:{{ founderBg }};color:rgb(var(--text))\" style-hover=\"background:rgb(var(--text) / 0.05)\">\n                  <span style=\"flex:1;display:flex;flex-direction:column;gap:2px\"><span style=\"font-family:var(--font-inter);font-size:0.86rem\">Sports founder<\u002Fspan><span class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ founderMeta }}<\u002Fspan><\u002Fspan>\n                  <span style=\"position:relative;display:inline-block;width:30px;height:16px;flex-shrink:0;border-radius:9999px;background:rgb(var(--basalt-deep));border:1px solid {{ founderTrackBorder }}\"><span style=\"position:absolute;top:2px;left:{{ founderKnobLeft }};width:10px;height:10px;border-radius:50%;background:{{ founderKnob }};transition:left .3s var(--liquid-ease)\"><\u002Fspan><\u002Fspan>\n                <\u002Fbutton>\n            <span class=\"label-overline\" style=\"color:var(--app-ink-soft, rgb(var(--text) / 0.7));display:block;margin-bottom:8px\">Role view<\u002Fspan>\n            <div style=\"display:flex;flex-wrap:wrap;gap:6px\">\n              <sc-for list=\"{{ roles }}\" as=\"r3\" hint-placeholder-count=\"6\">\n                <button onClick=\"{{ r3.onClick }}\" class=\"label-nav\" style=\"height:30px;min-height:44px;padding:0 11px;border-radius:9999px;border:1px solid var(--border);cursor:pointer;font-size:0.62rem;background:{{ r3.bg }};color:{{ r3.color }}\">{{ r3.label }}<\u002Fbutton>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fdiv>\n          <div style=\"height:1px;background:var(--border);margin:4px 0\"><\u002Fdiv>\n          <button onClick=\"{{ openRosterFromSettings }}\" style=\"display:flex;align-items:center;gap:11px;width:100%;min-height:44px;padding:11px 12px;border:none;background:transparent;border-radius:9px;cursor:pointer;text-align:left;font-family:var(--font-inter);font-size:0.92rem;color:rgb(var(--text))\" style-hover=\"background:rgb(var(--text) / 0.05)\">\n            <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"5\" y=\"8\" width=\"14\" height=\"10\" rx=\"2\"><\u002Frect><line x1=\"12\" y1=\"4\" x2=\"12\" y2=\"8\"><\u002Fline><circle cx=\"9.5\" cy=\"13\" r=\"1.2\" fill=\"currentColor\"><\u002Fcircle><circle cx=\"14.5\" cy=\"13\" r=\"1.2\" fill=\"currentColor\"><\u002Fcircle><\u002Fsvg>\n            <span style=\"flex:1\">Agent roster<\u002Fspan>\n            <span class=\"label-system\" style=\"font-size:0.62rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ rosterCount }}<\u002Fspan>\n          <\u002Fbutton>\n          <button onClick=\"{{ openRosterFromSettings }}\" style=\"display:flex;align-items:center;gap:11px;width:100%;min-height:44px;padding:11px 12px;border:none;background:transparent;border-radius:9px;cursor:pointer;text-align:left;font-family:var(--font-inter);font-size:0.92rem;color:rgb(var(--text))\" style-hover=\"background:rgb(var(--text) / 0.05)\">\n            <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"5 12 10 17 20 6\"><\u002Fpolyline><\u002Fsvg>\n            <span style=\"flex:1\">Background tasks<\u002Fspan>\n            <span class=\"label-system\" style=\"font-size:0.62rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ taskCount }}<\u002Fspan>\n          <\u002Fbutton>\n          <button onClick=\"{{ openRosterFromSettings }}\" style=\"display:flex;align-items:center;gap:11px;width:100%;min-height:44px;padding:11px 12px;border:none;background:transparent;border-radius:9px;cursor:pointer;text-align:left;font-family:var(--font-inter);font-size:0.92rem;color:rgb(var(--text))\" style-hover=\"background:rgb(var(--text) / 0.05)\">\n            <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 3 2.5 19.5h19z\"><\u002Fpath><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"14\"><\u002Fline><\u002Fsvg>\n            <span style=\"flex:1\">Human blockers<\u002Fspan>\n            <span class=\"label-system\" style=\"font-size:0.62rem;min-width:17px;padding:1px 6px;border-radius:9999px;background:rgb(var(--accent-red));color:rgb(var(--basalt-deep, 14 14 16));text-align:center\">{{ blockerCount }}<\u002Fspan>\n          <\u002Fbutton>\n          <div style=\"height:1px;background:var(--border);margin:4px 0\"><\u002Fdiv>\n          <button onClick=\"{{ openMegaFromSettings }}\" style=\"display:flex;align-items:center;gap:11px;width:100%;min-height:44px;padding:11px 12px;border:none;background:transparent;border-radius:9px;cursor:pointer;text-align:left;font-family:var(--font-inter);font-size:0.92rem;color:rgb(var(--text))\" style-hover=\"background:rgb(var(--text) / 0.05)\">\n            <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"><\u002Frect><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"><\u002Frect><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"><\u002Frect><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"><\u002Frect><\u002Fsvg>\n            <span style=\"flex:1\">App switcher<\u002Fspan>\n          <\u002Fbutton>\n          <\u002Fsc-if>\n          <button onClick=\"{{ toggleTheme }}\" style=\"display:flex;align-items:center;gap:11px;width:100%;min-height:44px;padding:11px 12px;border:none;background:transparent;border-radius:9px;cursor:pointer;text-align:left;font-family:var(--font-inter);font-size:0.92rem;color:rgb(var(--text))\" style-hover=\"background:rgb(var(--text) / 0.05)\">\n            <span style=\"display:inline-flex;color:rgb(var(--lead))\">{{ themeIcon }}<\u002Fspan>\n            <span style=\"flex:1\">Theme<\u002Fspan>\n            <span class=\"label-system\" style=\"font-size:0.62rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ themeLabel }}<\u002Fspan>\n          <\u002Fbutton>\n          <button onClick=\"{{ openHelpFromSettings }}\" style=\"display:flex;align-items:center;gap:11px;width:100%;min-height:44px;padding:11px 12px;border:none;background:transparent;border-radius:9px;cursor:pointer;text-align:left;font-family:var(--font-inter);font-size:0.92rem;color:rgb(var(--text))\" style-hover=\"background:rgb(var(--text) / 0.05)\">\n            <span style=\"display:inline-flex\">{{ helpIconEl }}<\u002Fspan>\n            <span style=\"flex:1\">Help &amp; walkthrough<\u002Fspan>\n          <\u002Fbutton>\n          <!-- F-7a (D27) · the ONE dev-tools switch. Turning it on is what reveals the frontend-state\n               control in the pane chrome; the four states are never product UI. -->\n          <button onClick=\"{{ toggleDevTools }}\" role=\"switch\" aria-checked=\"{{ devTools }}\" style=\"display:flex;align-items:center;gap:11px;width:100%;min-height:44px;padding:11px 12px;border:none;background:transparent;border-radius:9px;cursor:pointer;text-align:left;font-family:var(--font-inter);font-size:0.92rem;color:rgb(var(--text))\" style-hover=\"background:rgb(var(--text) / 0.05)\">\n            <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"9 8 5 12 9 16\"><\u002Fpolyline><polyline points=\"15 8 19 12 15 16\"><\u002Fpolyline><\u002Fsvg>\n            <span style=\"flex:1\">Dev tools<\u002Fspan>\n            <span class=\"label-system\" style=\"font-size:0.62rem;color:{{ devToolsColor }}\">{{ devToolsLabel }}<\u002Fspan>\n          <\u002Fbutton>\n          <button style=\"display:flex;align-items:center;gap:11px;width:100%;min-height:44px;padding:11px 12px;border:none;background:transparent;border-radius:9px;cursor:pointer;text-align:left;font-family:var(--font-inter);font-size:0.92rem;color:rgb(var(--text))\" style-hover=\"background:rgb(var(--text) / 0.05)\">\n            <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 9a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6z\"><\u002Fpath><path d=\"M10 20a2 2 0 0 0 4 0\"><\u002Fpath><\u002Fsvg>\n            <span style=\"flex:1\">Notifications<\u002Fspan>\n            <span class=\"label-system\" style=\"font-size:0.62rem;min-width:17px;padding:1px 6px;border-radius:9999px;background:rgb(var(--accent-red));color:rgb(var(--basalt-deep, 14 14 16));text-align:center\">1<\u002Fspan>\n          <\u002Fbutton>\n          <div style=\"height:1px;background:var(--border);margin:4px 0\"><\u002Fdiv>\n          <button style=\"height:44px;margin:4px;border-radius:9999px;border:1px solid rgb(var(--lead) / 0.5);background:transparent;color:rgb(var(--lead));font-family:var(--font-jetbrains);font-size:0.72rem;letter-spacing:0.05em;cursor:pointer\" style-hover=\"background:rgb(var(--lead) / 0.08)\">LOGIN<\u002Fbutton>\n        <\u002Fdiv>\n      <\u002Fsc-if>\n    <\u002Fdiv>\n\n    <!-- Body -->\n    <div style=\"position:relative;flex:1;display:flex;overflow:hidden\">\n\n      <!-- Tier 1 · Stream rail -->\n      <sc-if value=\"{{ railScrim }}\" hint-placeholder-val=\"{{ false }}\">\n        <div onClick=\"{{ closeRail }}\" style=\"position:fixed;inset:0;z-index:58;background:rgb(0 0 0 / 0.55)\"><\u002Fdiv>\n      <\u002Fsc-if>\n      <sc-if value=\"{{ railEdge }}\" hint-placeholder-val=\"{{ false }}\">\n        <div aria-hidden=\"true\" data-testid=\"rail-edge\" onMouseEnter=\"{{ railPeekOn }}\" style=\"position:absolute;top:0;left:0;bottom:0;width:2cm;z-index:45\"><\u002Fdiv>\n      <\u002Fsc-if>\n      <sc-if value=\"{{ railShow }}\" hint-placeholder-val=\"{{ true }}\">\n      <aside style=\"{{ railStyle }}\" onMouseEnter=\"{{ railPeekOn }}\" onMouseLeave=\"{{ railPeekOff }}\">\n        <div style=\"padding:16px 12px 8px;display:flex;align-items:center;gap:8px\">\n          <sc-if value=\"{{ showLabels }}\" hint-placeholder-val=\"{{ true }}\">\n            <span class=\"label-system\" style=\"font-size:0.6rem;letter-spacing:0.12em;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ railLabel }}<\u002Fspan>\n          <\u002Fsc-if>\n        <\u002Fdiv>\n        <nav data-testid=\"stream-rail\" style=\"flex:1;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:stable;padding:4px 6px 4px 8px;display:flex;flex-direction:column;gap:16px\">\n          <sc-for list=\"{{ railGroups }}\" as=\"g\" hint-placeholder-count=\"4\">\n            <div style=\"display:flex;flex-direction:column;gap:2px\">\n              <sc-if value=\"{{ g.headed }}\" hint-placeholder-val=\"{{ true }}\">\n                <button onClick=\"{{ g.onToggle }}\" aria-expanded=\"{{ g.open }}\" style=\"display:flex;align-items:center;gap:6px;width:100%;min-height:{{ touchMin }};padding:5px 10px 7px;border:none;background:transparent;cursor:pointer;text-align:left\" style-hover=\"opacity:0.75\">\n                  <span aria-hidden=\"true\" style=\"display:inline-flex;color:var(--app-ink-faint, rgb(var(--text) / 0.62));transform:{{ g.caret }};transition:transform .2s var(--liquid-ease)\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"11\" height=\"11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"9 6 15 12 9 18\"><\u002Fpolyline><\u002Fsvg><\u002Fspan>\n                  <span class=\"label-system\" style=\"flex:1;white-space:nowrap;font-size:0.56rem;letter-spacing:0.1em;color:var(--app-ink-faint, rgb(var(--text) / 0.62))\">{{ g.label }}<\u002Fspan>\n                  <span class=\"label-system\" style=\"font-size:0.56rem;color:var(--app-ink-faint, rgb(var(--text) / 0.62))\">{{ g.count }}<\u002Fspan>\n                <\u002Fbutton>\n              <\u002Fsc-if>\n              <sc-if value=\"{{ g.open }}\" hint-placeholder-val=\"{{ true }}\">\n              <sc-for list=\"{{ g.items }}\" as=\"it\" hint-placeholder-count=\"4\">\n                <button onClick=\"{{ it.onClick }}\" title=\"{{ it.label }}\" aria-current=\"{{ it.active }}\" style=\"position:relative;display:flex;align-items:center;gap:9px;width:100%;min-height:{{ touchMin }};padding:{{ it.pad }};border:none;background:transparent;border-radius:9px;cursor:pointer;text-align:left;font-family:var(--font-inter);font-size:0.8rem;color:{{ it.color }}\" style-hover=\"background:rgb(var(--text) / 0.05)\">\n                  <sc-if value=\"{{ it.active }}\" hint-placeholder-val=\"{{ false }}\">\n                    <span aria-hidden=\"true\" style=\"position:absolute;inset:0;border-radius:9px;background:rgb(var(--lead) / 0.1);border:1px solid rgb(var(--lead) / 0.25);box-shadow:inset 2px 0 0 rgb(var(--lead))\"><\u002Fspan>\n                  <\u002Fsc-if>\n                  <span style=\"position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:{{ it.iconBox }};height:{{ it.iconBox }};border-radius:50%;border:{{ it.iconRing }}\">{{ it.iconEl }}<\u002Fspan>\n                  <sc-if value=\"{{ showLabels }}\" hint-placeholder-val=\"{{ true }}\">\n                    <span style=\"position:relative;z-index:1;flex:1;white-space:nowrap\">{{ it.label }}<\u002Fspan>\n                  <\u002Fsc-if>\n                  <sc-if value=\"{{ it.dot }}\" hint-placeholder-val=\"{{ false }}\">\n                    <span aria-hidden=\"true\" title=\"has updates\" style=\"position:relative;z-index:1;width:7px;height:7px;border-radius:1px;background:rgb(var(--pastel-gold));box-shadow:0 0 8px rgb(var(--pastel-gold))\"><\u002Fspan>\n                  <\u002Fsc-if>\n                  <sc-if value=\"{{ it.badgeMini }}\" hint-placeholder-val=\"{{ false }}\">\n                    <span style=\"position:absolute;top:3px;right:2px;z-index:2;min-width:15px;height:15px;padding:0 4px;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;border-radius:9999px;background:rgb(var(--accent-red));color:rgb(var(--basalt-deep, 14 14 16));font-family:var(--font-jetbrains);font-size:0.54rem;line-height:1\">{{ it.badgeMini }}<\u002Fspan>\n                  <\u002Fsc-if>\n                  <sc-if value=\"{{ it.badgePill }}\" hint-placeholder-val=\"{{ false }}\">\n                    <span style=\"position:relative;z-index:1;min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;border-radius:9px;background:rgb(var(--accent-red));color:rgb(var(--basalt-deep, 14 14 16));font-family:var(--font-jetbrains);font-size:0.62rem\">{{ it.badgePill }}<\u002Fspan>\n                  <\u002Fsc-if>\n                <\u002Fbutton>\n              <\u002Fsc-for>\n              <\u002Fsc-if>\n            <\u002Fdiv>\n          <\u002Fsc-for>\n        <\u002Fnav>\n        <div style=\"border-top:1px solid var(--border);padding:8px 10px;display:flex;flex-direction:column;gap:2px\">\n          <sc-if value=\"{{ showOps }}\" hint-placeholder-val=\"{{ true }}\">\n          <button style=\"display:flex;align-items:center;gap:11px;width:100%;min-height:{{ touchMin }};padding:9px 11px;border:none;background:transparent;border-radius:9px;cursor:pointer;color:var(--app-ink-faint, rgb(var(--text) / 0.62));font-family:var(--font-inter);font-size:0.88rem\" style-hover=\"background:rgb(var(--text) / 0.05);color:rgb(var(--text))\">\n            <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 4a2 2 0 0 1 2-2h12v18H7a2 2 0 0 0-2 2z\"><\u002Fpath><line x1=\"9\" y1=\"6.5\" x2=\"15\" y2=\"6.5\"><\u002Fline><\u002Fsvg>\n            <sc-if value=\"{{ showLabels }}\" hint-placeholder-val=\"{{ true }}\"><span style=\"flex:1;text-align:left\">Runbook<\u002Fspan><\u002Fsc-if>\n          <\u002Fbutton>\n          <\u002Fsc-if>\n          <button onClick=\"{{ collapseAction }}\" style=\"display:flex;align-items:center;gap:11px;width:100%;min-height:{{ touchMin }};padding:9px 11px;border:none;background:transparent;border-radius:9px;cursor:pointer;color:var(--app-ink-faint, rgb(var(--text) / 0.62));font-family:var(--font-inter);font-size:0.88rem\" style-hover=\"background:rgb(var(--text) / 0.05);color:rgb(var(--text))\">\n            <span style=\"display:inline-flex\">{{ collapseIcon }}<\u002Fspan>\n            <sc-if value=\"{{ showLabels }}\" hint-placeholder-val=\"{{ true }}\"><span style=\"flex:1;text-align:left\">{{ collapseLabel }}<\u002Fspan><\u002Fsc-if>\n          <\u002Fbutton>\n        <\u002Fdiv>\n      <\u002Faside>\n      <\u002Fsc-if>\n\n      <!-- Main -->\n      <main style=\"flex:1;min-width:0;display:flex;flex-direction:column;overflow:hidden\">\n        <!-- Tier 2 · Subnav -->\n        <div style=\"display:flex;align-items:stretch;height:48px;border-bottom:1px solid var(--border);flex-shrink:0;background:rgb(var(--basalt-deep) / 0.55)\">\n          <!-- D198d · rail pin — mirror of the roster pin at the strip's right end. on = rail docked; off = rail hidden, it peeks from a 2cm left-edge zone. -->\n          <sc-if value=\"{{ isWide }}\" hint-placeholder-val=\"{{ true }}\">\n            <button onClick=\"{{ toggleRailPin }}\" role=\"switch\" aria-checked=\"{{ railPinned }}\" aria-label=\"Pin stream rail\" title=\"Pin stream rail · off = it appears when you hover the left edge\" style=\"min-height:{{ touchMin }};display:inline-flex;align-items:center;gap:9px;align-self:stretch;height:100%;padding:0 12px 0 14px;flex-shrink:0;border:none;border-right:1px solid var(--border);background:transparent;cursor:pointer;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">\n              <span style=\"width:34px;height:20px;border-radius:9999px;border:1px solid {{ railTrackBorder }};background:{{ railTrack }};position:relative;flex-shrink:0\"><span style=\"position:absolute;top:1px;left:{{ railKnobLeft }};width:16px;height:16px;border-radius:50%;background:rgb(var(--basalt-deep));transition:left .3s var(--liquid-ease)\"><\u002Fspan><\u002Fspan>\n              <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\"><\u002Frect><line x1=\"9\" y1=\"4\" x2=\"9\" y2=\"20\"><\u002Fline><\u002Fsvg>\n            <\u002Fbutton>\n          <\u002Fsc-if>\n        <div data-testid=\"subnav\" style=\"flex:1;min-width:0;display:flex;align-items:stretch;padding:0 var(--main-pad);overflow-x:auto;scrollbar-width:none;-webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(90deg,#000 calc(100% - 28px),transparent 100%)\">\n          <button onClick=\"{{ toggleRail }}\" aria-expanded=\"{{ railOpenState }}\" title=\"Switch stream\" aria-label=\"Switch stream\" style=\"display:flex;align-items:center;gap:7px;min-height:{{ touchMin }};padding:0 14px 0 0;margin-right:10px;border:none;border-right:1px solid var(--border);background:transparent;color:{{ streamChipColor }};flex-shrink:0;cursor:pointer;font-family:var(--font-jetbrains)\" style-hover=\"color:rgb(var(--lead))\">\n            <span style=\"display:inline-flex\">{{ streamIcon }}<\u002Fspan>\n            <span class=\"label-nav\" style=\"font-size:0.66rem;white-space:nowrap\">{{ activeLabel }}<\u002Fspan>\n            <span aria-hidden=\"true\" style=\"display:inline-flex;transform:{{ streamCaret }};transition:transform .2s var(--liquid-ease)\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"9 6 15 12 9 18\"><\u002Fpolyline><\u002Fsvg><\u002Fspan>\n          <\u002Fbutton>\n          <sc-if value=\"{{ showDealChip }}\" hint-placeholder-val=\"{{ false }}\">\n            <span style=\"display:inline-flex;align-items:center;gap:8px;align-self:center;margin-right:14px;padding:5px 12px;border-radius:9999px;border:1px solid rgb(var(--lead) / 0.35);background:rgb(var(--lead) / 0.1);color:rgb(var(--lead));font-family:var(--font-jetbrains);font-size:0.64rem;white-space:nowrap;flex-shrink:0\">\n              <svg viewBox=\"0 0 24 24\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"flex-shrink:0\"><path d=\"M20 7H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V8a1 1 0 0 0-1-1z\"><\u002Fpath><path d=\"M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2\"><\u002Fpath><\u002Fsvg>\n              {{ dealChip }}\n            <\u002Fspan>\n          <\u002Fsc-if>\n          <sc-if value=\"{{ isFlow }}\" hint-placeholder-val=\"{{ false }}\">\n            <div role=\"tablist\" aria-label=\"{{ flowLabel }} steps\" style=\"display:flex;align-items:center;gap:2px;flex-shrink:0;padding:0 10px;border-right:1px solid var(--border);margin-right:6px\">\n              <sc-for list=\"{{ flowSteps }}\" as=\"fs\" hint-placeholder-count=\"7\">\n                <button onClick=\"{{ fs.onClick }}\" title=\"{{ fs.label }}\" aria-current=\"{{ fs.active }}\" style=\"display:inline-flex;align-items:center;gap:6px;min-height:{{ touchMin }};padding:0 8px;border:none;border-radius:8px;background:{{ fs.bg }};cursor:pointer;color:{{ fs.color }};font-family:var(--font-jetbrains);font-size:0.6rem;letter-spacing:0.06em;white-space:nowrap\" style-hover=\"background:rgb(var(--text) / 0.05)\">\n                  <span>{{ fs.n }}<\u002Fspan>\n                  <sc-if value=\"{{ fs.active }}\" hint-placeholder-val=\"{{ false }}\"><span style=\"font-family:var(--font-inter);font-size:0.78rem;letter-spacing:0\">{{ fs.label }}<\u002Fspan><\u002Fsc-if>\n                <\u002Fbutton>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n          <sc-for list=\"{{ subItems }}\" as=\"s\" hint-placeholder-count=\"5\">\n            <sc-if value=\"{{ s.bucket }}\" hint-placeholder-val=\"{{ false }}\">\n              <span class=\"label-overline\" style=\"display:inline-flex;align-items:center;font-size:0.58rem;color:var(--app-ink-faint, rgb(var(--text) / 0.62));padding:0 14px;flex-shrink:0\">{{ s.label }}<\u002Fspan>\n            <\u002Fsc-if>\n            <sc-if value=\"{{ s.isTab }}\" hint-placeholder-val=\"{{ true }}\">\n              <button onClick=\"{{ s.onClick }}\" data-testid=\"subnav-tab\" data-screen-label=\"{{ s.label }}\" data-active=\"{{ s.active }}\" style=\"position:relative;display:inline-flex;align-items:center;gap:7px;min-height:{{ touchMin }};padding:0 13px;border:none;background:transparent;cursor:pointer;font-family:var(--font-inter);font-size:0.86rem;white-space:nowrap;flex-shrink:0;color:{{ s.color }}\" style-hover=\"color:rgb(var(--text))\">\n                <span>{{ s.label }}<\u002Fspan>\n                <sc-if value=\"{{ s.scaffold }}\" hint-placeholder-val=\"{{ false }}\">\n                  <span class=\"label-system\" style=\"font-size:0.5rem;padding:1px 5px;border-radius:5px;border:1px solid var(--border);color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">scaffold<\u002Fspan>\n                <\u002Fsc-if>\n                <sc-if value=\"{{ s.active }}\" hint-placeholder-val=\"{{ false }}\">\n                  <span aria-hidden=\"true\" style=\"position:absolute;left:8px;right:8px;bottom:0;height:2px;border-radius:2px;background:rgb(var(--lead));box-shadow:0 0 10px rgb(var(--lead))\"><\u002Fspan>\n                <\u002Fsc-if>\n              <\u002Fbutton>\n            <\u002Fsc-if>\n          <\u002Fsc-for>\n        <\u002Fdiv>\n          <sc-if value=\"{{ rosterFab }}\" hint-placeholder-val=\"{{ false }}\">\n            <button onClick=\"{{ toggleRoster }}\" role=\"switch\" aria-checked=\"{{ rosterPinned }}\" aria-label=\"Pin agent roster\" title=\"Pin agent roster · off = it appears when you hover the right edge\" style=\"min-height:{{ touchMin }};display:inline-flex;align-items:center;gap:9px;align-self:stretch;height:100%;padding:0 14px 0 12px;flex-shrink:0;border:none;border-left:1px solid var(--border);background:transparent;cursor:pointer;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">\n              <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"5\" y=\"8\" width=\"14\" height=\"10\" rx=\"2\"><\u002Frect><line x1=\"12\" y1=\"4\" x2=\"12\" y2=\"8\"><\u002Fline><circle cx=\"9.5\" cy=\"13\" r=\"1.2\" fill=\"currentColor\"><\u002Fcircle><circle cx=\"14.5\" cy=\"13\" r=\"1.2\" fill=\"currentColor\"><\u002Fcircle><\u002Fsvg>\n              <span style=\"width:34px;height:20px;border-radius:9999px;border:1px solid {{ rosterTrackBorder }};background:{{ rosterTrack }};position:relative;flex-shrink:0\"><span style=\"position:absolute;top:1px;left:{{ rosterKnobLeft }};width:16px;height:16px;border-radius:50%;background:{{ rosterKnob }};transition:left .3s var(--liquid-ease)\"><\u002Fspan><\u002Fspan>\n            <\u002Fbutton>\n          <\u002Fsc-if>\n        <\u002Fdiv>\n\n        <!-- Scroll region -->\n        <sc-if value=\"{{ paneBooting }}\" hint-placeholder-val=\"{{ false }}\">\n          <div role=\"status\" aria-live=\"polite\" style=\"display:flex;align-items:center;gap:8px;height:26px;padding:0 var(--main-pad);border-bottom:1px solid var(--border);background:rgb(var(--basalt-deep) / 0.4);flex-shrink:0\">\n            <span style=\"width:7px;height:7px;border-radius:50%;border:1.5px solid rgb(var(--lead));animation:inb-pulse 1.2s ease-in-out infinite\"><\u002Fspan>\n            <span class=\"mono\" style=\"font-size:0.58rem;letter-spacing:0.1em;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">LOADING {{ paneBootingLabel }} · first open<\u002Fspan>\n          <\u002Fdiv>\n        <\u002Fsc-if>\n        <div data-testid=\"pane-scroll\" style=\"flex:1;overflow-y:auto;padding:var(--main-pad)\">\n\n          <!-- Breadcrumb (canvas-native panes) -->\n          <sc-if value=\"{{ crumb }}\" hint-placeholder-val=\"{{ false }}\">\n            <div class=\"mono\" style=\"font-size:0.72rem;color:var(--app-ink-faint);margin-bottom:14px\">{{ crumb }}<\u002Fdiv>\n          <\u002Fsc-if>\n\n          <!-- W-0 · data-contract honesty marker (GTM-6): always visible when a surface is not live -->\n          <sc-if value=\"{{ contractBadge }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"display:flex;align-items:center;gap:9px;flex-wrap:wrap;margin-bottom:14px\">\n              <x-import component-from-global-scope=\"AaaSDesignSystem_2216a8.Badge\" variant=\"{{ contractBadge.variant }}\" hint-size=\"auto,22px\">{{ contractBadge.label }}<\u002Fx-import>\n              <span class=\"mono\" style=\"font-size:0.68rem;color:var(--app-ink-faint)\">{{ contractBadge.note }}<\u002Fspan>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n\n          <!-- Archive banner (D4, docs/DECISIONS.md — archived streams stay complete, never deleted) -->\n          <sc-if value=\"{{ archivedBanner }}\" hint-placeholder-val=\"{{ false }}\">\n            <div data-testid=\"archived-banner\" style=\"display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:10px 14px;margin-bottom:14px;border:1px dashed var(--app-pending, rgb(var(--pastel-lavender, 147 154 255)));border-radius:var(--radius);background:rgb(var(--pastel-lavender, 147 154 255) / 0.06)\">\n              <span class=\"label-system\" style=\"font-size:0.6rem;letter-spacing:0.12em;color:rgb(var(--pastel-lavender, 147 154 255))\">ARCHIVED<\u002Fspan>\n              <span style=\"font-size:0.78rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ archivedReasonText }}<\u002Fspan>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n\n          <!-- View strip (#1) — one icon per archetype the active tab declares in tabViews -->\n          <sc-if value=\"{{ hasViewStrip }}\" hint-placeholder-val=\"{{ false }}\">\n            <div data-testid=\"view-strip\" style=\"display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:var(--section-gap,22px)\">\n              <span class=\"label-overline\" style=\"color:var(--app-ink-faint);font-size:0.56rem;letter-spacing:0.14em\">{{ viewStripLabel }}<\u002Fspan>\n              <div style=\"display:flex;align-items:center;gap:4px;flex-wrap:wrap;padding:3px;border:1px solid var(--app-rule);border-radius:10px;background:rgb(var(--basalt-deep) / 0.45)\">\n                <sc-for list=\"{{ viewStrip }}\" as=\"v\" hint-placeholder-count=\"3\">\n                  <button onClick=\"{{ v.onClick }}\" title=\"{{ v.title }}\" aria-label=\"{{ v.title }}\" aria-pressed=\"{{ v.ariaPressed }}\" style=\"{{ v.btnStyle }}\">{{ v.iconEl }}<\u002Fbutton>\n                <\u002Fsc-for>\n              <\u002Fdiv>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n\n          <!-- Hero -->\n          <sc-if value=\"{{ showHero }}\" hint-placeholder-val=\"{{ true }}\">\n          <div data-testid=\"hero\" style=\"margin-bottom:var(--section-gap)\">\n            <div class=\"label-overline\" style=\"color:rgb(var(--lead));margin-bottom:14px\">{{ hero.overline }}<\u002Fdiv>\n            <div style=\"display:flex;justify-content:space-between;align-items:flex-start;gap:32px;flex-wrap:wrap\">\n              <div style=\"max-width:620px\">\n                <h1 style=\"font-size:clamp(2.3rem,4.4vw,3.5rem);margin:0 0 16px;color:rgb(var(--text));overflow-wrap:anywhere\">{{ hero.title }}<span style=\"color:rgb(var(--accent-red))\">.<\u002Fspan><\u002Fh1>\n                <p style=\"margin:0;color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-size:0.98rem;line-height:1.6;max-width:560px\">{{ hero.desc }}<\u002Fp>\n              <\u002Fdiv>\n              <div data-testid=\"hero-cta\" style=\"display:flex;gap:10px;flex-wrap:wrap;align-items:center\">\n                <sc-if value=\"{{ showOps }}\" hint-placeholder-val=\"{{ true }}\">\n                <button onClick=\"{{ openPalette }}\" style=\"display:inline-flex;align-items:center;gap:9px;padding:0 18px;height:44px;border-radius:var(--radius-btn);border:none;background:rgb(var(--lead));color:rgb(var(--basalt-deep));font-family:var(--font-inter);font-weight:700;font-size:0.82rem;letter-spacing:0.02em;cursor:pointer;box-shadow:0 0 22px rgb(var(--lead) / 0.28);transition:all .3s var(--liquid-ease)\" style-hover=\"transform:translate(2px,-2px);box-shadow:-4px 4px 0 rgb(var(--accent-red))\">\n                  <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"17\" height=\"17\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"><\u002Fcircle><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"><\u002Fline><\u002Fsvg>\n                  <span>Open command palette<\u002Fspan>\n                <\u002Fbutton>\n                <\u002Fsc-if>\n                <sc-for list=\"{{ hero.actions2 }}\" as=\"a\" hint-placeholder-count=\"2\">\n                  <button onClick=\"{{ a.onClick }}\" style=\"display:inline-flex;align-items:center;gap:8px;padding:0 15px;height:44px;border-radius:var(--radius-btn);border:1px solid var(--border);background:rgb(var(--basalt-surface) / 0.55);color:rgb(var(--text));font-family:var(--font-inter);font-size:0.82rem;font-weight:500;cursor:pointer;white-space:nowrap;transition:all .3s var(--liquid-ease)\" style-hover=\"border-color:rgb(var(--lead) / 0.4);color:rgb(var(--lead))\">\n                    <span style=\"display:inline-flex\">{{ a.iconEl }}<\u002Fspan>\n                    <span>{{ a.label }}<\u002Fspan>\n                  <\u002Fbutton>\n                <\u002Fsc-for>\n              <\u002Fdiv>\n            <\u002Fdiv>\n          <\u002Fdiv>\n\n          <!-- KPI strip -->\n          <div data-testid=\"kpi-strip\" style=\"display:grid;grid-template-columns:repeat(auto-fit,minmax(min(140px,100%),1fr));border-top:1px solid var(--border);border-bottom:1px solid var(--border);margin-bottom:var(--section-gap)\">\n            <sc-for list=\"{{ hero.kpis }}\" as=\"k\" hint-placeholder-count=\"4\">\n              <button onClick=\"{{ k.onClick }}\" style=\"{{ k.cellStyle }}\">\n                <div class=\"label-overline\" style=\"font-size:0.56rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7));margin-bottom:10px;display:flex;align-items:center;gap:6px\">{{ k.label }}<sc-if value=\"{{ k.isLink }}\" hint-placeholder-val=\"{{ false }}\"><svg viewBox=\"0 0 24 24\" width=\"11\" height=\"11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"flex-shrink:0;opacity:0.7\"><polyline points=\"9 6 15 12 9 18\"><\u002Fpolyline><\u002Fsvg><\u002Fsc-if><\u002Fdiv>\n                <div style=\"display:flex;align-items:center;gap:8px;flex-wrap:wrap\">\n                  <span class=\"kpi-v\" style=\"font-family:var(--font-jetbrains);font-size:1.65rem;font-weight:500;color:rgb(var(--text));line-height:1\">{{ k.value }}<\u002Fspan>\n                  <sc-if value=\"{{ k.delta }}\" hint-placeholder-val=\"{{ false }}\">\n                    <span style=\"font-family:var(--font-jetbrains);font-size:0.6rem;padding:2px 6px;border-radius:6px;color:{{ k.chipColor }};background:{{ k.chipBg }}\">{{ k.delta }}<\u002Fspan>\n                  <\u002Fsc-if>\n                <\u002Fdiv>\n              <\u002Fbutton>\n            <\u002Fsc-for>\n          <\u002Fdiv>\n          <\u002Fsc-if>\n\n\n        <!-- ══ F-7a (D27) · frontend states, hoisted OUT of the `kind` branch ══\n             Verifier finding (4.17a): participation is computed at STREAM level (`dataContract`) but\n             this block used to live inside `<sc-if value=\"{{ kind }}\">`, and `kind` is null for every\n             spec-backed (lens/`devPane`) tab — including the Overview tab those streams land on. So a\n             plane declared the contract and rendered nothing. Hoisted here, above BOTH the lens path\n             and the `kind` path, which is the honest reading of \"one control in the pane chrome that\n             every plane honours\". The lens and slot paths are suppressed when a state is forced\n             (`devPane`/`isSlots` are null unless `pane === 'default'`), so a forced state REPLACES\n             the plane's content on every path rather than sitting above it. ══ -->\n          <!-- F-7a (D27) · frontend-state control. DEV-ONLY: `stateCtl` is `hasStates && devTools`,\n               so this never renders as product UI. The four states themselves are a code contract —\n               the loading/empty/error/modeled blocks below are the ONE shared implementation every\n               participating plane renders, and no plane hand-rolls its own empty state. -->\n          <sc-if value=\"{{ stateCtl }}\" hint-placeholder-val=\"{{ false }}\">\n          <div data-testid=\"state-control\" style=\"display:flex;align-items:center;gap:10px;margin-bottom:18px;padding:6px 10px;border:1px dashed rgb(var(--lead) / 0.35);border-radius:9px;background:rgb(var(--lead) / 0.04)\">\n            <span class=\"label-overline\" style=\"font-size:0.56rem;color:rgb(var(--lead))\">Dev · state<\u002Fspan>\n            <div style=\"display:inline-flex;gap:2px;padding:3px;border-radius:9px;border:1px solid var(--app-rule);background:var(--app-surface)\">\n              <sc-for list=\"{{ paneStates }}\" as=\"ps\" hint-placeholder-count=\"4\">\n                <button onClick=\"{{ ps.onClick }}\" aria-pressed=\"{{ ps.active }}\" style=\"min-height:{{ touchMin }};padding:5px 12px;border:none;border-radius:6px;cursor:pointer;font-family:var(--font-jetbrains);font-size:0.68rem;letter-spacing:0.04em;text-transform:uppercase;background:{{ ps.activeBg }};color:{{ ps.activeColor }}\">{{ ps.label }}<\u002Fbutton>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fdiv>\n          <\u002Fsc-if>\n\n          <sc-if value=\"{{ hasStates }}\" hint-placeholder-val=\"{{ false }}\">\n\n          <!-- Loading -->\n          <sc-if value=\"{{ isLoading }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:var(--grid-gap)\">\n              <sc-for list=\"{{ skeletons }}\" as=\"sk\" hint-placeholder-count=\"8\">\n                <div style=\"height:120px;border-radius:var(--radius);border:1px solid var(--app-rule);background:var(--app-surface);animation:aaasSkeleton 1.4s ease-in-out infinite\"><\u002Fdiv>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n\n          <!-- Empty -->\n          <sc-if value=\"{{ isEmpty }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:80px 20px;border:1px dashed var(--app-rule);border-radius:var(--radius);background:var(--app-surface)\">\n              <span style=\"color:var(--app-ink-faint);margin-bottom:16px\">{{ stateBox.iconEl }}<\u002Fspan>\n              <div style=\"font-size:1.15rem;font-weight:700;color:var(--app-ink);margin-bottom:8px\">{{ stateBox.emptyTitle }}<\u002Fdiv>\n              <div style=\"color:var(--app-ink-soft);font-size:0.9rem;max-width:380px;margin-bottom:20px\">{{ stateBox.emptyBody }}<\u002Fdiv>\n              <button onClick=\"{{ emptyCtaClick }}\" style=\"height:40px;padding:0 18px;border-radius:var(--radius-btn);border:none;background:var(--app-accent);color:rgb(var(--basalt-deep));font-family:var(--font-inter);font-weight:700;font-size:0.82rem;cursor:pointer\">{{ stateBox.emptyCta }}<\u002Fbutton>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n\n          <!-- Error -->\n          <sc-if value=\"{{ isError }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:80px 20px;border:1px solid rgb(var(--accent-red) / 0.3);border-radius:var(--radius);background:rgb(var(--accent-red) / 0.05)\">\n              <span style=\"color:var(--app-fail);margin-bottom:16px\">{{ errIconEl }}<\u002Fspan>\n              <div style=\"font-size:1.05rem;font-weight:700;color:var(--app-ink);margin-bottom:10px\">Something went wrong<\u002Fdiv>\n              <div class=\"mono\" style=\"color:var(--app-fail);font-size:0.82rem;margin-bottom:20px\">{{ stateBox.errCode }}<\u002Fdiv>\n              <button onClick=\"{{ retry }}\" style=\"height:40px;padding:0 18px;border-radius:var(--radius-btn);border:1px solid var(--app-rule);background:transparent;color:var(--app-ink);font-family:var(--font-inter);font-weight:600;font-size:0.82rem;cursor:pointer\">Retry<\u002Fbutton>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n\n          <!-- MODELED state (WP-03) — dashed border + --app-pending. Binds automatically when a\n               pane's dataContract.status isn't 'live' (state-layer.js: resolve()), so a stub/planned\n               feed can never render a confident number by omission. Never colour-only: the dashed\n               border IS the shape signal, per the DS's status-by-shape rule. -->\n          <sc-if value=\"{{ isModeled }}\" hint-placeholder-val=\"{{ false }}\">\n            <div data-testid=\"state-modeled\" style=\"display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:80px 20px;border:1.5px dashed var(--app-pending);border-radius:var(--radius);background:rgb(176 107 255 / 0.06)\">\n              <span style=\"color:var(--app-pending);margin-bottom:16px\">{{ modeledBox.iconEl }}<\u002Fspan>\n              <div style=\"font-size:1.05rem;font-weight:700;color:var(--app-ink);margin-bottom:10px\">{{ modeledBox.title }}<\u002Fdiv>\n              <div style=\"color:var(--app-ink-soft);font-size:0.9rem;max-width:420px;margin-bottom:12px\">{{ modeledBox.body }}<\u002Fdiv>\n              <sc-if value=\"{{ modeledBox.source }}\" hint-placeholder-val=\"{{ false }}\">\n                <div class=\"mono\" style=\"color:var(--app-pending);font-size:0.72rem\">source · {{ modeledBox.source }}<\u002Fdiv>\n              <\u002Fsc-if>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n\n          <\u002Fsc-if>\n          <!-- /hasStates -->\n\n          <!-- D195 · tabs that moved to a promoted stream: one link chip each, on Overview only -->\n          <sc-if value=\"{{ hasMoved }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:var(--section-gap)\">\n              <span class=\"mono\" style=\"font-size:0.58rem;letter-spacing:0.12em;color:var(--app-ink-faint, rgb(var(--text) / 0.62));margin-right:4px\">MOVED<\u002Fspan>\n              <sc-for list=\"{{ movedCards }}\" as=\"mv\" hint-placeholder-count=\"3\">\n                <button onClick=\"{{ mv.onOpen }}\" title=\"{{ mv.meta }}\" style=\"display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 10px;border-radius:9999px;border:1px solid var(--border);background:transparent;color:rgb(var(--text));font-family:var(--font-inter);font-size:0.74rem;cursor:pointer\" style-hover=\"border-color:rgb(var(--lead) / 0.45);background:rgb(var(--lead) / 0.06)\"><span>{{ mv.title }}<\u002Fspan><span class=\"mono\" style=\"font-size:0.56rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ mv.meta }} →<\u002Fspan><\u002Fbutton>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n          <!-- D192 · v2 node → ArchetypeHost. Any v2 tab that declares an archetype renders through the engine's\n               own host (gate · diff · ledger · brief · process), not through the generic lens. -->\n          <sc-if value=\"{{ v2Arch }}\" hint-placeholder-val=\"{{ false }}\">\n            <div data-testid=\"v2-arch\" style=\"display:flex;flex-direction:column;gap:12px\">\n              <div class=\"mono\" style=\"font-size:0.6rem;letter-spacing:0.12em;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ v2ArchLabel }}<\u002Fdiv>\n              <sc-if value=\"{{ v2ArchNote }}\" hint-placeholder-val=\"{{ false }}\">\n                <div data-testid=\"v2-arch-note\" class=\"mono\" style=\"font-size:0.66rem;line-height:1.5;color:{{ v2ArchNoteColor }};text-wrap:pretty\">{{ v2ArchNote }}<\u002Fdiv>\n              <\u002Fsc-if>\n              <dc-import name=\"ArchetypeHost\" archetype=\"{{ v2Arch.type }}\" proposal=\"{{ v2Arch.proposal }}\" gates=\"{{ v2Arch.gates }}\" on-approve=\"{{ v2Arch.onApprove }}\" on-reject=\"{{ v2Arch.onReject }}\" blocked-reason=\"{{ v2Arch.blockedReason }}\" diff=\"{{ v2Arch.diff }}\" ledger=\"{{ v2Arch.ledger }}\" brief=\"{{ v2Arch.brief }}\" items=\"{{ v2Arch.items }}\" groups=\"{{ v2Arch.groups }}\" on-select=\"{{ v2Arch.onSelect }}\" template=\"{{ v2Arch.template }}\" instance=\"{{ v2Arch.instance }}\" note=\"{{ v2Arch.note }}\" note-tone=\"{{ v2Arch.noteTone }}\" hint-size=\"100%,520px\"><\u002Fdc-import>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n          <!-- Developer panes · one lens per subnav tab -->\n          <sc-if value=\"{{ devPane }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"margin-bottom:var(--section-gap)\">\n              <div style=\"display:flex;align-items:baseline;gap:12px;margin-bottom:14px;flex-wrap:wrap\">\n                <span class=\"label-overline\" style=\"color:rgb(var(--lead));font-size:0.62rem\">{{ devPane.title }}<\u002Fspan>\n                <span style=\"color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-size:0.85rem\">{{ devPane.note }}<\u002Fspan>\n                <sc-if value=\"{{ devPane.showViewSwitcher }}\" hint-placeholder-val=\"{{ false }}\">\n                  <div style=\"display:inline-flex;background:rgb(var(--basalt-deep) / 0.5);border:1px solid var(--app-rule);border-radius:9999px;padding:3px;margin-left:auto\">\n                    <button onClick=\"{{ devPane.setViewTable }}\" style=\"border:0;background:{{ devPane.tableBtnBg }};color:{{ devPane.tableBtnColor }};font-size:0.68rem;font-weight:600;padding:5px 12px;border-radius:9999px;cursor:pointer\">Table<\u002Fbutton>\n                    <button onClick=\"{{ devPane.setViewBoard }}\" style=\"border:0;background:{{ devPane.boardBtnBg }};color:{{ devPane.boardBtnColor }};font-size:0.68rem;font-weight:600;padding:5px 12px;border-radius:9999px;cursor:pointer\">Board<\u002Fbutton>\n                  <\u002Fdiv>\n                <\u002Fsc-if>\n              <\u002Fdiv>\n\n              <!-- table lens -->\n              <sc-if value=\"{{ devPane.isTable }}\" hint-placeholder-val=\"{{ false }}\">\n                <div style=\"border:1px solid var(--app-rule);border-radius:var(--radius);overflow:hidden;background:var(--app-surface)\">\n                  <div style=\"display:flex;align-items:center;gap:14px;padding:11px 16px;border-bottom:1px solid var(--app-rule);background:rgb(var(--basalt-deep) / 0.45)\">\n                    <sc-for list=\"{{ devPane.cols }}\" as=\"c2\" hint-placeholder-count=\"5\">\n                      <span class=\"label-system\" style=\"font-size:0.56rem;letter-spacing:0.12em;color:var(--app-ink-faint);flex:{{ c2.flex }};text-align:{{ c2.align }}\">{{ c2.label }}<\u002Fspan>\n                    <\u002Fsc-for>\n                  <\u002Fdiv>\n                  <sc-for list=\"{{ devPane.rows }}\" as=\"r4\" hint-placeholder-count=\"5\">\n                    <div onClick=\"{{ r4.onClick }}\" data-testid=\"lens-row\" style=\"display:flex;align-items:center;gap:14px;padding:13px 16px;border-bottom:1px solid var(--app-rule-soft);cursor:pointer\" style-hover=\"background:rgb(var(--text) / 0.03)\">\n                      <sc-for list=\"{{ r4.cells }}\" as=\"c3\" hint-placeholder-count=\"5\">\n                        <span style=\"flex:{{ c3.flex }};min-width:0;text-align:{{ c3.align }};font-family:{{ c3.font }};font-size:0.82rem;color:{{ c3.color }};white-space:nowrap;overflow:hidden;text-overflow:ellipsis\">{{ c3.v }}<\u002Fspan>\n                      <\u002Fsc-for>\n                    <\u002Fdiv>\n                  <\u002Fsc-for>\n                <\u002Fdiv>\n              <\u002Fsc-if>\n\n              <!-- board lens -->\n              <sc-if value=\"{{ devPane.isBoard }}\" hint-placeholder-val=\"{{ false }}\">\n                <div style=\"display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:var(--grid-gap);align-items:start\">\n                  <sc-for list=\"{{ devPane.columns }}\" as=\"col\" hint-placeholder-count=\"5\">\n                    <div style=\"border:1px solid var(--app-rule);border-radius:var(--radius);background:var(--app-surface);overflow:hidden\">\n                      <div style=\"display:flex;align-items:center;gap:8px;padding:10px 13px;border-bottom:1px solid var(--app-rule)\">\n                        <span style=\"width:7px;height:7px;border-radius:50%;background:{{ col.color }};box-shadow:0 0 8px {{ col.color }}\"><\u002Fspan>\n                        <span class=\"label-system\" style=\"font-size:0.58rem;letter-spacing:0.1em;color:var(--app-ink-soft);flex:1\">{{ col.label }}<\u002Fspan>\n                        <span class=\"mono\" style=\"font-size:0.62rem;color:var(--app-ink-faint)\">{{ col.count }}<\u002Fspan>\n                      <\u002Fdiv>\n                      <div style=\"padding:10px;display:flex;flex-direction:column;gap:8px;min-height:78px\">\n                        <sc-for list=\"{{ col.items }}\" as=\"ci\" hint-placeholder-count=\"2\">\n                          <div onClick=\"{{ ci.onClick }}\" data-testid=\"lens-card\" style=\"padding:11px 12px;border:1px solid var(--app-rule-soft);border-radius:9px;background:rgb(var(--basalt-deep) / 0.4);cursor:pointer\" style-hover=\"border-color:rgb(var(--lead) / 0.4)\">\n                            <div style=\"font-size:0.8rem;color:var(--app-ink);line-height:1.35\">{{ ci.title }}<\u002Fdiv>\n                            <div class=\"mono\" style=\"font-size:0.65rem;color:var(--app-ink-faint);margin-top:6px\">{{ ci.meta }}<\u002Fdiv>\n                          <\u002Fdiv>\n                        <\u002Fsc-for>\n                      <\u002Fdiv>\n                    <\u002Fdiv>\n                  <\u002Fsc-for>\n                <\u002Fdiv>\n              <\u002Fsc-if>\n\n              <!-- catalog / panel lens -->\n              <sc-if value=\"{{ devPane.isCards }}\" hint-placeholder-val=\"{{ false }}\">\n                <div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:var(--grid-gap)\">\n                  <sc-for list=\"{{ devPane.cards }}\" as=\"dc\" hint-placeholder-count=\"6\">\n                    <div onClick=\"{{ dc.onOpen }}\" role=\"{{ dc.role }}\" style=\"padding:var(--card-pad);border:1px solid var(--app-rule);border-radius:var(--radius);background:var(--app-surface);cursor:{{ dc.cursor }}\" style-hover=\"border-color:rgb(var(--lead) / 0.4)\">\n                      <div style=\"display:flex;align-items:flex-start;justify-content:space-between;gap:10px\">\n                        <span style=\"font-family:var(--font-jetbrains);font-size:0.9rem;color:var(--app-accent)\">{{ dc.title }}<\u002Fspan>\n                        <span class=\"mono\" style=\"flex-shrink:0;font-size:0.58rem;padding:2px 8px;border-radius:9999px;border:1px solid {{ dc.tagColor }};color:{{ dc.tagColor }}\">{{ dc.tag }}<\u002Fspan>\n                      <\u002Fdiv>\n                      <div style=\"margin-top:12px;color:var(--app-ink-soft);font-size:0.82rem;line-height:1.5\">{{ dc.meta }}<\u002Fdiv>\n                      <sc-if value=\"{{ dc.linked }}\" hint-placeholder-val=\"{{ false }}\">\n                        <div class=\"mono\" style=\"margin-top:10px;font-size:0.58rem;letter-spacing:0.08em;color:rgb(var(--lead))\">{{ dc.hint }}<\u002Fdiv>\n                      <\u002Fsc-if>\n                    <\u002Fdiv>\n                  <\u002Fsc-for>\n                <\u002Fdiv>\n              <\u002Fsc-if>\n\n              <!-- event-tail lens -->\n              <sc-if value=\"{{ devPane.isTail }}\" hint-placeholder-val=\"{{ false }}\">\n                <div style=\"border:1px solid var(--app-rule);border-radius:var(--radius);overflow:hidden;background:rgb(var(--basalt-deep) / 0.6)\">\n                  <div style=\"display:flex;align-items:center;gap:9px;padding:10px 15px;border-bottom:1px solid var(--app-rule)\">\n                    <span style=\"width:7px;height:7px;border-radius:50%;background:var(--app-accent);box-shadow:0 0 8px var(--app-accent)\"><\u002Fspan>\n                    <span class=\"label-system\" style=\"font-size:0.56rem;letter-spacing:0.12em;color:var(--app-ink-faint)\">{{ devPane.tailTitle }}<\u002Fspan>\n                  <\u002Fdiv>\n                  <div style=\"padding:14px 15px;display:flex;flex-direction:column;gap:6px\">\n                    <sc-for list=\"{{ devPane.lines }}\" as=\"ln\" hint-placeholder-count=\"8\">\n                      <div style=\"display:flex;gap:12px;font-family:var(--font-jetbrains);font-size:0.72rem\">\n                        <span style=\"color:var(--app-ink-faint);width:52px;flex-shrink:0\">{{ ln.time }}<\u002Fspan>\n                        <span style=\"width:40px;flex-shrink:0;color:{{ ln.color }}\">{{ ln.prefix }}<\u002Fspan>\n                        <span style=\"color:var(--app-ink-soft);min-width:0\">{{ ln.msg }}<\u002Fspan>\n                      <\u002Fdiv>\n                    <\u002Fsc-for>\n                  <\u002Fdiv>\n                <\u002Fdiv>\n              <\u002Fsc-if>\n\n\n\n              <!-- frames lens · real design files rendered in place -->\n              <sc-if value=\"{{ devPane.isFrames }}\" hint-placeholder-val=\"{{ false }}\">\n                <sc-if value=\"{{ devPane.hasSegs }}\" hint-placeholder-val=\"{{ false }}\">\n                  <div style=\"display:flex;flex-wrap:wrap;gap:7px;margin-bottom:16px\">\n                    <sc-for list=\"{{ devPane.segs }}\" as=\"sg\" hint-placeholder-count=\"4\">\n                      <button onClick=\"{{ sg.onClick }}\" class=\"label-nav\" style=\"height:30px;padding:0 13px;border-radius:9999px;border:1px solid {{ sg.border }};background:{{ sg.bg }};color:{{ sg.color }};cursor:pointer;font-size:0.62rem\">{{ sg.label }} · {{ sg.count }}<\u002Fbutton>\n                    <\u002Fsc-for>\n                  <\u002Fdiv>\n                <\u002Fsc-if>\n                <div style=\"display:grid;grid-template-columns:{{ devPane.grid }};gap:var(--grid-gap)\">\n                  <sc-for list=\"{{ devPane.frames }}\" as=\"fr\" hint-placeholder-count=\"6\">\n                    <div key=\"{{ fr.key }}\" style=\"border:1px solid var(--app-rule);border-radius:var(--radius);overflow:hidden;background:var(--app-surface)\" style-hover=\"border-color:rgb(var(--lead) / 0.4)\">\n                      <div style=\"position:relative;height:{{ fr.tileH }};overflow:hidden;background:rgb(var(--basalt-deep))\">\n                        <iframe key=\"{{ fr.key }}\" data-fit-w=\"{{ fr.w }}\" data-fit-h=\"{{ fr.h }}\" src=\"{{ fr.src }}\" srcdoc=\"{{ fr.srcdoc }}\" title=\"{{ fr.label }}\" ref=\"{{ fr.setFrameRef }}\" loading=\"lazy\" scrolling=\"no\" style=\"position:absolute;top:0;left:0;width:{{ fr.w }};height:{{ fr.h }};border:0;transform:scale({{ fr.scale }});transform-origin:top left;pointer-events:none\"><\u002Fiframe>\n                        <span style=\"position:absolute;top:8px;right:8px;z-index:2\"><dc-import name=\"FullscreenButton\" src=\"{{ fr.src }}\" srcdoc=\"{{ fr.srcdoc }}\" title=\"{{ fr.label }}\" meta=\"{{ fr.meta }}\" open-href=\"{{ fr.src }}\" hint-size=\"32px,32px\"><\u002Fdc-import><\u002Fspan>\n                      <\u002Fdiv>\n                      <div style=\"display:flex;align-items:baseline;gap:8px;padding:11px 13px;border-top:1px solid var(--app-rule)\">\n                        <span class=\"mono\" style=\"font-size:0.62rem;color:var(--app-accent);flex-shrink:0\">{{ fr.id }}<\u002Fspan>\n                        <span style=\"flex:1;min-width:0;font-family:var(--font-inter);font-size:0.78rem;color:var(--app-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis\">{{ fr.label }}<\u002Fspan>\n                        <a href=\"{{ fr.src }}\" target=\"_blank\" rel=\"noopener\" class=\"mono\" style=\"flex-shrink:0;font-size:0.6rem;color:var(--app-ink-faint);text-decoration:none\" style-hover=\"color:rgb(var(--lead))\">open ↗<\u002Fa>\n                      <\u002Fdiv>\n                      <div class=\"mono\" style=\"padding:0 13px 12px;font-size:0.6rem;color:var(--app-ink-faint)\">{{ fr.meta }}<\u002Fdiv>\n                    <\u002Fdiv>\n                  <\u002Fsc-for>\n                <\u002Fdiv>\n              <\u002Fsc-if>\n              <!-- bars lens -->\n              <sc-if value=\"{{ devPane.isBars }}\" hint-placeholder-val=\"{{ false }}\">\n                <div style=\"border:1px solid var(--app-rule);border-radius:var(--radius);background:var(--app-surface);padding:var(--card-pad);display:flex;flex-direction:column;gap:14px\">\n                  <sc-for list=\"{{ devPane.bars }}\" as=\"b3\" hint-placeholder-count=\"5\">\n                    <div style=\"display:flex;flex-direction:column;gap:7px\">\n                      <div style=\"display:flex;align-items:baseline;gap:10px\">\n                        <span style=\"flex:1;font-family:var(--font-inter);font-size:0.84rem;color:var(--app-ink)\">{{ b3.label }}<\u002Fspan>\n                        <span class=\"mono\" style=\"font-size:0.78rem;color:{{ b3.color }}\">{{ b3.value }}<\u002Fspan>\n                        <span class=\"mono\" style=\"font-size:0.66rem;color:var(--app-ink-faint);min-width:44px;text-align:right\">{{ b3.meta }}<\u002Fspan>\n                      <\u002Fdiv>\n                      <div style=\"height:6px;border-radius:9999px;background:rgb(var(--text) / 0.06);overflow:hidden\">\n                        <div style=\"height:100%;border-radius:9999px;width:{{ b3.pct }};background:{{ b3.color }}\"><\u002Fdiv>\n                      <\u002Fdiv>\n                    <\u002Fdiv>\n                  <\u002Fsc-for>\n                <\u002Fdiv>\n              <\u002Fsc-if>\n              <!-- queue / list lens -->\n              <sc-if value=\"{{ devPane.isList }}\" hint-placeholder-val=\"{{ false }}\">\n                <div style=\"display:flex;flex-direction:column;gap:8px\">\n                  <sc-for list=\"{{ devPane.items }}\" as=\"li2\" hint-placeholder-count=\"5\">\n                    <div style=\"display:flex;align-items:flex-start;gap:12px;padding:14px 16px;border:1px solid var(--app-rule);border-radius:var(--radius);background:var(--app-surface)\" style-hover=\"border-color:rgb(var(--lead) / 0.4)\">\n                      <span style=\"margin-top:5px;width:8px;height:8px;border-radius:50%;flex-shrink:0;background:{{ li2.color }};box-shadow:0 0 8px {{ li2.color }}\"><\u002Fspan>\n                      <div style=\"flex:1;min-width:0\">\n                        <div style=\"font-size:0.86rem;color:var(--app-ink);line-height:1.4\">{{ li2.title }}<\u002Fdiv>\n                        <div class=\"mono\" style=\"font-size:0.68rem;color:var(--app-ink-faint);margin-top:4px\">{{ li2.meta }}<\u002Fdiv>\n                      <\u002Fdiv>\n                      <span class=\"mono\" style=\"flex-shrink:0;font-size:0.6rem;padding:3px 9px;border-radius:9999px;border:1px solid {{ li2.color }};color:{{ li2.color }}\">{{ li2.chip }}<\u002Fspan>\n                    <\u002Fdiv>\n                  <\u002Fsc-for>\n                <\u002Fdiv>\n              <\u002Fsc-if>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n          <!-- Slot scaffold (template affordance) -->\n          <sc-if value=\"{{ isSlots }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"margin-bottom:var(--section-gap)\">\n              <x-import component-from-global-scope=\"AaaSDesignSystem_2216a8.Card\" variant=\"bedrock\" accent=\"circuit\" hint-size=\"100%,110px\">\n                <div class=\"label-nav\" style=\"color:rgb(var(--lead));margin-bottom:10px;font-size:0.7rem\">{{ calloutLabel }}<\u002Fdiv>\n                <div style=\"color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-size:0.95rem;line-height:1.6;max-width:840px\">{{ calloutBody }}<\u002Fdiv>\n              <\u002Fx-import>\n            <\u002Fdiv>\n            <div>\n              <div style=\"display:flex;align-items:baseline;gap:12px;margin-bottom:16px\">\n                <span class=\"label-overline\" style=\"color:rgb(var(--lead));font-size:0.62rem\">Content slots<\u002Fspan>\n                <span style=\"color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-size:0.85rem\">Drop reusable blocks here — this is the template scaffold<\u002Fspan>\n              <\u002Fdiv>\n              <div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:var(--grid-gap)\">\n                <sc-for list=\"{{ slots }}\" as=\"slot\" hint-placeholder-count=\"6\">\n                  <div style=\"border:1px dashed rgb(var(--text) / 0.2);border-radius:var(--radius);padding:22px;background:rgb(var(--basalt-surface) / 0.22);min-height:130px;display:flex;flex-direction:column;gap:12px\">\n                    <div style=\"display:flex;align-items:center;gap:10px;color:rgb(var(--lead))\">\n                      <span style=\"display:inline-flex\">{{ slot.iconEl }}<\u002Fspan>\n                      <span class=\"label-nav\" style=\"font-size:0.72rem;color:rgb(var(--text))\">{{ slot.name }}<\u002Fspan>\n                    <\u002Fdiv>\n                    <div style=\"color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-size:0.83rem;line-height:1.5\">{{ slot.desc }}<\u002Fdiv>\n                    <span class=\"label-system\" style=\"margin-top:auto;font-size:0.56rem;letter-spacing:0.14em;color:var(--app-ink-faint, rgb(var(--text) / 0.62))\">empty slot<\u002Fspan>\n                  <\u002Fdiv>\n                <\u002Fsc-for>\n              <\u002Fdiv>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n\n          <!-- ═══ Canvas-native panes (zui · foundry · cro) ═══ -->\n          <sc-if value=\"{{ kind }}\" hint-placeholder-val=\"{{ false }}\">\n\n            <!-- DEFAULT · pane bodies -->\n            <sc-if value=\"{{ isDefault }}\" hint-placeholder-val=\"{{ true }}\">\n\n              <!-- 01 · ZUI whiteboard -->\n              <sc-if value=\"{{ isZui }}\" hint-placeholder-val=\"{{ false }}\">\n                <div style=\"display:flex;gap:var(--grid-gap);align-items:stretch\">\n                  <div style=\"flex:1;min-width:0;position:relative;height:620px;border:1px solid var(--app-rule);border-radius:var(--radius);overflow:hidden;background:var(--app-bg);background-image:radial-gradient(var(--app-rule-soft) 1px, transparent 1px);background-size:24px 24px\">\n                    <div style=\"position:absolute;inset:0;overflow:auto\">{{ zui.canvasEl }}<\u002Fdiv>\n                    <!-- zoom control -->\n                    <div style=\"position:absolute;left:14px;bottom:14px;display:flex;align-items:center;gap:2px;padding:4px;border-radius:9px;border:1px solid var(--app-rule);background:var(--app-surface);backdrop-filter:blur(10px)\">\n                      <button onClick=\"{{ zui.zoomOut }}\" aria-label=\"Zoom out\" style=\"width:28px;height:28px;border:none;background:transparent;color:var(--app-ink);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;border-radius:6px\">{{ zui.minusEl }}<\u002Fbutton>\n                      <button onClick=\"{{ zui.zoomFit }}\" aria-label=\"Fit\" style=\"width:28px;height:28px;border:none;background:transparent;color:var(--app-ink);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;border-radius:6px\">{{ zui.fitEl }}<\u002Fbutton>\n                      <button onClick=\"{{ zui.zoomIn }}\" aria-label=\"Zoom in\" style=\"width:28px;height:28px;border:none;background:transparent;color:var(--app-ink);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;border-radius:6px\">{{ zui.plusEl }}<\u002Fbutton>\n                      <span class=\"mono\" style=\"min-width:44px;text-align:center;font-size:0.72rem;color:var(--app-ink-soft)\">{{ zui.zoom }}%<\u002Fspan>\n                    <\u002Fdiv>\n                    <!-- minimap -->\n                    <div style=\"position:absolute;right:14px;bottom:14px;border:1px solid var(--app-rule);border-radius:8px;overflow:hidden;background:var(--app-surface);backdrop-filter:blur(10px);padding:4px\">{{ zui.minimapEl }}<\u002Fdiv>\n                  <\u002Fdiv>\n                  <!-- detail pane -->\n                  <div style=\"width:360px;flex-shrink:0;border:1px solid var(--app-rule);border-radius:var(--radius);background:var(--app-elevated);display:flex;flex-direction:column;overflow:hidden\">\n                    <div style=\"padding:16px 18px;border-bottom:1px solid var(--app-rule)\">\n                      <div style=\"display:flex;align-items:center;gap:8px;margin-bottom:10px\">\n                        <span class=\"mono\" style=\"font-size:0.6rem;text-transform:uppercase;letter-spacing:0.08em;padding:2px 7px;border-radius:5px;border:1px solid var(--app-rule);color:var(--app-ink-faint)\">{{ zui.sel.type }}<\u002Fspan>\n                        <span class=\"mono\" style=\"font-size:0.62rem;padding:2px 8px;border-radius:9999px;border:1px solid {{ zui.sel.chipColor }};color:{{ zui.sel.chipColor }}\">{{ zui.sel.chipLabel }}<\u002Fspan>\n                      <\u002Fdiv>\n                      <div style=\"font-size:0.98rem;font-weight:650;color:var(--app-ink);line-height:1.35\">{{ zui.sel.title }}<\u002Fdiv>\n                    <\u002Fdiv>\n                    <div style=\"padding:16px 18px;display:flex;flex-direction:column;gap:16px;overflow-y:auto;flex:1\">\n                      <div style=\"display:flex;gap:22px\">\n                        <div><div class=\"label-overline\" style=\"font-size:0.5rem;color:var(--app-ink-faint);margin-bottom:5px\">Started<\u002Fdiv><div class=\"mono\" style=\"font-size:0.82rem;color:var(--app-ink)\">{{ zui.sel.started }}<\u002Fdiv><\u002Fdiv>\n                        <div><div class=\"label-overline\" style=\"font-size:0.5rem;color:var(--app-ink-faint);margin-bottom:5px\">Duration<\u002Fdiv><div class=\"mono\" style=\"font-size:0.82rem;color:var(--app-ink)\">{{ zui.sel.dur }}<\u002Fdiv><\u002Fdiv>\n                      <\u002Fdiv>\n                      <div><div class=\"label-overline\" style=\"font-size:0.5rem;color:var(--app-ink-faint);margin-bottom:6px\">Result<\u002Fdiv><div style=\"font-size:0.85rem;color:var(--app-ink-soft);line-height:1.5\">{{ zui.sel.summary }}<\u002Fdiv><\u002Fdiv>\n                      <div>\n                        <div class=\"label-overline\" style=\"font-size:0.5rem;color:var(--app-ink-faint);margin-bottom:6px\">Raw output<\u002Fdiv>\n                        <pre class=\"mono\" style=\"margin:0;font-size:0.72rem;line-height:1.5;color:var(--app-ink-soft);background:var(--app-bg);border:1px solid var(--app-rule);border-radius:8px;padding:12px;white-space:pre-wrap;max-height:150px;overflow:auto\">{{ zui.sel.output }}<\u002Fpre>\n                      <\u002Fdiv>\n                      <details style=\"border-top:1px solid var(--app-rule);padding-top:12px\">\n                        <summary class=\"label-nav\" style=\"font-size:0.62rem;color:var(--app-ink-soft);cursor:pointer\">Outline (accessible tree · {{ zui.outlineCount }})<\u002Fsummary>\n                        <div role=\"tree\" style=\"margin-top:10px;display:flex;flex-direction:column;gap:1px\">\n                          <sc-for list=\"{{ zui.outline }}\" as=\"o\" hint-placeholder-count=\"6\">\n                            <button role=\"treeitem\" onClick=\"{{ o.onClick }}\" style=\"display:flex;align-items:center;gap:8px;width:100%;padding:6px 8px;border:none;background:{{ o.bg }};border-radius:6px;cursor:pointer;text-align:left;font-size:0.78rem;color:var(--app-ink)\">\n                              <span style=\"width:6px;height:6px;border-radius:50%;flex-shrink:0;background:{{ o.chipColor }}\"><\u002Fspan>\n                              <span style=\"flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis\">{{ o.title }}<\u002Fspan>\n                            <\u002Fbutton>\n                          <\u002Fsc-for>\n                        <\u002Fdiv>\n                      <\u002Fdetails>\n                    <\u002Fdiv>\n                    <div style=\"padding:14px 18px;border-top:1px solid var(--app-rule);display:flex;gap:10px\">\n                      <button onClick=\"{{ zui.rerun }}\" style=\"flex:1;height:38px;border-radius:var(--radius-btn);border:1px solid rgb(var(--accent-red) / 0.4);background:transparent;color:var(--app-fail);font-family:var(--font-inter);font-weight:600;font-size:0.78rem;cursor:pointer\">Re-run node<\u002Fbutton>\n                      <button style=\"flex:1;height:38px;border-radius:var(--radius-btn);border:1px solid var(--app-rule);background:transparent;color:var(--app-ink);font-family:var(--font-inter);font-weight:500;font-size:0.78rem;cursor:pointer\">Add note<\u002Fbutton>\n                    <\u002Fdiv>\n                  <\u002Fdiv>\n                <\u002Fdiv>\n              <\u002Fsc-if>\n\n              <!-- 02 · Foundry roster → child DC (lazy) -->\n              <sc-if value=\"{{ isFoundry }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneFoundry\" screen-label=\"{{ activeLabel }}\" foundry=\"{{ foundry }}\" escIconEl=\"{{ escIconEl }}\" hint-size=\"100%,740px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 03 · CRO scores → child DC (lazy) -->\n              <sc-if value=\"{{ isCro }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneCro\" screen-label=\"{{ activeLabel }}\" cro=\"{{ cro }}\" hint-size=\"100%,740px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 03b · Marketing → child DC (lazy: mounts only when isMk) -->\n              <sc-if value=\"{{ isMk }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneMarketing\" screen-label=\"{{ activeLabel }}\" tab=\"{{ subLabel }}\" view=\"{{ activeView }}\" suite=\"{{ mkSuite }}\" on-confirm=\"{{ mkConfirm }}\" hint-size=\"100%,620px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 03f · Inbox · the agent console → child DC (lazy) -->\n              <sc-if value=\"{{ isInb }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneInbox\" agent-filter=\"{{ inboxAgent }}\" on-clear-agent=\"{{ clearInboxAgent }}\" extra-threads=\"{{ ctxThreads }}\" screen-label=\"{{ activeLabel }}\" tab=\"{{ subLabel }}\" on-act=\"{{ mkConfirm }}\" on-route=\"{{ inboxRoute }}\" hint-size=\"100%,880px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 03e · Costs · system usage → child DC (lazy) -->\n              <sc-if value=\"{{ isCs }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneCosts\" screen-label=\"{{ activeLabel }}\" tab=\"{{ subLabel }}\" hint-size=\"100%,860px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 03f · Quality · delivery + review + product health → child DC (lazy) -->\n              <sc-if value=\"{{ isQl }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneQuality\" screen-label=\"{{ activeLabel }}\" tab=\"{{ subLabel }}\" hint-size=\"100%,900px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 03g · Product · pipelines/agents/work/projects/sales/orders/threads → child DC (lazy) -->\n              <sc-if value=\"{{ isPdt }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneProduct\" screen-label=\"{{ activeLabel }}\" tab=\"{{ subLabel }}\" hint-size=\"100%,900px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 03h · Platform · control plane overview + integrations → child DC (lazy) -->\n              <sc-if value=\"{{ isPl }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PanePlatform\" screen-label=\"{{ activeLabel }}\" tab=\"{{ subLabel }}\" hint-size=\"100%,900px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 03i · Context · foundation/browse/assembly/sources → child DC (lazy) -->\n              <sc-if value=\"{{ isCtx }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneContext\" screen-label=\"{{ activeLabel }}\" tab=\"{{ subLabel }}\" extra-threads=\"{{ ctxThreads }}\" on-act=\"{{ mkConfirm }}\" on-confirm=\"{{ mkConfirm }}\" on-grant=\"{{ ctxGrant }}\" on-revoke=\"{{ ctxRevoke }}\" on-derive=\"{{ ctxDerive }}\" hint-size=\"100%,900px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 03i-2 · Context · Flows (CM-H) — strategic_gtm_flow through the SAME 'process' archetype\n                   the Client Index uses, validated before mount. Shell-side rather than inside PaneContext\n                   for the reason 'Tour steps' is: an archetype mount is config the shell owns, and the pane\n                   keeps owning its native tabs. -->\n              <sc-if value=\"{{ isCtxFlows }}\" hint-placeholder-val=\"{{ false }}\">\n                <sc-if value=\"{{ gtmValid }}\" hint-placeholder-val=\"{{ true }}\">\n                  <dc-import name=\"ArchetypeHost\" archetype=\"process\" template=\"{{ gtmTemplate }}\" instance=\"{{ gtmInstance }}\" note=\"{{ gtmNote }}\" noteTone=\"{{ gtmNoteTone }}\" hint-size=\"100%,600px\"><\u002Fdc-import>\n                <\u002Fsc-if>\n                <sc-if value=\"{{ !gtmValid }}\" hint-placeholder-val=\"{{ false }}\">\n                  <div data-testid=\"plane-validation-error\" style=\"display:flex;flex-direction:column;gap:8px;padding:16px;border:1px dashed var(--app-fail, rgb(var(--accent-red, 244 63 108)));border-radius:var(--radius,0.75rem);color:var(--app-fail, rgb(var(--accent-red, 244 63 108)))\">\n                    <span style=\"font-family:var(--font-jetbrains);font-size:0.68rem;letter-spacing:0.08em;text-transform:uppercase\">{{ gtmErrorTitle }}<\u002Fspan>\n                    <span style=\"font-size:0.8rem;color:var(--app-ink-soft)\">{{ gtmErrorDetail }}<\u002Fspan>\n                  <\u002Fdiv>\n                <\u002Fsc-if>\n              <\u002Fsc-if>\n\n              <!-- 03l · Video · reels + scene alignment (PT-4) → child DC (lazy) -->\n              <sc-if value=\"{{ isVid }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneVideo\" screen-label=\"{{ activeLabel }}\" tab=\"{{ subLabel }}\" hint-size=\"100%,900px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 03l · Feeds · per-plane endpoint reconciliation (AW-7) → child DC (lazy) -->\n              <sc-if value=\"{{ isFeeds }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneFeeds\" screen-label=\"{{ activeLabel }}\" contracts=\"{{ feedContracts }}\" hint-size=\"100%,900px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 03k · VO Studio · reels, cues, drift (PT-3) → child DC (lazy) -->              <sc-if value=\"{{ isVox }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneVoiceStudio\" screen-label=\"{{ activeLabel }}\" tab=\"{{ subLabel }}\" hint-size=\"100%,900px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 03j · Pitches · decks, slides, objects, video (PT-1) → child DC (lazy) -->\n              <sc-if value=\"{{ isPitches }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PanePitches\" screen-label=\"{{ activeLabel }}\" tab=\"{{ subLabel }}\" planned-icon=\"{{ pitchPlannedIcon }}\" hint-size=\"100%,900px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 03d · Design-system ops → child DC (lazy) -->\n              <sc-if value=\"{{ isDs }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneDesign\" screen-label=\"{{ activeLabel }}\" tab=\"{{ subLabel }}\" hint-size=\"100%,820px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 03c · Podcast · daily-brief pipeline → child DC (lazy) -->\n              <sc-if value=\"{{ isPd }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PanePodcast\" screen-label=\"{{ activeLabel }}\" pd=\"{{ pd }}\" hint-size=\"100%,760px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 04 · Onboarding → child DC (lazy) -->\n              <sc-if value=\"{{ isOnboarding }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneOnboarding\" screen-label=\"{{ activeLabel }}\" onb=\"{{ onb }}\" helpIconEl=\"{{ helpIconEl }}\" hint-size=\"100%,760px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 05 · Account & Settings → child DC (lazy) -->\n              <sc-if value=\"{{ isAccount }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneAccount\" screen-label=\"{{ activeLabel }}\" acc=\"{{ acc }}\" hint-size=\"100%,740px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 06 · ICP Builder → child DC (lazy) -->\n              <sc-if value=\"{{ isIcp }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneIcp\" screen-label=\"{{ activeLabel }}\" icp=\"{{ icp }}\" stop=\"{{ stop }}\" hint-size=\"100%,740px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 07 · Leads → child DC (lazy) -->\n              <sc-if value=\"{{ isLeads }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneLeads\" screen-label=\"{{ activeLabel }}\" leads=\"{{ leads }}\" hint-size=\"100%,780px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 07b · Scout → child DC (lazy, self-fetches via fleet-api) -->\n              <sc-if value=\"{{ isScout }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneScout\" screen-label=\"{{ activeLabel }}\" hint-size=\"100%,700px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 07c · Composer → child DC (lazy, self-fetches via fleet-api) -->\n              <sc-if value=\"{{ isComposer }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneComposer\" screen-label=\"{{ activeLabel }}\" hint-size=\"100%,700px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 07d · Convert → child DC (lazy, self-fetches via fleet-api) -->\n              <sc-if value=\"{{ isConvert }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneConvert\" screen-label=\"{{ activeLabel }}\" hint-size=\"100%,700px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 07e · Knowledge Base → child DC (lazy, self-fetches via fleet-api) -->\n              <sc-if value=\"{{ isKb }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneKb\" screen-label=\"{{ activeLabel }}\" hint-size=\"100%,700px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 07f · GrowthRun (SD-0 orchestration spine) → child DC (lazy, self-fetches via fleet-api) -->\n              <sc-if value=\"{{ isGrowthRun }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneGrowthRun\" screen-label=\"{{ activeLabel }}\" hint-size=\"100%,700px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- Twelve bespoke lenses (ep- pp- md- bb- pb- db- vb- ar- sc- rl- fe- im-) -->\n              <sc-if value=\"{{ isLens }}\" hint-placeholder-val=\"{{ false }}\">\n                <div style=\"display:flex;flex-direction:column;gap:var(--grid-gap)\">\n\n                  <sc-if value=\"{{ lens.isEmail }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"display:flex;gap:14px;align-items:flex-start;flex-wrap:wrap\">\n                      <aside style=\"width:min(232px,100%);flex-shrink:0;background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);padding:12px;display:flex;flex-direction:column;gap:4px\">\n                        <div class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.14em;margin-bottom:6px\">TEMPLATES · 67<\u002Fdiv>\n                        <sc-for list=\"{{ lens.email.list }}\" as=\"le\" hint-placeholder-count=\"5\">\n                          <button onClick=\"{{ le.onClick }}\" style=\"display:flex;flex-direction:column;gap:2px;padding:8px 10px;text-align:left;cursor:pointer;border-radius:var(--radius-btn);background:{{ le.bg }};border:1px solid {{ le.bd }}\">\n                            <span class=\"mono\" style=\"font-size:0.78rem;color:var(--app-ink)\">{{ le.name }}<\u002Fspan>\n                            <span class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.08em\">{{ le.cat }} · {{ le.opens }} opens<\u002Fspan>\n                          <\u002Fbutton>\n                        <\u002Fsc-for>\n                      <\u002Faside>\n                      <div style=\"flex:1;min-width:min(320px,100%);background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);padding:18px\">\n                        <div style=\"display:flex;flex-direction:column;gap:3px;padding-bottom:14px;margin-bottom:18px;border-bottom:1px solid var(--app-rule)\">\n                          <div class=\"mono\" style=\"font-size:0.68rem;color:var(--app-ink-soft);overflow-wrap:anywhere\"><span style=\"color:var(--app-ink-faint)\">from<\u002Fspan> {{ lens.email.from }}<\u002Fdiv>\n                          <div class=\"mono\" style=\"font-size:0.68rem;color:var(--app-ink-soft);overflow-wrap:anywhere\"><span style=\"color:var(--app-ink-faint)\">to<\u002Fspan> {{ lens.email.to }}<\u002Fdiv>\n                          <div style=\"font-size:0.86rem;font-weight:600;color:var(--app-ink);margin-top:4px;text-wrap:pretty\">{{ lens.email.subject }}<\u002Fdiv>\n                          <div class=\"mono\" style=\"font-size:0.64rem;color:var(--app-ink-faint);font-style:italic\">{{ lens.email.preheader }}<\u002Fdiv>\n                        <\u002Fdiv>\n                        <div style=\"max-width:600px;margin:0 auto;background:var(--app-elevated);border:1px solid var(--app-rule);border-radius:var(--radius);overflow:hidden\">\n                          <div style=\"padding:22px 28px;border-bottom:1px solid var(--app-rule);display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap\">\n                            <span style=\"display:inline-flex;align-items:center;gap:8px\">\n                              <span style=\"font-weight:900;font-size:18px;color:var(--app-ink)\">&#9671;<\u002Fspan>\n                              <span class=\"mono\" style=\"font-size:0.7rem;letter-spacing:0.2em;color:var(--app-ink)\">AAAS<\u002Fspan>\n                            <\u002Fspan>\n                            <span class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.12em\">TRANSACTIONAL<\u002Fspan>\n                          <\u002Fdiv>\n                          <div style=\"padding:32px 28px 26px;text-align:center;background:color-mix(in srgb,var(--app-accent) 4%,var(--app-elevated));border-bottom:1px solid var(--app-rule)\">\n                            <div class=\"mono\" style=\"font-size:0.66rem;color:var(--app-ink-faint);letter-spacing:0.14em;margin-bottom:8px\">{{ lens.email.heroLabel }}<\u002Fdiv>\n                            <div style=\"font-weight:700;font-size:38px;color:var(--app-accent);line-height:1\">{{ lens.email.heroAmount }}<\u002Fdiv>\n                          <\u002Fdiv>\n                          <div style=\"padding:22px 28px;display:flex;flex-direction:column;gap:11px\">\n                            <sc-for list=\"{{ lens.email.body }}\" as=\"lb\" hint-placeholder-count=\"5\">\n                              <p style=\"font-size:0.85rem;color:var(--app-ink);line-height:1.7;margin:0;text-wrap:pretty\">{{ lb.t }}<\u002Fp>\n                            <\u002Fsc-for>\n                            <a href=\"#\" style=\"display:inline-flex;align-items:center;gap:8px;padding:12px 22px;background:var(--app-accent);color:rgb(var(--basalt-deep));border-radius:9999px;text-decoration:none;font-size:0.8rem;font-weight:600;margin-top:6px;align-self:flex-start\">{{ lens.email.cta }}<\u002Fa>\n                          <\u002Fdiv>\n                          <div class=\"mono\" style=\"padding:16px 28px;border-top:1px solid var(--app-rule);font-size:0.64rem;color:var(--app-ink-faint);line-height:1.6;text-align:center;text-wrap:pretty\">{{ lens.email.footer }}<\u002Fdiv>\n                        <\u002Fdiv>\n                      <\u002Fdiv>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n\n                  <sc-if value=\"{{ lens.isPages }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"display:flex;gap:14px;align-items:flex-start;flex-wrap:wrap\">\n                      <aside style=\"width:min(210px,100%);flex-shrink:0;background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);padding:12px;display:flex;flex-direction:column;gap:5px\">\n                        <div class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.14em;margin-bottom:6px\">PAGES · 12<\u002Fdiv>\n                        <sc-for list=\"{{ lens.pages.list }}\" as=\"lp\" hint-placeholder-count=\"5\">\n                          <button onClick=\"{{ lp.onClick }}\" style=\"display:flex;flex-direction:column;gap:2px;padding:8px 10px;text-align:left;cursor:pointer;border-radius:var(--radius-btn);background:{{ lp.bg }};border:1px solid {{ lp.bd }}\">\n                            <span style=\"font-size:0.8rem;color:var(--app-ink)\">{{ lp.name }}<\u002Fspan>\n                            <span class=\"mono\" style=\"font-size:0.6rem;color:var(--app-accent);overflow-wrap:anywhere\">{{ lp.route }}<\u002Fspan>\n                            <span class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.08em\">{{ lp.meta }}<\u002Fspan>\n                          <\u002Fbutton>\n                        <\u002Fsc-for>\n                      <\u002Faside>\n                      <div style=\"flex:1;min-width:min(320px,100%);background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);overflow:hidden\">\n                        <div style=\"display:flex;align-items:center;gap:12px;padding:8px 14px;background:var(--app-elevated);border-bottom:1px solid var(--app-rule);flex-wrap:wrap\">\n                          <span style=\"display:inline-flex;gap:6px;flex-shrink:0\">\n                            <span style=\"width:11px;height:11px;border-radius:50%;background:rgb(var(--accent-red))\"><\u002Fspan>\n                            <span style=\"width:11px;height:11px;border-radius:50%;background:rgb(var(--pastel-gold))\"><\u002Fspan>\n                            <span style=\"width:11px;height:11px;border-radius:50%;background:rgb(var(--accent-teal))\"><\u002Fspan>\n                          <\u002Fspan>\n                          <span class=\"mono\" style=\"flex:1;min-width:min(140px,100%);display:inline-flex;align-items:center;gap:6px;padding:6px 14px;background:var(--app-surface);border:1px solid var(--app-rule);border-radius:9999px;font-size:0.66rem;color:var(--app-ink-soft);overflow-wrap:anywhere\">{{ lens.pages.url }}<\u002Fspan>\n                          <span class=\"mono\" style=\"font-size:0.56rem;color:var(--app-ink-faint);letter-spacing:0.14em\">LIGHTHOUSE 98<\u002Fspan>\n                        <\u002Fdiv>\n                        <div style=\"background:var(--app-bg);padding-bottom:22px;container-type:inline-size\">\n                          <div class=\"mono\" style=\"display:flex;align-items:center;gap:20px;padding:14px 28px;background:var(--app-surface);border-bottom:1px solid var(--app-rule);font-size:0.66rem;color:var(--app-ink-soft);letter-spacing:0.08em;flex-wrap:wrap\">\n                            <span style=\"font-weight:900;font-size:0.82rem;letter-spacing:0.12em;color:var(--app-ink)\">AAAS<\u002Fspan>\n                            <span>Agents<\u002Fspan><span>Pricing<\u002Fspan><span>Docs<\u002Fspan>\n                            <span style=\"margin-left:auto;padding:6px 14px;background:var(--app-accent);color:rgb(var(--basalt-deep));border-radius:9999px\">Activate<\u002Fspan>\n                          <\u002Fdiv>\n                          <div style=\"padding:48px 28px 32px;text-align:center\">\n                            <div class=\"mono\" style=\"font-size:0.66rem;color:var(--app-accent);letter-spacing:0.2em;margin-bottom:14px\">{{ lens.pages.eyebrow }}<\u002Fdiv>\n                            <h2 style=\"font-weight:900;font-size:clamp(20px,11cqi,54px);line-height:0.95;letter-spacing:-0.04em;color:var(--app-ink);margin:0 auto 14px;max-width:16ch;overflow-wrap:anywhere\">\n                              <sc-for list=\"{{ lens.pages.lines }}\" as=\"ll\" hint-placeholder-count=\"2\">\n                                <span style=\"display:block\">{{ ll.t }}<\u002Fspan>\n                              <\u002Fsc-for>\n                            <\u002Fh2>\n                            <p style=\"font-size:0.94rem;color:var(--app-ink-soft);max-width:52ch;margin:0 auto 22px;line-height:1.6;text-wrap:pretty\">{{ lens.pages.sub }}<\u002Fp>\n                            <div style=\"display:flex;gap:12px;justify-content:center;margin-bottom:30px;flex-wrap:wrap\">\n                              <sc-for list=\"{{ lens.pages.ctas }}\" as=\"lc\" hint-placeholder-count=\"2\">\n                                <button style=\"padding:12px 22px;background:{{ lc.bg }};color:{{ lc.ink }};border:1px solid {{ lc.bd }};border-radius:9999px;font-size:0.8rem;font-weight:600;cursor:pointer\">{{ lc.label }}<\u002Fbutton>\n                              <\u002Fsc-for>\n                            <\u002Fdiv>\n                            <div style=\"display:flex;justify-content:center;gap:40px;padding-top:22px;border-top:1px solid var(--app-rule);flex-wrap:wrap\">\n                              <sc-for list=\"{{ lens.pages.stats }}\" as=\"ls\" hint-placeholder-count=\"3\">\n                                <span style=\"text-align:center\">\n                                  <span style=\"display:block;font-weight:700;font-size:26px;color:var(--app-accent)\">{{ ls.v }}<\u002Fspan>\n                                  <span class=\"mono\" style=\"display:block;font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.14em;text-transform:uppercase\">{{ ls.l }}<\u002Fspan>\n                                <\u002Fspan>\n                              <\u002Fsc-for>\n                            <\u002Fdiv>\n                          <\u002Fdiv>\n                          <div style=\"padding:0 28px\">\n                            <div style=\"display:grid;grid-template-columns:repeat(auto-fit,minmax(min(170px,100%),1fr));gap:10px\">\n                              <sc-for list=\"{{ lens.pages.features }}\" as=\"lf\" hint-placeholder-count=\"6\">\n                                <div style=\"display:flex;align-items:center;gap:10px;padding:14px;background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius-btn);font-size:0.78rem;color:var(--app-ink)\">\n                                  <span style=\"width:6px;height:6px;border-radius:50%;flex-shrink:0;background:var(--app-accent)\"><\u002Fspan>\n                                  <span style=\"text-wrap:pretty\">{{ lf.label }}<\u002Fspan>\n                                <\u002Fdiv>\n                              <\u002Fsc-for>\n                            <\u002Fdiv>\n                          <\u002Fdiv>\n                        <\u002Fdiv>\n                      <\u002Fdiv>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n\n                  <sc-if value=\"{{ lens.isDocs }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"display:flex;gap:14px;align-items:flex-start;flex-wrap:wrap\">\n                      <aside style=\"width:min(212px,100%);flex-shrink:0;background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);padding:12px;display:flex;flex-direction:column;gap:12px\">\n                        <sc-for list=\"{{ lens.docs.tree }}\" as=\"lt\" hint-placeholder-count=\"4\">\n                          <div style=\"display:flex;flex-direction:column;gap:2px\">\n                            <div class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.14em;margin-bottom:4px\">{{ lt.head }}<\u002Fdiv>\n                            <sc-for list=\"{{ lt.items }}\" as=\"li\" hint-placeholder-count=\"3\">\n                              <button onClick=\"{{ li.onClick }}\" style=\"padding:5px 9px;text-align:left;cursor:pointer;border:none;border-radius:var(--radius-btn);font-size:0.76rem;background:{{ li.bg }};color:{{ li.ink }};text-wrap:pretty\">{{ li.label }}<\u002Fbutton>\n                            <\u002Fsc-for>\n                          <\u002Fdiv>\n                        <\u002Fsc-for>\n                      <\u002Faside>\n                      <article style=\"flex:1;min-width:min(300px,100%);background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);padding:26px 30px;display:flex;flex-direction:column;gap:12px;max-width:76ch\">\n                        <sc-for list=\"{{ lens.docs.blocks }}\" as=\"lm\" hint-placeholder-count=\"10\">\n                          <div>\n                            <sc-if value=\"{{ lm.isH1 }}\" hint-placeholder-val=\"{{ false }}\">\n                              <h2 style=\"font-size:26px;font-weight:700;color:var(--app-ink);margin:0;letter-spacing:-0.02em;text-wrap:pretty\">{{ lm.t }}<\u002Fh2>\n                            <\u002Fsc-if>\n                            <sc-if value=\"{{ lm.isH2 }}\" hint-placeholder-val=\"{{ false }}\">\n                              <h3 style=\"font-size:16px;font-weight:600;color:var(--app-ink);margin:8px 0 0;text-wrap:pretty\">{{ lm.t }}<\u002Fh3>\n                            <\u002Fsc-if>\n                            <sc-if value=\"{{ lm.isP }}\" hint-placeholder-val=\"{{ true }}\">\n                              <p style=\"font-size:0.9rem;color:var(--app-ink-soft);line-height:1.75;margin:0;text-wrap:pretty\">{{ lm.t }}<\u002Fp>\n                            <\u002Fsc-if>\n                            <sc-if value=\"{{ lm.isCode }}\" hint-placeholder-val=\"{{ false }}\">\n                              <pre class=\"mono\" style=\"margin:0;font-size:0.76rem;line-height:1.65;color:var(--app-accent);background:var(--app-bg);border:1px solid var(--app-rule);border-radius:var(--radius-btn);padding:14px 16px;overflow-x:auto;white-space:pre-wrap\">{{ lm.t }}<\u002Fpre>\n                            <\u002Fsc-if>\n                            <sc-if value=\"{{ lm.isUl }}\" hint-placeholder-val=\"{{ false }}\">\n                              <div style=\"display:flex;flex-direction:column;gap:5px\">\n                                <sc-for list=\"{{ lm.items }}\" as=\"lu\" hint-placeholder-count=\"3\">\n                                  <span style=\"display:flex;gap:10px;font-size:0.88rem;color:var(--app-ink-soft);line-height:1.6\">\n                                    <span style=\"color:var(--app-accent);flex-shrink:0\">&middot;<\u002Fspan><span style=\"text-wrap:pretty\">{{ lu.label }}<\u002Fspan>\n                                  <\u002Fspan>\n                                <\u002Fsc-for>\n                              <\u002Fdiv>\n                            <\u002Fsc-if>\n                          <\u002Fdiv>\n                        <\u002Fsc-for>\n                      <\u002Farticle>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n\n                  <sc-if value=\"{{ lens.isBanners }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"display:flex;flex-direction:column;gap:18px\">\n                      <sc-for list=\"{{ lens.banners }}\" as=\"lg\" hint-placeholder-count=\"2\">\n                        <div style=\"background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);padding:16px 18px\">\n                          <div style=\"display:flex;align-items:center;gap:14px;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid var(--app-rule);flex-wrap:wrap\">\n                            <span class=\"mono\" style=\"font-size:0.6rem;letter-spacing:0.18em;color:var(--app-accent)\">{{ lg.eyebrow }}<\u002Fspan>\n                            <span style=\"font-size:0.84rem;font-weight:600;color:var(--app-ink);text-wrap:pretty\">{{ lg.label }}<\u002Fspan>\n                            <span style=\"margin-left:auto;display:inline-flex;gap:6px;flex-wrap:wrap\">\n                              <button class=\"mono\" style=\"padding:4px 10px;background:var(--app-elevated);border:1px solid var(--app-rule);border-radius:9999px;color:var(--app-ink-soft);font-size:0.6rem;letter-spacing:0.1em;cursor:pointer\">EXPORT ALL<\u002Fbutton>\n                              <button class=\"mono\" style=\"padding:4px 10px;background:var(--app-elevated);border:1px solid var(--app-rule);border-radius:9999px;color:var(--app-ink-soft);font-size:0.6rem;letter-spacing:0.1em;cursor:pointer\">BRAND CHECK<\u002Fbutton>\n                            <\u002Fspan>\n                          <\u002Fdiv>\n                          <div style=\"display:grid;grid-template-columns:repeat(auto-fit,minmax(min(210px,100%),1fr));gap:14px;align-items:start\">\n                            <sc-for list=\"{{ lg.formats }}\" as=\"lo\" hint-placeholder-count=\"6\">\n                              <div style=\"display:flex;flex-direction:column;gap:6px\">\n                                <div style=\"position:relative;border-radius:var(--radius-btn);border:1px solid var(--app-rule);overflow:hidden;color:#fff;text-shadow:0 1px 4px rgba(0,0,0,.4);padding:22px;display:flex;flex-direction:column;justify-content:center;aspect-ratio:{{ lo.ar }};background:{{ lo.bg }}\">\n                                  <span style=\"display:flex;flex-direction:column;gap:4px;max-width:74%\">\n                                    <span class=\"mono\" style=\"font-size:0.6rem;letter-spacing:0.16em;opacity:0.85\">{{ lg.eyebrow }}<\u002Fspan>\n                                    <span style=\"font-weight:800;letter-spacing:-0.02em;line-height:1.05;font-size:{{ lo.ts }};text-wrap:pretty\">{{ lg.title }}<\u002Fspan>\n                                    <span style=\"font-size:0.78rem;opacity:0.85;margin-top:2px;text-wrap:pretty\">{{ lg.sub }}<\u002Fspan>\n                                    <span class=\"mono\" style=\"display:inline-flex;align-items:center;gap:6px;margin-top:10px;padding:6px 12px;background:rgba(0,0,0,.4);border:1px solid rgba(255,255,255,.3);border-radius:9999px;font-size:0.66rem;align-self:flex-start\">{{ lg.cta }}<\u002Fspan>\n                                  <\u002Fspan>\n                                  <span class=\"mono\" style=\"position:absolute;bottom:8px;right:8px;font-size:0.56rem;padding:2px 6px;background:rgba(0,0,0,.6);border-radius:9999px;letter-spacing:0.08em\">{{ lo.ratio }}<\u002Fspan>\n                                <\u002Fdiv>\n                                <div class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.08em\">{{ lo.label }} &middot; {{ lo.px }}<\u002Fdiv>\n                              <\u002Fdiv>\n                            <\u002Fsc-for>\n                          <\u002Fdiv>\n                        <\u002Fdiv>\n                      <\u002Fsc-for>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n\n                  <sc-if value=\"{{ lens.isPosters }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(min(210px,100%),1fr));gap:18px;align-items:end\">\n                      <sc-for list=\"{{ lens.posters }}\" as=\"lz\" hint-placeholder-count=\"6\">\n                        <div style=\"display:flex;flex-direction:column;gap:8px\">\n                          <div style=\"border:1px solid var(--app-rule);border-radius:var(--radius-btn);padding:24px 20px;color:#fff;text-shadow:0 1px 6px rgba(0,0,0,.6);display:flex;flex-direction:column;justify-content:space-between;overflow:hidden;aspect-ratio:{{ lz.ar }};background:{{ lz.bg }}\">\n                            <span class=\"mono\" style=\"font-size:0.66rem;letter-spacing:0.2em\">{{ lz.eyebrow }}<\u002Fspan>\n                            <span style=\"display:block\">\n                              <span style=\"display:block;font-weight:900;letter-spacing:-0.03em;line-height:0.95;font-size:{{ lz.ts }}\">\n                                <sc-for list=\"{{ lz.lines }}\" as=\"lw\" hint-placeholder-count=\"2\">\n                                  <span style=\"display:block\">{{ lw.t }}<\u002Fspan>\n                                <\u002Fsc-for>\n                              <\u002Fspan>\n                              <span style=\"display:block;font-size:0.72rem;opacity:0.82;margin-top:12px;line-height:1.4;text-wrap:pretty\">{{ lz.sub }}<\u002Fspan>\n                            <\u002Fspan>\n                            <span class=\"mono\" style=\"font-size:0.7rem;letter-spacing:0.2em;opacity:0.7\">AAAS<\u002Fspan>\n                          <\u002Fdiv>\n                          <div class=\"mono\" style=\"display:flex;justify-content:space-between;gap:8px;font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.08em;flex-wrap:wrap\">\n                            <span>{{ lz.size }} &middot; {{ lz.orient }}<\u002Fspan><span>{{ lz.surface }}<\u002Fspan>\n                          <\u002Fdiv>\n                        <\u002Fdiv>\n                      <\u002Fsc-for>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n\n                  <sc-if value=\"{{ lens.isDecks }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"display:flex;flex-direction:column;gap:18px\">\n                      <sc-for list=\"{{ lens.decks }}\" as=\"ld\" hint-placeholder-count=\"3\">\n                        <div style=\"background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);padding:16px 18px\">\n                          <div style=\"display:flex;align-items:center;gap:12px;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid var(--app-rule);flex-wrap:wrap\">\n                            <span style=\"font-size:0.86rem;font-weight:600;color:var(--app-ink);text-wrap:pretty\">{{ ld.title }}<\u002Fspan>\n                            <span class=\"mono\" style=\"padding:2px 8px;background:var(--app-elevated);border:1px solid var(--app-rule);border-radius:9999px;font-size:0.58rem;letter-spacing:0.12em;color:var(--app-ink-soft);text-transform:uppercase\">{{ ld.kind }}<\u002Fspan>\n                            <span class=\"mono\" style=\"margin-left:auto;font-size:0.62rem;color:var(--app-ink-faint);letter-spacing:0.08em\">{{ ld.meta }}<\u002Fspan>\n                            <button style=\"padding:5px 12px;background:var(--app-accent);color:rgb(var(--basalt-deep));border:none;border-radius:9999px;font-size:0.68rem;font-weight:600;cursor:pointer\">Present<\u002Fbutton>\n                          <\u002Fdiv>\n                          <div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(min(130px,100%),1fr));gap:8px\">\n                            <sc-for list=\"{{ ld.thumbs }}\" as=\"lh\" hint-placeholder-count=\"8\">\n                              <div style=\"display:flex;flex-direction:column;gap:4px\">\n                                <div style=\"aspect-ratio:16 / 9;border-radius:var(--radius-btn);border:1px solid var(--app-rule);position:relative;background:{{ lh.bg }}\">\n                                  <span class=\"mono\" style=\"position:absolute;top:5px;left:6px;padding:1px 5px;background:rgba(0,0,0,.55);color:#fff;border-radius:9999px;font-size:0.54rem\">{{ lh.n }}<\u002Fspan>\n                                <\u002Fdiv>\n                                <div class=\"mono\" style=\"font-size:0.58rem;color:var(--app-ink-faint);letter-spacing:0.08em;text-align:center\">{{ lh.label }}<\u002Fdiv>\n                              <\u002Fdiv>\n                            <\u002Fsc-for>\n                          <\u002Fdiv>\n                        <\u002Fdiv>\n                      <\u002Fsc-for>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n\n                  <sc-if value=\"{{ lens.isVideos }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"display:flex;flex-direction:column;gap:14px\">\n                      <div style=\"background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);overflow:hidden\">\n                        <div style=\"aspect-ratio:16 / 9;position:relative;display:flex;align-items:center;justify-content:center;background:{{ lens.videos.bg }}\">\n                          <button style=\"width:64px;height:64px;border-radius:50%;background:rgba(0,0,0,.55);border:1px solid rgba(255,255,255,.3);color:#fff;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;font-size:20px\">&#9654;<\u002Fbutton>\n                          <span class=\"mono\" style=\"position:absolute;bottom:12px;right:12px;padding:3px 9px;background:rgba(0,0,0,.6);color:#fff;border-radius:9999px;font-size:0.62rem\">{{ lens.videos.duration }}<\u002Fspan>\n                        <\u002Fdiv>\n                        <div style=\"padding:14px 18px;display:flex;align-items:center;gap:12px;flex-wrap:wrap\">\n                          <span style=\"flex:1;min-width:min(180px,100%);display:flex;flex-direction:column;gap:3px\">\n                            <span style=\"font-size:0.9rem;font-weight:600;color:var(--app-ink);text-wrap:pretty\">{{ lens.videos.title }}<\u002Fspan>\n                            <span class=\"mono\" style=\"font-size:0.62rem;color:var(--app-ink-faint);letter-spacing:0.08em\">{{ lens.videos.kind }} &middot; {{ lens.videos.views }}<\u002Fspan>\n                          <\u002Fspan>\n                          <button style=\"padding:6px 13px;background:var(--app-elevated);border:1px solid var(--app-rule);border-radius:9999px;color:var(--app-ink-soft);font-size:0.7rem;cursor:pointer\">Captions<\u002Fbutton>\n                          <button style=\"padding:6px 13px;background:var(--app-elevated);border:1px solid var(--app-rule);border-radius:9999px;color:var(--app-ink-soft);font-size:0.7rem;cursor:pointer\">Download<\u002Fbutton>\n                        <\u002Fdiv>\n                      <\u002Fdiv>\n                      <div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(min(180px,100%),1fr));gap:12px\">\n                        <sc-for list=\"{{ lens.videos.grid }}\" as=\"lv\" hint-placeholder-count=\"6\">\n                          <button onClick=\"{{ lv.onClick }}\" style=\"display:flex;flex-direction:column;padding:0;text-align:left;cursor:pointer;background:var(--app-surface);border:1px solid {{ lv.bd }};border-radius:var(--radius);overflow:hidden\">\n                            <span style=\"position:relative;display:block;aspect-ratio:16 / 9;background:{{ lv.bg }}\">\n                              <span class=\"mono\" style=\"position:absolute;bottom:6px;right:6px;padding:1px 6px;background:rgba(0,0,0,.6);color:#fff;border-radius:9999px;font-size:0.56rem\">{{ lv.duration }}<\u002Fspan>\n                            <\u002Fspan>\n                            <span style=\"display:block;padding:10px 12px\">\n                              <span style=\"display:block;font-size:0.76rem;font-weight:600;color:var(--app-ink);line-height:1.4;text-wrap:pretty\">{{ lv.title }}<\u002Fspan>\n                              <span class=\"mono\" style=\"display:block;font-size:0.58rem;color:var(--app-ink-faint);margin-top:3px;letter-spacing:0.08em\">{{ lv.meta }}<\u002Fspan>\n                            <\u002Fspan>\n                          <\u002Fbutton>\n                        <\u002Fsc-for>\n                      <\u002Fdiv>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n\n                  <sc-if value=\"{{ lens.isDrafts }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"display:flex;gap:14px;align-items:flex-start;flex-wrap:wrap\">\n                      <article style=\"flex:1;min-width:min(320px,100%);background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);padding:26px 30px;max-width:74ch\">\n                        <div style=\"padding-bottom:14px;margin-bottom:18px;border-bottom:1px solid var(--app-rule)\">\n                          <h2 style=\"font-size:28px;font-weight:700;color:var(--app-ink);margin:0 0 6px;letter-spacing:-0.02em;text-wrap:pretty\">{{ lens.drafts.title }}<\u002Fh2>\n                          <div class=\"mono\" style=\"font-size:0.66rem;color:var(--app-ink-soft);letter-spacing:0.06em\">{{ lens.drafts.author }}<\u002Fdiv>\n                          <div class=\"mono\" style=\"font-size:0.62rem;color:var(--app-ink-faint);letter-spacing:0.08em;margin-top:2px\">{{ lens.drafts.meta }}<\u002Fdiv>\n                        <\u002Fdiv>\n                        <div style=\"display:flex;flex-direction:column;gap:13px\">\n                          <sc-for list=\"{{ lens.drafts.blocks }}\" as=\"lk\" hint-placeholder-count=\"7\">\n                            <div>\n                              <sc-if value=\"{{ lk.isH2 }}\" hint-placeholder-val=\"{{ false }}\">\n                                <h3 style=\"font-size:16px;font-weight:600;color:var(--app-ink);margin:8px 0 0;text-wrap:pretty\">{{ lk.t }}<\u002Fh3>\n                              <\u002Fsc-if>\n                              <sc-if value=\"{{ lk.isP }}\" hint-placeholder-val=\"{{ true }}\">\n                                <p style=\"font-size:0.94rem;color:var(--app-ink-soft);line-height:1.8;margin:0;text-wrap:pretty\">{{ lk.t }}<\u002Fp>\n                              <\u002Fsc-if>\n                              <sc-if value=\"{{ lk.isQuote }}\" hint-placeholder-val=\"{{ false }}\">\n                                <p style=\"font-size:1.05rem;color:var(--app-ink);line-height:1.6;margin:6px 0;padding:14px 20px;background:var(--app-elevated);border-radius:var(--radius);font-style:italic;text-wrap:pretty\">{{ lk.t }}<\u002Fp>\n                              <\u002Fsc-if>\n                            <\u002Fdiv>\n                          <\u002Fsc-for>\n                        <\u002Fdiv>\n                      <\u002Farticle>\n                      <aside style=\"width:min(212px,100%);flex-shrink:0;display:flex;flex-direction:column;gap:12px\">\n                        <div style=\"background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);padding:12px\">\n                          <div class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.14em;margin-bottom:8px\">OUTLINE<\u002Fdiv>\n                          <sc-for list=\"{{ lens.drafts.outline }}\" as=\"lq\" hint-placeholder-count=\"2\">\n                            <div style=\"padding:4px 0;font-size:0.76rem;color:var(--app-ink-soft);text-wrap:pretty\">{{ lq.label }}<\u002Fdiv>\n                          <\u002Fsc-for>\n                        <\u002Fdiv>\n                        <div style=\"background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);padding:12px\">\n                          <div class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.14em;margin-bottom:8px\">GATE CHECKS<\u002Fdiv>\n                          <sc-for list=\"{{ lens.drafts.checks }}\" as=\"lx\" hint-placeholder-count=\"4\">\n                            <div style=\"display:flex;align-items:center;gap:8px;padding:4px 0;font-size:0.74rem;color:var(--app-ink-soft)\">\n                              <span style=\"color:{{ lx.ink }};flex-shrink:0\">{{ lx.glyph }}<\u002Fspan><span>{{ lx.label }}<\u002Fspan>\n                            <\u002Fdiv>\n                          <\u002Fsc-for>\n                        <\u002Fdiv>\n                        <div style=\"background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);padding:12px;display:flex;flex-direction:column;gap:4px\">\n                          <div class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.14em;margin-bottom:4px\">QUEUE · 38<\u002Fdiv>\n                          <sc-for list=\"{{ lens.drafts.list }}\" as=\"ly\" hint-placeholder-count=\"6\">\n                            <button onClick=\"{{ ly.onClick }}\" style=\"display:flex;flex-direction:column;gap:2px;padding:7px 9px;text-align:left;cursor:pointer;border-radius:var(--radius-btn);background:{{ ly.bg }};border:1px solid {{ ly.bd }}\">\n                              <span style=\"font-size:0.72rem;color:var(--app-ink);line-height:1.35;text-wrap:pretty\">{{ ly.title }}<\u002Fspan>\n                              <span class=\"mono\" style=\"font-size:0.56rem;letter-spacing:0.1em;color:{{ ly.chipInk }}\">{{ ly.chip }} &middot; {{ ly.meta }}<\u002Fspan>\n                            <\u002Fbutton>\n                          <\u002Fsc-for>\n                        <\u002Fdiv>\n                      <\u002Faside>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n\n                  <sc-if value=\"{{ lens.isSupport }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"display:flex;gap:12px;align-items:flex-start;flex-wrap:wrap\">\n                      <aside style=\"width:min(300px,100%);flex-shrink:0;background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);padding:12px;display:flex;flex-direction:column;gap:4px\">\n                        <div class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.14em;margin-bottom:6px\">OPEN THREADS · 3<\u002Fdiv>\n                        <sc-for list=\"{{ lens.support.list }}\" as=\"lt2\" hint-placeholder-count=\"3\">\n                          <button onClick=\"{{ lt2.onClick }}\" style=\"display:flex;flex-direction:column;gap:4px;padding:10px 12px;text-align:left;cursor:pointer;border-radius:var(--radius-btn);background:{{ lt2.bg }};border:1px solid {{ lt2.bd }}\">\n                            <span style=\"display:flex;justify-content:space-between;align-items:center;gap:8px\">\n                              <span style=\"font-size:0.8rem;font-weight:600;color:var(--app-ink)\">{{ lt2.customer }}<\u002Fspan>\n                              <span class=\"mono\" style=\"font-size:0.58rem;color:var(--app-ink-faint)\">{{ lt2.ago }}<\u002Fspan>\n                            <\u002Fspan>\n                            <span style=\"font-size:0.7rem;color:var(--app-ink-soft);line-height:1.4;text-wrap:pretty\">{{ lt2.preview }}<\u002Fspan>\n                            <span class=\"mono\" style=\"display:inline-flex;gap:8px;flex-wrap:wrap;font-size:0.58rem;color:var(--app-ink-faint);letter-spacing:0.08em;margin-top:2px\">\n                              <span style=\"padding:1px 6px;border-radius:9999px;background:{{ lt2.prioBg }};color:{{ lt2.prioInk }}\">{{ lt2.prio }}<\u002Fspan>\n                              <span>{{ lt2.tag }}<\u002Fspan><span>{{ lt2.tier }}<\u002Fspan>\n                            <\u002Fspan>\n                          <\u002Fbutton>\n                        <\u002Fsc-for>\n                      <\u002Faside>\n                      <div style=\"flex:1;min-width:min(320px,100%);background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);display:flex;flex-direction:column;overflow:hidden\">\n                        <div style=\"padding:14px 18px;border-bottom:1px solid var(--app-rule)\">\n                          <div style=\"font-size:0.96rem;font-weight:600;color:var(--app-ink)\">{{ lens.support.customer }}<\u002Fdiv>\n                          <div class=\"mono\" style=\"font-size:0.62rem;color:var(--app-ink-faint);letter-spacing:0.04em;margin-top:2px;overflow-wrap:anywhere\">{{ lens.support.meta }}<\u002Fdiv>\n                        <\u002Fdiv>\n                        <div style=\"padding:18px;display:flex;flex-direction:column;gap:12px\">\n                          <sc-for list=\"{{ lens.support.messages }}\" as=\"lms\" hint-placeholder-count=\"3\">\n                            <div style=\"display:grid;grid-template-columns:36px minmax(0,1fr);gap:10px\">\n                              <span class=\"mono\" style=\"width:36px;height:36px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:0.72rem;font-weight:600;background:{{ lms.avBg }};color:{{ lms.avInk }}\">{{ lms.av }}<\u002Fspan>\n                              <span style=\"min-width:0\">\n                                <span class=\"mono\" style=\"display:block;font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.04em;margin-bottom:4px\">{{ lms.author }} &middot; {{ lms.ts }}<\u002Fspan>\n                                <span style=\"display:block;font-size:0.82rem;color:var(--app-ink);line-height:1.6;background:{{ lms.bg }};border:1px solid {{ lms.bd }};padding:10px 14px;border-radius:var(--radius);text-wrap:pretty\">{{ lms.text }}<\u002Fspan>\n                              <\u002Fspan>\n                            <\u002Fdiv>\n                          <\u002Fsc-for>\n                        <\u002Fdiv>\n                        <div style=\"border-top:1px solid var(--app-rule);padding:12px 16px;display:flex;flex-direction:column;gap:8px\">\n                          <div style=\"display:flex;gap:4px;flex-wrap:wrap\">\n                            <sc-for list=\"{{ lens.support.snippets }}\" as=\"lsn\" hint-placeholder-count=\"5\">\n                              <button class=\"mono\" style=\"padding:4px 10px;background:var(--app-elevated);border:1px solid var(--app-rule);border-radius:9999px;color:var(--app-ink-soft);font-size:0.6rem;cursor:pointer\">{{ lsn.label }}<\u002Fbutton>\n                            <\u002Fsc-for>\n                          <\u002Fdiv>\n                          <textarea rows=\"3\" placeholder=\"Reply — or pick a snippet above\" style=\"background:var(--app-elevated);border:1px solid var(--app-rule);border-radius:var(--radius-btn);padding:10px 14px;color:var(--app-ink);font-size:0.82rem;font-family:var(--font-inter);resize:vertical;outline:none\"><\u002Ftextarea>\n                          <div style=\"display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap\">\n                            <span class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint)\">SLA 4 h &middot; 2 h 12 m remaining<\u002Fspan>\n                            <button style=\"display:inline-flex;align-items:center;gap:6px;padding:8px 14px;background:var(--app-accent);color:rgb(var(--basalt-deep));border:none;border-radius:var(--radius-btn);font-size:0.74rem;font-weight:600;cursor:pointer\">Send reply<\u002Fbutton>\n                          <\u002Fdiv>\n                        <\u002Fdiv>\n                      <\u002Fdiv>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n\n                  <sc-if value=\"{{ lens.isReplays }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"display:flex;flex-direction:column;gap:12px\">\n                      <div style=\"display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:12px 16px;background:color-mix(in srgb,var(--app-accent) 6%,var(--app-surface));border:1px dashed color-mix(in srgb,var(--app-accent) 35%,var(--app-rule));border-radius:var(--radius);color:var(--app-ink-soft);font-size:0.76rem\">\n                        <span style=\"text-wrap:pretty\">Replay-from-step re-runs a trace from any decision branch with a different choice &mdash; the counterfactual, without re-running the whole job.<\u002Fspan>\n                        <span class=\"mono\" style=\"margin-left:auto;padding:2px 8px;background:color-mix(in srgb,var(--app-warn) 18%,transparent);color:var(--app-warn);border-radius:9999px;letter-spacing:0.14em;font-size:0.6rem\">PHASE 2<\u002Fspan>\n                      <\u002Fdiv>\n                      <div style=\"background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);overflow:hidden\">\n                        <div class=\"mono\" style=\"display:grid;grid-template-columns:minmax(0,0.8fr) minmax(0,0.8fr) minmax(0,1.4fr) minmax(0,1.6fr) minmax(0,1.1fr) minmax(0,0.8fr);gap:12px;padding:10px 16px;background:var(--app-elevated);font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.14em;min-width:0\">\n                          <span>REPLAY<\u002Fspan><span>PARENT<\u002Fspan><span>FROM STEP<\u002Fspan><span>OVERRIDE<\u002Fspan><span>STATUS<\u002Fspan><span style=\"text-align:right\">Δ COST<\u002Fspan>\n                        <\u002Fdiv>\n                        <sc-for list=\"{{ lens.replays }}\" as=\"lr\" hint-placeholder-count=\"4\">\n                          <div style=\"display:grid;grid-template-columns:minmax(0,0.8fr) minmax(0,0.8fr) minmax(0,1.4fr) minmax(0,1.6fr) minmax(0,1.1fr) minmax(0,0.8fr);gap:12px;padding:11px 16px;border-top:1px solid var(--app-rule-soft);font-size:0.76rem;align-items:center;min-width:0\">\n                            <span class=\"mono\" style=\"color:var(--app-accent);overflow-wrap:anywhere\">{{ lr.id }}<\u002Fspan>\n                            <span class=\"mono\" style=\"color:var(--app-ink-soft);overflow-wrap:anywhere\">{{ lr.parent }}<\u002Fspan>\n                            <span class=\"mono\" style=\"color:var(--app-ink-soft);font-size:0.7rem;overflow-wrap:anywhere\">{{ lr.step }}<\u002Fspan>\n                            <span class=\"mono\" style=\"color:var(--app-accent);font-size:0.7rem;overflow-wrap:anywhere\">{{ lr.override }}<\u002Fspan>\n                            <span class=\"mono\" style=\"font-size:0.6rem;padding:2px 8px;border-radius:9999px;letter-spacing:0.12em;text-transform:uppercase;width:fit-content;background:{{ lr.bg }};color:{{ lr.ink }}\">{{ lr.status }}<\u002Fspan>\n                            <span class=\"mono\" style=\"text-align:right;color:{{ lr.costInk }};overflow-wrap:anywhere\">{{ lr.cost }}<\u002Fspan>\n                          <\u002Fdiv>\n                        <\u002Fsc-for>\n                      <\u002Fdiv>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n\n                  <sc-if value=\"{{ lens.isFlow }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"display:flex;flex-direction:column;gap:12px\">\n                      <div style=\"display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:12px 16px;background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius)\">\n                        <span class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.14em\">VARIANT<\u002Fspan>\n                        <sc-for list=\"{{ lens.flow.variants }}\" as=\"lfv\" hint-placeholder-count=\"2\">\n                          <button onClick=\"{{ lfv.onClick }}\" style=\"display:inline-flex;align-items:center;gap:8px;padding:6px 14px;background:var(--app-elevated);border:1px solid {{ lfv.bd }};border-radius:var(--radius-btn);color:{{ lfv.ink }};font-size:0.74rem;cursor:pointer\">\n                            <span>{{ lfv.label }}<\u002Fspan><span class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint)\">{{ lfv.pct }}<\u002Fspan>\n                          <\u002Fbutton>\n                        <\u002Fsc-for>\n                        <button class=\"mono\" style=\"padding:6px 14px;background:transparent;border:1px dashed var(--app-rule);border-radius:var(--radius-btn);color:var(--app-ink-faint);font-size:0.7rem;cursor:pointer\">+ New variant<\u002Fbutton>\n                      <\u002Fdiv>\n                      <div style=\"background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);padding:18px;display:flex;flex-direction:column;gap:0\">\n                        <sc-for list=\"{{ lens.flow.steps }}\" as=\"lfs\" hint-placeholder-count=\"6\">\n                          <div style=\"display:flex;flex-direction:column\">\n                            <div style=\"display:grid;grid-template-columns:44px minmax(0,1fr);gap:14px\">\n                              <span class=\"mono\" style=\"width:38px;height:38px;border-radius:50%;background:var(--app-accent);color:rgb(var(--basalt-deep));display:inline-flex;align-items:center;justify-content:center;font-size:0.86rem;font-weight:600\">{{ lfs.n }}<\u002Fspan>\n                              <span style=\"background:var(--app-elevated);border:1px solid var(--app-rule);border-radius:var(--radius);padding:12px 14px;display:flex;flex-direction:column;gap:8px;min-width:0\">\n                                <span style=\"display:flex;align-items:center;gap:8px;flex-wrap:wrap\">\n                                  <span style=\"flex:1;min-width:min(160px,100%);padding:6px 10px;background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius-btn);color:var(--app-ink);font-size:0.8rem;font-weight:500;text-wrap:pretty\">{{ lfs.title }}<\u002Fspan>\n                                  <span class=\"mono\" style=\"padding:4px 10px;background:color-mix(in srgb,var(--app-accent) 14%,transparent);color:var(--app-accent);border:1px solid color-mix(in srgb,var(--app-accent) 35%,var(--app-rule));border-radius:9999px;font-size:0.6rem;letter-spacing:0.08em\">{{ lfs.mode }}<\u002Fspan>\n                                <\u002Fspan>\n                                <span style=\"padding:6px 10px;background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius-btn);color:var(--app-ink-soft);font-size:0.74rem;text-wrap:pretty\">{{ lfs.tagline }}<\u002Fspan>\n                                <span style=\"display:flex;gap:4px;flex-wrap:wrap\">\n                                  <sc-for list=\"{{ lfs.fields }}\" as=\"lff\" hint-placeholder-count=\"3\">\n                                    <span class=\"mono\" style=\"display:inline-flex;align-items:center;gap:4px;padding:2px 8px;background:var(--app-surface);border:1px solid var(--app-rule);border-radius:9999px;font-size:0.6rem;color:var(--app-ink-soft)\">{{ lff.label }}<\u002Fspan>\n                                  <\u002Fsc-for>\n                                  <button class=\"mono\" style=\"padding:2px 10px;background:transparent;border:1px dashed var(--app-rule);border-radius:9999px;color:var(--app-ink-soft);font-size:0.6rem;cursor:pointer\">+ field<\u002Fbutton>\n                                <\u002Fspan>\n                              <\u002Fspan>\n                            <\u002Fdiv>\n                            <sc-if value=\"{{ lfs.last }}\" hint-placeholder-val=\"{{ false }}\">\n                              <span><\u002Fspan>\n                            <\u002Fsc-if>\n                            <span class=\"mono\" style=\"color:var(--app-ink-faint);display:flex;justify-content:center;padding:6px 0 12px;font-size:0.8rem\">&darr;<\u002Fspan>\n                          <\u002Fdiv>\n                        <\u002Fsc-for>\n                        <button class=\"mono\" style=\"display:inline-flex;align-items:center;gap:8px;align-self:flex-start;padding:12px 18px;background:transparent;border:2px dashed var(--app-rule);border-radius:var(--radius);color:var(--app-ink-faint);font-size:0.74rem;cursor:pointer\">+ Add step<\u002Fbutton>\n                      <\u002Fdiv>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n\n                  <sc-if value=\"{{ lens.isImp }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"display:flex;flex-direction:column;gap:12px\">\n                      <div style=\"display:flex;gap:10px;align-items:center;flex-wrap:wrap;padding:14px 18px;background:color-mix(in srgb,var(--app-fail) 6%,var(--app-surface));border:1px solid color-mix(in srgb,var(--app-fail) 30%,var(--app-rule));border-radius:var(--radius);color:var(--app-fail)\">\n                        <span style=\"flex:1;min-width:min(220px,100%)\">\n                          <span class=\"mono\" style=\"display:block;font-size:0.6rem;letter-spacing:0.14em;font-weight:600;margin-bottom:3px\">IMPERSONATION IS AUDITED<\u002Fspan>\n                          <span style=\"display:block;font-size:0.78rem;color:var(--app-ink-soft);text-wrap:pretty\">Every session is logged with actor, target, reason and duration. The customer is notified. Writes require a second confirm.<\u002Fspan>\n                        <\u002Fspan>\n                      <\u002Fdiv>\n                      <div style=\"display:grid;grid-template-columns:repeat(auto-fit,minmax(min(180px,100%),1fr));gap:12px\">\n                        <sc-for list=\"{{ lens.imp.policy }}\" as=\"lip\" hint-placeholder-count=\"3\">\n                          <div style=\"padding:14px 16px;background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius)\">\n                            <div class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.14em\">{{ lip.label }}<\u002Fdiv>\n                            <div style=\"font-size:1.05rem;color:var(--app-ink);margin-top:4px;text-wrap:pretty\">{{ lip.v }}<\u002Fdiv>\n                          <\u002Fdiv>\n                        <\u002Fsc-for>\n                      <\u002Fdiv>\n                      <div style=\"background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);padding:16px 18px;display:flex;flex-direction:column;gap:10px\">\n                        <div class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.14em\">START A SESSION &middot; REASON REQUIRED<\u002Fdiv>\n                        <div style=\"display:flex;gap:8px;flex-wrap:wrap\">\n                          <input class=\"mono\" placeholder=\"uid or email\" style=\"flex:1;min-width:min(200px,100%);background:var(--app-elevated);border:1px solid var(--app-rule);border-radius:var(--radius-btn);padding:10px 14px;color:var(--app-ink);font-size:0.78rem;outline:none\">\n                          <button style=\"padding:10px 18px;background:var(--app-fail);color:rgb(var(--basalt-deep, 14 14 16));border:none;border-radius:var(--radius-btn);font-size:0.78rem;font-weight:600;cursor:pointer\">Start session<\u002Fbutton>\n                        <\u002Fdiv>\n                        <div style=\"display:flex;gap:6px;flex-wrap:wrap\">\n                          <sc-for list=\"{{ lens.imp.reasons }}\" as=\"lir\" hint-placeholder-count=\"5\">\n                            <button onClick=\"{{ lir.onClick }}\" style=\"padding:6px 13px;background:{{ lir.bg }};border:1px solid {{ lir.bd }};border-radius:9999px;color:{{ lir.ink }};font-size:0.72rem;cursor:pointer;text-wrap:pretty\">{{ lir.label }}<\u002Fbutton>\n                          <\u002Fsc-for>\n                        <\u002Fdiv>\n                      <\u002Fdiv>\n                      <div style=\"background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);padding:16px 18px\">\n                        <div class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.14em;margin-bottom:10px\">RECENT SESSIONS · 30 DAY RETENTION<\u002Fdiv>\n                        <sc-for list=\"{{ lens.imp.recent }}\" as=\"lin\" hint-placeholder-count=\"3\">\n                          <div style=\"display:flex;gap:12px;align-items:center;flex-wrap:wrap;padding:9px 0;border-top:1px solid var(--app-rule-soft)\">\n                            <span class=\"mono\" style=\"flex:1;min-width:min(180px,100%);font-size:0.74rem;color:var(--app-ink);overflow-wrap:anywhere\">{{ lin.uid }}<\u002Fspan>\n                            <span class=\"mono\" style=\"font-size:0.66rem;color:var(--app-accent)\">{{ lin.actor }}<\u002Fspan>\n                            <span class=\"mono\" style=\"font-size:0.66rem;color:var(--app-ink-soft)\">{{ lin.duration }}<\u002Fspan>\n                            <span style=\"font-size:0.72rem;color:var(--app-ink-soft);text-wrap:pretty\">{{ lin.reason }}<\u002Fspan>\n                            <span class=\"mono\" style=\"font-size:0.62rem;color:var(--app-ink-faint)\">{{ lin.ts }}<\u002Fspan>\n                          <\u002Fdiv>\n                        <\u002Fsc-for>\n                      <\u002Fdiv>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n                <\u002Fdiv>\n              <\u002Fsc-if>\n\n              <!-- Content · overview cards / design gallery / social board -->\n              <sc-if value=\"{{ isCnt }}\" hint-placeholder-val=\"{{ false }}\">\n                <div style=\"display:flex;flex-direction:column;gap:var(--grid-gap)\">\n\n                  <sc-if value=\"{{ cnt.isOverview }}\" hint-placeholder-val=\"{{ true }}\">\n                    <div style=\"display:flex;flex-direction:column;gap:14px\">\n                      <div class=\"mono\" style=\"font-size:0.7rem;color:var(--app-ink-faint);text-wrap:pretty\">{{ cnt.note }}<\u002Fdiv>\n                      <div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(min(240px,100%),1fr));gap:12px\">\n                        <sc-for list=\"{{ cnt.cards }}\" as=\"cc\" hint-placeholder-count=\"12\">\n                          <div style=\"background:var(--app-surface);border:1px solid {{ cc.bd }};border-radius:var(--radius);padding:16px 18px;display:flex;flex-direction:column;gap:8px\">\n                            <div style=\"display:flex;align-items:flex-start;gap:8px\">\n                              <span style=\"width:7px;height:7px;border-radius:50%;flex-shrink:0;margin-top:5px;background:{{ cc.dot }}\"><\u002Fspan>\n                              <span style=\"flex:1;min-width:0;font-size:0.84rem;font-weight:600;color:var(--app-ink);line-height:1.4;text-wrap:pretty\">{{ cc.title }}<\u002Fspan>\n                            <\u002Fdiv>\n                            <div style=\"font-size:1.9rem;font-weight:500;color:var(--app-ink);line-height:1;font-family:var(--font-jetbrains)\">{{ cc.kpi }}<\u002Fdiv>\n                            <div class=\"mono\" style=\"font-size:0.62rem;color:var(--app-ink-faint);letter-spacing:0.1em;text-transform:uppercase\">{{ cc.meta }}<\u002Fdiv>\n                            <div style=\"font-size:0.72rem;color:var(--app-ink-soft);line-height:1.5;text-wrap:pretty\">{{ cc.sub }}<\u002Fdiv>\n                            <svg viewBox=\"0 0 120 24\" preserveAspectRatio=\"none\" style=\"width:100%;height:24px;margin-top:auto\">\n                              <path d=\"M0 18 C 12 6, 24 6, 36 14 S 60 22, 72 12 S 96 4, 120 10\" fill=\"none\" stroke=\"{{ cc.spark }}\" stroke-width=\"1.4\" stroke-linecap=\"round\"><\u002Fpath>\n                            <\u002Fsvg>\n                          <\u002Fdiv>\n                        <\u002Fsc-for>\n                      <\u002Fdiv>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n\n                  <sc-if value=\"{{ cnt.isDesigns }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"display:flex;flex-direction:column;gap:14px\">\n                      <div style=\"display:flex;gap:6px;align-items:center;flex-wrap:wrap;padding:10px 14px;background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius)\">\n                        <span class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint);letter-spacing:0.14em;margin-right:4px\">SURFACE<\u002Fspan>\n                        <sc-for list=\"{{ cnt.surfaces }}\" as=\"cs\" hint-placeholder-count=\"5\">\n                          <button onClick=\"{{ cs.onClick }}\" style=\"display:inline-flex;align-items:center;gap:5px;padding:5px 12px;background:{{ cs.bg }};border:1px solid {{ cs.bd }};border-radius:9999px;color:{{ cs.ink }};font-size:11px;cursor:pointer\">\n                            <span>{{ cs.label }}<\u002Fspan><span class=\"mono\" style=\"font-size:10px;opacity:.7\">{{ cs.count }}<\u002Fspan>\n                          <\u002Fbutton>\n                        <\u002Fsc-for>\n                      <\u002Fdiv>\n                      <div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(min(210px,100%),1fr));gap:12px\">\n                        <sc-for list=\"{{ cnt.tiles }}\" as=\"cg\" hint-placeholder-count=\"12\">\n                          <button onClick=\"{{ cg.onClick }}\" style=\"display:flex;flex-direction:column;padding:0;text-align:left;cursor:pointer;background:var(--app-surface);border:1px solid var(--app-rule);border-radius:var(--radius);overflow:hidden\">\n                            <span style=\"position:relative;display:block;aspect-ratio:4 / 3;background:{{ cg.bg }}\">\n                              <span class=\"mono\" style=\"position:absolute;top:8px;left:8px;padding:2px 8px;background:rgba(0,0,0,.55);color:#fff;border-radius:9999px;font-size:9px;letter-spacing:.1em;text-transform:uppercase\">{{ cg.mark }}<\u002Fspan>\n                              <span class=\"mono\" style=\"position:absolute;bottom:8px;right:8px;padding:2px 6px;background:rgba(0,0,0,.6);color:#fff;border-radius:9999px;font-size:9px\">{{ cg.ratio }}<\u002Fspan>\n                            <\u002Fspan>\n                            <span style=\"display:block;padding:11px 13px\">\n                              <span style=\"display:block;font-size:0.8rem;font-weight:600;color:var(--app-ink);line-height:1.4;text-wrap:pretty\">{{ cg.title }}<\u002Fspan>\n                              <span class=\"mono\" style=\"display:block;font-size:0.62rem;color:var(--app-ink-faint);margin-top:3px\">{{ cg.surface }} &middot; {{ cg.updated }}<\u002Fspan>\n                            <\u002Fspan>\n                          <\u002Fbutton>\n                        <\u002Fsc-for>\n                      <\u002Fdiv>\n                      <sc-if value=\"{{ cnt.lightbox }}\" hint-placeholder-val=\"{{ false }}\">\n                        <div onClick=\"{{ cnt.lightbox.close }}\" style=\"position:fixed;inset:0;z-index:62;background:rgb(0 0 0 / 0.74);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:5vh 4vw\">\n                          <div onClick=\"{{ stop }}\" style=\"display:flex;flex-wrap:wrap;gap:0;max-width:min(1000px,94vw);border:1px solid var(--app-rule);border-radius:var(--radius);background:var(--app-surface);overflow:hidden\">\n                            <span style=\"flex:1;min-width:min(320px,100%);aspect-ratio:{{ cnt.lightbox.ar }};background:{{ cnt.lightbox.bg }};position:relative;display:block\">\n                              <span class=\"mono\" style=\"position:absolute;bottom:12px;left:12px;padding:3px 10px;background:rgba(0,0,0,.6);color:#fff;border-radius:9999px;font-size:10px;letter-spacing:.12em;text-transform:uppercase\">{{ cnt.lightbox.mark }} &middot; {{ cnt.lightbox.ratio }}<\u002Fspan>\n                            <\u002Fspan>\n                            <span style=\"width:min(260px,100%);flex-shrink:0;padding:20px 22px;display:flex;flex-direction:column;gap:8px\">\n                              <span style=\"font-size:1rem;font-weight:600;color:var(--app-ink);text-wrap:pretty\">{{ cnt.lightbox.title }}<\u002Fspan>\n                              <span class=\"mono\" style=\"font-size:0.68rem;color:var(--app-ink-soft)\">{{ cnt.lightbox.surface }} &middot; {{ cnt.lightbox.mark }} &middot; {{ cnt.lightbox.ratio }}<\u002Fspan>\n                              <span class=\"mono\" style=\"font-size:0.68rem;color:var(--app-ink-faint)\">Updated {{ cnt.lightbox.updated }}<\u002Fspan>\n                              <button style=\"margin-top:auto;padding:9px 14px;background:var(--app-accent);color:rgb(var(--basalt-deep));border:none;border-radius:var(--radius-btn);font-size:12px;font-weight:600;cursor:pointer\">Open in Figma<\u002Fbutton>\n                              <button style=\"padding:9px 14px;background:transparent;color:var(--app-ink-soft);border:1px solid var(--app-rule);border-radius:var(--radius-btn);font-size:12px;cursor:pointer\">Export PNG<\u002Fbutton>\n                              <button onClick=\"{{ cnt.lightbox.close }}\" class=\"mono\" style=\"padding:6px;background:transparent;border:none;color:var(--app-ink-faint);font-size:10px;letter-spacing:.12em;cursor:pointer\">CLOSE<\u002Fbutton>\n                            <\u002Fspan>\n                          <\u002Fdiv>\n                        <\u002Fdiv>\n                      <\u002Fsc-if>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n\n                  <sc-if value=\"{{ cnt.isSocial }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(min(340px,100%),1fr));gap:14px\">\n                      <sc-for list=\"{{ cnt.posts }}\" as=\"cp\" hint-placeholder-count=\"4\">\n                        <div style=\"background:var(--app-surface);border:1px solid var(--app-rule);border-top:3px solid {{ cp.top }};border-radius:var(--radius);padding:14px 16px;display:flex;flex-direction:column;gap:10px\">\n                          <div style=\"display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap\">\n                            <span class=\"mono\" style=\"display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.12em;color:{{ cp.top }}\">\n                              <span style=\"width:6px;height:6px;border-radius:50%;background:{{ cp.top }}\"><\u002Fspan>{{ cp.ch }}\n                              <sc-if value=\"{{ cp.hasTag }}\" hint-placeholder-val=\"{{ false }}\">\n                                <span style=\"margin-left:8px;font-size:9px;padding:1px 8px;background:color-mix(in srgb,var(--app-warn) 18%,transparent);color:var(--app-warn);border-radius:9999px;letter-spacing:.14em\">{{ cp.tag }}<\u002Fspan>\n                              <\u002Fsc-if>\n                            <\u002Fspan>\n                            <span class=\"mono\" style=\"font-size:10px;color:var(--app-ink-faint);letter-spacing:.08em\">{{ cp.when }}<\u002Fspan>\n                          <\u002Fdiv>\n                          <div style=\"display:flex;align-items:center;gap:10px\">\n                            <span class=\"mono\" style=\"width:36px;height:36px;border-radius:50%;flex-shrink:0;background:var(--app-accent);color:rgb(var(--basalt-deep));display:inline-flex;align-items:center;justify-content:center;font-size:13px;font-weight:600\">{{ cp.av }}<\u002Fspan>\n                            <span style=\"display:flex;flex-direction:column;gap:1px;min-width:0\">\n                              <span style=\"font-size:13px;font-weight:600;color:var(--app-ink)\">{{ cp.author }}<\u002Fspan>\n                              <span class=\"mono\" style=\"font-size:10px;color:var(--app-ink-faint);overflow-wrap:anywhere\">{{ cp.handle }}<\u002Fspan>\n                            <\u002Fspan>\n                          <\u002Fdiv>\n                          <div style=\"font-size:13px;line-height:1.55;color:var(--app-ink-soft);white-space:pre-wrap;text-wrap:pretty\">{{ cp.body }}<\u002Fdiv>\n                          <sc-if value=\"{{ cp.hasMedia }}\" hint-placeholder-val=\"{{ true }}\">\n                            <div style=\"display:inline-flex;align-items:center;gap:8px;padding:10px 14px;background:var(--app-elevated);border:1px dashed var(--app-rule);border-radius:var(--radius-btn);color:var(--app-ink-soft);font-size:11px;align-self:flex-start\">{{ cp.media }}<\u002Fdiv>\n                          <\u002Fsc-if>\n                          <sc-if value=\"{{ cp.hasStats }}\" hint-placeholder-val=\"{{ true }}\">\n                            <div class=\"mono\" style=\"display:inline-flex;gap:16px;font-size:11px;color:var(--app-ink-faint);padding-top:8px;border-top:1px dashed var(--app-rule-soft);margin-top:auto;flex-wrap:wrap\">{{ cp.stats }}<\u002Fdiv>\n                          <\u002Fsc-if>\n                        <\u002Fdiv>\n                      <\u002Fsc-for>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n                <\u002Fdiv>\n              <\u002Fsc-if>\n\n              <!-- 14 · Domains · hub launcher → child DC (lazy) -->\n              <sc-if value=\"{{ isHub }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneDomainHub\" screen-label=\"{{ activeLabel }}\" hub=\"{{ hub }}\" hint-size=\"100%,620px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 15 · Domains → child DC (lazy: mounts only when isDomain) -->\n              <sc-if value=\"{{ isDomain }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneDomains\" screen-label=\"{{ activeLabel }}\" dom=\"{{ dom }}\" hint-size=\"100%,760px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 12 · Ads · Launch Readiness → child DC (lazy) -->\n              <sc-if value=\"{{ isLaunch }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneAdsLaunch\" screen-label=\"{{ activeLabel }}\" launch=\"{{ launch }}\" hint-size=\"100%,700px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 13 · Ads · Account Overview → child DC (lazy) -->\n              <sc-if value=\"{{ isAdsAccount }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneAdsAccount\" screen-label=\"{{ activeLabel }}\" adsAcct=\"{{ adsAcct }}\" hint-size=\"100%,700px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 11 · Introduction · the public landing page → child DC (lazy) -->\n              <sc-if value=\"{{ isLanding }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneIntro\" screen-label=\"{{ activeLabel }}\" landing=\"{{ landing }}\" hint-size=\"100%,820px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 19 · Growth Fleet 'Content calendar (config)' — same real CALENDAR data as\n                   PaneGrowth's own tab, through ArchetypeHost (live-data proof, 2026-08-06) -->\n              <sc-if value=\"{{ isGrowthCalConfig }}\" hint-placeholder-val=\"{{ false }}\">\n                <sc-if value=\"{{ growthCalValid }}\" hint-placeholder-val=\"{{ true }}\">\n                  <dc-import name=\"ArchetypeHost\" archetype=\"calendar\" items=\"{{ growthCalItems }}\" hint-size=\"100%,400px\"><\u002Fdc-import>\n                <\u002Fsc-if>\n                <sc-if value=\"{{ !growthCalValid }}\" hint-placeholder-val=\"{{ false }}\">\n                  <div data-testid=\"plane-validation-error\" style=\"display:flex;flex-direction:column;gap:8px;padding:16px;border:1px dashed var(--app-fail, rgb(var(--accent-red, 244 63 108)));border-radius:var(--radius,0.75rem);color:var(--app-fail, rgb(var(--accent-red, 244 63 108)))\">\n                    <span style=\"font-family:var(--font-jetbrains);font-size:0.68rem;letter-spacing:0.08em;text-transform:uppercase\">{{ growthCalErrorTitle }}<\u002Fspan>\n                    <span style=\"font-size:0.8rem;color:var(--app-ink-soft)\">{{ growthCalErrorDetail }}<\u002Fspan>\n                  <\u002Fdiv>\n                <\u002Fsc-if>\n              <\u002Fsc-if>\n\n              <!-- 18 · Phase 2 Batch 3 — 'Tour steps' via ArchetypeHost, validated before mount\n                   (docs/BLOCK-HOST-FINDING.md §4: fail visibly, never silently) -->\n              <sc-if value=\"{{ isTourSteps }}\" hint-placeholder-val=\"{{ false }}\">\n                <sc-if value=\"{{ archetypeValid }}\" hint-placeholder-val=\"{{ true }}\">\n                  <dc-import name=\"ArchetypeHost\" archetype=\"board\" items=\"{{ tourStepsItems }}\" hint-size=\"100%,400px\"><\u002Fdc-import>\n                <\u002Fsc-if>\n                <sc-if value=\"{{ !archetypeValid }}\" hint-placeholder-val=\"{{ false }}\">\n                  <div data-testid=\"plane-validation-error\" style=\"display:flex;flex-direction:column;gap:8px;padding:16px;border:1px dashed var(--app-fail, rgb(var(--accent-red, 244 63 108)));border-radius:var(--radius,0.75rem);color:var(--app-fail, rgb(var(--accent-red, 244 63 108)))\">\n                    <span style=\"font-family:var(--font-jetbrains);font-size:0.68rem;letter-spacing:0.08em;text-transform:uppercase\">{{ archetypeErrorTitle }}<\u002Fspan>\n                    <span style=\"font-size:0.8rem;color:var(--app-ink-soft)\">{{ archetypeErrorDetail }}<\u002Fspan>\n                  <\u002Fdiv>\n                <\u002Fsc-if>\n              <\u002Fsc-if>\n\n              <!-- 17 · Growth Fleet · native port of the growth-fleet patch → child DC (lazy) -->\n              <sc-if value=\"{{ isGrowth }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneGrowth\" screen-label=\"{{ activeLabel }}\" sub=\"{{ growthSub }}\" hint-size=\"100%,900px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n\n              <!-- 18 · Proposals · request → offering match → builder → review/sign → pipeline → child DC (lazy) -->\n              <sc-if value=\"{{ isProposals }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneProposals\" screen-label=\"{{ activeLabel }}\" kind=\"{{ proposalKind }}\" tab=\"{{ subLabel }}\" hint-size=\"100%,900px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 22 · Approvals — retired as a standalone mount 2026-09-25 (NIGHT-PLAN P1, item 1):\n                   PaneInbox renders 'Awaiting approval' natively now; PaneApprovals is mounted\n                   FROM INSIDE PaneInbox's detail column for the services engagement thread only. -->\n\n              <!-- 21 · Client Index · one client's proposals + deliverables + invoices (Batch 4) -->\n              <sc-if value=\"{{ isClientIndex }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneClientIndex\" screen-label=\"{{ activeLabel }}\" hint-size=\"100%,700px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 23 · Client Index · Process — whole-tree overview via ArchetypeHost (WP-51) -->\n              <sc-if value=\"{{ isClientProcess }}\" hint-placeholder-val=\"{{ false }}\">\n                <sc-if value=\"{{ processValid }}\" hint-placeholder-val=\"{{ true }}\">\n                  <dc-import name=\"ArchetypeHost\" archetype=\"process\" template=\"{{ processTemplate }}\" instance=\"{{ processInstance }}\" onStart=\"{{ startEngagement }}\" onUndo=\"{{ undoEngagement }}\" note=\"{{ procNote }}\" noteTone=\"{{ procNoteTone }}\" hint-size=\"100%,600px\"><\u002Fdc-import>\n                <\u002Fsc-if>\n                <sc-if value=\"{{ !processValid }}\" hint-placeholder-val=\"{{ false }}\">\n                  <div data-testid=\"plane-validation-error\" style=\"display:flex;flex-direction:column;gap:8px;padding:16px;border:1px dashed var(--app-fail, rgb(var(--accent-red, 244 63 108)));border-radius:var(--radius,0.75rem);color:var(--app-fail, rgb(var(--accent-red, 244 63 108)))\">\n                    <span style=\"font-family:var(--font-jetbrains);font-size:0.68rem;letter-spacing:0.08em;text-transform:uppercase\">{{ processErrorTitle }}<\u002Fspan>\n                    <span style=\"font-size:0.8rem;color:var(--app-ink-soft)\">{{ processErrorDetail }}<\u002Fspan>\n                  <\u002Fdiv>\n                <\u002Fsc-if>\n              <\u002Fsc-if>\n\n              <!-- W2 · Onboarding › Getting started — servicesWizard as a process plane (D150) -->\n              <sc-if value=\"{{ isOnbWizard }}\" hint-placeholder-val=\"{{ false }}\">\n                <section data-section=\"Onboarding · Services Toggle\" data-archetype=\"process\" style=\"display:flex;flex-direction:column;gap:16px\">\n                  <sc-if value=\"{{ wizValid }}\" hint-placeholder-val=\"{{ true }}\">\n                    <div style=\"display:flex;flex-wrap:wrap;align-items:center;gap:14px 20px;padding:18px 20px;border:1px solid var(--app-rule);border-radius:var(--radius);background:var(--app-surface)\">\n                      <div style=\"flex:1 1 280px;min-width:0;display:flex;flex-direction:column;gap:4px\">\n                        <span class=\"label-overline\" style=\"font-size:0.58rem;color:var(--app-accent)\">{{ wizStep.of }}<\u002Fspan>\n                        <span style=\"font-size:1.05rem;font-weight:700;color:var(--app-ink)\">{{ wizStep.title }}<\u002Fspan>\n                        <span style=\"font-size:0.84rem;color:var(--app-ink-soft)\">{{ wizStep.tagline }}<\u002Fspan>\n                      <\u002Fdiv>\n                      <div style=\"display:flex;gap:8px;flex-wrap:wrap\">\n                        <button onClick=\"{{ wizStep.back }}\" style=\"height:40px;padding:0 16px;border-radius:var(--radius-btn);border:1px solid var(--app-rule);background:transparent;color:var(--app-ink);font-family:var(--font-inter);font-weight:600;font-size:0.82rem;cursor:pointer\">Back<\u002Fbutton>\n                        <button onClick=\"{{ wizStep.next }}\" style=\"height:40px;padding:0 18px;border-radius:var(--radius-btn);border:none;background:var(--app-accent);color:rgb(var(--basalt-deep, 14 14 16));font-family:var(--font-inter);font-weight:700;font-size:0.82rem;cursor:pointer\">Next<\u002Fbutton>\n                        <button onClick=\"{{ wizStep.openServices }}\" style=\"height:40px;padding:0 16px;border-radius:var(--radius-btn);border:1px solid var(--app-rule);background:transparent;color:var(--app-ink);font-family:var(--font-inter);font-weight:600;font-size:0.82rem;cursor:pointer\">Open Services<\u002Fbutton>\n                      <\u002Fdiv>\n                    <\u002Fdiv>\n                    <dc-import name=\"ArchetypeHost\" archetype=\"process\" template=\"{{ wizTemplate }}\" instance=\"{{ wizInstance }}\" hint-size=\"100%,520px\"><\u002Fdc-import>\n                  <\u002Fsc-if>\n                  <sc-if value=\"{{ !wizValid }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div data-testid=\"plane-validation-error\" style=\"display:flex;flex-direction:column;gap:8px;padding:16px;border:1px dashed var(--app-fail, rgb(var(--accent-red, 244 63 108)));border-radius:var(--radius,0.75rem);color:var(--app-fail, rgb(var(--accent-red, 244 63 108)))\">\n                      <span style=\"font-family:var(--font-jetbrains);font-size:0.68rem;letter-spacing:0.08em;text-transform:uppercase\">invalid process plane<\u002Fspan>\n                      <span style=\"font-size:0.8rem;color:var(--app-ink-soft)\">{{ wizErrorDetail }}<\u002Fspan>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n                <\u002Fsection>\n              <\u002Fsc-if>\n\n              <!-- 25 · diff · brief · ledger archetypes, each on real project data (WP-55/56/57) -->\n              <sc-if value=\"{{ isPkgDiff }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"ArchetypeHost\" archetype=\"diff\" diff=\"{{ diffSpec }}\" hint-size=\"100%,420px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n              <sc-if value=\"{{ isClientBrief }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"ArchetypeHost\" archetype=\"brief\" brief=\"{{ briefSpec }}\" hint-size=\"100%,520px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n              <sc-if value=\"{{ isClientLedger }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"ArchetypeHost\" archetype=\"ledger\" ledger=\"{{ ledgerSpec }}\" hint-size=\"100%,480px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 24 · Client Index · Approve — the 'gate' archetype fed by approvals.js's real queue (WP-52) -->\n              <sc-if value=\"{{ isClientGate }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"ArchetypeHost\" archetype=\"gate\" proposal=\"{{ gateProposal }}\" gates=\"{{ gateRowsReal }}\" blockedReason=\"{{ gateBlockedReason }}\" onApprove=\"{{ gateApprove }}\" onReject=\"{{ gateReject }}\" hint-size=\"100%,420px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 20 · Package Builder · catalog → composer → quote → child DC (lazy) -->\n              <sc-if value=\"{{ isPackages }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PanePackages\" screen-label=\"{{ activeLabel }}\" tab=\"{{ subLabel }}\" hint-size=\"100%,900px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 19 · Deliverables · client-facing content viewer, ported from the mined Content Viewer package → child DC (lazy) -->\n              <sc-if value=\"{{ isDelivery }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneDelivery\" screen-label=\"{{ activeLabel }}\" tab=\"{{ subLabel }}\" hint-size=\"100%,900px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 16 · Production Protocol · native port of the source screen → child DC (lazy) -->\n              <sc-if value=\"{{ isProto }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneProtocol\" screen-label=\"{{ activeLabel }}\" hint-size=\"100%,900px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 15 · Intelligence · Unified Ask (ua- spec) → child DC (lazy) -->\n              <sc-if value=\"{{ isAsk }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneAsk\" screen-label=\"{{ activeLabel }}\" ua=\"{{ ua }}\" hint-size=\"100%,820px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 14 · Work · agent swimlanes → child DC (lazy) -->\n              <sc-if value=\"{{ isWk }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneWork\" screen-label=\"{{ activeLabel }}\" wk=\"{{ wk }}\" hint-size=\"100%,760px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 13 · Services · toggle grid → child DC (lazy) -->\n              <sc-if value=\"{{ isSvc }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneServices\" screen-label=\"{{ activeLabel }}\" svc=\"{{ svc }}\" hint-size=\"100%,740px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 13b · Personal Home · native port of A-Personal Home (O5) → child DC (lazy) -->\n              <sc-if value=\"{{ isHome }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneHome\" screen-label=\"{{ activeLabel }}\" tab=\"{{ homeTab }}\" on-tab=\"{{ homeGoTab }}\" on-stream=\"{{ homeGoStream }}\" hint-size=\"100%,860px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 12 · Finance · EÜR grid → child DC (lazy) -->\n              <sc-if value=\"{{ isFin }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneFinance\" screen-label=\"{{ activeLabel }}\" fin=\"{{ fin }}\" hint-size=\"100%,760px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 11 · Decisions · Zooey run trace → child DC (lazy) -->\n              <sc-if value=\"{{ isRuntrace }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneDecisions\" screen-label=\"{{ activeLabel }}\" dt=\"{{ dt }}\" sel=\"{{ sel }}\" hint-size=\"100%,780px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 11b · Review · split-diff + PR panel → child DC (lazy) -->\n              <sc-if value=\"{{ isRev }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneReview\" screen-label=\"{{ activeLabel }}\" rev=\"{{ rev }}\" hint-size=\"100%,780px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 10 · MVP client canvas → child DC (lazy) -->\n              <sc-if value=\"{{ isClient }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneClientCanvas\" screen-label=\"{{ activeLabel }}\" client=\"{{ client }}\" hint-size=\"100%,760px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 09 · GTM outreach → child DC (lazy) -->\n              <sc-if value=\"{{ isMail }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneOutreach\" screen-label=\"{{ activeLabel }}\" mail=\"{{ mail }}\" tab=\"{{ subLabel }}\" suite=\"{{ outSuite }}\" on-confirm=\"{{ mkConfirm }}\" hint-size=\"100%,780px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n              <!-- D200: Outbound · Send = Outreach › Queue + Follow-up ladder as one node -->\n              <sc-if value=\"{{ isMailLadder }}\" hint-placeholder-val=\"{{ false }}\">\n                <div style=\"margin-top:var(--section-gap)\">\n                  <dc-import name=\"PaneOutreach\" screen-label=\"{{ activeLabel }}\" mail=\"{{ mail }}\" tab=\"Follow-up ladder\" suite=\"{{ outSuite }}\" on-confirm=\"{{ mkConfirm }}\" hint-size=\"100%,600px\"><\u002Fdc-import>\n                <\u002Fdiv>\n              <\u002Fsc-if>\n\n              <!-- 08a · Campaign Builder v2 → child DC (lazy) -->\n              <sc-if value=\"{{ isBuilder }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneAdsBuilder\" screen-label=\"{{ activeLabel }}\" builder=\"{{ builder }}\" builderAlertIcon=\"{{ builderAlertIcon }}\" builderClockIcon=\"{{ builderClockIcon }}\" hint-size=\"100%,780px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 08b · Assets & Brand Safety → child DC (lazy) -->\n              <sc-if value=\"{{ isAssets }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneAdsAssets\" screen-label=\"{{ activeLabel }}\" assetsInfoIcon=\"{{ assetsInfoIcon }}\" assets=\"{{ assets }}\" hint-size=\"100%,700px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 08c · Conversions → child DC (lazy) -->\n              <sc-if value=\"{{ isConv }}\" hint-placeholder-val=\"{{ false }}\">\n                <dc-import name=\"PaneAdsConversions\" screen-label=\"{{ activeLabel }}\" conv=\"{{ conv }}\" hint-size=\"100%,680px\"><\u002Fdc-import>\n              <\u002Fsc-if>\n\n              <!-- 08d · Budget Pacing & Kill-Switch -->\n              <sc-if value=\"{{ isPacing }}\" hint-placeholder-val=\"{{ false }}\">\n                <div style=\"max-width:1200px;display:flex;flex-direction:column;gap:var(--section-gap)\">\n                  <!-- pacing bars -->\n                  <div style=\"display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:var(--grid-gap)\">\n                    <sc-for list=\"{{ pacing.phases }}\" as=\"p\" hint-placeholder-count=\"3\">\n                      <div style=\"border:1px solid var(--app-rule);border-radius:var(--radius);background:var(--app-surface);padding:18px\">\n                        <div style=\"display:flex;align-items:center;justify-content:space-between;margin-bottom:6px\">\n                          <span style=\"font-size:0.92rem;font-weight:700;color:var(--app-ink)\">{{ p.phase }}<\u002Fspan>\n                          <span class=\"mono\" style=\"font-size:0.56rem;padding:2px 8px;border-radius:9999px;border:1px solid {{ p.badge.color }};color:{{ p.badge.color }}\">{{ p.badge.label }}<\u002Fspan>\n                        <\u002Fdiv>\n                        <div class=\"mono\" style=\"font-size:0.7rem;color:var(--app-ink-faint);margin-bottom:14px\">{{ p.window }} · {{ p.budget }}<\u002Fdiv>\n                        <div style=\"height:8px;border-radius:4px;background:var(--app-rule-soft);overflow:hidden;margin-bottom:8px\"><div style=\"width:{{ p.pctW }};height:100%;background:{{ p.barColor }};border-radius:4px;transition:width .5s var(--liquid-ease)\"><\u002Fdiv><\u002Fdiv>\n                        <div style=\"display:flex;justify-content:space-between\"><span class=\"mono\" style=\"font-size:0.72rem;color:var(--app-ink)\">{{ p.spent }}<\u002Fspan><span class=\"mono\" style=\"font-size:0.72rem;color:var(--app-ink-faint)\">{{ p.pct }}%<\u002Fspan><\u002Fdiv>\n                        <div style=\"font-size:0.76rem;color:var(--app-ink-faint);margin-top:10px;line-height:1.4\">{{ p.target }}<\u002Fdiv>\n                      <\u002Fdiv>\n                    <\u002Fsc-for>\n                  <\u002Fdiv>\n\n                  <div style=\"display:flex;gap:var(--grid-gap);align-items:flex-start;flex-wrap:wrap\">\n                    <!-- greenlight checklist -->\n                    <div style=\"flex:1;min-width:min(320px,100%);border:1px solid var(--app-rule);border-radius:var(--radius);background:var(--app-surface);overflow:hidden\">\n                      <div style=\"padding:14px 18px;border-bottom:1px solid var(--app-rule);display:flex;align-items:center;gap:10px\">\n                        <span class=\"label-nav\" style=\"font-size:0.64rem;color:var(--app-ink);flex:1\">Day-14 greenlight · ALL required<\u002Fspan>\n                        <span class=\"mono\" style=\"font-size:0.7rem;color:{{ pacing.glColor }}\">{{ pacing.glMet }} / {{ pacing.glTotal }}<\u002Fspan>\n                      <\u002Fdiv>\n                      <sc-for list=\"{{ pacing.greenlight }}\" as=\"g\" hint-placeholder-count=\"4\">\n                        <div style=\"display:flex;align-items:center;gap:12px;padding:13px 18px;border-bottom:1px solid var(--app-rule)\">\n                          <span style=\"width:22px;height:22px;border-radius:6px;flex-shrink:0;border:1.5px solid {{ g.color }};display:inline-flex;align-items:center;justify-content:center;color:{{ g.color }}\">{{ g.icon }}<\u002Fspan>\n                          <div style=\"flex:1;min-width:0\"><div style=\"font-size:0.85rem;color:var(--app-ink)\">{{ g.crit }}<\u002Fdiv><div class=\"mono\" style=\"font-size:0.68rem;color:var(--app-ink-faint);margin-top:2px\">target {{ g.target }}<\u002Fdiv><\u002Fdiv>\n                          <span class=\"mono\" style=\"font-size:0.82rem;color:{{ g.color }}\">{{ g.valueLabel }}<\u002Fspan>\n                        <\u002Fdiv>\n                      <\u002Fsc-for>\n                      <div style=\"padding:12px 18px;font-size:0.76rem;color:var(--app-ink-faint);font-style:italic\">Any miss → shrink, don't scale.<\u002Fdiv>\n                    <\u002Fdiv>\n\n                    <!-- bid progression -->\n                    <div style=\"flex:1;min-width:min(320px,100%);border:1px solid var(--app-rule);border-radius:var(--radius);background:var(--app-surface);overflow:hidden\">\n                      <div style=\"padding:14px 18px;border-bottom:1px solid var(--app-rule)\"><span class=\"label-nav\" style=\"font-size:0.64rem;color:var(--app-ink)\">Bid strategy progression<\u002Fspan><\u002Fdiv>\n                      <sc-for list=\"{{ pacing.bidprog }}\" as=\"b\" hint-placeholder-count=\"5\">\n                        <div style=\"display:flex;align-items:center;gap:12px;padding:12px 18px;border-bottom:1px solid var(--app-rule)\">\n                          <span style=\"width:8px;height:8px;border-radius:50%;flex-shrink:0;background:{{ b.color }};box-shadow:0 0 8px {{ b.color }}\"><\u002Fspan>\n                          <div style=\"flex:1;min-width:0\"><div class=\"mono\" style=\"font-size:0.78rem;color:var(--app-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis\">{{ b.campaign }}<\u002Fdiv><div style=\"font-size:0.74rem;color:var(--app-ink-faint);margin-top:2px\">{{ b.label }}<\u002Fdiv><\u002Fdiv>\n                        <\u002Fdiv>\n                      <\u002Fsc-for>\n                    <\u002Fdiv>\n                  <\u002Fdiv>\n\n                  <!-- kill-switch traffic lights -->\n                  <div style=\"border:1px solid var(--app-rule);border-radius:var(--radius);background:var(--app-surface);overflow:hidden\">\n                    <div style=\"padding:14px 18px;border-bottom:1px solid var(--app-rule);display:flex;align-items:center;gap:12px\">\n                      <span class=\"label-nav\" style=\"font-size:0.64rem;color:var(--app-ink);flex:1\">Kill-switch thresholds · live vs /campaigns/metrics<\u002Fspan>\n                      <span class=\"mono\" style=\"font-size:0.66rem;color:var(--app-success)\">● {{ pacing.killGreen }}<\u002Fspan>\n                      <span class=\"mono\" style=\"font-size:0.66rem;color:var(--app-warn)\">● {{ pacing.killAmber }}<\u002Fspan>\n                      <span class=\"mono\" style=\"font-size:0.66rem;color:var(--app-fail)\">● {{ pacing.killRed }}<\u002Fspan>\n                    <\u002Fdiv>\n                    <sc-for list=\"{{ pacing.kill }}\" as=\"k\" hint-placeholder-count=\"8\">\n                      <div style=\"display:flex;align-items:center;gap:14px;padding:13px 18px;border-bottom:1px solid var(--app-rule);flex-wrap:wrap\">\n                        <span style=\"width:10px;height:10px;border-radius:50%;flex-shrink:0;background:{{ k.color }};box-shadow:0 0 8px {{ k.color }}\"><\u002Fspan>\n                        <span class=\"mono\" style=\"font-size:0.62rem;width:74px;flex-shrink:0;color:{{ k.scopeColor }};text-transform:uppercase\">{{ k.scope }}<\u002Fspan>\n                        <span style=\"flex:1;min-width:180px;font-size:0.82rem;color:var(--app-ink);line-height:1.4\">{{ k.rule }}<\u002Fspan>\n                        <span class=\"mono\" style=\"font-size:0.72rem;color:{{ k.color }};flex-shrink:0;min-width:0;text-align:right;white-space:normal\">{{ k.hint }}<\u002Fspan>\n                      <\u002Fdiv>\n                    <\u002Fsc-for>\n                    <div style=\"padding:16px 18px;display:flex;align-items:center;gap:14px;background:rgb(var(--accent-red) / 0.04)\">\n                      <div style=\"flex:1\"><div style=\"font-size:0.86rem;font-weight:600;color:var(--app-ink)\">Emergency: Pause ALL campaigns<\u002Fdiv><div style=\"font-size:0.76rem;color:var(--app-ink-faint);margin-top:2px\">Highest blast radius — loops PAUSED across every enabled campaign. Type-to-confirm + hold.<\u002Fdiv><\u002Fdiv>\n                      <button onClick=\"{{ pacing.openPauseAll }}\" style=\"display:inline-flex;align-items:center;gap:9px;height:42px;padding:0 18px;border-radius:var(--radius-btn);border:1px solid var(--app-fail);background:rgb(var(--accent-red) / 0.1);color:var(--app-fail);font-family:var(--font-inter);font-weight:700;font-size:0.82rem;cursor:pointer\">{{ pacing.powerIcon }} Pause ALL<\u002Fbutton>\n                    <\u002Fdiv>\n                  <\u002Fdiv>\n                <\u002Fdiv>\n              <\u002Fsc-if>\n\n            <\u002Fsc-if>\n          <\u002Fsc-if>\n\n        <\u002Fdiv>\n      <\u002Fmain>\n\n      <!-- WP-20: Record-detail plane. Fixed overlay so it never squeezes the ~318px content column\n           per its acceptance criterion; backdrop click + Esc + × all close. -->\n      <sc-if value=\"{{ detailOpen }}\" hint-placeholder-val=\"{{ false }}\">\n        <div data-testid=\"detail-backdrop\" onClick=\"{{ detail.close }}\" style=\"position:fixed;inset:0;z-index:60;background:rgb(0 0 0 / 0.68);backdrop-filter:blur(4px)\"><\u002Fdiv>\n        <aside data-testid=\"detail-drawer\" role=\"dialog\" aria-label=\"Record detail\" style=\"position:fixed;top:0;right:0;bottom:0;z-index:61;width:{{ detailWidth }};max-width:100%;background:rgb(var(--basalt-surface));border-left:1px solid var(--app-rule);box-shadow:-16px 0 60px rgb(0 0 0 / 0.6);display:flex;flex-direction:column;overflow-y:auto;transition:width .22s var(--liquid-ease)\">\n          <div style=\"display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:20px 20px 16px;border-bottom:1px solid var(--app-rule)\">\n            <div style=\"min-width:0\">\n              <div class=\"label-overline\" style=\"font-size:0.6rem;color:var(--app-accent);margin-bottom:6px;overflow-wrap:anywhere\">{{ detail.kind }} · {{ detail.id }}<\u002Fdiv>\n              <div style=\"font-family:var(--font-inter);font-weight:700;font-size:1.05rem;color:var(--app-ink);overflow-wrap:anywhere\">{{ detail.title }}<\u002Fdiv>\n            <\u002Fdiv>\n            <button onClick=\"{{ detail.close }}\" aria-label=\"Close\" data-testid=\"detail-close\" style=\"flex-shrink:0;width:30px;height:30px;border-radius:8px;border:1px solid var(--app-rule);background:transparent;color:var(--app-ink-soft);cursor:pointer;display:flex;align-items:center;justify-content:center\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"><\u002Fpath><\u002Fsvg>\n            <\u002Fbutton>\n          <\u002Fdiv>\n          <div style=\"padding:14px 20px\">\n            <span class=\"mono\" style=\"font-size:0.66rem;padding:3px 10px;border-radius:9999px;color:{{ detail.chipColor }};background:rgb(var(--text) / 0.06);border:1px solid var(--app-rule)\">{{ detail.chip }}<\u002Fspan>\n          <\u002Fdiv>\n          <div style=\"display:flex;gap:8px;padding:0 20px 16px;flex-wrap:wrap\">\n            <button onClick=\"{{ detail.openFullPage }}\" style=\"height:32px;padding:0 12px;border-radius:var(--radius-btn);border:1px solid var(--app-rule);background:transparent;color:var(--app-ink);font-family:var(--font-inter);font-size:0.72rem;font-weight:600;cursor:pointer\">Open full page<\u002Fbutton>\n            <button style=\"height:32px;padding:0 12px;border-radius:var(--radius-btn);border:1px solid var(--app-rule);background:transparent;color:var(--app-ink-soft);font-family:var(--font-inter);font-size:0.72rem;cursor:pointer\">Replay<\u002Fbutton>\n            <button style=\"height:32px;padding:0 12px;border-radius:var(--radius-btn);border:1px solid var(--app-rule);background:transparent;color:var(--app-ink-soft);font-family:var(--font-inter);font-size:0.72rem;cursor:pointer\">Copy ID<\u002Fbutton>\n          <\u002Fdiv>\n\n          <!-- item 13: split-view Readable/Rich media toggle — only once escalated -->\n          <sc-if value=\"{{ detailSplit }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"display:flex;align-items:center;gap:10px;padding:0 20px 14px\">\n              <span class=\"label-overline\" style=\"font-size:0.56rem;color:var(--app-ink-faint)\">VIEW<\u002Fspan>\n              <div style=\"display:inline-flex;background:rgb(var(--basalt-deep) / 0.5);border:1px solid var(--app-rule);border-radius:9999px;padding:3px\">\n                <button onClick=\"{{ setViewerReadable }}\" style=\"border:0;background:{{ viewerReadableBg }};color:{{ viewerReadableInk }};font-size:0.68rem;font-weight:600;padding:5px 12px;border-radius:9999px;cursor:pointer;font-family:var(--font-inter)\">Readable<\u002Fbutton>\n                <button onClick=\"{{ setViewerRich }}\" style=\"border:0;background:{{ viewerRichBg }};color:{{ viewerRichInk }};font-size:0.68rem;font-weight:600;padding:5px 12px;border-radius:9999px;cursor:pointer;font-family:var(--font-inter)\">Rich media<\u002Fbutton>\n              <\u002Fdiv>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n\n          <!-- Readable: plain typographic rows, legible measure, no embeds -->\n          <!-- Toolbox document · the predetermined context fields, editable. The list comes from the\n               template's own schema (context_keys + fields[].context_map/ai_prompt); values come from the\n               tenant context pack. Editing here is what \"the user edits, the system optimises\" means. -->\n          <sc-if value=\"{{ detail.isDoc }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"display:flex;flex-direction:column;gap:14px;padding:4px 20px 24px\">\n              <div style=\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\">\n                <span class=\"label-overline\" style=\"font-size:0.56rem;color:var(--app-ink-faint)\">Context fields · {{ detail.doc.count }} declared by this template · {{ detail.doc.summary }}<\u002Fspan>\n                <span style=\"flex:1\"><\u002Fspan>\n                <button onClick=\"{{ detail.doc.optimise }}\" disabled=\"{{ detail.doc.busy }}\" style=\"display:inline-flex;align-items:center;gap:7px;height:32px;padding:0 13px;border:none;border-radius:var(--radius-btn);background:var(--app-accent);color:rgb(var(--basalt-deep));font-size:0.72rem;font-weight:600;cursor:pointer\">{{ detail.doc.optimiseLabel }}<\u002Fbutton>\n                <button onClick=\"{{ detail.doc.finalise }}\" style=\"display:inline-flex;align-items:center;gap:7px;height:32px;padding:0 13px;border:1px solid var(--app-rule);border-radius:var(--radius-btn);background:transparent;color:var(--app-ink-soft);font-size:0.72rem;cursor:pointer\">{{ detail.doc.finaliseLabel }}<\u002Fbutton>\n              <\u002Fdiv>\n              <!-- Optimise proposes; it never writes silently. Accept applies, Discard drops. -->\n              <sc-if value=\"{{ detail.docDiff }}\" hint-placeholder-val=\"{{ false }}\">\n                <div style=\"display:flex;flex-direction:column;gap:10px;padding:13px;border:1px solid var(--app-accent);border-radius:var(--radius);background:rgb(var(--basalt-deep) / 0.7)\">\n                  <div style=\"display:flex;align-items:center;gap:9px;flex-wrap:wrap\">\n                    <span class=\"label-overline\" style=\"font-size:0.56rem;color:var(--app-accent)\">Proposed · review before it is written<\u002Fspan>\n                    <span style=\"flex:1\"><\u002Fspan>\n                    <sc-if value=\"{{ detail.docDiff.hasRows }}\" hint-placeholder-val=\"{{ false }}\">\n                      <button onClick=\"{{ detail.docDiff.accept }}\" style=\"height:28px;padding:0 12px;border:none;border-radius:var(--radius-btn);background:var(--app-success);color:rgb(var(--basalt-deep));font-size:0.7rem;font-weight:600;cursor:pointer\">Accept<\u002Fbutton>\n                    <\u002Fsc-if>\n                    <button onClick=\"{{ detail.docDiff.discard }}\" style=\"height:28px;padding:0 12px;border:1px solid var(--app-rule);border-radius:var(--radius-btn);background:transparent;color:var(--app-ink-soft);font-size:0.7rem;cursor:pointer\">Discard<\u002Fbutton>\n                  <\u002Fdiv>\n                  <sc-if value=\"{{ detail.docDiff.note }}\" hint-placeholder-val=\"{{ false }}\">\n                    <span style=\"font-size:0.72rem;color:var(--app-warn);line-height:1.5\">{{ detail.docDiff.note }}<\u002Fspan>\n                  <\u002Fsc-if>\n                  <sc-for list=\"{{ detail.docDiff.rows }}\" as=\"dr2\" hint-placeholder-count=\"3\">\n                    <div style=\"display:flex;flex-direction:column;gap:3px;padding:8px 0;border-top:1px solid var(--app-rule-soft)\">\n                      <span class=\"mono\" style=\"font-size:0.6rem;color:var(--app-ink-faint)\">{{ dr2.label }}<\u002Fspan>\n                      <span style=\"font-size:0.78rem;color:var(--app-ink);line-height:1.5\">{{ dr2.after }}<\u002Fspan>\n                    <\u002Fdiv>\n                  <\u002Fsc-for>\n                <\u002Fdiv>\n              <\u002Fsc-if>\n              <sc-for list=\"{{ detail.doc.fields }}\" as=\"fd\" hint-placeholder-count=\"6\">\n                <div style=\"display:flex;flex-direction:column;gap:6px;padding:11px 13px;border:1px solid var(--app-rule);border-radius:var(--radius);background:rgb(var(--basalt-deep) / 0.45)\">\n                  <div style=\"display:flex;align-items:center;gap:9px;flex-wrap:wrap\">\n                    <span style=\"width:7px;height:7px;border-radius:50%;flex-shrink:0;background:{{ fd.dot }}\"><\u002Fspan>\n                    <span style=\"font-size:0.8rem;font-weight:600;color:var(--app-ink)\">{{ fd.label }}<\u002Fspan>\n                    <span class=\"mono\" style=\"font-size:0.58rem;letter-spacing:0.08em;text-transform:uppercase;color:{{ fd.dot }}\">{{ fd.state }}<\u002Fspan>\n                    <span style=\"flex:1\"><\u002Fspan>\n                    <span class=\"mono\" style=\"font-size:0.58rem;color:var(--app-ink-faint)\">{{ fd.source }}<\u002Fspan>\n                  <\u002Fdiv>\n                  <textarea value=\"{{ fd.value }}\" onChange=\"{{ fd.onChange }}\" rows=\"{{ fd.rows }}\" placeholder=\"{{ fd.placeholder }}\" style=\"width:100%;box-sizing:border-box;resize:vertical;padding:9px 11px;border:1px solid var(--app-rule);border-radius:9px;background:rgb(var(--basalt-surface) / 0.7);color:var(--app-ink);font:400 0.8rem/1.5 var(--font-inter);outline:none\" style-focus=\"border-color:var(--app-accent)\"><\u002Ftextarea>\n                  <sc-if value=\"{{ fd.hint }}\" hint-placeholder-val=\"{{ false }}\">\n                    <span style=\"font-size:0.68rem;color:var(--app-ink-faint);line-height:1.45\">{{ fd.hint }}<\u002Fspan>\n                  <\u002Fsc-if>\n                <\u002Fdiv>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n          <sc-if value=\"{{ viewerIsReadable }}\" hint-placeholder-val=\"{{ true }}\">\n            <div style=\"display:flex;flex-direction:column;gap:2px;padding:4px 20px 24px;max-width:{{ readableMaxWidth }}\">\n              <sc-for list=\"{{ detail.sections }}\" as=\"sec\" hint-placeholder-count=\"4\">\n                <div style=\"display:grid;grid-template-columns:minmax(min(100px,40%),120px) 1fr;gap:10px;padding:9px 0;border-bottom:1px solid var(--app-rule-soft)\">\n                  <span class=\"label-overline\" style=\"font-size:0.56rem;color:var(--app-ink-faint)\">{{ sec.label }}<\u002Fspan>\n                  <span style=\"font-family:var(--font-inter);font-size:0.82rem;color:var(--app-ink);line-height:1.6;min-width:0;overflow-wrap:anywhere;max-width:68ch\">{{ sec.value }}<\u002Fspan>\n                <\u002Fdiv>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n\n          <!-- Rich media: card-per-section, heavier visual weight (split-view only, needs the width) -->\n          <sc-if value=\"{{ viewerIsRich }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(min(220px,100%),1fr));gap:12px;padding:4px 20px 24px\">\n              <sc-for list=\"{{ detail.sections }}\" as=\"sec2\" hint-placeholder-count=\"4\">\n                <div style=\"display:flex;flex-direction:column;gap:8px;padding:16px;border:1px solid var(--app-rule);border-radius:var(--radius);background:rgb(var(--basalt-deep) / 0.4)\">\n                  <span class=\"label-system\" style=\"font-size:0.56rem;letter-spacing:0.1em;color:var(--app-accent)\">{{ sec2.label }}<\u002Fspan>\n                  <span style=\"font-family:var(--font-inter);font-size:0.95rem;font-weight:500;color:var(--app-ink);line-height:1.5;overflow-wrap:anywhere\">{{ sec2.value }}<\u002Fspan>\n                <\u002Fdiv>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n        <\u002Faside>\n      <\u002Fsc-if>\n\n      <!-- Right panel · Agent roster -->      <sc-if value=\"{{ rosterSlim }}\" hint-placeholder-val=\"{{ false }}\">\n        <button onClick=\"{{ toggleRoster }}\" aria-label=\"Open agent roster\" title=\"Open agent roster\" data-testid=\"roster-slim\" style=\"position:absolute;top:0;right:0;bottom:0;width:44px;z-index:44;border:none;border-left:1px solid var(--border);background:rgb(var(--basalt-deep) / 0.85);display:flex;flex-direction:column;align-items:center;gap:8px;padding-top:12px;cursor:pointer;color:rgb(var(--text))\">\n          <sc-for list=\"{{ slimCounts }}\" as=\"c\" hint-placeholder-count=\"2\">\n            <span style=\"display:inline-flex;align-items:center;gap:3px;height:20px;padding:0 6px;border-radius:9999px;background:{{ c.chipBg }};border:1px solid {{ c.chipBorder }}\">\n              <sc-if value=\"{{ c.isNeeds }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"8\" height=\"8\" aria-hidden=\"true\"><path d=\"M5 0.6 9.4 5 5 9.4 0.6 5z\" fill=\"rgb(var(--status-needs, 244 63 108))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if>\n              <sc-if value=\"{{ c.isStuck }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"8\" height=\"8\" aria-hidden=\"true\"><path d=\"M5 0.8 9.5 9H0.5z\" fill=\"rgb(var(--status-stuck, 248 217 116))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if>\n              <sc-if value=\"{{ c.isDone }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"8\" height=\"8\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"4.3\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\"><\u002Fcircle><path d=\"M3 5.1 4.4 6.5 7.1 3.7\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><\u002Fpath><\u002Fsvg><\u002Fsc-if>\n              <span style=\"font-family:var(--font-jetbrains);font-size:0.6rem\">{{ c.n }}<\u002Fspan>\n            <\u002Fspan>\n          <\u002Fsc-for>\n          <sc-for list=\"{{ slimShapes }}\" as=\"c\" hint-placeholder-count=\"2\"><span style=\"display:inline-flex\"><sc-if value=\"{{ c.isRunning }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"10\" height=\"10\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"3.2\" fill=\"rgb(var(--status-running, 0 243 255))\"><\u002Fcircle><circle cx=\"5\" cy=\"5\" r=\"4.5\" fill=\"none\" stroke=\"rgb(var(--status-running, 0 243 255) / 0.35)\" stroke-width=\"1\"><\u002Fcircle><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ c.isIdle }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"10\" height=\"10\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"3.6\" fill=\"none\" stroke=\"rgb(var(--status-idle, 224 224 222) / 0.5)\" stroke-width=\"1.4\"><\u002Fcircle><\u002Fsvg><\u002Fsc-if><\u002Fspan><\u002Fsc-for>\n        <\u002Fbutton>\n      <\u002Fsc-if>\n      <sc-if value=\"{{ rosterEdge }}\" hint-placeholder-val=\"{{ false }}\">\n        <div aria-hidden=\"true\" data-testid=\"roster-edge\" onMouseEnter=\"{{ rosterPeekOn }}\" style=\"position:absolute;top:0;right:0;bottom:0;width:2cm;z-index:45\"><\u002Fdiv>\n      <\u002Fsc-if>\n      <sc-if value=\"{{ rightPanel }}\" hint-placeholder-val=\"{{ true }}\">\n        <aside data-testid=\"roster-panel\" style=\"{{ rosterStyle }}\" onMouseLeave=\"{{ rosterPeekOff }}\">\n          <div style=\"display:flex;align-items:center;gap:8px;color:var(--app-ink-soft, rgb(var(--text) / 0.7));margin-bottom:14px\">\n            <sc-if value=\"{{ rosterOpen }}\" hint-placeholder-val=\"{{ true }}\">\n              <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"5\" y=\"8\" width=\"14\" height=\"10\" rx=\"2\"><\u002Frect><line x1=\"12\" y1=\"4\" x2=\"12\" y2=\"8\"><\u002Fline><circle cx=\"12\" cy=\"4\" r=\"1.2\" fill=\"currentColor\"><\u002Fcircle><circle cx=\"9.5\" cy=\"13\" r=\"1.2\" fill=\"currentColor\"><\u002Fcircle><circle cx=\"14.5\" cy=\"13\" r=\"1.2\" fill=\"currentColor\"><\u002Fcircle><\u002Fsvg>\n            <\u002Fsc-if>\n            <sc-if value=\"{{ rosterOpen }}\" hint-placeholder-val=\"{{ true }}\">\n              <span class=\"label-nav\" style=\"font-size:0.64rem;white-space:nowrap;flex:1\">Agent roster · {{ rosterCount }}<\u002Fspan>\n            <\u002Fsc-if>\n            <sc-if value=\"{{ rosterPeeking }}\" hint-placeholder-val=\"{{ false }}\">\n            <span class=\"mono\" style=\"font-size:0.54rem;letter-spacing:0.1em;color:var(--app-ink-faint, rgb(var(--text) / 0.62))\">PIN<\u002Fspan>\n            <button onClick=\"{{ toggleRoster }}\" role=\"switch\" aria-checked=\"{{ rosterPinned }}\" aria-label=\"Pin agent roster\" title=\"Pin agent roster\" style=\"flex-shrink:0;width:34px;height:20px;border-radius:9999px;border:1px solid {{ rosterTrackBorder }};background:{{ rosterTrack }};position:relative;cursor:pointer;padding:0;transition:background .3s var(--liquid-ease),border-color .3s var(--liquid-ease)\">\n              <span style=\"position:absolute;top:1px;left:{{ rosterKnobLeft }};width:16px;height:16px;border-radius:50%;background:{{ rosterKnob }};transition:left .3s var(--liquid-ease)\"><\u002Fspan>\n            <\u002Fbutton>\n            <\u002Fsc-if>\n          <\u002Fdiv>\n          <sc-if value=\"{{ rosterOpen }}\" hint-placeholder-val=\"{{ true }}\">\n          <!-- D177 · roster by attention. Same four kinds of content (agents · projects · tasks · blockers), one\n               source (agent-registry.js): blockers = NEEDS YOU, tasks = progress on the agent row, projects = the\n               project view. Status shapes use the pinned DS base palette — tenants override --accent-*, status must not move. -->\n          <div role=\"tablist\" aria-label=\"Roster view\" style=\"display:flex;gap:2px;padding:2px;border-radius:8px;border:1px solid var(--border);margin-bottom:10px\">\n            <sc-for list=\"{{ rvViews }}\" as=\"v\" hint-placeholder-count=\"2\">\n              <button role=\"tab\" aria-selected=\"{{ v.on }}\" onClick=\"{{ v.pick }}\" style=\"flex:1;height:26px;border:none;border-radius:6px;background:{{ v.bg }};color:{{ v.ink }};font-family:var(--font-jetbrains);font-size:0.58rem;letter-spacing:0.1em;cursor:pointer\">{{ v.label }}<\u002Fbutton>\n            <\u002Fsc-for>\n          <\u002Fdiv>\n          <div style=\"display:flex;flex-wrap:wrap;gap:5px;margin-bottom:12px\">\n            <sc-for list=\"{{ rvChips }}\" as=\"c\" hint-placeholder-count=\"5\">\n              <button onClick=\"{{ c.pick }}\" aria-pressed=\"{{ c.on }}\" title=\"{{ c.label }}\" style=\"display:inline-flex;align-items:center;gap:5px;height:24px;padding:0 8px;border-radius:9999px;border:1px solid {{ c.bd }};background:{{ c.bg }};color:rgb(var(--text));font-family:var(--font-jetbrains);font-size:0.6rem;cursor:pointer\"><sc-if value=\"{{ c.isNeeds }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"8\" height=\"8\" aria-hidden=\"true\"><path d=\"M5 0.6 9.4 5 5 9.4 0.6 5z\" fill=\"rgb(var(--status-needs, 244 63 108))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ c.isStuck }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"8\" height=\"8\" aria-hidden=\"true\"><path d=\"M5 0.8 9.5 9H0.5z\" fill=\"rgb(var(--status-stuck, 248 217 116))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ c.isDone }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"8\" height=\"8\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"4.3\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\"><\u002Fcircle><path d=\"M3 5.1 4.4 6.5 7.1 3.7\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ c.isRunning }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"8\" height=\"8\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"3.2\" fill=\"rgb(var(--status-running, 0 243 255))\"><\u002Fcircle><circle cx=\"5\" cy=\"5\" r=\"4.5\" fill=\"none\" stroke=\"rgb(var(--status-running, 0 243 255) / 0.35)\" stroke-width=\"1\"><\u002Fcircle><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ c.isIdle }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"8\" height=\"8\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"3.6\" fill=\"none\" stroke=\"rgb(var(--status-idle, 224 224 222) / 0.5)\" stroke-width=\"1.4\"><\u002Fcircle><\u002Fsvg><\u002Fsc-if><span>{{ c.n }}<\u002Fspan><span style=\"color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ c.short }}<\u002Fspan><\u002Fbutton>\n            <\u002Fsc-for>\n          <\u002Fdiv>\n          <sc-if value=\"{{ rvAttention }}\" hint-placeholder-val=\"{{ true }}\">\n            <div style=\"display:flex;flex-direction:column;gap:12px\">\n              <sc-for list=\"{{ rvGroups }}\" as=\"g\" hint-placeholder-count=\"4\">\n                <div>\n                  <button onClick=\"{{ g.toggle }}\" aria-expanded=\"{{ g.open }}\" style=\"display:flex;align-items:center;gap:7px;width:100%;padding:0;margin-bottom:4px;border:none;background:transparent;cursor:pointer;color:var(--app-ink-soft, rgb(var(--text) / 0.7));text-align:left\" style-hover=\"color:rgb(var(--text))\">\n                    <span aria-hidden=\"true\" style=\"display:inline-flex;transform:{{ g.caret }};transition:transform .2s var(--liquid-ease)\"><svg viewBox=\"0 0 24 24\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"><\u002Fpolyline><\u002Fsvg><\u002Fspan>\n                    <sc-if value=\"{{ g.isNeeds }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><path d=\"M5 0.6 9.4 5 5 9.4 0.6 5z\" fill=\"rgb(var(--status-needs, 244 63 108))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ g.isStuck }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><path d=\"M5 0.8 9.5 9H0.5z\" fill=\"rgb(var(--status-stuck, 248 217 116))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ g.isDone }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"4.3\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\"><\u002Fcircle><path d=\"M3 5.1 4.4 6.5 7.1 3.7\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ g.isRunning }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"3.2\" fill=\"rgb(var(--status-running, 0 243 255))\"><\u002Fcircle><circle cx=\"5\" cy=\"5\" r=\"4.5\" fill=\"none\" stroke=\"rgb(var(--status-running, 0 243 255) / 0.35)\" stroke-width=\"1\"><\u002Fcircle><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ g.isIdle }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"3.6\" fill=\"none\" stroke=\"rgb(var(--status-idle, 224 224 222) / 0.5)\" stroke-width=\"1.4\"><\u002Fcircle><\u002Fsvg><\u002Fsc-if>\n                    <span class=\"label-nav\" style=\"font-size:0.62rem;flex:1\">{{ g.label }} · {{ g.n }}<\u002Fspan>\n                    <span class=\"mono\" style=\"font-size:0.54rem;color:var(--app-ink-faint, rgb(var(--text) / 0.62))\">{{ g.hint }}<\u002Fspan>\n                  <\u002Fbutton>\n                  <sc-if value=\"{{ g.open }}\" hint-placeholder-val=\"{{ true }}\">\n                    <sc-for list=\"{{ g.rows }}\" as=\"r\" hint-placeholder-count=\"2\">\n                    <div style=\"display:flex;align-items:flex-start;gap:9px;padding:8px 6px;border-radius:8px\" style-hover=\"background:rgb(var(--text) / 0.04)\">\n                      <span style=\"display:inline-flex;margin-top:4px\"><sc-if value=\"{{ r.isNeeds }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"10\" height=\"10\" aria-hidden=\"true\"><path d=\"M5 0.6 9.4 5 5 9.4 0.6 5z\" fill=\"rgb(var(--status-needs, 244 63 108))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ r.isStuck }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"10\" height=\"10\" aria-hidden=\"true\"><path d=\"M5 0.8 9.5 9H0.5z\" fill=\"rgb(var(--status-stuck, 248 217 116))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ r.isDone }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"10\" height=\"10\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"4.3\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\"><\u002Fcircle><path d=\"M3 5.1 4.4 6.5 7.1 3.7\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ r.isRunning }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"10\" height=\"10\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"3.2\" fill=\"rgb(var(--status-running, 0 243 255))\"><\u002Fcircle><circle cx=\"5\" cy=\"5\" r=\"4.5\" fill=\"none\" stroke=\"rgb(var(--status-running, 0 243 255) / 0.35)\" stroke-width=\"1\"><\u002Fcircle><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ r.isIdle }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"10\" height=\"10\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"3.6\" fill=\"none\" stroke=\"rgb(var(--status-idle, 224 224 222) / 0.5)\" stroke-width=\"1.4\"><\u002Fcircle><\u002Fsvg><\u002Fsc-if><\u002Fspan>\n                      <div style=\"flex:1;min-width:0\">\n                        <button onClick=\"{{ r.open }}\" title=\"{{ r.title }}\" style=\"display:flex;align-items:baseline;gap:8px;width:100%;padding:0;border:none;background:transparent;cursor:pointer;text-align:left;color:rgb(var(--text))\">\n                          <span style=\"flex:1;min-width:0;font-family:var(--font-inter);font-size:0.8rem;font-weight:{{ r.weight }};white-space:nowrap;overflow:hidden;text-overflow:ellipsis\">{{ r.name }}<\u002Fspan>\n                          <span class=\"mono\" style=\"flex-shrink:0;font-size:0.6rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ r.since }}<\u002Fspan>\n                        <\u002Fbutton>\n                        <div style=\"font-size:0.74rem;line-height:1.4;color:var(--app-ink-soft, rgb(var(--text) / 0.7));margin-top:2px\">{{ r.doing }}<\u002Fdiv>\n                        <div class=\"mono\" style=\"font-size:0.58rem;letter-spacing:0.06em;color:var(--app-ink-faint, rgb(var(--text) / 0.62));margin-top:3px\">{{ r.projectLabel }}<\u002Fdiv>\n                        <div data-roster-actions=\"1\" style=\"display:flex;gap:4px;margin-top:6px\">\n                          <button onClick=\"{{ r.nudge }}\" title=\"Send a prompt to this agent via Ask\" style=\"height:22px;padding:0 8px;border-radius:6px;border:1px solid var(--border);background:transparent;color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-family:var(--font-jetbrains);font-size:0.56rem;letter-spacing:0.05em;cursor:pointer\" style-hover=\"color:rgb(var(--text));border-color:rgb(var(--lead) / 0.4)\">Nudge<\u002Fbutton>\n                          <sc-if value=\"{{ r.canPause }}\" hint-placeholder-val=\"{{ false }}\"><button onClick=\"{{ r.pause }}\" title=\"Pause — asks for confirmation\" style=\"height:22px;padding:0 8px;border-radius:6px;border:1px solid var(--border);background:transparent;color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-family:var(--font-jetbrains);font-size:0.56rem;letter-spacing:0.05em;cursor:pointer\" style-hover=\"color:rgb(var(--text));border-color:rgb(var(--status-stuck, 248 217 116) / 0.6)\">Pause<\u002Fbutton><\u002Fsc-if>\n                          <button onClick=\"{{ r.trace }}\" title=\"Open the run trace in Platform ops › Runs\" style=\"height:22px;padding:0 8px;border-radius:6px;border:1px solid var(--border);background:transparent;color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-family:var(--font-jetbrains);font-size:0.56rem;letter-spacing:0.05em;cursor:pointer\" style-hover=\"color:rgb(var(--text));border-color:rgb(var(--lead) / 0.4)\">Trace<\u002Fbutton>\n                        <\u002Fdiv>\n                        <sc-if value=\"{{ r.hasProgress }}\" hint-placeholder-val=\"{{ false }}\">\n                          <div style=\"display:flex;align-items:center;gap:8px;margin-top:6px\">\n                            <span style=\"flex:1;height:3px;border-radius:9999px;background:rgb(var(--text) / 0.1);overflow:hidden\"><span style=\"display:block;height:100%;width:{{ r.pct }};background:rgb(var(--status-running, 0 243 255));border-radius:9999px\"><\u002Fspan><\u002Fspan>\n                            <span class=\"mono\" style=\"flex-shrink:0;font-size:0.56rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ r.progressLabel }}<\u002Fspan>\n                          <\u002Fdiv>\n                        <\u002Fsc-if>\n                        <sc-if value=\"{{ r.hasTask }}\" hint-placeholder-val=\"{{ false }}\">\n                          <div class=\"mono\" style=\"font-size:0.58rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7));margin-top:4px\">task · {{ r.task }}<\u002Fdiv>\n                        <\u002Fsc-if>\n                      <\u002Fdiv>\n                      <sc-if value=\"{{ r.hasCta }}\" hint-placeholder-val=\"{{ false }}\">\n                        <button onClick=\"{{ r.act }}\" style=\"flex-shrink:0;height:24px;padding:0 9px;border-radius:7px;border:1px solid rgb(var(--status-needs, 244 63 108) / 0.5);background:transparent;color:rgb(var(--text));font-family:var(--font-jetbrains);font-size:0.6rem;letter-spacing:0.05em;cursor:pointer\" style-hover=\"background:rgb(var(--status-needs, 244 63 108) / 0.12)\">{{ r.cta }}<\u002Fbutton>\n                      <\u002Fsc-if>\n                    <\u002Fdiv>\n                    <\u002Fsc-for>\n                  <\u002Fsc-if>\n                <\u002Fdiv>\n              <\u002Fsc-for>\n              <sc-if value=\"{{ rvEmpty }}\" hint-placeholder-val=\"{{ false }}\">\n                <div class=\"mono\" style=\"font-size:0.66rem;color:var(--app-ink-faint, rgb(var(--text) / 0.62));padding:6px\">no agents in this state<\u002Fdiv>\n              <\u002Fsc-if>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n          <sc-if value=\"{{ rvProjectView }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"display:flex;flex-direction:column;gap:12px\">\n              <sc-for list=\"{{ rvProjects }}\" as=\"g\" hint-placeholder-count=\"4\">\n                <div>\n                  <button onClick=\"{{ g.toggle }}\" aria-expanded=\"{{ g.open }}\" style=\"display:flex;align-items:center;gap:7px;width:100%;padding:0;margin-bottom:4px;border:none;background:transparent;cursor:pointer;color:rgb(var(--text));text-align:left\">\n                    <span aria-hidden=\"true\" style=\"display:inline-flex;transform:{{ g.caret }};transition:transform .2s var(--liquid-ease);color:var(--app-ink-soft, rgb(var(--text) / 0.7))\"><svg viewBox=\"0 0 24 24\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"><\u002Fpolyline><\u002Fsvg><\u002Fspan>\n                    <sc-if value=\"{{ g.isNeeds }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><path d=\"M5 0.6 9.4 5 5 9.4 0.6 5z\" fill=\"rgb(var(--status-needs, 244 63 108))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ g.isStuck }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><path d=\"M5 0.8 9.5 9H0.5z\" fill=\"rgb(var(--status-stuck, 248 217 116))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ g.isDone }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"4.3\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\"><\u002Fcircle><path d=\"M3 5.1 4.4 6.5 7.1 3.7\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ g.isRunning }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"3.2\" fill=\"rgb(var(--status-running, 0 243 255))\"><\u002Fcircle><circle cx=\"5\" cy=\"5\" r=\"4.5\" fill=\"none\" stroke=\"rgb(var(--status-running, 0 243 255) / 0.35)\" stroke-width=\"1\"><\u002Fcircle><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ g.isIdle }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"9\" height=\"9\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"3.6\" fill=\"none\" stroke=\"rgb(var(--status-idle, 224 224 222) / 0.5)\" stroke-width=\"1.4\"><\u002Fcircle><\u002Fsvg><\u002Fsc-if>\n                    <span style=\"flex:1;min-width:0;display:flex;flex-direction:column;gap:1px\"><span style=\"font-family:var(--font-inter);font-size:0.78rem;line-height:1.3\">{{ g.label }}<\u002Fspan><span class=\"mono\" style=\"font-size:0.58rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ g.hint }}<\u002Fspan><\u002Fspan>\n                  <\u002Fbutton>\n                  <sc-if value=\"{{ g.open }}\" hint-placeholder-val=\"{{ true }}\">\n                    <sc-for list=\"{{ g.rows }}\" as=\"r\" hint-placeholder-count=\"2\">\n                    <div style=\"display:flex;align-items:flex-start;gap:9px;padding:8px 6px;border-radius:8px\" style-hover=\"background:rgb(var(--text) / 0.04)\">\n                      <span style=\"display:inline-flex;margin-top:4px\"><sc-if value=\"{{ r.isNeeds }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"10\" height=\"10\" aria-hidden=\"true\"><path d=\"M5 0.6 9.4 5 5 9.4 0.6 5z\" fill=\"rgb(var(--status-needs, 244 63 108))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ r.isStuck }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"10\" height=\"10\" aria-hidden=\"true\"><path d=\"M5 0.8 9.5 9H0.5z\" fill=\"rgb(var(--status-stuck, 248 217 116))\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ r.isDone }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"10\" height=\"10\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"4.3\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\"><\u002Fcircle><path d=\"M3 5.1 4.4 6.5 7.1 3.7\" fill=\"none\" stroke=\"rgb(var(--status-done, 0 212 184))\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><\u002Fpath><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ r.isRunning }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"10\" height=\"10\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"3.2\" fill=\"rgb(var(--status-running, 0 243 255))\"><\u002Fcircle><circle cx=\"5\" cy=\"5\" r=\"4.5\" fill=\"none\" stroke=\"rgb(var(--status-running, 0 243 255) / 0.35)\" stroke-width=\"1\"><\u002Fcircle><\u002Fsvg><\u002Fsc-if><sc-if value=\"{{ r.isIdle }}\" hint-placeholder-val=\"{{ false }}\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 10 10\" width=\"10\" height=\"10\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"3.6\" fill=\"none\" stroke=\"rgb(var(--status-idle, 224 224 222) / 0.5)\" stroke-width=\"1.4\"><\u002Fcircle><\u002Fsvg><\u002Fsc-if><\u002Fspan>\n                      <div style=\"flex:1;min-width:0\">\n                        <button onClick=\"{{ r.open }}\" title=\"{{ r.title }}\" style=\"display:flex;align-items:baseline;gap:8px;width:100%;padding:0;border:none;background:transparent;cursor:pointer;text-align:left;color:rgb(var(--text))\">\n                          <span style=\"flex:1;min-width:0;font-family:var(--font-inter);font-size:0.8rem;font-weight:{{ r.weight }};white-space:nowrap;overflow:hidden;text-overflow:ellipsis\">{{ r.name }}<\u002Fspan>\n                          <span class=\"mono\" style=\"flex-shrink:0;font-size:0.6rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ r.since }}<\u002Fspan>\n                        <\u002Fbutton>\n                        <div style=\"font-size:0.74rem;line-height:1.4;color:var(--app-ink-soft, rgb(var(--text) / 0.7));margin-top:2px\">{{ r.doing }}<\u002Fdiv>\n                        <div class=\"mono\" style=\"font-size:0.58rem;letter-spacing:0.06em;color:var(--app-ink-faint, rgb(var(--text) / 0.62));margin-top:3px\">{{ r.projectLabel }}<\u002Fdiv>\n                        <div data-roster-actions=\"1\" style=\"display:flex;gap:4px;margin-top:6px\">\n                          <button onClick=\"{{ r.nudge }}\" title=\"Send a prompt to this agent via Ask\" style=\"height:22px;padding:0 8px;border-radius:6px;border:1px solid var(--border);background:transparent;color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-family:var(--font-jetbrains);font-size:0.56rem;letter-spacing:0.05em;cursor:pointer\" style-hover=\"color:rgb(var(--text));border-color:rgb(var(--lead) / 0.4)\">Nudge<\u002Fbutton>\n                          <sc-if value=\"{{ r.canPause }}\" hint-placeholder-val=\"{{ false }}\"><button onClick=\"{{ r.pause }}\" title=\"Pause — asks for confirmation\" style=\"height:22px;padding:0 8px;border-radius:6px;border:1px solid var(--border);background:transparent;color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-family:var(--font-jetbrains);font-size:0.56rem;letter-spacing:0.05em;cursor:pointer\" style-hover=\"color:rgb(var(--text));border-color:rgb(var(--status-stuck, 248 217 116) / 0.6)\">Pause<\u002Fbutton><\u002Fsc-if>\n                          <button onClick=\"{{ r.trace }}\" title=\"Open the run trace in Platform ops › Runs\" style=\"height:22px;padding:0 8px;border-radius:6px;border:1px solid var(--border);background:transparent;color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-family:var(--font-jetbrains);font-size:0.56rem;letter-spacing:0.05em;cursor:pointer\" style-hover=\"color:rgb(var(--text));border-color:rgb(var(--lead) / 0.4)\">Trace<\u002Fbutton>\n                        <\u002Fdiv>\n                        <sc-if value=\"{{ r.hasProgress }}\" hint-placeholder-val=\"{{ false }}\">\n                          <div style=\"display:flex;align-items:center;gap:8px;margin-top:6px\">\n                            <span style=\"flex:1;height:3px;border-radius:9999px;background:rgb(var(--text) / 0.1);overflow:hidden\"><span style=\"display:block;height:100%;width:{{ r.pct }};background:rgb(var(--status-running, 0 243 255));border-radius:9999px\"><\u002Fspan><\u002Fspan>\n                            <span class=\"mono\" style=\"flex-shrink:0;font-size:0.56rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ r.progressLabel }}<\u002Fspan>\n                          <\u002Fdiv>\n                        <\u002Fsc-if>\n                        <sc-if value=\"{{ r.hasTask }}\" hint-placeholder-val=\"{{ false }}\">\n                          <div class=\"mono\" style=\"font-size:0.58rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7));margin-top:4px\">task · {{ r.task }}<\u002Fdiv>\n                        <\u002Fsc-if>\n                      <\u002Fdiv>\n                      <sc-if value=\"{{ r.hasCta }}\" hint-placeholder-val=\"{{ false }}\">\n                        <button onClick=\"{{ r.act }}\" style=\"flex-shrink:0;height:24px;padding:0 9px;border-radius:7px;border:1px solid rgb(var(--status-needs, 244 63 108) / 0.5);background:transparent;color:rgb(var(--text));font-family:var(--font-jetbrains);font-size:0.6rem;letter-spacing:0.05em;cursor:pointer\" style-hover=\"background:rgb(var(--status-needs, 244 63 108) / 0.12)\">{{ r.cta }}<\u002Fbutton>\n                      <\u002Fsc-if>\n                    <\u002Fdiv>\n                    <\u002Fsc-for>\n                  <\u002Fsc-if>\n                <\u002Fdiv>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n          <!-- D176/D177 · Ask the agent — pinned at the roster bottom on every stream; chevron collapses the conversation, the input stays -->\n          <div data-testid=\"roster-ask\" style=\"position:sticky;bottom:-16px;margin:auto -16px -16px;padding:12px 16px 16px;border-top:1px solid rgb(var(--lead) / 0.3);background:rgb(var(--basalt-deep));display:flex;flex-direction:column;flex:{{ askGrow }};min-height:0\">\n            <div style=\"display:flex;align-items:center;gap:8px;color:rgb(var(--lead))\">\n              <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 12a8 8 0 0 1-11.6 7.1L4 20.5l1.4-4.9A8 8 0 1 1 21 12z\"><\u002Fpath><line x1=\"9\" y1=\"10.5\" x2=\"15\" y2=\"10.5\"><\u002Fline><line x1=\"9\" y1=\"13.5\" x2=\"13\" y2=\"13.5\"><\u002Fline><\u002Fsvg>\n              <span class=\"label-nav\" style=\"font-size:0.64rem;flex:1;color:rgb(var(--text))\">Ask the agent<\u002Fspan>\n              <button onClick=\"{{ askToggle }}\" aria-expanded=\"{{ askOn }}\" aria-label=\"Collapse or expand the conversation\" title=\"Collapse or expand the conversation\" style=\"flex-shrink:0;border:none;background:transparent;color:var(--app-ink-soft, rgb(var(--text) / 0.7));cursor:pointer;display:inline-flex;padding:2px;transform:{{ askCaret }};transition:transform .2s var(--liquid-ease)\" style-hover=\"color:rgb(var(--text))\"><svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 15 12 9 18 15\"><\u002Fpolyline><\u002Fsvg><\u002Fbutton>\n            <\u002Fdiv>\n            <sc-if value=\"{{ askOn }}\" hint-placeholder-val=\"{{ true }}\">\n              <div class=\"mono\" style=\"margin-top:8px;font-size:0.58rem;letter-spacing:0.1em;color:var(--app-ink-soft, rgb(var(--text) / 0.7));white-space:nowrap;overflow:hidden;text-overflow:ellipsis\">SCOPE · {{ askScope }}<\u002Fdiv>\n              <sc-if value=\"{{ askEmpty }}\" hint-placeholder-val=\"{{ true }}\">\n                <div style=\"display:flex;flex-wrap:wrap;gap:4px;margin-top:8px\">\n                  <sc-for list=\"{{ askSuggest }}\" as=\"sg\" hint-placeholder-count=\"3\">\n                    <button onClick=\"{{ sg.ask }}\" style=\"max-width:100%;height:24px;padding:0 9px;border-radius:9999px;border:1px solid var(--border);background:transparent;color:rgb(var(--text));font-family:var(--font-inter);font-size:0.68rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer\" style-hover=\"border-color:rgb(var(--lead) / 0.45);background:rgb(var(--lead) / 0.06)\">{{ sg.q }}<\u002Fbutton>\n                  <\u002Fsc-for>\n                <\u002Fdiv>\n              <\u002Fsc-if>\n              <div style=\"display:flex;flex-direction:column;justify-content:flex-end;gap:8px;margin-top:10px;flex:1 1 auto;min-height:0;max-height:62vh;overflow-y:auto\">\n                <sc-for list=\"{{ askMsgs }}\" as=\"m\" hint-placeholder-count=\"0\">\n                  <sc-if value=\"{{ m.isUser }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"align-self:flex-end;max-width:88%;padding:7px 10px;border-radius:10px 10px 2px 10px;background:rgb(var(--text) / 0.08);color:rgb(var(--text));font-size:0.76rem;line-height:1.4\">{{ m.text }}<\u002Fdiv>\n                  <\u002Fsc-if>\n                  <sc-if value=\"{{ m.isAgent }}\" hint-placeholder-val=\"{{ false }}\">\n                    <div style=\"display:flex;flex-direction:column;gap:6px;padding:8px 10px;border-radius:10px 10px 10px 2px;border:1px solid var(--border);background:rgb(var(--basalt-deep) / 0.5)\">\n                      <div style=\"font-size:0.76rem;line-height:1.45;color:rgb(var(--text));white-space:pre-wrap\">{{ m.text }}<\u002Fdiv>\n                      <div class=\"mono\" style=\"font-size:0.56rem;letter-spacing:0.08em;color:var(--app-ink-faint, rgb(var(--text) / 0.62))\">{{ m.meta }}<\u002Fdiv>\n                      <div style=\"display:flex;gap:6px;flex-wrap:wrap\">\n                        <button onClick=\"{{ m.onFile }}\" style=\"height:24px;padding:0 9px;border-radius:7px;border:1px solid rgb(var(--lead) / 0.35);background:transparent;color:rgb(var(--lead));font-family:var(--font-jetbrains);font-size:0.58rem;letter-spacing:0.05em;cursor:pointer\" style-hover=\"background:rgb(var(--lead) / 0.1)\">{{ m.fileLabel }}<\u002Fbutton>\n                        <button onClick=\"{{ m.onOpen }}\" style=\"height:24px;padding:0 9px;border-radius:7px;border:1px solid var(--border);background:transparent;color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-family:var(--font-jetbrains);font-size:0.58rem;letter-spacing:0.05em;cursor:pointer\" style-hover=\"color:rgb(var(--text))\">Open in Intelligence<\u002Fbutton>\n                      <\u002Fdiv>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n                <\u002Fsc-for>\n                <sc-if value=\"{{ askBusy }}\" hint-placeholder-val=\"{{ false }}\">\n                  <div class=\"mono\" style=\"font-size:0.62rem;color:rgb(var(--lead))\">thinking · {{ askScopeShort }}<\u002Fdiv>\n                <\u002Fsc-if>\n              <\u002Fdiv>\n            <\u002Fsc-if>\n            <div style=\"display:flex;align-items:center;gap:6px;margin-top:10px;padding:4px 4px 4px 10px;border-radius:9999px;border:1px solid var(--border);background:rgb(var(--basalt-deep) / 0.6)\">\n              <input value=\"{{ askDraft }}\" onChange=\"{{ askInput }}\" onKeyDown=\"{{ askKey }}\" onFocus=\"{{ askFocus }}\" placeholder=\"{{ askPlaceholder }}\" aria-label=\"Ask the agent\" style=\"flex:1;min-width:0;border:none;outline:none;background:transparent;color:rgb(var(--text));font-family:var(--font-inter);font-size:0.78rem;height:30px\">\n              <button onClick=\"{{ askSendDraft }}\" aria-label=\"Send\" style=\"width:30px;height:30px;flex-shrink:0;border-radius:50%;border:none;background:rgb(var(--lead));color:rgb(var(--basalt-deep));display:inline-flex;align-items:center;justify-content:center;cursor:pointer\"><svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"><\u002Fline><polyline points=\"13 6 19 12 13 18\"><\u002Fpolyline><\u002Fsvg><\u002Fbutton>\n            <\u002Fdiv>\n            <sc-if value=\"{{ askHasLog }}\" hint-placeholder-val=\"{{ false }}\">\n              <button onClick=\"{{ askClear }}\" class=\"mono\" style=\"margin-top:6px;border:none;background:transparent;padding:2px 0;font-size:0.56rem;letter-spacing:0.1em;color:var(--app-ink-faint, rgb(var(--text) / 0.62));cursor:pointer\" style-hover=\"color:rgb(var(--text))\">CLEAR CONVERSATION<\u002Fbutton>\n            <\u002Fsc-if>\n          <\u002Fdiv>\n          <\u002Fsc-if>\n        <\u002Faside>\n      <\u002Fsc-if>\n\n    <\u002Fdiv>\n  <\u002Fdiv>\n\n\n  <!-- Run-discovery step-flow modal -->\n  <sc-if value=\"{{ leadsDisco }}\" hint-placeholder-val=\"{{ false }}\">\n    <div onClick=\"{{ leadsDisco.close }}\" style=\"position:fixed;inset:0;z-index:56;background:rgb(0 0 0 / 0.55);backdrop-filter:blur(3px);display:flex;align-items:center;justify-content:center;padding:24px\">\n      <div onClick=\"{{ stop }}\" class=\"glass\" style=\"width:min(560px,94vw);max-height:84vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:var(--shadow-menu);border-radius:var(--radius)\">\n        <div style=\"padding:18px 22px;border-bottom:1px solid var(--app-rule);display:flex;align-items:center;gap:10px\">\n          <span style=\"color:var(--app-accent);display:inline-flex\">{{ leadsDisco.radarIcon }}<\u002Fspan>\n          <span class=\"label-nav\" style=\"font-size:0.66rem;color:var(--app-ink);flex:1\">Run discovery · {{ leadsDisco.stepName }}<\u002Fspan>\n          <button onClick=\"{{ leadsDisco.close }}\" aria-label=\"Close\" style=\"border:none;background:transparent;color:var(--app-ink-soft);cursor:pointer;display:inline-flex;padding:4px\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"><\u002Fline><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"><\u002Fline><\u002Fsvg><\u002Fbutton>\n        <\u002Fdiv>\n        <!-- step rail -->\n        <div style=\"display:flex;align-items:center;gap:6px;padding:14px 22px;border-bottom:1px solid var(--app-rule)\">\n          <sc-for list=\"{{ leadsDisco.steps }}\" as=\"s\" hint-placeholder-count=\"5\">\n            <div style=\"display:flex;align-items:center;gap:6px\">\n              <span style=\"width:24px;height:24px;border-radius:50%;flex-shrink:0;background:{{ s.numBg }};color:{{ s.numColor }};font-family:var(--font-jetbrains);font-size:0.68rem;font-weight:600;display:inline-flex;align-items:center;justify-content:center\">{{ s.glyph }}<\u002Fspan>\n              <span class=\"mono\" style=\"font-size:0.62rem;color:var(--app-ink-soft)\">{{ s.label }}<\u002Fspan>\n            <\u002Fdiv>\n          <\u002Fsc-for>\n        <\u002Fdiv>\n        <div style=\"padding:22px;flex:1;overflow-y:auto;min-height:180px\">\n          <sc-if value=\"{{ leadsDisco.isICP }}\" hint-placeholder-val=\"{{ true }}\">\n            <div class=\"label-overline\" style=\"font-size:0.54rem;color:var(--app-ink-faint);margin-bottom:12px\">Pick a saved ICP<\u002Fdiv>\n            <div style=\"display:flex;flex-direction:column;gap:9px\">\n              <sc-for list=\"{{ leadsDisco.icps }}\" as=\"o\" hint-placeholder-count=\"3\">\n                <button onClick=\"{{ o.onClick }}\" style=\"text-align:left;padding:14px;border-radius:10px;border:1.5px solid {{ o.border }};background:{{ o.bg }};color:{{ o.color }};font-family:var(--font-inter);font-size:0.88rem;font-weight:600;cursor:pointer\">{{ o.name }}<\u002Fbutton>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n          <sc-if value=\"{{ leadsDisco.isCriteria }}\" hint-placeholder-val=\"{{ false }}\">\n            <div class=\"label-overline\" style=\"font-size:0.54rem;color:var(--app-ink-faint);margin-bottom:10px\">Max leads<\u002Fdiv>\n            <div style=\"display:inline-flex;align-items:center;gap:2px;padding:4px;border-radius:9px;border:1px solid var(--app-rule);background:var(--app-surface);margin-bottom:22px\">\n              <button onClick=\"{{ leadsDisco.maxDown }}\" style=\"width:32px;height:32px;border:none;background:transparent;color:var(--app-ink);cursor:pointer;border-radius:6px;font-size:1.1rem\">−<\u002Fbutton>\n              <span class=\"mono\" style=\"min-width:64px;text-align:center;font-size:0.9rem;color:var(--app-ink)\">{{ leadsDisco.maxLeads }}<\u002Fspan>\n              <button onClick=\"{{ leadsDisco.maxUp }}\" style=\"width:32px;height:32px;border:none;background:transparent;color:var(--app-ink);cursor:pointer;border-radius:6px;font-size:1.1rem\">+<\u002Fbutton>\n            <\u002Fdiv>\n            <div class=\"label-overline\" style=\"font-size:0.54rem;color:var(--app-ink-faint);margin-bottom:10px\">Sources<\u002Fdiv>\n            <div style=\"display:flex;flex-direction:column;gap:2px\">\n              <sc-for list=\"{{ leadsDisco.sources }}\" as=\"src\" hint-placeholder-count=\"4\">\n                <div style=\"display:flex;align-items:center;gap:12px;padding:10px 0;border-bottom:1px solid var(--app-rule)\">\n                  <span style=\"flex:1;font-size:0.86rem;color:var(--app-ink)\">{{ src.label }}<\u002Fspan>\n                  <button onClick=\"{{ src.toggle }}\" role=\"switch\" aria-checked=\"{{ src.on }}\" style=\"width:36px;height:22px;flex-shrink:0;border-radius:9999px;border:none;background:{{ src.track }};position:relative;cursor:pointer;padding:0\"><span style=\"position:absolute;top:2px;left:{{ src.knob }};width:18px;height:18px;border-radius:50%;background:#fff;transition:left .25s var(--liquid-ease)\"><\u002Fspan><\u002Fbutton>\n                <\u002Fdiv>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n          <sc-if value=\"{{ leadsDisco.isPreview }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"display:flex;flex-direction:column;align-items:center;text-align:center;padding:16px\">\n              <div class=\"mono\" style=\"font-size:2rem;color:var(--app-accent);margin-bottom:6px\">≈ 236<\u002Fdiv>\n              <div style=\"font-size:0.9rem;color:var(--app-ink);margin-bottom:4px\">estimated matches (validate-only)<\u002Fdiv>\n              <div style=\"font-size:0.8rem;color:var(--app-ink-faint)\">No credits spent until you confirm.<\u002Fdiv>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n          <sc-if value=\"{{ leadsDisco.isConfirm }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"font-size:0.9rem;color:var(--app-ink);line-height:1.6\">Discovery will append up to <span class=\"mono\" style=\"color:var(--app-accent)\">{{ leadsDisco.maxLeads }}<\u002Fspan> validated leads to your list. Confirm to spend discovery credits.<\u002Fdiv>\n          <\u002Fsc-if>\n          <sc-if value=\"{{ leadsDisco.isRun }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"display:flex;align-items:center;gap:12px;padding:16px;border-radius:10px;border:1px solid var(--app-rule);background:var(--app-surface)\">\n              <span style=\"width:10px;height:10px;border-radius:50%;background:{{ leadsDisco.statusColor }};box-shadow:0 0 8px {{ leadsDisco.statusColor }}\"><\u002Fspan>\n              <span class=\"mono\" style=\"font-size:0.86rem;color:var(--app-ink);text-transform:uppercase;flex:1\">{{ leadsDisco.statusLabel }}<\u002Fspan>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n        <\u002Fdiv>\n        <div style=\"padding:16px 22px;border-top:1px solid var(--app-rule);display:flex;gap:10px\">\n          <sc-if value=\"{{ leadsDisco.canPrev }}\" hint-placeholder-val=\"{{ false }}\">\n            <button onClick=\"{{ leadsDisco.prev }}\" style=\"height:40px;padding:0 18px;border-radius:var(--radius-btn);border:1px solid var(--app-rule);background:transparent;color:var(--app-ink);font-family:var(--font-inter);font-weight:500;font-size:0.84rem;cursor:pointer\">Back<\u002Fbutton>\n          <\u002Fsc-if>\n          <span style=\"flex:1\"><\u002Fspan>\n          <sc-if value=\"{{ leadsDisco.canNext }}\" hint-placeholder-val=\"{{ true }}\">\n            <button onClick=\"{{ leadsDisco.next }}\" style=\"height:40px;padding:0 20px;border-radius:var(--radius-btn);border:none;background:var(--app-accent);color:rgb(var(--basalt-deep));font-family:var(--font-inter);font-weight:700;font-size:0.84rem;cursor:pointer\">{{ leadsDisco.nextLabel }}<\u002Fbutton>\n          <\u002Fsc-if>\n          <sc-if value=\"{{ leadsDisco.isRun }}\" hint-placeholder-val=\"{{ false }}\">\n            <button onClick=\"{{ leadsDisco.close }}\" style=\"height:40px;padding:0 20px;border-radius:var(--radius-btn);border:none;background:var(--app-accent);color:rgb(var(--basalt-deep));font-family:var(--font-inter);font-weight:700;font-size:0.84rem;cursor:pointer\">Done<\u002Fbutton>\n          <\u002Fsc-if>\n        <\u002Fdiv>\n      <\u002Fdiv>\n    <\u002Fdiv>\n  <\u002Fsc-if>\n\n  <!-- Coach-mark overlay (onboarding spotlight) -->\n  <sc-if value=\"{{ coachOpen }}\" hint-placeholder-val=\"{{ false }}\">\n    <div onClick=\"{{ closeCoach }}\" style=\"position:fixed;inset:0;z-index:58;background:rgb(0 0 0 / 0.55);backdrop-filter:blur(1px)\">\n      <div style=\"position:absolute;left:14px;top:110px;width:210px;bottom:60px;border-radius:12px;box-shadow:0 0 0 3px var(--app-accent),0 0 40px var(--app-accent-glow);pointer-events:none\"><\u002Fdiv>\n      <div onClick=\"{{ stop }}\" class=\"glass\" style=\"position:absolute;left:240px;top:150px;width:300px;padding:18px;border-radius:var(--radius);box-shadow:var(--shadow-menu)\">\n        <div class=\"label-overline\" style=\"font-size:0.54rem;color:var(--app-accent);margin-bottom:8px\">Coach-mark · 1 of 3<\u002Fdiv>\n        <div style=\"font-size:0.98rem;font-weight:650;color:var(--app-ink);margin-bottom:6px\">This is your stream rail<\u002Fdiv>\n        <p style=\"margin:0 0 16px;font-size:0.84rem;color:var(--app-ink-soft);line-height:1.5\">Every AaaS surface lives here, grouped by area. Your personal streams — Onboarding, Home, Account — sit at the top.<\u002Fp>\n        <div style=\"display:flex;gap:8px;justify-content:flex-end\">\n          <button onClick=\"{{ closeCoach }}\" style=\"height:34px;padding:0 14px;border-radius:var(--radius-btn);border:1px solid var(--app-rule);background:transparent;color:var(--app-ink);font-family:var(--font-inter);font-size:0.8rem;cursor:pointer\">Skip tour<\u002Fbutton>\n          <button onClick=\"{{ closeCoach }}\" style=\"height:34px;padding:0 16px;border-radius:var(--radius-btn);border:none;background:var(--app-accent);color:rgb(var(--basalt-deep));font-family:var(--font-inter);font-weight:700;font-size:0.8rem;cursor:pointer\">Next →<\u002Fbutton>\n        <\u002Fdiv>\n      <\u002Fdiv>\n    <\u002Fdiv>\n  <\u002Fsc-if>\n\n  <!-- Emergency Pause ALL — type-to-confirm THEN hold-to-confirm -->\n  <sc-if value=\"{{ pauseAllData }}\" hint-placeholder-val=\"{{ false }}\">\n    <div onClick=\"{{ pauseAllData.cancel }}\" style=\"position:fixed;inset:0;z-index:62;background:rgb(0 0 0 / 0.65);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:24px\">\n      <div onClick=\"{{ stop }}\" class=\"glass\" style=\"width:min(460px,94vw);overflow:hidden;box-shadow:var(--shadow-menu);border-radius:var(--radius);border:1px solid rgb(var(--accent-red) / 0.4)\">\n        <div style=\"padding:22px 22px 16px\">\n          <div class=\"label-overline\" style=\"font-size:0.56rem;color:var(--app-fail);margin-bottom:12px\">Emergency · PAUSE ALL<\u002Fdiv>\n          <div style=\"font-size:1rem;font-weight:700;color:var(--app-ink);line-height:1.4\">Pause every enabled campaign<\u002Fdiv>\n          <div style=\"font-size:0.83rem;color:var(--app-ink-soft);margin-top:10px;line-height:1.5\">Highest blast radius in the surface — loops <span class=\"mono\">/campaigns/{id}/status<\u002Fspan> with <span class=\"mono\">PAUSED<\u002Fspan> across all live campaigns. This is the one deliberate exception to hold-only: type <span class=\"mono\" style=\"color:var(--app-fail)\">PAUSE ALL<\u002Fspan> to arm.<\u002Fdiv>\n        <\u002Fdiv>\n        <div style=\"padding:0 22px 16px\">\n          <input value=\"{{ pauseAllData.typed }}\" onChange=\"{{ pauseAllData.onType }}\" placeholder=\"Type PAUSE ALL to arm\" style=\"width:100%;height:44px;padding:0 14px;border-radius:var(--radius-btn);border:1px solid {{ pauseAllData.armColor }};background:var(--app-bg);color:var(--app-ink);font-family:var(--font-jetbrains);font-size:0.9rem;letter-spacing:0.08em;outline:none\" />\n        <\u002Fdiv>\n        <div style=\"padding:0 22px 22px;display:flex;gap:12px;align-items:center\">\n          <button onClick=\"{{ pauseAllData.cancel }}\" style=\"height:46px;padding:0 18px;border-radius:var(--radius-btn);border:1px solid var(--app-rule);background:transparent;color:var(--app-ink);font-family:var(--font-inter);font-weight:500;font-size:0.84rem;cursor:pointer\">Cancel<\u002Fbutton>\n          <sc-if value=\"{{ pauseAllData.armed }}\" hint-placeholder-val=\"{{ false }}\">\n            <button onMouseDown=\"{{ pauseAllData.holdStart }}\" onMouseUp=\"{{ pauseAllData.holdCancel }}\" onMouseLeave=\"{{ pauseAllData.holdCancel }}\" onTouchStart=\"{{ pauseAllData.holdStart }}\" onTouchEnd=\"{{ pauseAllData.holdCancel }}\" style=\"flex:1;position:relative;height:46px;border-radius:var(--radius-btn);border:none;background:var(--app-fail);color:rgb(var(--basalt-deep, 14 14 16));font-family:var(--font-inter);font-weight:700;font-size:0.84rem;cursor:pointer;overflow:hidden;user-select:none\">\n              <span style=\"position:absolute;left:0;top:0;bottom:0;width:{{ pauseAllData.fillWidth }};background:rgb(0 0 0 / 0.28);transition:width 1.2s linear\"><\u002Fspan>\n              <span style=\"position:relative;z-index:1\">Hold to PAUSE ALL<\u002Fspan>\n            <\u002Fbutton>\n          <\u002Fsc-if>\n          <sc-if value=\"{{ pauseAllData.disarmed }}\" hint-placeholder-val=\"{{ true }}\">\n            <button disabled=\"true\" style=\"flex:1;height:46px;border-radius:var(--radius-btn);border:1px solid var(--app-rule);background:transparent;color:var(--app-ink-faint);font-family:var(--font-inter);font-weight:700;font-size:0.84rem;cursor:not-allowed\">Hold to PAUSE ALL<\u002Fbutton>\n          <\u002Fsc-if>\n        <\u002Fdiv>\n      <\u002Fdiv>\n    <\u002Fdiv>\n  <\u002Fsc-if>\n\n  <!-- D2 permission model: DENIED overlay for a resolution-point attempt at an unpermitted stream\n       (docs/CANVAS-IMPROVEMENT-PLAN.md item 15). Rail/⌘K/mega already filter these out; this covers\n       inboxRoute, the one entry point that can't simply omit the option. -->\n  <sc-if value=\"{{ accessDeniedData }}\" hint-placeholder-val=\"{{ false }}\">\n    <div data-testid=\"access-denied-scrim\" onClick=\"{{ accessDeniedData.close }}\" style=\"position:fixed;inset:0;z-index:60;background:rgb(0 0 0 / 0.6);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:20px\">\n      <div data-testid=\"access-denied-dialog\" onClick=\"{{ stop }}\" class=\"glass\" style=\"width:min(420px,94vw);overflow:hidden;box-shadow:var(--shadow-menu);border-radius:var(--radius)\">\n        <div style=\"padding:22px 22px 18px\">\n          <div class=\"label-overline\" style=\"font-size:0.56rem;color:rgb(var(--pastel-lavender, 147 154 255));margin-bottom:12px\">NOT AVAILABLE IN THIS ROLE<\u002Fdiv>\n          <div style=\"font-size:1rem;font-weight:650;color:var(--app-ink);line-height:1.45\">{{ accessDeniedData.label }} is not reachable from the {{ accessDeniedData.role }} role.<\u002Fdiv>\n          <div style=\"font-size:0.82rem;color:var(--app-ink-soft);margin-top:10px\">Switch to Owner (or the role that owns this stream) to open it.<\u002Fdiv>\n        <\u002Fdiv>\n        <div style=\"padding:0 22px 22px\">\n          <button onClick=\"{{ accessDeniedData.close }}\" style=\"height:44px;padding:0 18px;border-radius:var(--radius-btn);border:1px solid var(--app-rule);background:transparent;color:var(--app-ink);font-family:var(--font-inter);font-size:0.82rem;cursor:pointer\">Dismiss<\u002Fbutton>\n        <\u002Fdiv>\n      <\u002Fdiv>\n    <\u002Fdiv>\n  <\u002Fsc-if>\n\n  <!-- HoldToConfirm — two-step mutating-action guard -->\n  <sc-if value=\"{{ confirmData }}\" hint-placeholder-val=\"{{ false }}\">\n    <div onClick=\"{{ confirmData.cancel }}\" style=\"position:fixed;inset:0;z-index:60;background:rgb(0 0 0 / 0.6);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:24px\">\n      <div onClick=\"{{ stop }}\" class=\"glass\" style=\"width:min(440px,94vw);overflow:hidden;box-shadow:var(--shadow-menu);border-radius:var(--radius)\">\n        <div style=\"padding:22px 22px 18px\">\n          <div class=\"label-overline\" style=\"font-size:0.56rem;color:var(--app-fail);margin-bottom:12px\">Confirm · {{ confirmData.verb }}<\u002Fdiv>\n          <div style=\"font-size:1rem;font-weight:650;color:var(--app-ink);line-height:1.45\">{{ confirmData.title }}<\u002Fdiv>\n          <div style=\"font-size:0.82rem;color:var(--app-ink-soft);margin-top:10px\">This is a mutating action. Press and hold to confirm.<\u002Fdiv>\n        <\u002Fdiv>\n        <div style=\"padding:0 22px 22px;display:flex;gap:12px;align-items:center\">\n          <button onClick=\"{{ confirmData.cancel }}\" style=\"height:44px;padding:0 18px;border-radius:var(--radius-btn);border:1px solid var(--app-rule);background:transparent;color:var(--app-ink);font-family:var(--font-inter);font-weight:500;font-size:0.84rem;cursor:pointer\">Cancel<\u002Fbutton>\n          <button onMouseDown=\"{{ confirmData.holdStart }}\" onMouseUp=\"{{ confirmData.holdCancel }}\" onMouseLeave=\"{{ confirmData.holdCancel }}\" onTouchStart=\"{{ confirmData.holdStart }}\" onTouchEnd=\"{{ confirmData.holdCancel }}\" style=\"flex:1;position:relative;height:44px;border-radius:var(--radius-btn);border:none;background:var(--app-fail);color:rgb(var(--basalt-deep, 14 14 16));font-family:var(--font-inter);font-weight:700;font-size:0.84rem;cursor:pointer;overflow:hidden;user-select:none\">\n            <span style=\"position:absolute;left:0;top:0;bottom:0;width:{{ confirmData.fillWidth }};background:rgb(0 0 0 / 0.28);transition:width 1.2s linear\"><\u002Fspan>\n            <span style=\"position:relative;z-index:1\">Hold to {{ confirmData.verb }}<\u002Fspan>\n          <\u002Fbutton>\n        <\u002Fdiv>\n      <\u002Fdiv>\n    <\u002Fdiv>\n  <\u002Fsc-if>\n\n  <!-- W2 · Submit a brief — Projects List source :349, copy verbatim -->\n  <sc-if value=\"{{ briefWizData }}\" hint-placeholder-val=\"{{ false }}\">\n    <div onClick=\"{{ briefWizData.close }}\" style=\"position:fixed;inset:0;z-index:60;background:rgb(0 0 0 / 0.6);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:20px\">\n      <div onClick=\"{{ stop }}\" data-section=\"Submit a brief\" class=\"glass\" style=\"width:min(560px,100%);max-height:88vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:var(--shadow-menu);border-radius:var(--radius)\">\n        <div style=\"display:flex;align-items:center;justify-content:space-between;gap:12px;padding:18px 22px;border-bottom:1px solid var(--app-rule)\">\n          <div style=\"display:flex;align-items:center;gap:14px;flex-wrap:wrap\">\n            <sc-for list=\"{{ briefWizData.steps }}\" as=\"st\" hint-placeholder-count=\"3\">\n              <div style=\"display:flex;align-items:center;gap:8px\">\n                <span style=\"width:22px;height:22px;display:grid;place-items:center;border-radius:50%;font-family:var(--font-jetbrains);font-size:0.68rem;color:{{ st.fg }};background:{{ st.bg }};border:1px solid {{ st.border }}\">{{ st.n }}<\u002Fspan>\n                <span class=\"label-overline\" style=\"font-size:0.6rem;color:{{ st.labelColor }}\">{{ st.label }}<\u002Fspan>\n              <\u002Fdiv>\n            <\u002Fsc-for>\n          <\u002Fdiv>\n          <button onClick=\"{{ briefWizData.close }}\" aria-label=\"Close\" style=\"width:44px;height:44px;border:none;background:transparent;color:var(--app-ink-soft);cursor:pointer;font-size:1.3rem;line-height:1\">×<\u002Fbutton>\n        <\u002Fdiv>\n        <div style=\"flex:1;overflow-y:auto;padding:24px 22px\">\n          <sc-if value=\"{{ briefWizData.isStep0 }}\" hint-placeholder-val=\"{{ true }}\">\n            <div style=\"display:flex;flex-direction:column;gap:10px\">\n              <h3 style=\"margin:0;font-size:1.1rem;color:var(--app-ink)\">Describe the work<\u002Fh3>\n              <p style=\"margin:0 0 6px;font-size:0.84rem;color:var(--app-ink-soft);text-wrap:pretty\">Write what you need done. Be specific about scope, sources, and the output you expect.<\u002Fp>\n              <textarea value=\"{{ briefWizData.text }}\" onInput=\"{{ briefWizData.onText }}\" placeholder=\"e.g. Teardown our top 12 competitors' pricing pages and summarize positioning gaps in a table…\" style=\"width:100%;min-height:170px;resize:vertical;padding:14px;border:1px solid var(--app-rule);background:var(--app-bg);color:var(--app-ink);border-radius:var(--radius-btn);font-family:var(--font-inter);font-size:0.9rem;line-height:1.5;outline:none\"><\u002Ftextarea>\n              <span class=\"mono\" style=\"font-size:0.64rem;color:var(--app-ink-faint)\">{{ briefWizData.count }} characters<\u002Fspan>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n          <sc-if value=\"{{ briefWizData.isStep1 }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"display:flex;flex-direction:column;gap:10px\">\n              <h3 style=\"margin:0;font-size:1.1rem;color:var(--app-ink)\">Pick an agent<\u002Fh3>\n              <p style=\"margin:0 0 6px;font-size:0.84rem;color:var(--app-ink-soft)\">Choose which curated agent runs this brief.<\u002Fp>\n              <div style=\"display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:10px\">\n                <sc-for list=\"{{ briefWizData.agents }}\" as=\"a\" hint-placeholder-count=\"6\">\n                  <button onClick=\"{{ a.pick }}\" style=\"text-align:left;padding:14px;border:1px solid {{ a.border }};background:{{ a.bg }};border-radius:var(--radius);cursor:pointer;display:flex;flex-direction:column;gap:6px;transition:all .3s var(--liquid-ease)\">\n                    <span class=\"mono\" style=\"font-size:0.78rem;color:var(--app-ink)\">{{ a.label }}<\u002Fspan>\n                    <span style=\"font-size:0.74rem;line-height:1.4;color:var(--app-ink-soft)\">{{ a.desc }}<\u002Fspan>\n                    <span class=\"label-overline\" style=\"font-size:0.54rem;color:var(--app-ink-faint)\">{{ a.skills }} skills<\u002Fspan>\n                  <\u002Fbutton>\n                <\u002Fsc-for>\n              <\u002Fdiv>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n          <sc-if value=\"{{ briefWizData.isStep2 }}\" hint-placeholder-val=\"{{ false }}\">\n            <div style=\"display:flex;flex-direction:column;gap:10px\">\n              <h3 style=\"margin:0;font-size:1.1rem;color:var(--app-ink)\">Confirm &amp; submit<\u002Fh3>\n              <p style=\"margin:0 0 6px;font-size:0.84rem;color:var(--app-ink-soft)\">Review before it starts. You'll be able to track the run from Projects.<\u002Fp>\n              <div style=\"padding:16px;border:1px solid var(--app-rule);border-radius:var(--radius);background:var(--app-bg);display:flex;flex-direction:column;gap:14px\">\n                <div style=\"display:flex;flex-direction:column;gap:6px\"><span class=\"label-overline\" style=\"font-size:0.54rem;color:var(--app-ink-faint)\">Brief<\u002Fspan><p style=\"margin:0;font-size:0.84rem;line-height:1.5;color:var(--app-ink);white-space:pre-wrap\">{{ briefWizData.preview }}<\u002Fp><\u002Fdiv>\n                <div style=\"display:flex;gap:24px;flex-wrap:wrap\">\n                  <div style=\"display:flex;flex-direction:column;gap:4px\"><span class=\"label-overline\" style=\"font-size:0.54rem;color:var(--app-ink-faint)\">Agent<\u002Fspan><span class=\"mono\" style=\"font-size:0.78rem;color:var(--app-ink)\">{{ briefWizData.agentLabel }}<\u002Fspan><\u002Fdiv>\n                  <div style=\"display:flex;flex-direction:column;gap:4px\"><span class=\"label-overline\" style=\"font-size:0.54rem;color:var(--app-ink-faint)\">Est. cost<\u002Fspan><span class=\"mono\" style=\"font-size:0.78rem;color:var(--app-accent)\">~{{ briefWizData.estCost }} credits<\u002Fspan><\u002Fdiv>\n                <\u002Fdiv>\n              <\u002Fdiv>\n            <\u002Fdiv>\n          <\u002Fsc-if>\n        <\u002Fdiv>\n        <div style=\"display:flex;align-items:center;gap:10px;padding:16px 22px;border-top:1px solid var(--app-rule)\">\n          <button onClick=\"{{ briefWizData.back }}\" style=\"height:44px;padding:0 16px;border-radius:var(--radius-btn);border:1px solid var(--app-rule);background:transparent;color:var(--app-ink);font-family:var(--font-inter);font-size:0.84rem;cursor:pointer;opacity:{{ briefWizData.backOpacity }}\">Back<\u002Fbutton>\n          <span style=\"flex:1\"><\u002Fspan>\n          <sc-if value=\"{{ briefWizData.notLastStep }}\" hint-placeholder-val=\"{{ true }}\">\n            <button onClick=\"{{ briefWizData.next }}\" style=\"height:44px;padding:0 20px;border-radius:var(--radius-btn);border:none;background:var(--app-accent);color:rgb(var(--basalt-deep, 14 14 16));font-family:var(--font-inter);font-weight:700;font-size:0.84rem;cursor:pointer;opacity:{{ briefWizData.nextOpacity }}\">Continue<\u002Fbutton>\n          <\u002Fsc-if>\n          <sc-if value=\"{{ briefWizData.isStep2 }}\" hint-placeholder-val=\"{{ false }}\">\n            <button onClick=\"{{ briefWizData.submit }}\" style=\"height:44px;padding:0 20px;border-radius:var(--radius-btn);border:none;background:var(--app-accent);color:rgb(var(--basalt-deep, 14 14 16));font-family:var(--font-inter);font-weight:700;font-size:0.84rem;cursor:pointer\">Submit brief →<\u002Fbutton>\n          <\u002Fsc-if>\n        <\u002Fdiv>\n      <\u002Fdiv>\n    <\u002Fdiv>\n  <\u002Fsc-if>\n\n  <!-- Command palette -->\n  <!-- Intro tour · Connect → Contextualize → Automate (handoff: app/intro-tour.jsx) -->\n  <sc-if value=\"{{ tour }}\" hint-placeholder-val=\"{{ false }}\">\n    <div onClick=\"{{ tour.skip }}\" style=\"position:fixed;inset:0;z-index:64;background:rgb(0 0 0 / 0.72);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:5vh 20px\">\n      <div onClick=\"{{ stop }}\" style=\"position:relative;width:min(680px,94vw);max-height:88vh;overflow-y:auto;padding:34px 34px 24px;border:1px solid rgb(var(--lead) / 0.22);border-radius:var(--radius);background:rgb(var(--basalt-bright));box-shadow:0 30px 90px rgba(0,0,0,0.6)\">\n        <div style=\"display:flex;align-items:center;gap:12px;margin-bottom:20px\">\n          <span class=\"mono\" style=\"font-size:0.58rem;letter-spacing:0.14em;color:var(--app-ink-soft, rgb(var(--text) / 0.7));flex:1\">{{ tour.stepLabel }}<\u002Fspan>\n          <button onClick=\"{{ tour.skip }}\" class=\"mono\" style=\"height:28px;padding:0 11px;border-radius:9999px;border:1px solid var(--border);background:transparent;color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-size:0.6rem;letter-spacing:0.1em;cursor:pointer\" style-hover=\"border-color:rgb(var(--accent-red) / 0.4);color:rgb(var(--accent-red))\">SKIP TOUR<\u002Fbutton>\n        <\u002Fdiv>\n\n        <span style=\"display:inline-flex;align-items:center;justify-content:center;width:54px;height:54px;border-radius:14px;border:1px solid rgb(var(--lead) / 0.35);background:rgb(var(--lead) / 0.08);color:rgb(var(--lead));box-shadow:0 0 26px rgb(var(--lead) / 0.18)\">{{ tour.glyphEl }}<\u002Fspan>\n        <div class=\"label-overline\" style=\"margin-top:20px;font-size:0.58rem;color:rgb(var(--lead))\">{{ tour.eyebrow }}<\u002Fdiv>\n        <h2 style=\"margin:12px 0 0;font-size:clamp(1.5rem,3vw,2rem);line-height:1.12;font-weight:900;letter-spacing:-0.035em;color:rgb(var(--text));text-wrap:pretty\">{{ tour.title }}<\u002Fh2>\n        <p style=\"margin:14px 0 0;font-size:0.98rem;line-height:1.65;color:var(--app-ink-soft, rgb(var(--text) / 0.7));max-width:560px;text-wrap:pretty\">{{ tour.body }}<\u002Fp>\n\n        <sc-if value=\"{{ tour.isPipeline }}\" hint-placeholder-val=\"{{ false }}\">\n          <div style=\"display:flex;align-items:center;gap:10px;margin-top:26px;flex-wrap:wrap\">\n            <sc-for list=\"{{ tour.pipeline }}\" as=\"pi\" hint-placeholder-count=\"3\">\n              <div style=\"flex:1;min-width:150px;display:flex;flex-direction:column;gap:8px;padding:16px;border:1px solid var(--border);border-radius:var(--radius);background:rgb(var(--basalt-deep) / 0.5)\">\n                <span style=\"display:inline-flex;color:rgb(var(--lead))\">{{ pi.iconEl }}<\u002Fspan>\n                <span class=\"mono\" style=\"font-size:0.66rem;letter-spacing:0.08em;color:rgb(var(--text))\">{{ pi.n }} · {{ pi.label }}<\u002Fspan>\n              <\u002Fdiv>\n              <span style=\"display:{{ pi.arrow }};align-items:center;color:var(--app-ink-soft, rgb(var(--text) / 0.7));flex-shrink:0\">→<\u002Fspan>\n            <\u002Fsc-for>\n          <\u002Fdiv>\n        <\u002Fsc-if>\n\n        <sc-if value=\"{{ tour.isPlatforms }}\" hint-placeholder-val=\"{{ false }}\">\n          <div style=\"display:flex;flex-wrap:wrap;gap:7px;margin-top:24px\">\n            <sc-for list=\"{{ tour.platforms }}\" as=\"pf\" hint-placeholder-count=\"10\">\n              <span class=\"mono\" style=\"display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 12px;border-radius:9999px;border:1px solid var(--border);background:rgb(var(--basalt-deep) / 0.5);color:rgb(var(--text) / 0.8);font-size:0.62rem\">\n                <span style=\"width:5px;height:5px;border-radius:50%;background:rgb(var(--lead))\"><\u002Fspan>{{ pf.label }}\n              <\u002Fspan>\n            <\u002Fsc-for>\n            <span class=\"mono\" style=\"display:inline-flex;align-items:center;height:30px;padding:0 12px;border-radius:9999px;border:1px dashed rgb(var(--lead) / 0.4);color:rgb(var(--lead));font-size:0.62rem\">+ any REST API<\u002Fspan>\n          <\u002Fdiv>\n        <\u002Fsc-if>\n\n        <sc-if value=\"{{ tour.isCentral }}\" hint-placeholder-val=\"{{ false }}\">\n          <div style=\"display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;margin-top:24px\">\n            <sc-for list=\"{{ tour.central }}\" as=\"ce\" hint-placeholder-count=\"4\">\n              <div style=\"display:flex;align-items:center;gap:9px;padding:14px;border:1px solid var(--border);border-radius:var(--radius);background:rgb(var(--basalt-deep) / 0.5);opacity:{{ ce.op }}\">\n                <span style=\"width:7px;height:7px;border-radius:50%;background:rgb(var(--lead));box-shadow:0 0 8px rgb(var(--lead))\"><\u002Fspan>\n                <span class=\"mono\" style=\"font-size:0.66rem;color:rgb(var(--text))\">{{ ce.label }}<\u002Fspan>\n              <\u002Fdiv>\n            <\u002Fsc-for>\n          <\u002Fdiv>\n        <\u002Fsc-if>\n\n        <sc-if value=\"{{ tour.isAgents }}\" hint-placeholder-val=\"{{ false }}\">\n          <div style=\"display:flex;flex-direction:column;gap:12px;margin-top:24px\">\n            <sc-for list=\"{{ tour.agents }}\" as=\"ag\" hint-placeholder-count=\"3\">\n              <div style=\"display:flex;align-items:center;gap:14px\">\n                <span class=\"mono\" style=\"width:120px;flex-shrink:0;font-size:0.66rem;color:rgb(var(--lead))\">{{ ag.name }}<\u002Fspan>\n                <span style=\"flex:1;height:6px;border-radius:9999px;background:rgb(var(--text) / 0.08);overflow:hidden;display:block\">\n                  <span style=\"display:block;height:100%;width:{{ ag.pct }};border-radius:9999px;background:rgb(var(--lead))\"><\u002Fspan>\n                <\u002Fspan>\n                <span class=\"mono\" style=\"width:44px;text-align:right;flex-shrink:0;font-size:0.64rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ ag.pct }}<\u002Fspan>\n              <\u002Fdiv>\n            <\u002Fsc-for>\n          <\u002Fdiv>\n        <\u002Fsc-if>\n\n        <sc-if value=\"{{ tour.isFinal }}\" hint-placeholder-val=\"{{ false }}\">\n          <div style=\"display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:24px\">\n            <sc-for list=\"{{ tour.pipeline }}\" as=\"pf2\" hint-placeholder-count=\"3\">\n              <div style=\"display:flex;align-items:center;gap:9px;padding:14px;border:1px solid rgb(var(--lead) / 0.25);border-radius:var(--radius);background:rgb(var(--lead) / 0.06)\">\n                <span style=\"display:inline-flex;color:rgb(var(--lead))\">{{ pf2.iconEl }}<\u002Fspan>\n                <span class=\"mono\" style=\"font-size:0.66rem;color:rgb(var(--text))\">{{ pf2.label }}<\u002Fspan>\n              <\u002Fdiv>\n            <\u002Fsc-for>\n          <\u002Fdiv>\n        <\u002Fsc-if>\n\n        <div style=\"display:flex;align-items:center;gap:16px;margin-top:28px;padding-top:20px;border-top:1px solid var(--border);flex-wrap:wrap\">\n          <div style=\"display:flex;align-items:center;gap:6px;flex:1\">\n            <sc-for list=\"{{ tour.dots }}\" as=\"dt\" hint-placeholder-count=\"5\">\n              <button onClick=\"{{ dt.onClick }}\" aria-label=\"Go to step\" style=\"width:{{ dt.w }};height:8px;border-radius:9999px;border:none;background:{{ dt.bg }};cursor:pointer;padding:0;transition:width .3s var(--liquid-ease)\"><\u002Fbutton>\n            <\u002Fsc-for>\n          <\u002Fdiv>\n          <sc-if value=\"{{ tour.canBack }}\" hint-placeholder-val=\"{{ false }}\">\n            <button onClick=\"{{ tour.back }}\" style=\"height:42px;padding:0 18px;border-radius:var(--radius-btn);border:1px solid var(--border);background:transparent;color:rgb(var(--text));font-family:var(--font-inter);font-size:0.86rem;cursor:pointer\" style-hover=\"border-color:rgb(var(--lead) / 0.4);color:rgb(var(--lead))\">Back<\u002Fbutton>\n          <\u002Fsc-if>\n          <button onClick=\"{{ tour.next }}\" style=\"display:inline-flex;align-items:center;gap:9px;height:42px;padding:0 20px;border-radius:var(--radius-btn);border:none;background:rgb(var(--lead));color:rgb(var(--basalt-deep));font-family:var(--font-inter);font-weight:700;font-size:0.86rem;cursor:pointer;transition:all .3s var(--liquid-ease)\" style-hover=\"transform:translate(2px,-2px);box-shadow:-4px 4px 0 rgb(var(--accent-red))\">\n            <span>{{ tour.nextLabel }}<\u002Fspan>\n            <span style=\"display:inline-flex\">{{ tour.arrowEl }}<\u002Fspan>\n          <\u002Fbutton>\n        <\u002Fdiv>\n      <\u002Fdiv>\n    <\u002Fdiv>\n  <\u002Fsc-if>\n\n  <!-- Floating agent chat -->\n  <sc-if value=\"{{ chat.isTab }}\" hint-placeholder-val=\"{{ true }}\">\n    <button onClick=\"{{ chat.open }}\" title=\"Ask the agent\" style=\"position:fixed;right:18px;bottom:18px;z-index:57;display:inline-flex;align-items:center;gap:9px;height:44px;padding:0 16px 0 14px;border-radius:9999px;border:1px solid rgb(var(--lead) / 0.45);background:rgb(var(--basalt-bright));color:rgb(var(--text));box-shadow:0 10px 34px rgba(0,0,0,0.45);cursor:pointer\" style-hover=\"border-color:rgb(var(--lead));box-shadow:0 10px 34px rgba(0,0,0,0.5),0 0 22px rgb(var(--lead) / 0.25)\">\n      <span style=\"width:8px;height:8px;border-radius:50%;background:rgb(var(--accent-teal));box-shadow:0 0 8px rgb(var(--accent-teal));animation:aaasPulse 2s ease-in-out infinite\"><\u002Fspan>\n      <span style=\"display:inline-flex;color:rgb(var(--lead))\">{{ chat.sparkEl }}<\u002Fspan>\n      <span class=\"label-nav\" style=\"font-size:0.66rem\">Ask the agent<\u002Fspan>\n    <\u002Fbutton>\n  <\u002Fsc-if>\n  <sc-if value=\"{{ chat.isOpen }}\" hint-placeholder-val=\"{{ false }}\">\n    <div role=\"complementary\" aria-label=\"Agent chat\" style=\"position:fixed;right:18px;bottom:18px;z-index:57;width:min(372px,92vw);max-height:76vh;display:flex;flex-direction:column;border:1px solid var(--border);border-radius:var(--radius);background:rgb(var(--basalt-bright));box-shadow:0 18px 60px rgba(0,0,0,0.55);overflow:hidden\">\n      <div style=\"display:flex;align-items:flex-start;gap:12px;padding:14px 15px;border-bottom:1px solid var(--border)\">\n        <div style=\"flex:1;min-width:0\">\n          <div style=\"font-family:var(--font-inter);font-weight:800;font-size:0.82rem;letter-spacing:-0.01em;color:rgb(var(--text))\">{{ chat.title }}<span style=\"color:rgb(var(--accent-red))\">.<\u002Fspan><\u002Fdiv>\n          <div class=\"mono\" style=\"display:flex;align-items:center;gap:7px;margin-top:5px;font-size:0.6rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">\n            <span style=\"width:6px;height:6px;border-radius:50%;background:rgb(var(--accent-teal));box-shadow:0 0 7px rgb(var(--accent-teal))\"><\u002Fspan>\n            <span>Live<\u002Fspan><span>&middot;<\u002Fspan><span>{{ chat.meta }}<\u002Fspan>\n          <\u002Fdiv>\n        <\u002Fdiv>\n        <span class=\"mono\" style=\"font-size:0.62rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ chat.turnsLabel }}<\u002Fspan>\n        <button onClick=\"{{ chat.close }}\" aria-label=\"Minimize\" style=\"width:26px;height:26px;border-radius:7px;border:1px solid var(--border);background:transparent;color:rgb(var(--text));display:inline-flex;align-items:center;justify-content:center;cursor:pointer\" style-hover=\"border-color:rgb(var(--lead) / 0.4);color:rgb(var(--lead))\">{{ chat.minIconEl }}<\u002Fbutton>\n      <\u002Fdiv>\n      <div style=\"flex:1;min-height:120px;overflow-y:auto;padding:14px 15px;display:flex;flex-direction:column;gap:10px\">\n        <sc-for list=\"{{ chat.msgs }}\" as=\"cm\" hint-placeholder-count=\"1\">\n          <div style=\"align-self:{{ cm.align }};max-width:88%;padding:10px 13px;border-radius:12px;border:{{ cm.border }};background:{{ cm.bg }};color:{{ cm.color }};font-size:0.85rem;line-height:1.55\">{{ cm.text }}<\u002Fdiv>\n        <\u002Fsc-for>\n        <sc-if value=\"{{ chat.spent }}\" hint-placeholder-val=\"{{ false }}\">\n          <div class=\"mono\" style=\"align-self:center;font-size:0.6rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">Demo limit reached &middot; ask in Support for more<\u002Fdiv>\n        <\u002Fsc-if>\n        <sc-if value=\"{{ chat.showSuggests }}\" hint-placeholder-val=\"{{ true }}\">\n          <div style=\"display:flex;flex-wrap:wrap;gap:6px;margin-top:2px\">\n            <sc-for list=\"{{ chat.suggests }}\" as=\"sg2\" hint-placeholder-count=\"3\">\n              <button onClick=\"{{ sg2.onClick }}\" class=\"mono\" style=\"height:26px;padding:0 10px;border-radius:9999px;border:1px solid var(--border);background:transparent;color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-size:0.6rem;cursor:pointer\" style-hover=\"border-color:rgb(var(--lead) / 0.4);color:rgb(var(--lead))\">{{ sg2.label }}<\u002Fbutton>\n            <\u002Fsc-for>\n          <\u002Fdiv>\n        <\u002Fsc-if>\n      <\u002Fdiv>\n      <div style=\"padding:12px 13px;border-top:1px solid var(--border);background:rgb(var(--basalt-deep) / 0.4)\">\n        <div style=\"display:flex;gap:8px\">\n          <input ref=\"{{ chat.inRef }}\" value=\"{{ chat.draft }}\" onChange=\"{{ chat.onDraft }}\" onKeyDown=\"{{ chat.onKey }}\" placeholder=\"Ask the agent&hellip;\" style=\"flex:1;min-width:0;height:38px;padding:0 12px;border-radius:9px;border:1px solid var(--border);background:rgb(var(--basalt-surface) / 0.6);color:rgb(var(--text));font-family:var(--font-inter);font-size:0.85rem;outline:none\">\n          <button onClick=\"{{ chat.send }}\" class=\"mono\" style=\"flex-shrink:0;display:inline-flex;align-items:center;gap:6px;height:38px;padding:0 13px;border-radius:9px;border:none;background:rgb(var(--lead));color:rgb(var(--basalt-deep));font-size:0.62rem;letter-spacing:0.08em;cursor:pointer\">{{ chat.sendIconEl }}SEND<\u002Fbutton>\n        <\u002Fdiv>\n        <div class=\"mono\" style=\"display:flex;justify-content:space-between;margin-top:9px;font-size:0.56rem;letter-spacing:0.1em;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">\n          <span>{{ chat.turnsLabel }} TURNS LEFT<\u002Fspan><span>{{ brand.wordmark }} FRAMEWORK<\u002Fspan>\n        <\u002Fdiv>\n      <\u002Fdiv>\n    <\u002Fdiv>\n  <\u002Fsc-if>\n\n  <sc-if value=\"{{ paletteOpen }}\" hint-placeholder-val=\"{{ false }}\">\n    <div onClick=\"{{ closePalette }}\" style=\"position:fixed;inset:0;z-index:50;background:rgb(0 0 0 / 0.55);backdrop-filter:blur(3px);display:flex;align-items:flex-start;justify-content:center;padding-top:11vh\">\n      <div onClick=\"{{ stop }}\" class=\"glass\" style=\"width:min(640px,92vw);max-height:72vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:var(--shadow-menu)\">\n        <div style=\"display:flex;align-items:center;gap:12px;padding:16px 18px;border-bottom:1px solid var(--border)\">\n          <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"rgb(var(--lead))\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"><\u002Fcircle><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"><\u002Fline><\u002Fsvg>\n          <input ref=\"{{ setPaletteRef }}\" value=\"{{ q }}\" onChange=\"{{ onQ }}\" placeholder=\"Command, view, stream, or sub-page…\" style=\"flex:1;border:none;background:transparent;outline:none;color:rgb(var(--text));font-family:var(--font-inter);font-size:1rem\" />\n          <span style=\"font-family:var(--font-jetbrains);font-size:0.7rem;padding:2px 7px;border-radius:6px;border:1px solid var(--border);color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">esc<\u002Fspan>\n        <\u002Fdiv>\n        <div style=\"flex:1;overflow-y:auto;padding:8px\">\n          <sc-for list=\"{{ paletteGroups }}\" as=\"pg\" hint-placeholder-count=\"2\">\n            <div style=\"padding:4px 0\">\n              <div class=\"label-overline\" style=\"font-size:0.54rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7));padding:6px 10px\">{{ pg.group }}<\u002Fdiv>\n              <sc-for list=\"{{ pg.items }}\" as=\"cmd\" hint-placeholder-count=\"4\">\n                <button onClick=\"{{ cmd.onClick }}\" style=\"display:flex;align-items:center;gap:11px;width:100%;min-height:{{ touchMin }};padding:9px 10px;border:none;background:transparent;border-radius:8px;cursor:pointer;color:rgb(var(--text));font-family:var(--font-inter);font-size:0.88rem;text-align:left\" style-hover=\"background:rgb(var(--lead) / 0.1)\">\n                  <span style=\"display:inline-flex;color:rgb(var(--lead))\">{{ cmd.iconEl }}<\u002Fspan>\n                  <span style=\"flex:1\">{{ cmd.label }}<\u002Fspan>\n                  <span style=\"display:inline-flex;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\"><svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\"><\u002Fline><polyline points=\"14 6 20 12 14 18\"><\u002Fpolyline><\u002Fsvg><\u002Fspan>\n                <\u002Fbutton>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fsc-for>\n        <\u002Fdiv>\n        <div style=\"display:flex;gap:18px;padding:11px 18px;border-top:1px solid var(--border)\">\n          <span class=\"mono\" style=\"font-size:0.66rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">↑↓ navigate<\u002Fspan>\n          <span class=\"mono\" style=\"font-size:0.66rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">↵ run<\u002Fspan>\n          <span class=\"mono\" style=\"font-size:0.66rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">esc close<\u002Fspan>\n        <\u002Fdiv>\n      <\u002Fdiv>\n    <\u002Fdiv>\n  <\u002Fsc-if>\n\n  <!-- Mega-menu app switcher -->\n  <sc-if value=\"{{ megaOpen }}\" hint-placeholder-val=\"{{ false }}\">\n    <div onClick=\"{{ closeMega }}\" style=\"position:fixed;inset:0;z-index:55;background:rgb(0 0 0 / 0.5);backdrop-filter:blur(3px);display:flex;align-items:flex-start;justify-content:center;padding:6vh 10px 24px 24px\">\n      <div onClick=\"{{ stop }}\" class=\"glass\" style=\"width:min(1040px,96vw);max-height:82vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:var(--shadow-menu)\">\n        <div style=\"display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--border)\">\n          <span class=\"label-nav\" style=\"font-size:0.66rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">App switcher<\u002Fspan>\n          <button onClick=\"{{ closeMega }}\" aria-label=\"Close\" style=\"border:none;background:transparent;color:var(--app-ink-soft, rgb(var(--text) / 0.7));cursor:pointer;display:inline-flex;align-items:center;justify-content:center;min-width:{{ touchMin }};min-height:{{ touchMin }};padding:4px\" style-hover=\"color:rgb(var(--text))\">\n            <svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"><\u002Fline><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"><\u002Fline><\u002Fsvg>\n          <\u002Fbutton>\n        <\u002Fdiv>\n        <div data-mega-scroll=\"1\" style=\"flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:22px\">\n          <div>\n            <div class=\"label-overline\" style=\"font-size:0.58rem;color:rgb(var(--lead));margin-bottom:12px\">Domains · live sites<\u002Fdiv>\n            <div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px\">\n              <sc-for list=\"{{ domainSiteTiles }}\" as=\"ds\" hint-placeholder-count=\"6\">\n                <!-- A tile is a card, not a button: the domain opens from its header row, and its\n                     pages open from a menu that appears on hover. The menu is deliberately NOT\n                     inline chips (first attempt, rejected 2026-09-22 — operator: \"the overview stays\n                     functional and clean\"): eleven domains with their page lists always expanded\n                     turns a one-glance map of the estate into a wall of links. Hover keeps the grid\n                     scannable and still puts every page one move away.\n                     Why it has to be here at all: the domain-site view below renders NO chrome (see\n                     its comment), so nothing downstream can show a tab strip — the page list lives\n                     here or a multi-page domain is reachable only at page 0. That was a real\n                     defect: the apex gained Home (the planet door) and Replay, and neither was\n                     reachable or even visible in the shell.\n                     No `overflow:hidden` on the card — the menu has to escape it. -->\n                <div onMouseEnter=\"{{ ds.onEnter }}\" onMouseLeave=\"{{ ds.onLeave }}\" style=\"position:relative\">\n                  <div style=\"display:flex;flex-direction:column;border-radius:var(--radius);border:1px solid {{ ds.border }};background:{{ ds.bg }}\">\n                    <button onClick=\"{{ ds.onClick }}\" onFocus=\"{{ ds.onEnter }}\" style=\"display:flex;align-items:center;gap:11px;padding:12px 14px;border:none;background:transparent;border-radius:var(--radius);cursor:pointer;text-align:left;min-height:{{ touchMin }}\">\n                      <span style=\"display:inline-flex;color:rgb(var(--lead));width:32px;height:32px;border-radius:8px;align-items:center;justify-content:center;background:rgb(var(--lead) / 0.12);flex-shrink:0\">{{ ds.iconEl }}<\u002Fspan>\n                      <span style=\"min-width:0;flex:1\">\n                        <span style=\"display:block;font-weight:600;color:rgb(var(--text));font-size:0.84rem\">{{ ds.label }}<\u002Fspan>\n                        <span class=\"mono\" style=\"display:block;color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-size:0.62rem;margin-top:2px\">{{ ds.tld }} · {{ ds.pageCount }}<\u002Fspan>\n                      <\u002Fspan>\n                      <sc-if value=\"{{ ds.hasPages }}\" hint-placeholder-val=\"{{ false }}\">\n                        <span aria-hidden=\"true\" style=\"flex-shrink:0;color:rgb(var(--lead));transform:{{ ds.caret }};transition:transform 200ms var(--liquid-ease);display:inline-flex\">\n                          <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"><\u002Fpolyline><\u002Fsvg>\n                        <\u002Fspan>\n                      <\u002Fsc-if>\n                    <\u002Fbutton>\n                  <\u002Fdiv>\n                  <sc-if value=\"{{ ds.open }}\" hint-placeholder-val=\"{{ false }}\">\n                    <!-- One style hole, and it has to be one: the menu's placement is MEASURED at\n                         hover time (see onEnter) because it cannot be known at parse time. It is\n                         positioned against the switcher's scroll container, never the viewport —\n                         `.glass` carries a backdrop-filter, which makes it the containing block for\n                         position:fixed too, so a \"portal out\" would still be clipped by the same\n                         box. Staying inside the scroller and fitting the menu to it is the fix. -->\n                    <div class=\"glass\" style=\"{{ ds.menuStyle }}\">\n                      <sc-for list=\"{{ ds.pages }}\" as=\"dp\" hint-placeholder-count=\"0\">\n                        <button onClick=\"{{ dp.onClick }}\" title=\"{{ dp.title }}\" style=\"display:flex;align-items:center;gap:10px;flex-shrink:0;min-height:36px;padding:7px 10px;border:none;background:transparent;border-radius:8px;color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-size:0.78rem;cursor:pointer;text-align:left\" style-hover=\"background:rgb(var(--lead) / 0.12);color:rgb(var(--text))\">\n                          <span class=\"mono\" style=\"flex-shrink:0;width:16px;font-size:0.56rem;color:rgb(var(--lead))\">{{ dp.idx }}<\u002Fspan>\n                          <span style=\"flex:1;min-width:0\">{{ dp.label }}<\u002Fspan>\n                        <\u002Fbutton>\n                      <\u002Fsc-for>\n                    <\u002Fdiv>\n                  <\u002Fsc-if>\n                <\u002Fdiv>\n              <\u002Fsc-for>\n            <\u002Fdiv>\n          <\u002Fdiv>\n          <div style=\"border-top:1px solid var(--border)\"><\u002Fdiv>\n          <div class=\"label-overline\" style=\"font-size:0.58rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7));margin-top:-10px\">Streams · inside Canvas<\u002Fdiv>\n          <sc-for list=\"{{ megaGroups }}\" as=\"mg\" hint-placeholder-count=\"5\">\n            <div>\n              <div class=\"label-overline\" style=\"font-size:0.58rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7));margin-bottom:12px\">{{ mg.label }}<\u002Fdiv>\n              <div style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px\">\n                <sc-for list=\"{{ mg.tiles }}\" as=\"tile\" hint-placeholder-count=\"4\">\n                  <button onClick=\"{{ tile.onClick }}\" style=\"display:flex;align-items:flex-start;gap:14px;padding:16px;border-radius:var(--radius);border:1px solid var(--border);background:rgb(var(--basalt-surface) / 0.4);cursor:pointer;text-align:left\" style-hover=\"border-color:rgb(var(--lead) / 0.4);background:rgb(var(--basalt-surface) / 0.6)\">\n                    <span style=\"display:inline-flex;color:rgb(var(--lead));width:40px;height:40px;border-radius:10px;align-items:center;justify-content:center;background:rgb(var(--lead) / 0.1);flex-shrink:0\">{{ tile.iconEl }}<\u002Fspan>\n                    <span style=\"min-width:0\">\n                      <span style=\"display:block;font-weight:600;color:rgb(var(--text));font-size:0.95rem\">{{ tile.label }}<\u002Fspan>\n                      <span style=\"display:block;color:var(--app-ink-soft, rgb(var(--text) / 0.7));font-size:0.8rem;margin-top:3px\">{{ tile.tag }}<\u002Fspan>\n                      <span class=\"label-system\" style=\"display:block;font-size:0.56rem;color:var(--app-ink-faint, rgb(var(--text) / 0.62));margin-top:8px\">{{ tile.surfaces }}<\u002Fspan>\n                    <\u002Fspan>\n                  <\u002Fbutton>\n                <\u002Fsc-for>\n              <\u002Fdiv>\n            <\u002Fdiv>\n          <\u002Fsc-for>\n        <\u002Fdiv>\n        <div style=\"display:flex;justify-content:space-between;padding:12px 20px;border-top:1px solid var(--border)\">\n          <span class=\"mono\" style=\"font-size:0.66rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">tap any tile to open · esc to close<\u002Fspan>\n          <span class=\"mono\" style=\"font-size:0.66rem;color:var(--app-ink-soft, rgb(var(--text) / 0.7))\">{{ domFooter }}<\u002Fspan>\n        <\u002Fdiv>\n      <\u002Fdiv>\n    <\u002Fdiv>\n  <\u002Fsc-if>\n\n  <!-- Domain-site live view · FULL shell deactivation. No chrome of its own: the\n       framed domain's own ecosystem bar carries the one way back (its CANVAS\n       button), so the shell does not stack a second bar with a duplicate of that\n       domain's link set on top of it. Escape works too. -->\n  <sc-if value=\"{{ domainSiteActive }}\" hint-placeholder-val=\"{{ false }}\">\n    <div style=\"position:fixed;inset:0;z-index:80;background:rgb(var(--basalt-deep));display:flex;flex-direction:column\">\n      <iframe data-domain-frame=\"1\" data-src=\"{{ domainSiteView.iframeSrc }}\" style=\"flex:1;border:none;width:100%;background:#08080a\"><\u002Fiframe>\n    <\u002Fdiv>\n  <\u002Fsc-if>\n\n<\u002Fdiv>\n<\u002Fx-dc>\n<script type=\"text/x-dc\" data-dc-script data-props=\"{&quot;$preview&quot;:{&quot;width&quot;:414,&quot;height&quot;:896},&quot;showAllPlanes&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:true,&quot;tsType&quot;:&quot;boolean&quot;,&quot;section&quot;:&quot;Stage&quot;,&quot;label&quot;:&quot;Show every plane (ignore role gating)&quot;},&quot;accentLead&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;circuit&quot;,&quot;red&quot;],&quot;default&quot;:&quot;circuit&quot;,&quot;tsType&quot;:&quot;'circuit'|'red'&quot;,&quot;section&quot;:&quot;Theme&quot;},&quot;density&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;comfortable&quot;,&quot;compact&quot;],&quot;default&quot;:&quot;comfortable&quot;,&quot;tsType&quot;:&quot;'comfortable'|'compact'&quot;,&quot;section&quot;:&quot;Layout&quot;},&quot;rightPanel&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:true,&quot;tsType&quot;:&quot;boolean&quot;,&quot;section&quot;:&quot;Layout&quot;},&quot;brandWordmark&quot;:{&quot;editor&quot;:&quot;text&quot;,&quot;default&quot;:&quot;AAAS&quot;,&quot;tsType&quot;:&quot;string&quot;,&quot;section&quot;:&quot;Brand&quot;},&quot;brandTagline&quot;:{&quot;editor&quot;:&quot;text&quot;,&quot;default&quot;:&quot;CURATED AI AGENTS&quot;,&quot;tsType&quot;:&quot;string&quot;,&quot;section&quot;:&quot;Brand&quot;},&quot;autoApproveDemo&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:false,&quot;tsType&quot;:&quot;boolean&quot;,&quot;section&quot;:&quot;Gates&quot;,&quot;label&quot;:&quot;Auto-approve (demo) — every HITL gate resolves instantly, still logged&quot;}}\">\nclass Component extends DCLogic {\n  // GENERATED with the metas above — base path (both encoded and literal-space forms) → resource id.\n  static GALLERY_RES = {\"a1-welcome-aaas-com/welcome-email.html\":\"gal1\",\"a10-welcome-pics/welcome-aaas-pics.html\":\"gal2\",\"a11-welcome-blog/welcome-aaas-blog.html\":\"gal3\",\"a12-welcome-academy/welcome-email.html\":\"gal4\",\"a2-welcome-select/welcome-aaas-select.html\":\"gal5\",\"a3-welcome-diy/welcome-email.html\":\"gal6\",\"a4-welcome-builders/welcome-aaas-builders.html\":\"gal7\",\"a6-welcome-name/welcome-aaas-name.html\":\"gal8\",\"a7-welcome-company/welcome-aaas-company.html\":\"gal9\",\"a8-welcome-business/welcome-aaas-business.html\":\"gal10\",\"a9-welcome-aaas-today/Welcome Email.html\":\"gal11\",\"a9-welcome-aaas-today/Welcome%20Email.html\":\"gal11\",\"b1-product-update/Product Update Monthly.html\":\"gal12\",\"b1-product-update/Product%20Update%20Monthly.html\":\"gal12\",\"b2-context-engine/Context Engine Email.html\":\"gal13\",\"b2-context-engine/Context%20Engine%20Email.html\":\"gal13\",\"b3-skills-offering/skills-explainer.html\":\"gal14\",\"b4-vault-offering/Vault Explainer Email.html\":\"gal15\",\"b4-vault-offering/Vault%20Explainer%20Email.html\":\"gal15\",\"c1-trial-expiring/index.html\":\"gal16\",\"c2-first-run-success/First Run Success Email.html\":\"gal17\",\"c2-first-run-success/First%20Run%20Success%20Email.html\":\"gal17\",\"c3-dormant-winback/Dormant 7d Winback Email.html\":\"gal18\",\"c3-dormant-winback/Dormant%207d%20Winback%20Email.html\":\"gal18\",\"c4-referral-invite/Referral Invite Email.html\":\"gal19\",\"c4-referral-invite/Referral%20Invite%20Email.html\":\"gal19\",\"c5-billing-receipt/Billing Receipt Email.html\":\"gal20\",\"c5-billing-receipt/Billing%20Receipt%20Email.html\":\"gal20\",\"c6-eval-summary/index.html\":\"gal21\",\"c7-weekly-digest/Weekly Digest Email.html\":\"gal22\",\"c7-weekly-digest/Weekly%20Digest%20Email.html\":\"gal22\",\"c8-feedback-nps/Feedback NPS Email.html\":\"gal23\",\"c8-feedback-nps/Feedback%20NPS%20Email.html\":\"gal23\",\"d1-security-advisory/Security Advisory Email.html\":\"gal24\",\"d1-security-advisory/Security%20Advisory%20Email.html\":\"gal24\",\"d2-founder-letter/Founder Letter Email.html\":\"gal25\",\"d2-founder-letter/Founder%20Letter%20Email.html\":\"gal25\",\"d3-case-study/case-study-email.html\":\"gal26\",\"d4-beta-invite/Beta Feature Invite.html\":\"gal27\",\"d4-beta-invite/Beta%20Feature%20Invite.html\":\"gal27\",\"d5-agent-published/Agent Published Email.html\":\"gal28\",\"d5-agent-published/Agent%20Published%20Email.html\":\"gal28\",\"d6-earnings-payout/Payout Notification.html\":\"gal29\",\"d6-earnings-payout/Payout%20Notification.html\":\"gal29\",\"e1-password-reset/Password Reset.html\":\"gal30\",\"e1-password-reset/Password%20Reset.html\":\"gal30\",\"e2-gdpr-export/GDPR Data Export Email.html\":\"gal31\",\"e2-gdpr-export/GDPR%20Data%20Export%20Email.html\":\"gal31\",\"e3-cancellation/Subscription Cancellation Confirmed.html\":\"gal32\",\"e3-cancellation/Subscription%20Cancellation%20Confirmed.html\":\"gal32\",\"e4-waitlist-position/Waitlist Position Update Email.html\":\"gal33\",\"e4-waitlist-position/Waitlist%20Position%20Update%20Email.html\":\"gal33\",\"email-header/Email Header.html\":\"gal34\",\"email-header/Email%20Header.html\":\"gal34\",\"gtm-email2-followup-delivery/project/02 Result Ready.html\":\"gal35\",\"gtm-email2-followup-delivery/project/02%20Result%20Ready.html\":\"gal35\",\"gtm-email3-update-new-agents/project/Update Email - New Agents.html\":\"gal36\",\"gtm-email3-update-new-agents/project/Update%20Email%20-%20New%20Agents.html\":\"gal36\",\"gtm-email4/project/Architecture Brief Email.html\":\"gal37\",\"gtm-email4/project/Architecture%20Brief%20Email.html\":\"gal37\",\"gtm/cold-a-conversion.html\":\"gal38\",\"gtm/cold-b-web-video.html\":\"gal39\",\"gtm/delivery-page.html\":\"gal40\",\"gtm/delivery-ready.html\":\"gal41\",\"linkedin-post/linkedin-post.html\":\"gal42\",\"p0-1-li-carousel-arch/LinkedIn Carousel.html\":\"gal43\",\"p0-1-li-carousel-arch/LinkedIn%20Carousel.html\":\"gal43\",\"p0-10-threads-hook/index.html\":\"gal44\",\"p0-11-reels-cover/Reels Cover.html\":\"gal45\",\"p0-11-reels-cover/Reels%20Cover.html\":\"gal45\",\"p0-12-hn-og-card/og-card.html\":\"gal46\",\"p0-2-ig-carousel-anatomy/Anatomy Carousel.html\":\"gal47\",\"p0-2-ig-carousel-anatomy/Anatomy%20Carousel.html\":\"gal47\",\"p0-3-x-banner-hook/Banner.html\":\"gal48\",\"p0-4-vault-skill-card/Skill Repost Card.html\":\"gal49\",\"p0-4-vault-skill-card/Skill%20Repost%20Card.html\":\"gal49\",\"p0-5-yt-thumbnail/Thumbnail Template.html\":\"gal50\",\"p0-5-yt-thumbnail/Thumbnail%20Template.html\":\"gal50\",\"p0-6-github-preview/index.html\":\"gal51\",\"p0-7-producthunt-card/launch-card.html\":\"gal52\",\"p0-8-before-after-banner/Before-After Banner.html\":\"gal53\",\"p0-8-before-after-banner/Before-After%20Banner.html\":\"gal53\",\"p0-9-stat-drop-poster/Stat Drop Poster.html\":\"gal54\",\"p0-9-stat-drop-poster/Stat%20Drop%20Poster.html\":\"gal54\",\"s1-1-linkedin-announcement/LinkedIn Announcement.html\":\"gal55\",\"s1-1-linkedin-announcement/LinkedIn%20Announcement.html\":\"gal55\",\"s1-10-postmortem/Postmortem Square.html\":\"gal56\",\"s1-10-postmortem/Postmortem%20Square.html\":\"gal56\",\"s1-11-linkedin-stat/Stat of the Week.html\":\"gal57\",\"s1-11-linkedin-stat/Stat%20of%20the%20Week.html\":\"gal57\",\"s1-12-tool-comparison/Comparison Post.html\":\"gal58\",\"s1-12-tool-comparison/Comparison%20Post.html\":\"gal58\",\"s1-13-linkedin-event-invite/project/Event Invite.html\":\"gal59\",\"s1-13-linkedin-event-invite/project/Event%20Invite.html\":\"gal59\",\"s1-14-blog-promo/Stat of the Week.html\":\"gal60\",\"s1-14-blog-promo/Stat%20of%20the%20Week.html\":\"gal60\",\"s1-15-linkedin-podcast-clip/project/Podcast Clip Square.html\":\"gal61\",\"s1-15-linkedin-podcast-clip/project/Podcast%20Clip%20Square.html\":\"gal61\",\"s1-16-linkedin-carousel-supervisor/Supervisor Patterns Carousel.html\":\"gal62\",\"s1-16-linkedin-carousel-supervisor/Supervisor%20Patterns%20Carousel.html\":\"gal62\",\"s1-17-carousel-eval-101/project/Evals 101 Carousel.html\":\"gal63\",\"s1-17-carousel-eval-101/project/Evals%20101%20Carousel.html\":\"gal63\",\"s1-18-carousel-mcp-explained/project/MCP Carousel.html\":\"gal64\",\"s1-18-carousel-mcp-explained/project/MCP%20Carousel.html\":\"gal64\",\"s1-19-carousel-7-mistakes/7 Mistakes Carousel.html\":\"gal65\",\"s1-19-carousel-7-mistakes/7%20Mistakes%20Carousel.html\":\"gal65\",\"s1-2-whitepaper-drop/LinkedIn Whitepaper Drop.html\":\"gal66\",\"s1-2-whitepaper-drop/LinkedIn%20Whitepaper%20Drop.html\":\"gal66\",\"s1-20-carousel-launch-deep-dive/project/index.html\":\"gal67\",\"s1-21-carousel-case-numbers/LinkedIn Carousel - AaaS Case Study.html\":\"gal68\",\"s1-21-carousel-case-numbers/LinkedIn%20Carousel%20-%20AaaS%20Case%20Study.html\":\"gal68\",\"s1-22-carousel-pricing-explainer/project/Pricing Carousel.html\":\"gal69\",\"s1-22-carousel-pricing-explainer/project/Pricing%20Carousel.html\":\"gal69\",\"s1-23-carousel-builder-guide/project/Build - Publish Carousel.html\":\"gal70\",\"s1-23-carousel-builder-guide/project/Build%20-%20Publish%20Carousel.html\":\"gal70\",\"s1-24-carousel-week-in-ai/project/Week in AI Carousel.html\":\"gal71\",\"s1-24-carousel-week-in-ai/project/Week%20in%20AI%20Carousel.html\":\"gal71\",\"s1-25-carousel-glossary/project/Glossary Carousel.html\":\"gal72\",\"s1-25-carousel-glossary/project/Glossary%20Carousel.html\":\"gal72\",\"s1-26-x-launch-card/Launch Card.html\":\"gal73\",\"s1-26-x-launch-card/Launch%20Card.html\":\"gal73\",\"s1-27-x-new-model-reaction/project/reaction-card.html\":\"gal74\",\"s1-28-x-stat-bomb/stat-bomb.html\":\"gal75\",\"s1-29-x-quote-customer/project/Quote Card.html\":\"gal76\",\"s1-29-x-quote-customer/project/Quote%20Card.html\":\"gal76\",\"s1-3-new-llm-take/Reaction Post.html\":\"gal77\",\"s1-3-new-llm-take/Reaction%20Post.html\":\"gal77\",\"s1-30-x-thread-cover/Launch Card.html\":\"gal78\",\"s1-30-x-thread-cover/Launch%20Card.html\":\"gal78\",\"s1-31-x-meme-distillation/project/Meme - distracted-boyfriend.html\":\"gal79\",\"s1-31-x-meme-distillation/project/Meme%20-%20distracted-boyfriend.html\":\"gal79\",\"s1-32-x-vs-comparison/project/comparison-card.html\":\"gal80\",\"s1-33-x-product-update/Launch Card.html\":\"gal81\",\"s1-33-x-product-update/Launch%20Card.html\":\"gal81\",\"s1-34-x-recruiting/project/Recruiting Card.html\":\"gal82\",\"s1-34-x-recruiting/project/Recruiting%20Card.html\":\"gal82\",\"s1-35-x-event-invite/project/Event Invite.html\":\"gal83\",\"s1-35-x-event-invite/project/Event%20Invite.html\":\"gal83\",\"s1-36-x-changelog-strip/project/Weekly Changelog Strip.html\":\"gal84\",\"s1-36-x-changelog-strip/project/Weekly%20Changelog%20Strip.html\":\"gal84\",\"s1-37-x-shipped-week/Recruiting Card.html\":\"gal85\",\"s1-37-x-shipped-week/Recruiting%20Card.html\":\"gal85\",\"s1-38-x-postmortem-tease/project/Event Invite.html\":\"gal86\",\"s1-38-x-postmortem-tease/project/Event%20Invite.html\":\"gal86\",\"s1-39-x-quotation-paper/project/Paper Quote Card.html\":\"gal87\",\"s1-39-x-quotation-paper/project/Paper%20Quote%20Card.html\":\"gal87\",\"s1-4-linkedin-did-you-know/project/LinkedIn Did You Know.html\":\"gal88\",\"s1-4-linkedin-did-you-know/project/LinkedIn%20Did%20You%20Know.html\":\"gal88\",\"s1-40-x-conference-promo/project/Event Invite.html\":\"gal89\",\"s1-40-x-conference-promo/project/Event%20Invite.html\":\"gal89\",\"s1-41-ig-announcement/Instagram Announcement Card.html\":\"gal90\",\"s1-41-ig-announcement/Instagram%20Announcement%20Card.html\":\"gal90\",\"s1-42-ig-blog-tease/project/Instagram Blog Tease 4x5.html\":\"gal91\",\"s1-42-ig-blog-tease/project/Instagram%20Blog%20Tease%204x5.html\":\"gal91\",\"s1-43-ig-story-stat/Instagram Story Stat.html\":\"gal92\",\"s1-43-ig-story-stat/Instagram%20Story%20Stat.html\":\"gal92\",\"s1-44-ig-story-quote/project/Instagram Announcement Card.html\":\"gal93\",\"s1-44-ig-story-quote/project/Instagram%20Announcement%20Card.html\":\"gal93\",\"s1-45-ig-reels-cover/Reels Cover.html\":\"gal94\",\"s1-45-ig-reels-cover/Reels%20Cover.html\":\"gal94\",\"s1-46-ig-reels-shocker/project/Reels Cover - Contrarian Shocker.html\":\"gal95\",\"s1-46-ig-reels-shocker/project/Reels%20Cover%20-%20Contrarian%20Shocker.html\":\"gal95\",\"s1-47-tiktok-hook-frame/project/Instagram Story Stat.html\":\"gal96\",\"s1-47-tiktok-hook-frame/project/Instagram%20Story%20Stat.html\":\"gal96\",\"s1-48-yt-shorts-cover/project/YouTube Shorts Cover.html\":\"gal97\",\"s1-48-yt-shorts-cover/project/YouTube%20Shorts%20Cover.html\":\"gal97\",\"s1-49-ig-carousel-cover/Instagram Carousel Cover.html\":\"gal98\",\"s1-49-ig-carousel-cover/Instagram%20Carousel%20Cover.html\":\"gal98\",\"s1-5-contrarian-take/linkedin-contrarian-take.html\":\"gal99\",\"s1-50-ig-carousel-end-cta/project/Closing CTA - Deep Dive.html\":\"gal100\",\"s1-50-ig-carousel-end-cta/project/Closing%20CTA%20-%20Deep%20Dive.html\":\"gal100\",\"s1-6-customer-quote/Customer Quote Square.html\":\"gal101\",\"s1-6-customer-quote/Customer%20Quote%20Square.html\":\"gal101\",\"s1-7-milestone/Milestone Post.html\":\"gal102\",\"s1-7-milestone/Milestone%20Post.html\":\"gal102\",\"s1-8-linkedin-hire-announcement/project/Welcome Card.html\":\"gal103\",\"s1-8-linkedin-hire-announcement/project/Welcome%20Card.html\":\"gal103\",\"s10-1-reddit-banner/Reddit Subreddit Banner.html\":\"gal104\",\"s10-1-reddit-banner/Reddit%20Subreddit%20Banner.html\":\"gal104\",\"s12-6-telegram-sticker/Telegram Sticker Pack Cover.html\":\"gal105\",\"s12-6-telegram-sticker/Telegram%20Sticker%20Pack%20Cover.html\":\"gal105\",\"s13-1-snapchat-snap/Snapchat Snap S13.1.html\":\"gal106\",\"s13-1-snapchat-snap/Snapchat%20Snap%20S13.1.html\":\"gal106\",\"s13-2-snap-story-tile/index.html\":\"gal107\",\"s13-3-snap-discover/index.html\":\"gal108\",\"s14-1-whatsapp-status/index.html\":\"gal109\",\"s14-2-wa-business-profile/profile-photo.html\":\"gal110\",\"s14-3-wa-broadcast/index.html\":\"gal111\",\"s15-1-hn-og/og-cards.html\":\"gal112\",\"s15-2-lobsters-og/Lobsters - S15.2 - OG Card.html\":\"gal113\",\"s15-2-lobsters-og/Lobsters%20-%20S15.2%20-%20OG%20Card.html\":\"gal113\",\"s15-3-devto-cover/Dev.to Cover.html\":\"gal114\",\"s15-3-devto-cover/Dev.to%20Cover.html\":\"gal114\",\"s15-4-devto-share/Dev.to Social Share Card.html\":\"gal115\",\"s15-4-devto-share/Dev.to%20Social%20Share%20Card.html\":\"gal115\",\"s2-1-doc-carousel-cover/index.html\":\"gal116\",\"s2-10-linkedin-newsletter/linkedin-newsletter-cover.html\":\"gal117\",\"s2-11-carousel-architecture-3slide/project/LinkedIn Carousel.html\":\"gal118\",\"s2-11-carousel-architecture-3slide/project/LinkedIn%20Carousel.html\":\"gal118\",\"s2-12-text-only-quote/project/S2.12 LinkedIn quote card.html\":\"gal119\",\"s2-12-text-only-quote/project/S2.12%20LinkedIn%20quote%20card.html\":\"gal119\",\"s2-2-doc-carousel-slide/project/S2.2 LinkedIn carousel slide.html\":\"gal120\",\"s2-2-doc-carousel-slide/project/S2.2%20LinkedIn%20carousel%20slide.html\":\"gal120\",\"s2-3-doc-carousel-cta/project/LinkedIn S2.3 - Carousel CTA.html\":\"gal121\",\"s2-3-doc-carousel-cta/project/LinkedIn%20S2.3%20-%20Carousel%20CTA.html\":\"gal121\",\"s2-4-linkedin-video-cover/S2.4 LinkedIn video-cover-frame.html\":\"gal122\",\"s2-4-linkedin-video-cover/S2.4%20LinkedIn%20video-cover-frame.html\":\"gal122\",\"s2-5-personal-banner/project/LinkedIn Banner.html\":\"gal123\",\"s2-5-personal-banner/project/LinkedIn%20Banner.html\":\"gal123\",\"s2-6-linkedin-company-banner/LinkedIn Banner.html\":\"gal124\",\"s2-6-linkedin-company-banner/LinkedIn%20Banner.html\":\"gal124\",\"s2-7-linkedin-event-cover/LinkedIn event banner.html\":\"gal125\",\"s2-7-linkedin-event-cover/LinkedIn%20event%20banner.html\":\"gal125\",\"s2-8-linkedin-article-hero/index.html\":\"gal126\",\"s2-9-poll-card/project/LinkedIn Poll Card.html\":\"gal127\",\"s2-9-poll-card/project/LinkedIn%20Poll%20Card.html\":\"gal127\",\"s3-1-x-single-image/S3.1 X single-image landscape.html\":\"gal128\",\"s3-1-x-single-image/S3.1%20X%20single-image%20landscape.html\":\"gal128\",\"s3-10-x-stat-week/index.html\":\"gal129\",\"s3-11-event-recap/project/X event-recap card.html\":\"gal130\",\"s3-11-event-recap/project/X%20event-recap%20card.html\":\"gal130\",\"s3-12-gif-still-cover/project/S3.12 - X GIF Still Cover.html\":\"gal131\",\"s3-12-gif-still-cover/project/S3.12%20-%20X%20GIF%20Still%20Cover.html\":\"gal131\",\"s3-2-x-summary-large/S3.2 X summary-large-card.html\":\"gal132\",\"s3-2-x-summary-large/S3.2%20X%20summary-large-card.html\":\"gal132\",\"s3-3-x-quote-square/S3.3 - x-quote-card-square.html\":\"gal133\",\"s3-3-x-quote-square/S3.3%20-%20x-quote-card-square.html\":\"gal133\",\"s3-4-x-thread-numbered/index.html\":\"gal134\",\"s3-5-x-profile-header/index.html\":\"gal135\",\"s3-6-poll-card/project/poll-card.html\":\"gal136\",\"s3-7-spaces-promo/project/spaces-promo.html\":\"gal137\",\"s3-8-meme-reaction/project/x-meme-reaction-square.html\":\"gal138\",\"s3-9-x-launch-announce/S3.9 - x-launch-announce.html\":\"gal139\",\"s3-9-x-launch-announce/S3.9%20-%20x-launch-announce.html\":\"gal139\",\"s4-1-ig-feed-square/S4.1 - ig-feed-square.html\":\"gal140\",\"s4-1-ig-feed-square/S4.1%20-%20ig-feed-square.html\":\"gal140\",\"s9-2-pinterest-pin-long/Pinterest pin-long.html\":\"gal141\",\"s9-2-pinterest-pin-long/Pinterest%20pin-long.html\":\"gal141\",\"s9-3-pin-idea-cover/Pinterest Idea Pin Cover.html\":\"gal142\",\"s9-3-pin-idea-cover/Pinterest%20Idea%20Pin%20Cover.html\":\"gal142\",\"s9-4-pin-idea-inner/S9.4 Pinterest idea-pin-inner.html\":\"gal143\",\"s9-4-pin-idea-inner/S9.4%20Pinterest%20idea-pin-inner.html\":\"gal143\",\"s9-5-pin-board-cover/index.html\":\"gal144\",\"s9-6-pin-tutorial/Pinterest tutorial pin.html\":\"gal145\",\"s9-6-pin-tutorial/Pinterest%20tutorial%20pin.html\":\"gal145\",\"social/s11-1-substack-post-hero/Substack post-hero.html\":\"gal146\",\"social/s11-1-substack-post-hero/Substack%20post-hero.html\":\"gal146\",\"social/s12-1-discord-server-banner/index.html\":\"gal147\",\"social/s16-1-quote-card-square-attributed/S16.1 Quote Card.html\":\"gal148\",\"social/s16-1-quote-card-square-attributed/S16.1%20Quote%20Card.html\":\"gal148\",\"social/s6-1-fb-feed-square/S6.1 - fb-feed-square.html\":\"gal149\",\"social/s6-1-fb-feed-square/S6.1%20-%20fb-feed-square.html\":\"gal149\",\"social/s7-1-yt-thumbnail-standard/index.html\":\"gal150\",\"unified-email-template/project/Email Templates.dc.html\":\"gal151\",\"unified-email-template/project/Email%20Templates.dc.html\":\"gal151\",\"lifecycle/digest-aaas-com/project/preview.html\":\"gal152\",\"lifecycle/digest-academy/project/AcademyLifecycleDigest Preview.html\":\"gal153\",\"lifecycle/digest-academy/project/AcademyLifecycleDigest%20Preview.html\":\"gal153\",\"lifecycle/digest-boutique/project/preview.html\":\"gal154\",\"lifecycle/digest-builders/project/lifecycle-digest-preview.html\":\"gal155\",\"lifecycle/digest-canvas/project/Lifecycle Digest Canvas.html\":\"gal156\",\"lifecycle/digest-canvas/project/Lifecycle%20Digest%20Canvas.html\":\"gal156\",\"lifecycle/digest-dispatcher/project/preview.html\":\"gal157\",\"lifecycle/digest-diy/project/Email Lifecycle Digest - aaas.diy.html\":\"gal158\",\"lifecycle/digest-diy/project/Email%20Lifecycle%20Digest%20-%20aaas.diy.html\":\"gal158\",\"lifecycle/digest-pics/project/LifecycleDigestEmail.preview.html\":\"gal159\",\"lifecycle/digest-select/project/preview.html\":\"gal160\",\"lifecycle/digest-today/project/feature-launch-today-email.html\":\"gal161\",\"lifecycle/digest-vault/project/email-lifecycle-digest-vault.html\":\"gal162\",\"lifecycle/dormant-aaas-com/project/Email Preview - Lifecycle Dormant.html\":\"gal163\",\"lifecycle/dormant-aaas-com/project/Email%20Preview%20-%20Lifecycle%20Dormant.html\":\"gal163\",\"lifecycle/dormant-blog/project/Lifecycle Digest Canvas.html\":\"gal164\",\"lifecycle/dormant-blog/project/Lifecycle%20Digest%20Canvas.html\":\"gal164\",\"lifecycle/dormant-boutique/project/email-preview.html\":\"gal165\",\"lifecycle/dormant-dispatcher/project/email-preview.html\":\"gal166\",\"lifecycle/dormant-forum/project/email-lifecycle-dormant-forum.html\":\"gal167\",\"lifecycle/dormant-pics/preview.html\":\"gal168\",\"lifecycle/dormant-quest/project/Dormant Quest Email Preview.html\":\"gal169\",\"lifecycle/dormant-quest/project/Dormant%20Quest%20Email%20Preview.html\":\"gal169\",\"lifecycle/dormant-select/project/DormantSelectEmail.preview.html\":\"gal170\",\"lifecycle/dormant-today/project/email-lifecycle-dormant-today.html\":\"gal171\",\"lifecycle/dormant-toolbox/project/email-preview.html\":\"gal172\",\"lifecycle/trial-d3-aaas-com/project/preview.html\":\"gal173\",\"lifecycle/trial-d3-blog/project/Trial Day 3 Email.html\":\"gal174\",\"lifecycle/trial-d3-blog/project/Trial%20Day%203%20Email.html\":\"gal174\",\"lifecycle/trial-d3-boutique/project/preview.html\":\"gal175\",\"lifecycle/trial-d3-canvas/project/Trial Day-3 Email.html\":\"gal176\",\"lifecycle/trial-d3-canvas/project/Trial%20Day-3%20Email.html\":\"gal176\",\"lifecycle/trial-d3-cross-property/project/lifecycle-trial-d3-cross-property.html\":\"gal177\",\"lifecycle/trial-d3-dispatcher/project/dispatcher-preview.html\":\"gal178\",\"lifecycle/trial-d3-name/project/email-preview.html\":\"gal179\",\"lifecycle/trial-d3-pics/project/Email Preview - lifecycle-trial-d3-pics.html\":\"gal180\",\"lifecycle/trial-d3-pics/project/Email%20Preview%20-%20lifecycle-trial-d3-pics.html\":\"gal180\",\"lifecycle/trial-d3-quest/project/email-lifecycle-dormant-today.html\":\"gal181\",\"lifecycle/trial-d7-boutique/project/email-lifecycle-trial-d7-boutique.html\":\"gal182\",\"lifecycle/trial-d7-builders/project/Trial Day 7 Email - Builders.html\":\"gal183\",\"lifecycle/trial-d7-builders/project/Trial%20Day%207%20Email%20-%20Builders.html\":\"gal183\",\"lifecycle/trial-d7-cross-property/project/preview.html\":\"gal184\",\"lifecycle/trial-d7-diy/project/email-preview.html\":\"gal185\",\"social/s1-1-linkedin-product-announcement-square/LinkedIn Announcement.html\":\"gal186\",\"social/s1-1-linkedin-product-announcement-square/LinkedIn%20Announcement.html\":\"gal186\",\"social/s1-2-linkedin-whitepaper-drop-square/LinkedIn Whitepaper Drop.html\":\"gal187\",\"social/s1-2-linkedin-whitepaper-drop-square/LinkedIn%20Whitepaper%20Drop.html\":\"gal187\",\"social/s1-3-linkedin-new-llm-take-square/Reaction Post.html\":\"gal188\",\"social/s1-3-linkedin-new-llm-take-square/Reaction%20Post.html\":\"gal188\",\"social/s1-9-linkedin-framework-explainer-square/LinkedIn Framework Explainer.html\":\"gal189\",\"social/s1-9-linkedin-framework-explainer-square/LinkedIn%20Framework%20Explainer.html\":\"gal189\",\"social/s2-1-linkedin-document-carousel-cover/index.html\":\"gal190\",\"social/s2-2-linkedin-document-carousel-slide/S2.2 LinkedIn carousel slide.html\":\"gal191\",\"social/s2-2-linkedin-document-carousel-slide/S2.2%20LinkedIn%20carousel%20slide.html\":\"gal191\",\"social/s2-3-linkedin-document-carousel-cta/LinkedIn S2.3 - Carousel CTA.html\":\"gal192\",\"social/s2-3-linkedin-document-carousel-cta/LinkedIn%20S2.3%20-%20Carousel%20CTA.html\":\"gal192\",\"social/s3-1-x-single-image-landscape/S3.1 X single-image landscape.html\":\"gal193\",\"social/s3-1-x-single-image-landscape/S3.1%20X%20single-image%20landscape.html\":\"gal193\",\"social/s3-2-x-card-summary-large/S3.2 X summary-large-card.html\":\"gal194\",\"social/s3-2-x-card-summary-large/S3.2%20X%20summary-large-card.html\":\"gal194\",\"social/s4-1-ig-feed-square/S4.1 - ig-feed-square.html\":\"gal195\",\"social/s4-1-ig-feed-square/S4.1%20-%20ig-feed-square.html\":\"gal195\",\"social/s4-13-ig-bio-pinned-square/S4.13 - ig-bio-pinned-square.html\":\"gal196\",\"social/s4-13-ig-bio-pinned-square/S4.13%20-%20ig-bio-pinned-square.html\":\"gal196\",\"social/s4-2-ig-feed-portrait/index.html\":\"gal197\",\"social/s5-2-tiktok-vertical-talking-head-frame/index.html\":\"gal198\",\"social/s5-4-tiktok-slideshow-slide/index.html\":\"gal199\",\"social/s5-6-tiktok-profile-banner/S5.6 - TikTok profile cover.html\":\"gal200\",\"social/s5-6-tiktok-profile-banner/S5.6%20-%20TikTok%20profile%20cover.html\":\"gal200\",\"social/s6-5-fb-event-cover/S6.5 - fb-event-cover.html\":\"gal201\",\"social/s6-5-fb-event-cover/S6.5%20-%20fb-event-cover.html\":\"gal201\",\"social/s6-6-fb-group-cover/Facebook Group Cover.html\":\"gal202\",\"social/s6-6-fb-group-cover/Facebook%20Group%20Cover.html\":\"gal202\",\"social/s7-3-yt-shorts-cover/YT Shorts Cover - S7.3.html\":\"gal203\",\"social/s7-3-yt-shorts-cover/YT%20Shorts%20Cover%20-%20S7.3.html\":\"gal203\",\"social/s7-4-yt-channel-banner/index.html\":\"gal204\",\"emails/b1-product-update-monthly/Product Update Monthly.html\":\"gal205\",\"emails/b1-product-update-monthly/Product%20Update%20Monthly.html\":\"gal205\",\"emails/b2-context-engine-explainer/Context Engine Email.html\":\"gal206\",\"emails/b2-context-engine-explainer/Context%20Engine%20Email.html\":\"gal206\",\"emails/b5-context-packages/B5-context-packages/index.html\":\"gal207\",\"emails/b6-context-engine/b6-context-engine-handoff/index.html\":\"gal208\",\"emails/b7-agent-traces/email/index.html\":\"gal209\",\"emails/c10-weekly-agent-digest/index.html\":\"gal210\",\"emails/c11-quiet-on-your-end/index.html\":\"gal211\",\"emails/c12-weekly-digest/index.html\":\"gal212\",\"emails/c9-weekly-digest/index.html\":\"gal213\",\"emails/d1-security-advisory/Security Advisory Email.html\":\"gal214\",\"emails/d1-security-advisory/Security%20Advisory%20Email.html\":\"gal214\",\"emails/d2-founder-letter/Founder Letter Email.html\":\"gal215\",\"emails/d2-founder-letter/Founder%20Letter%20Email.html\":\"gal215\",\"emails/d3-case-study-spotlight/case-study-email.html\":\"gal216\",\"emails/d5-agent-published-notification/Agent Published Email.html\":\"gal217\",\"emails/d5-agent-published-notification/Agent%20Published%20Email.html\":\"gal217\",\"emails/d6-earnings-payout-notification/Payout Notification.html\":\"gal218\",\"emails/d6-earnings-payout-notification/Payout%20Notification.html\":\"gal218\",\"emails/e1-password-reset/Password Reset.html\":\"gal219\",\"emails/e1-password-reset/Password%20Reset.html\":\"gal219\",\"emails/e2-gdpr-export-ready/GDPR Data Export Email.html\":\"gal220\",\"emails/e2-gdpr-export-ready/GDPR%20Data%20Export%20Email.html\":\"gal220\",\"emails/e3-subscription-cancellation-confirmed/Subscription Cancellation Confirmed.html\":\"gal221\",\"emails/e3-subscription-cancellation-confirmed/Subscription%20Cancellation%20Confirmed.html\":\"gal221\",\"emails/e4-waitlist-position-update/Waitlist Position Update Email.html\":\"gal222\",\"emails/e4-waitlist-position-update/Waitlist%20Position%20Update%20Email.html\":\"gal222\"};\n  // Splits off any query string, resolves the base file through window.__resources (published\n  // build) or the relative assets/gallery/ path (dev/project mode), then reattaches the query.\n  // A .dc.html gallery entry is a Design Component, not a static page: it loads the DC runtime and\n  // (for T* templates) a design-system bundle through its own RELATIVE script/link tags. In a\n  // published artifact the file is served from a blob URL with no directory, so those relative paths\n  // 404 and the preview renders raw {{ }} holes unstyled. Fix: fetch the file's text, rewrite its\n  // dependency URLs to the shared blobs declared in <head>, and hand the result to the iframe as\n  // srcdoc — the iframe then needs no sibling files at all. Dev/project mode keeps the plain src\n  // path (siblings resolve normally there), so this only kicks in for published builds.\n  /* The active tenant's pack, as a boot script for srcdoc preview frames. A published preview is\n     a blob document that boots BEFORE any shell broadcast can reach it, so the brand object must\n     already be in place when its runtime boots: window.__dcBrand is the runtime's first-choice\n     source (see support.js brandDiscoverStandalone). Demo brands with no manifest synthesise the\n     wordmark/tagline pair the shell itself displays. The default tenant needs nothing:\n     every template's literals already speak the default brand, and injecting it would be a no-op\n     dressed as work. '<' is escaped so a pack string cannot close the injected script early. */\n  dcBrandBoot(key) {\n    if (!key || key === 'aaas') return '';\n    const tn = (this._tenantPacks || {})[key];\n    const b = this.BRANDS[key];\n    const brand = tn && tn.brand ? tn.brand : (b ? { wordmark: b.name, tagline: b.tag } : null);\n    if (!brand) return '';\n    try { return '<scr' + 'ipt>window.__dcBrand={brand:' + JSON.stringify(brand).replace(/<\u002Fg, '\\\\u003c') + '};<\u002Fscr' + 'ipt>'; }\n    catch (e) { return ''; }\n  }\n\n  dcSrcdocFor(relPath) {\n    const R = window.__resources || {};\n    if (!R['dc-support']) return undefined;              // dev/project mode — plain src works\n    const base = relPath.split('?')[0];\n    if (!/\\.dc(-[a-z0-9]+)?\\.html$/.test(base)) return undefined;\n    this._dcDocs = this._dcDocs || {};\n    if (this._dcDocs[base] !== undefined) return this._dcDocs[base] || undefined;\n    const fileUrl = this.resolveGallerySrc(base);\n    if (!fileUrl || fileUrl.indexOf('blob:') !== 0) return undefined;\n    this._dcDocs[base] = '';                             // in-flight marker (one fetch per file)\n    fetch(fileUrl).then(r => r.text()).then(text => {\n      let out = text\n        .split('./support.js').join(R['dc-support'])\n        .split('../../_ds/aaas-design-system-2216a8d1-f354-4115-9de4-7172d50864b1/styles.css').join(R['dc-tpl-ds-css'] || '')\n        .split('../../_ds/aaas-design-system-2216a8d1-f354-4115-9de4-7172d50864b1/_ds_bundle.js').join(R['dc-tpl-ds-js'] || '')\n        .split('_ds/aaas-design-system-019dcd9e-ff54-7700-8621-440d6d45ae91/_ds_bundle.js').join(R['dc-proto-ds-js'] || '');\n      const boot = this.dcBrandBoot(this.state.brandKey);\n      if (boot) out = out.replace(/<head([^>]*)>/i, '<head$1>' + boot);\n      this._dcDocs[base] = out;\n      this.forceUpdate();\n    }).catch(() => { this._dcDocs[base] = ''; });\n    return undefined;\n  }\n\n  // White-label bridge: gallery/template previews are separate documents (iframes), so they can't\n  // read the shell's CSS custom properties directly. Each preview file that opted in (see\n  // aaas-brand-bridge.js, injected at author time) listens for this postMessage and repaints its\n  // own --basalt-*/--circuit-glow/etc. vars to match — same mechanism the shell itself uses to\n  // reskin, just relayed across the iframe boundary. Called onLoad per-frame, and re-broadcast to\n  // every mounted frame whenever the brand switcher changes (see the brand onClick handlers).\n  /* Places a domain tile's page menu. The menu is absolutely positioned inside the tile,\n     so its box is clipped by the switcher's scroll container (`overflow-y:auto`) and by\n     `.glass` above it (`overflow:hidden`). The first hover implementation ignored both and\n     opened downward at a fixed offset with no height cap: measured at a 540px viewport, the\n     apex tile's 7-item menu lost 196px (Foundry, Projects, API Docs, Partner briefing all\n     unreachable) and `select`, whose tile bottom already sits 4px past the scroller edge,\n     opened its menu entirely outside the visible box — the feature's whole purpose, direct\n     routes, was unreachable exactly where it was needed.\n\n     Fixing the pixels would not have held: the space below a tile depends on which grid row\n     it is in, how many rows there are and the viewport, so any constant is wrong for some\n     tile. This measures instead, against the SCROLLER rather than the viewport (positioning\n     against the viewport is what produced the clipped menus, and a position:fixed \"portal\"\n     would not escape either — `.glass` has a backdrop-filter, which makes it the containing\n     block for fixed descendants as well). Three decisions, in order:\n       1. open downward if the items fit below;\n       2. otherwise flip upward if there is more room there;\n       3. cap max-height to whichever side was chosen and let the menu scroll internally —\n          the backstop for the 8-item lists (academy) that fit on neither side.\n     Re-measured on every hover, so it stays correct across resize and scroll with no\n     listener to leak. */\n  openTileMenu(d, e) {\n    const pages = (d.pages || []).length;\n    if (pages < 2) { this.setState({ megaTile: d.id, megaTileCss: '' }); return; }\n    const el = e && e.currentTarget;\n    const ITEM = 36, GAP = 2, PAD = 14, GUTTER = 8;\n    const need = pages * ITEM + (pages - 1) * GAP + PAD;\n    let css;\n    const box = el && el.closest && el.closest('[data-mega-scroll]');\n    if (!el || !box) {\n      /* No measurement possible (never seen in practice, but the menu must still be usable):\n         open downward with a conservative cap rather than an unbounded box. */\n      css = 'top:calc(100% - 6px);max-height:240px;';\n    } else {\n      const t = el.getBoundingClientRect(), s2 = box.getBoundingClientRect();\n      const below = s2.bottom - t.bottom - GUTTER;\n      const above = t.top - s2.top - GUTTER;\n      const down = need <= below || below >= above;\n      const room = Math.max(96, Math.floor(down ? below : above));\n      css = (down ? 'top:calc(100% - 6px);' : 'bottom:calc(100% - 6px);')\n          + 'max-height:' + Math.min(need, room) + 'px;';\n    }\n    this.setState({\n      megaTile: d.id,\n      megaTileCss: 'position:absolute;left:0;right:0;z-index:60;overflow-y:auto;'\n        + 'border:1px solid rgb(var(--lead) / 0.35);border-radius:var(--radius);padding:6px;'\n        + 'display:flex;flex-direction:column;gap:2px;box-shadow:var(--shadow-menu);' + css\n    });\n  }\n\n  /* Moves data-src → src for the domain-site overlay. See the iframe's comment:\n     a {{ }} hole in a real src is fetched verbatim while the template streams,\n     so the attribute starts as data-src and is promoted here, only ever to a\n     value that is not still a placeholder. Idempotent — it no-ops once src\n     already matches, so it is safe to call from both lifecycle hooks. */\n  componentDidUpdate(pp, ps) {\n    this.syncDomainFrame();\n    this.watchPaneBoot();\n    this.fitFrames();\n    this.applySportsText(!!this.state.founder);\n    if (this.props.autoApproveDemo) this._autoApprove();\n    // prevState is not guaranteed by the DC runtime — track the pane key ourselves.\n    const key = this.state.active + '|' + this.state.sub + '|' + !!this.state.mvp;\n    if (key !== this._paneKey) { this._paneKey = key; this.anchorToCta(); }\n  }\n  // D165: every pane opens with the hero's action row (\"Open command palette\") at the top of the scroll\n  // region; the overline, title and description stay above it, one scroll up.\n  anchorToCta() {\n    const go = () => {\n      const sc = document.querySelector('[data-testid=\"pane-scroll\"]'); if (!sc) return;\n      const cta = sc.querySelector('[data-testid=\"hero-cta\"]');\n      if (!cta) { sc.scrollTop = 0; return; }\n      sc.scrollTop = Math.max(0, cta.getBoundingClientRect().top - sc.getBoundingClientRect().top + sc.scrollTop - 16); };\n    requestAnimationFrame(() => requestAnimationFrame(go));\n  }\n\n\n  /* D172 · frame thumbnails fill their tile. The lens computed scale from a fixed 268/880px tile,\n     but the grid track is 1fr, so wide tiles showed the page in their left third. Measured here\n     against the tile itself on every update and resize. */\n  fitFrames() {\n    if (typeof document === 'undefined') return;\n    if (!this._fitResize) { this._fitResize = () => this.fitFrames(); window.addEventListener('resize', this._fitResize); }\n    requestAnimationFrame(() => document.querySelectorAll('iframe[data-fit-w]').forEach(f => {\n      const box = f.parentElement; if (!box) return;\n      const w = parseFloat(f.getAttribute('data-fit-w')) || 1200, h = parseFloat(f.getAttribute('data-fit-h')) || 1200;\n      const tw = box.clientWidth; if (!tw) return;\n      const sc = tw / w;\n      f.style.transform = 'scale(' + sc + ')';\n      box.style.height = Math.min(Math.round(h * sc), Math.round(tw * 1.25)) + 'px';\n    }));\n  }\n\n  /* D172 · Founder = text-only sports re-cut. A phrase map rewrites visible text nodes across every\n     pane (child DCs included — same document); the originals are kept per node and restored when the\n     switch goes off. Nothing in STREAMS or the panes is edited, so the AaaS copy stays the source. */\n  static SPORTS_TEXT = [\n    ['Helsing inbound pilot', 'Harbour FC partner pilot'], ['Helsing', 'Harbour FC'],\n    ['Nordlicht Fahrradmanufaktur', 'Nordlicht Running Club'], ['Werkhaus Küchen', 'Werkhaus Athletics'], ['Alpsteig Outdoor', 'Alpsteig Trail Club'],\n    ['Studio Grau Interior', 'Studio Grau Climbing'], ['Hafenrad Bremen', 'Hafenrad Cycling'], ['Moorlicht Kerzen', 'Moorlicht Rowing'],\n    ['Acme Dynamics', 'Acme Sports'], ['Northwind Labs', 'Northwind Athletics'], ['Helios Freight', 'Helios Triathlon'], ['Petal Health', 'Petal Physio'],\n    ['Ferrix Systems', 'Ferrix Fitness'], ['Ballast & Co', 'Ballast Boxing'], ['Quorvela', 'Quorvela FC'], ['Acme Ops', 'Acme Sports'],\n    ['B2B SaaS', 'sports clubs'], ['SaaS', 'sports tech'], ['B2B', 'club'], ['SMBs', 'grassroots clubs'], ['SMB', 'grassroots club'],\n    ['AI brief', 'sports brief'], ['AI agents', 'coaching agents'], ['market research', 'scouting'],\n    ['customers', 'athletes'], ['customer', 'athlete'], ['clients', 'clubs'], ['client', 'club'],\n    ['businesses', 'clubs'], ['business', 'club'], ['companies', 'clubs'], ['company', 'club'],\n    ['users', 'athletes'], ['user', 'athlete'], ['buyers', 'members'], ['buyer', 'member'],\n    ['subscribers', 'members'], ['subscriber', 'member'], ['newsletters', 'training briefs'], ['newsletter', 'training brief'],\n    ['leads', 'club leads'], ['ICP', 'athlete profile'], ['prospects', 'scouted clubs'], ['prospect', 'scouted club'],\n    ['products', 'programmes'], ['product', 'programme'], ['invoices', 'membership fees'], ['invoice', 'membership fee'],\n    ['contracts', 'player contracts'], ['contract', 'player contract'], ['vendors', 'suppliers'], ['vendor', 'supplier'],\n    ['proposals', 'sponsor proposals'], ['proposal', 'sponsor proposal'], ['deals', 'sponsor deals'], ['deal', 'sponsor deal'],\n    ['audiences', 'fanbases'], ['audience', 'fanbase'], ['teams', 'squads'], ['team', 'squad'],\n    ['enterprises', 'federations'], ['enterprise', 'federation'], ['agencies', 'academies'], ['agency', 'academy'],\n    ['episodes', 'match episodes'], ['campaigns', 'matchday campaigns'], ['campaign', 'matchday campaign'], ['CRM', 'club CRM']\n  ];\n  sportsRe() {\n    if (this._sportsRe) return this._sportsRe;\n    const L = Component.SPORTS_TEXT, map = {};\n    L.forEach(([a, b]) => { map[a.toLowerCase()] = b; });\n    const esc = (x) => x.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n    const re = new RegExp('\\\\b(' + L.map(([a]) => esc(a)).join('|') + ')\\\\b', 'gi');\n    this._sportsRe = { re, map };\n    return this._sportsRe;\n  }\n  sportsCut(txt) {\n    const { re, map } = this.sportsRe();\n    return txt.replace(re, (m) => {\n      const out = map[m.toLowerCase()]; if (!out) return m;\n      if (m.length > 1 && m === m.toUpperCase() && /[A-Z]/.test(m) && m !== 'ICP' && m !== 'CRM' && m !== 'SMB' && m !== 'SaaS' && m !== 'B2B') return out.toUpperCase();\n      if (m[0] === m[0].toUpperCase() && m[0] !== m[0].toLowerCase()) return out[0].toUpperCase() + out.slice(1);\n      return out;\n    });\n  }\n  applySportsText(on) {\n    if (typeof document === 'undefined') return;\n    this._sportsNodes = this._sportsNodes || new Map();\n    const SKIP = { SCRIPT: 1, STYLE: 1, TEXTAREA: 1, INPUT: 1, CODE: 1, IFRAME: 1 };\n    const visit = (n) => {\n      const p = n.parentNode; if (!p || SKIP[p.nodeName] || (p.closest && p.closest('[data-no-sports]'))) return;\n      const v = n.nodeValue; if (!v || !/[A-Za-z]{3}/.test(v)) return;\n      const rec = this._sportsNodes.get(n);\n      if (rec && v === rec.out) return;\n      const out = this.sportsCut(v);\n      if (out === v) { if (rec) this._sportsNodes.delete(n); return; }\n      this._sportsNodes.set(n, { orig: v, out }); n.nodeValue = out;\n    };\n    const sweep = (root) => { const w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT); let n; while ((n = w.nextNode())) visit(n); };\n    if (on) {\n      if (!this._sportsObs) {\n        this._sportsObs = new MutationObserver(recs => { if (!this.state.founder) return; recs.forEach(r => {\n          if (r.type === 'characterData') visit(r.target);\n          else r.addedNodes.forEach(a => { if (a.nodeType === 3) visit(a); else if (a.nodeType === 1) sweep(a); });\n        }); });\n        this._sportsObs.observe(document.body, { childList: true, subtree: true, characterData: true });\n        sweep(document.body);\n      }\n    } else if (this._sportsObs) {\n      this._sportsObs.disconnect(); this._sportsObs = null;\n      this._sportsNodes.forEach((rec, n) => { if (n.nodeValue === rec.out) n.nodeValue = rec.orig; });\n      this._sportsNodes.clear();\n    }\n  }\n\n  syncDomainFrame() {\n    try {\n      const f = document.querySelector('iframe[data-domain-frame]');\n      if (!f) return;\n      const want = f.getAttribute('data-src') || '';\n      if (!want || want.indexOf('{{') !== -1) return;\n      if (f.getAttribute('src') !== want) f.setAttribute('src', want);\n    } catch (e) {}\n  }\n\n  postBrandToFrame(e) {\n    const win = e && e.target && e.target.contentWindow;\n    if (!win) return;\n    const dark = this.state.theme !== 'light';\n    const brandKey = this.state.mvp && this.state.brandKey === 'aaas' ? 'squr' : this.state.brandKey;\n    const brand = this.brandOrPlaceholder(brandKey);   // unknown keys render their own placeholder, never tenant-0\n    const vars = brand.dark && brand.light ? (dark ? brand.dark : brand.light) : undefined;   // an unknown or incomplete brand applies NO token override — the shell default stands, never a borrow of tenant-0's (permanently undefined) brand entry\n    try { win.postMessage({ type: '__dc_brand_tokens', vars }, '*'); } catch (err) {}\n  }\n\n  /* D190 · tenant copy in frames. A frame whose src carries ?copy=tenant gets its visible text re-cut after\n     load: the active brand's name/tag replace the AaaS marks, ctx.company.* fills name/tagline when present,\n     and the Founder text map applies when Founder is on. Same-origin only (project mode); the bundle can't\n     reach into srcdoc frames from another origin, so it falls back silently. Originals are never edited —\n     it is the same node-level swap D172 uses in the shell. */\n  /* D209 (P3d) · tenant copy pack. tenants/<brandKey>/copy/index.json lists phrase maps ({from: to}, keys\n     starting with _ are notes); each map carries a `match` regex over the frame's src (null = every frame).\n     Loaded per brandKey and cached; a tenant with no pack YET resolves to null and the frame falls back\n     to the brand name/tag pairs + the Founder word map — but a miss is RECOVERABLE, not permanent: the\n     cached null is dropped and re-probed after tenantCopyMissCooldownMs, so a pack added later (or a\n     transient 404) is picked up within the page's life instead of being cached absent forever.\n     A new vertical = a folder of JSON. */\n  tenantCopy(brandKey) {\n    this._tenantCopy = this._tenantCopy || {};\n    const missAt = (this._tenantCopyMissAt || {})[brandKey];\n    if (missAt !== undefined && Date.now() - missAt < (typeof this.tenantCopyMissCooldownMs === 'number' ? this.tenantCopyMissCooldownMs : 30000)) return null;   // re-probe at most this often (an explicit 0 disables the cooldown)\n    if (brandKey in this._tenantCopy) return this._tenantCopy[brandKey];\n    this._tenantCopy[brandKey] = null;   // sync answer while loading\n    const base = this.rootPrefix + 'tenants/' + brandKey + '/copy/';\n    // A map is {from: to}; store.json nests its pairs under data / index / sections (per-page groups from the re-cut tool),\n    // so descend into object values and keep only string → string entries. Keys starting with _ are notes.\n    const flat = (o, out) => { out = out || []; Object.keys(o || {}).forEach(k => { if (k[0] === '_') return; const v = o[k]; if (typeof v === 'string') out.push([dec(k), dec(v)]); else if (v && typeof v === 'object' && !Array.isArray(v)) flat(v, out); }); return out; };\n    const dec = (t) => String(t).replace(/&amp;/g, '&').replace(/&ldquo;/g, '\\u201c').replace(/&rdquo;/g, '\\u201d').replace(/&rsquo;/g, '\\u2019').replace(/&lsquo;/g, '\\u2018').replace(/&nbsp;/g, '\\u00a0').replace(/&mdash;/g, '\\u2014');\n    fetch(base + 'index.json').then(r => r.ok ? r.json() : null).then(idx => {\n      if (!idx || !Array.isArray(idx.maps)) return null;\n      return Promise.all(idx.maps.map(m => fetch(base + m.file).then(r => r.ok ? r.json() : null).catch(() => null).then(j => j && ({\n        match: m.match ? new RegExp(m.match, 'i') : null,\n        pairs: flat(j).sort((a, b) => b[0].length - a[0].length) })))).then(list => list.filter(Boolean));\n    }).catch(() => null).then(maps => {\n      if (!maps || !maps.length) {   // recoverable miss: drop the cached null so a later frame re-probes (cooldown above)\n        delete this._tenantCopy[brandKey];\n        (this._tenantCopyMissAt = this._tenantCopyMissAt || {})[brandKey] = Date.now();\n        return;\n      }\n      if (this._tenantCopyMissAt) delete this._tenantCopyMissAt[brandKey];\n      this._tenantCopy[brandKey] = maps;\n      if ((this.state.brandKey || 'aaas') === brandKey) setTimeout(() => this.broadcastBrandToFrames(), 0);   // re-cut open frames now that the pack is here\n    });\n    return null;\n  }\n  recutFrameCopy(frame) {\n    try {\n      const doc = frame && frame.contentDocument; if (!doc || !doc.body) return;\n      const brandKey = this.state.brandKey || 'aaas', brand = this.brandOrPlaceholder(brandKey);\n      const src = String(frame.getAttribute('src') || '');\n      const pack = brandKey !== 'aaas' ? this.tenantCopy(brandKey) : null;\n      const packPairs = pack ? pack.filter(m => !m.match || m.match.test(src)).reduce((a, m) => a.concat(m.pairs), []) : [];\n      // Source precedence: active brand (not AaaS) → real tenant pack → nothing. The scaffold example pack\n      // (\"Example Co GmbH\", no _provenance) is a shape fixture and never renders as data (CONTEXT-MANAGEMENT-PLAN §1.5).\n      const ctx = window.__dcContext || {}, co = ctx.company || {};\n      const realPack = !!(ctx._provenance || co._provenance) && !/^Example Co/i.test(String(co.name || ''));\n      const name = brandKey !== 'aaas' ? brand.name : (realPack && co.name ? String(co.name) : null);\n      const tag = brandKey !== 'aaas' ? brand.tag : (realPack ? ((co.names && co.names.tagline) || co.tagline || null) : null);\n      const pairs = [];\n      if (name) pairs.push(['AaaS Inc', name], ['Agents as a Service', name], ['agents-as-a-service.com', name.toLowerCase().replace(/[^a-z0-9]+/g, '') + '.com'], ['AaaS', name]);\n      if (tag) pairs.push(['Curated AI agents, automated end-to-end.', tag], ['CURATED AI AGENTS', String(tag).toUpperCase()]);\n      // no pairs and no Founder → still run, so a previous re-cut is restored to the originals\n      const SKIP = { SCRIPT: 1, STYLE: 1, TEXTAREA: 1, INPUT: 1 };\n      this._frameOrig = this._frameOrig || new WeakMap();\n      let orig = this._frameOrig.get(doc); if (!orig) { orig = new Map(); this._frameOrig.set(doc, orig); }\n      const w = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT); let n; let hits = 0;\n      while ((n = w.nextNode())) {\n        if (!n.parentNode || SKIP[n.parentNode.nodeName]) continue;\n        const base = orig.has(n) ? orig.get(n) : n.nodeValue;   // always re-cut from the original\n        if (!base || !/[A-Za-z]{3}/.test(base)) continue;\n        let out = base;\n        packPairs.forEach(([a, b]) => { if (out.indexOf(a) !== -1) out = out.split(a).join(b); });   // tenant phrases first (longest first)\n        pairs.forEach(([a, b]) => { if (out.indexOf(a) !== -1) out = out.split(a).join(b); });\n        if (this.state.founder) out = this.sportsCut(out);\n        if (out !== base) { if (!orig.has(n)) orig.set(n, base); hits++; }\n        if (n.nodeValue !== out) n.nodeValue = out;\n      }\n      doc.body.setAttribute('data-copy', pairs.length || packPairs.length || this.state.founder ? 'tenant' : 'generic');\n      doc.body.setAttribute('data-copy-pack', packPairs.length ? brandKey : 'none');\n      doc.body.setAttribute('data-copy-hits', String(hits));\n    } catch (e) {}\n  }\n  broadcastBrandToFrames() {\n    if (typeof document === 'undefined') return;\n    document.querySelectorAll('iframe').forEach(f => { this.postBrandToFrame({ target: f }); if (/copy=tenant/.test(f.getAttribute('src') || '')) this.recutFrameCopy(f); });\n  }\n\n  resolveGallerySrc(relPath) {\n    const qIdx = relPath.indexOf('?');\n    const base = qIdx === -1 ? relPath : relPath.slice(0, qIdx);\n    const qs = qIdx === -1 ? '' : relPath.slice(qIdx);\n    const id = Component.GALLERY_RES[base];\n    const R = window.__resources || {};\n    // Asset classes live as siblings of gallery/ since the 2026-09-03 consolidation: a '../blocks/',\n    // '../templates/' or '../screens/' entry is rebased onto assets/ instead of hopping through gallery/.\n    // D171: '../domains/' frames (Websites stream) resolve from the project root like the domain-site overlay.\n    if (/^\\.\\.\\/(domains|templates|decks|videos)\\//.test(base)) return (this.rootPrefix || '') + 'assets/' + base.slice(3) + qs;\n    const rebased = /^\\.\\.\\/(blocks|templates|screens)\\//.test(base) ? '../../assets/' + base.slice(3) : '../../assets/gallery/' + base;\n    const resolved = (id && R[id]) || rebased;\n    return resolved + qs;\n  }\n\n  constructor(props) {\n    super(props);\n    this.state = { theme: 'dark', ops: true, active: 'inbox', sub: 0, collapsed: false, palette: false, mega: false, q: '', tasks: ['t1', 't2', 't3'], rosterOpen: true,\n      // PR-1 fix (2026-09-06): the client-stream roster needs its OWN open-state. `rosterOpen` is\n      // initialised true and `_rz()` keeps it `!mob`, i.e. true on desktop — so any FAB condition that\n      // AND-ed `!rosterOpen` could never fire there. Default false = suppressed on `mode:'user'` streams,\n      // and the FAB opens it. Separate field, so leaving a client stream restores the normal desktop panel.\n      rosterClient: false,\n      // CM-G: consumerId -> { label, kind, at, block, fields[] } marked stale by the CM-5 fan-out.\n      ctxStale: {},\n      // CM-G: CM-5 threads this session, in the INBOX-CONTRACT §3 shape, passed to PaneInbox as a prop.\n      // Held here because `inbox-threads.js` deliberately does not store (\"Inbox projects, it never owns\").\n      ctxThreads: [], askOn: false, askLog: [], askDraft: '', askBusy: false, groupOpen: {}, blockersDone: [], rosterSections: {}, role: 'Library', roleMenu: false, galSeg: {},\n      // 4.15f: measured at construction, never hardcoded — a literal here makes the first paint's\n      // branch a race with componentDidMount's _rz(), which is why two probes at 924px disagreed.\n      mob: typeof window !== 'undefined' ? window.innerWidth <= 720 : true,\n      narrow: typeof window !== 'undefined' ? window.innerWidth <= 1000 : true,\n      // mob <= 720px drives the drawer/sheet layout. narrow <= 1000px: the wide top bar's intrinsic\n      // row is ~971px, so below that the brand text and the search label collapse rather than pushing\n      // LOGIN off the clip edge (4.15d).\n      // WP-20: record-detail plane. Selection survives view/tab changes deliberately (not reset\n      // by nav) so a user browsing a stream keeps their open record until they explicitly close it.\n      detailRecord: null,\n      detailSelId: null,\n      detailSplit: false,       // item 13: drawer escalated to split view (Open full page)\n      docEdits: {},             // toolbox document field edits, keyed '<templateId>·<fieldId>'\n      docAi: {},                // accepted Optimise output, same key space\n      docDiff: null,            // { id, name, rows:[{field,label,before,after}] } awaiting accept/discard\n      docBusy: false,           // Optimise in flight\n      docFinal: {},             // templateId → { v, at, fields } — Finalise snapshots\n      viewerMode: 'readable',   // item 13: Readable/Rich media, restored from localStorage 'canvas.viewerMode'\n      accessDenied: null,      // D2 permission model: { label, role } shown as a DENIED overlay\n      mvp: false,                // customer-facing MVP canvas (client sees only their own project)\n      // F-7a (D27) · dev tools. Off by default — the frontend-state control is not product UI.\n      // On via ?dev=1, the Settings-sheet switch, or a prior session's localStorage.\n      devTools: (() => {\n        try {\n          if (/[?&]dev=1/.test(location.search)) return true;\n          return localStorage.getItem('canvas.devTools') === '1';\n        } catch (e) { return false; }\n      })(),\n      chat: 'tab',               // floating agent chat: tab | open | hidden\n      chatDraft: '',\n      chatTurns: 3,\n      briefOK: {}, briefOpen: null, hoverConf: null, sbIdx: 0, sbPick: null,\n      dtTab: 0, dtOpen: { s04: true }, svcOn: {}, wkSel: 0, uaOff: {},\n      chatMsgs: null,            // null = seeded per audience on first render\n      brandKey: 'aaas',          // white-label token set driving the whole canvas\n      viewMode: {},              // per-tab Table/Board pick, keyed 'streamId·tabLabel' (#1 in the improvement plan)\n      brandMenu: false,\n      tour: -1,                  // intro tour step; -1 = closed (localStorage 'canvas.introSeen')\n      railHidden: false,         // wide-screen: stream chip hides the docked rail\n      rosterMobileInit: false,   // roster starts collapsed on mobile\n      railOpen: false,           // stream-rail drawer (mobile)\n      domainSite: null,          // { id, page } — live domain-site overlay (mega-menu \"Domains\" tiles)\n      settings: false,           // top-bar settings sheet (mobile)\n      pane: 'default',           // default | loading | empty | error  (per-pane state selector)\n      zuiSel: 'n7',              // selected process-tree node id\n      zuiZoom: 100,              // whiteboard zoom %\n      croDim: null,              // selected CRO dimension\n      confirm: null,             // { title, verb } → HoldToConfirm overlay\n      onbStep: 2,                // active onboarding step\n      onbDone: { 0: true, 1: true },  // completed steps\n      coach: false,              // coach-mark overlay visible\n      helpMenu: false,           // ? menu in the top bar (2026-09-03)\n      accSection: 'Home landing',// selected settings section\n      landing: 'onboarding',     // Home-landing preference\n      notifs: { digest: true, alerts: true, nurture: false },\n      icpDrawer: null,           // open persona id in ICP detail drawer\n      icpGate: null,             // { allow:[], consent:[], block:[] } — lazily seeded in renderVals\n      leadSel: [],               // selected lead row ids\n      disco: null,               // Run-discovery modal: { step, icp, maxLeads, sources, status }\n      specSel: '01-search-brand.json',  // Campaign Builder selected spec\n      runSel: null,              // D202 · Platform ops › Runs card selected → Replay mounts that run\n      createFlag: false,         // false = /campaigns/create still a 501 stub\n      assetChecks: {},           // Assets & Brand Safety operator checklist { groupId: 'applied'|'review' }\n      pauseAll: null };          // { typed } → emergency Pause ALL modal\n\n    // ══ Developer role · seed data (from the Agent Workspace patch, AW-01..09) ══\n    this.AW_RUNS = [\n      { id: 'r-372', title: 'claude-code · context-loop refactor', status: 'run', statusLabel: 'running', harness: 'claude-code', tier: 'sonnet', worktree: 'feat/372-context-loop', stage: 'CODE', elapsed: '00:18:44', cost: '$0.62', note: 'canvas#372 · 4 files dirty' },\n      { id: 'r-368', title: 'claude-code · outbox retry policy', status: 'run', statusLabel: 'running', harness: 'claude-code', tier: 'sonnet', worktree: 'feat/368-retry', stage: 'SPECIFY', elapsed: '00:06:12', cost: '$0.11', note: 'canvas#368' },\n      { id: 'r-360', title: 'codex · migrate outbox schema', status: 'warn', statusLabel: 'blocked', harness: 'codex', tier: 'opus', worktree: 'feat/360-outbox', stage: 'TEST', elapsed: '01:02:10', cost: '$1.14', note: 'needs approval — destructive migration' },\n      { id: 'r-357', title: 'codex · rate-limit dashboard', status: 'warn', statusLabel: 'blocked', harness: 'codex', tier: 'sonnet', worktree: 'feat/357-ratelimit', stage: 'CODE', elapsed: '00:24:38', cost: '$0.34', note: 'missing GRAFANA_TOKEN' },\n      { id: 'r-355', title: 'gemini · docstring pass', status: 'warn', statusLabel: 'awaiting approval', harness: 'gemini', tier: 'sonnet', worktree: 'chore/355-docstrings', stage: 'REVIEW', elapsed: '00:41:02', cost: '$0.28', note: 'ready to open PR' },\n      { id: 'r-349', title: 'claude-code · rate-limit backoff', status: 'ok', statusLabel: 'done', harness: 'claude-code', tier: 'sonnet', worktree: 'fix/349-backoff', stage: 'DELIVER', elapsed: '00:52:19', cost: '$0.41', note: 'PR #401 merged' },\n      { id: 'r-341', title: 'codex · MCP handshake fix', status: 'fail', statusLabel: 'failed', harness: 'codex', tier: 'sonnet', worktree: 'fix/341-mcp', stage: 'TEST', elapsed: '00:12:55', cost: '$0.09', note: '2 failures in mcp_handshake_test' }\n    ];\n    this.AW_AUTOMATIONS = [\n      { id: 'a1', title: 'Nightly context reindex', trigger: 'cron · 03:00 UTC', backend: 'build-vm-01', state: 'ok', stateLabel: 'active', last: '8h ago', fires: '31' },\n      { id: 'a2', title: 'PR opened → review run', trigger: 'github · pull_request', backend: 'Cloud Run', state: 'ok', stateLabel: 'active', last: '22m ago', fires: '14' },\n      { id: 'a3', title: 'Slack /run command', trigger: 'slack · slash', backend: 'Local', state: 'ok', stateLabel: 'active', last: '2h ago', fires: '6' },\n      { id: 'a4', title: 'Stripe webhook → ledger post', trigger: 'webhook · stripe', backend: 'build-vm-01', state: 'warn', stateLabel: 'paused', last: '3d ago', fires: '0' },\n      { id: 'a5', title: 'Poll upstream release feed', trigger: 'poll · 15m', backend: 'Cloud Run', state: 'fail', stateLabel: 'failed', last: '40m ago', fires: '2' }\n    ];\n    this.AW_BACKENDS = [\n      { id: 'be-1', title: 'This machine', type: 'local', state: 'ok', stateLabel: 'reachable', capacity: '4 concurrent', use: 'secrets-local tasks', note: 'Full FS · your creds' },\n      { id: 'be-2', title: 'build-vm-01', type: 'remote', state: 'ok', stateLabel: 'reachable', capacity: '2 concurrent', use: 'automations', note: 'Isolated · attached repo copy' },\n      { id: 'be-3', title: 'Cloud Run — us-central1', type: 'cloud', state: 'ok', stateLabel: 'reachable', capacity: 'auto-scales', use: 'foundry deploys', note: 'Ephemeral · cost-metered' }\n    ];\n    this.AW_LIBRARY = [\n      { title: 'pdf-extract', kind: 'Skills', meta: 'Parse and chunk PDFs into context blocks. 1.2k installs.' },\n      { title: 'stripe-ledger', kind: 'Skills', meta: 'Post and reconcile Stripe events against the ledger.' },\n      { title: 'brand-voice', kind: 'Skills', meta: 'Rewrite copy against a stored brand voice profile.' },\n      { title: 'playwright-mcp', kind: 'MCP', meta: 'Drive a real browser — 14 tools, headless or headed.' },\n      { title: 'postgres-mcp', kind: 'MCP', meta: 'Read-only SQL with row caps and query timeouts.' },\n      { title: 'gh-review', kind: 'Plugins', meta: 'Open, annotate and merge PRs from inside a run.' }\n    ];\n    this.AW_INBOX = [\n      { title: 'Approve destructive migration on outbox schema', meta: 'r-360 · codex · blocking a run · 46m', sev: 'fail', chip: 'blocking' },\n      { title: 'Grant GRAFANA_TOKEN to rate-limit dashboard run', meta: 'r-357 · secret grant · 1h 12m', sev: 'fail', chip: 'permission' },\n      { title: 'Open PR for docstring pass', meta: 'r-355 · gemini · awaiting approval · 38m', sev: 'warn', chip: 'approval' },\n      { title: 'Raise Cloud Run concurrency to 8', meta: 'backends · cost impact ~$6/day · 2d', sev: 'warn', chip: 'billing' },\n      { title: 'Confirm MCP server allow-list change', meta: 'library · playwright-mcp · 4h', sev: 'warn', chip: 'permission' }\n    ];\n    this.AW_DIFF = [\n      { file: 'apps/engine/src/context/loop.ts', add: '+128', del: '-64', note: 'extracted loop driver',\n        lines: [\n          { t: 'ctx', o: 12, n: 12, s: 'export class ContextLoop {' },\n          { t: 'ctx', o: 13, n: 13, s: '  constructor(private cfg: LoopConfig) {}' },\n          { t: 'del', o: 14, n: null, s: '  run(input: string): LoopResult {' },\n          { t: 'del', o: 15, n: null, s: '    // inline driver logic — 80 lines, hard to test in isolation' },\n          { t: 'add', o: null, n: 14, s: '  run(input: string): LoopResult {' },\n          { t: 'add', o: null, n: 15, s: '    return this.driver.execute(input, this.cfg);' },\n          { t: 'ctx', o: 16, n: 16, s: '  }' },\n          { t: 'add', o: null, n: 17, s: '' },\n          { t: 'add', o: null, n: 18, s: '  private driver = new LoopDriver(this.cfg);' },\n          { t: 'ctx', o: 17, n: 19, s: '}' }\n        ] },\n      { file: 'apps/engine/src/context/window.ts', add: '+42', del: '-9', note: 'token budget guard',\n        lines: [\n          { t: 'ctx', o: 8, n: 8, s: 'function fitWindow(tokens: number, budget: number) {' },\n          { t: 'del', o: 9, n: null, s: '  return tokens; // no guard' },\n          { t: 'add', o: null, n: 9, s: '  if (tokens > budget) throw new WindowBudgetError(tokens, budget);' },\n          { t: 'add', o: null, n: 10, s: '  return tokens;' },\n          { t: 'ctx', o: 10, n: 11, s: '}' }\n        ] },\n      { file: 'apps/engine/test/loop.test.ts', add: '+96', del: '-0', note: 'new coverage',\n        lines: [\n          { t: 'add', o: null, n: 1, s: \"describe('ContextLoop', () => {\" },\n          { t: 'add', o: null, n: 2, s: \"  it('delegates to LoopDriver', () => { ... });\" },\n          { t: 'add', o: null, n: 3, s: \"  it('rejects a run over the token budget', () => { ... });\" },\n          { t: 'add', o: null, n: 4, s: '});' }\n        ] },\n      { file: 'packages/shared/types/run.ts', add: '+11', del: '-3', note: 'RunStage union',\n        lines: [\n          { t: 'del', o: 4, n: null, s: \"export type RunStage = 'plan' | 'code' | 'test' | 'deliver';\" },\n          { t: 'add', o: null, n: 4, s: \"export type RunStage = 'plan' | 'code' | 'test' | 'review' | 'deliver';\" }\n        ] },\n      { file: 'docs/engine/context-loop.md', add: '+34', del: '-12', note: 'updated diagram',\n        lines: [\n          { t: 'del', o: 22, n: null, s: '![old diagram](./loop-v1.png)' },\n          { t: 'add', o: null, n: 22, s: '![loop driver diagram](./loop-v2.png)' },\n          { t: 'add', o: null, n: 23, s: '' },\n          { t: 'add', o: null, n: 24, s: 'The driver now owns retry and budget-guard logic previously inline in `run()`.' }\n        ] }\n    ];\n    this.AW_TEMPLATES = [\n      { title: 'first-run demo', kind: 'Gallery', meta: 'Detect engine, connect, spawn a demo run. Under 5 minutes.' },\n      { title: 'nightly reindex', kind: 'Automations', meta: 'Cron automation with a worktree-isolated agent.' },\n      { title: 'pr-reviewer', kind: 'Blueprints', meta: 'Agent blueprint: review diff, run checks, comment.' },\n      { title: 'spec → code → test', kind: 'Workflows', meta: 'Three-node workflow with an approval gate before deliver.' },\n      { title: 'cost-guard', kind: 'Blueprints', meta: 'Kills a run that exceeds its per-run budget.' },\n      { title: 'inbox triage', kind: 'Workflows', meta: 'Routes open loops to the right operator queue.' }\n    ];\n\n    // 'Inbox' is the generic role — the console view everyone shares, top of the dropdown.\n    // FQ-8 (D156): roles are templates; 'Inbox' left the View menu — Use covers it. ROLE_RAIL.Inbox and\n    // PERMISSIONS.Inbox stay so a minimal seat can still be granted.\n    // D164 (operator 2026-09-24): 'New user' is the client MVP canvas as a role view; 'Inbox' is back (reorder, never remove).\n    // D166: the Admin role view is shown as \"Account\" and leads the dropdown (internal key stays 'Admin').\n    // Operator 2026-10-02 (D224 follow-up): four views only. Founder (its own switch) · User (= Admin, the perfected MVP)\n    // · Delivery (= New user, first contact) · Library (every other role view as a category). Owner/Developer/Marketing/\n    // GTM/Sales/Finance keep their ROLE_RAIL + PERMISSIONS (seats can still be granted); they leave the menu only.\n    this.ROLES = ['Admin', 'New user', 'Library'];\n    this.ROLE_LABEL = { Admin: 'User', 'New user': 'Delivery', Owner: 'CEO', GTM: 'Go-to-market' };\n\n    // Each role sees its own STREAMS categories. Streams themselves are shared —\n    // a role is a view onto the same map, never a duplicate definition.\n    // D166 (operator 2026-09-24): Inbox sits bare above each role view's first group. The shared streams live in\n    // the Account role view, in three stream segments; Admin's own streams follow as a fourth.\n    // Operator 2026-10-03: Websites leaves Inbox for a new Content category — except under Founder, who runs the full\n    // infrastructure and keeps it under Inbox (render-time swap, see FOUNDER_INBOX in the rail build).\n    this.INBOX_TOP = { id: 'inboxTop', label: 'Inbox', bare: true, ids: ['inbox'] };\n    this.CONTENT_CAT = { id: 'contentCat', label: 'Content', ids: ['websites'] };\n    this.FOUNDER_INBOX = ['websites'];\n    this.ACCOUNT_SEGMENTS = [\n      { id: 'accContext', label: 'Context', ids: ['home', 'account', 'onboarding', 'team', 'context', 'intelligence', 'protocol', 'knowledge2', 'account2'] },\n      { id: 'accTools', label: 'Tools', ids: ['select', 'foundry', 'toolbox'] },\n      { id: 'accTemplates', label: 'Templates', ids: ['design', 'video', 'pitches', 'design2', 'estate2'] }\n    ];\n    this.ACCOUNT_IDS = [].concat(...this.ACCOUNT_SEGMENTS.map(g => g.ids));\n    this.GENERIC_GROUP = this.INBOX_TOP;\n    const GEN = this.INBOX_TOP;\n    this.ROLE_RAIL = {\n      Inbox: [GEN],\n      Developer: [GEN,\n        { id: 'build', label: 'Build', ids: ['agentRuns', 'workspace', 'automations'] },\n        { id: 'ship', label: 'Ship', ids: ['review', 'backends'] },\n        { id: 'observe', label: 'Observe', ids: ['observability', 'quality', 'ops2'] },\n        { id: 'extend', label: 'Extend', ids: ['library', 'templates', 'foundry2'] }\n      ],\n      Marketing: [GEN,\n        { id: 'produce', label: 'Produce', ids: ['podcast', 'content', 'voice', 'content2', 'podcast2', 'design2', 'social2'] },\n        { id: 'ads', label: 'Paid ads', ids: ['ads', 'ads2'] },\n        { id: 'operate', label: 'Operate', ids: ['marketing', 'outreach', 'growth', 'product', 'cro', 'outbound2', 'growth2'] }\n      ],\n      // ── GTM Role View · docs/GTM-FLEET-BUILD-PLAN.md §4 ──────────────────────\n      // Pure configuration: no new streams, no new panes. Numbered group labels carry the\n      // chronology (research → offer → context → audience → assets → send → deliver → learn);\n      // every id resolves to a dashboard that already ships.\n      GTM: [GEN,   // D166: bare Inbox on top; the rest of GTM's rail is untouched\n        { id: 'gtmResearch', label: '01 · Research', ids: ['podcast', 'intelligence', 'select'] },\n        { id: 'gtmOffer', label: '02 · Offer', ids: ['packages'] },\n        { id: 'gtmContext', label: '03 · Context', ids: ['protocol', 'clientIndex', 'context'] },\n        { id: 'gtmAudience', label: '04 · Audience', ids: ['icp', 'leads'] },\n        { id: 'gtmAssets', label: '05 · Assets', ids: ['content', 'pitches', 'voice', 'video', 'ads'] },\n        { id: 'gtmSend', label: '06 · Send', ids: ['outreach'] },\n        { id: 'gtmDeliver', label: '07 · Deliver', ids: ['deliverables'] },\n        { id: 'gtmLearn', label: '08 · Learn', ids: ['marketing', 'proposalPipeline', 'growth'] },\n        { id: 'gtmGrowthFleet', label: '09 · Growth Fleet', ids: ['scout', 'composer', 'convert', 'kb', 'outbound2', 'growth2'] },\n        { id: 'gtmControl', label: 'Control room', ids: ['growthRun', 'quality', 'ops', 'toolbox', 'ops2'] }\n      ],\n      Sales: [GEN,\n        { id: 'gtm', label: 'Go-to-market', ids: ['icp', 'leads', 'scout', 'composer', 'convert', 'kb', 'outbound2', 'social2'] },\n        { id: 'proposals', label: 'Proposals', ids: ['packages', 'proposalRequest', 'offeringMatch', 'proposalBuilder', 'proposalReview', 'proposalPipeline', 'clientIndex'] }\n      ],\n      Finance: [GEN,\n        { id: 'money', label: 'Money', ids: ['costs', 'finances', 'billing', 'money2'] }\n      ],\n      Owner: null,   // null = the full rail (RAIL_GROUPS)\n      // Account = the MVP role view: a short MVP rail. Every other stream lives in the Library role.\n      // (ACCOUNT_SEGMENTS / ACCOUNT_IDS are left untouched: other roles still share those permissions.)\n      Admin: [GEN,\n        { id: 'mvpWork', label: 'Work', ids: ['projects', 'automations', 'deliverables'] },\n        { id: 'mvpOrg', label: 'Org', ids: ['account2', 'foundry2'] },\n        { id: 'mvpAudit', label: 'Audit & money', ids: ['ops2', 'money2'] },\n        { id: 'mvpContext', label: 'Context', ids: ['home', 'context', 'knowledge2'] },\n        this.CONTENT_CAT\n      ]\n    };\n\n    this.PROJECTS = [\n      { id: 'p1', label: 'Canvas 2.0 · adaptive shell', meta: '11 surfaces · 4 in canvas', stage: 'R1', state: 'run' },\n      { id: 'p2', label: 'Helsing inbound pilot', meta: 'sales-agt · 4 sources', stage: 'live', state: 'ok' },\n      { id: 'p3', label: 'Context vault migration', meta: '5,400 skills · reindex', stage: 'P1', state: 'warn' },\n      { id: 'p4', label: 'aaas.today daily cycle', meta: 'gen-agt · 3 picks/day', stage: 'live', state: 'ok' }\n    ];\n\n    this.BLOCKERS = [\n      { id: 'b1', label: 'Approve €4.2k Meta budget raise', meta: 'marketing-agt · waiting 2h', sev: 'red', cta: 'Approve' },\n      { id: 'b2', label: 'Sign DPA for Helsing pilot', meta: 'legal · due today', sev: 'gold', cta: 'Open' },\n      { id: 'b3', label: 'Confirm ICP country gating', meta: 'icp-agt · blocks discovery', sev: 'gold', cta: 'Review' }\n    ];\n\n    // ── GTM email agent · mirrors the web shell's outreach stream (lens-rendered on mobile) ──\n    this.GTM_THREADS = [\n      { id: 'm1', variant: 'Cold A', doc: 'cold-a-conversion.html', status: 'queued', when: 'in 12 m',\n        company: 'Nordlicht Fahrradmanufaktur', to: 'lena@nordlicht-bikes.de', subject: 'nordlicht-bikes.de — 11 product photos, no motion' },\n      { id: 'm2', variant: 'Cold B', doc: 'cold-b-web-video.html', status: 'queued', when: 'in 26 m',\n        company: 'Werkhaus Küchen', to: 'kontakt@werkhaus-kuechen.de', subject: 'A 30-second film for werkhaus-kuechen.de' },\n      { id: 'm3', variant: 'Cold A', doc: 'cold-a-conversion.html', status: 'clicked', when: '2 h',\n        company: 'Alpsteig Outdoor', to: 'r.keller@alpsteig.ch', subject: 'alpsteig.ch — spec tables, no motion' },\n      { id: 'm4', variant: 'Delivery', doc: 'delivery-ready.html', status: 'sent', when: '4 h',\n        company: 'Alpsteig Outdoor', to: 'r.keller@alpsteig.ch', subject: 'Your film for alpsteig.ch is ready' },\n      { id: 'm5', variant: 'Cold B', doc: 'cold-b-web-video.html', status: 'opened', when: '1 d',\n        company: 'Studio Grau Interior', to: 'hello@studiograu.de', subject: 'A 30-second film for studiograu.de' },\n      { id: 'm6', variant: 'Cold A', doc: 'cold-a-conversion.html', status: 'bounced', when: '1 d',\n        company: 'Hafenrad Bremen', to: 'info@hafenrad-bremen.de', subject: 'hafenrad-bremen.de — 6 product photos, no motion' }\n    ];\n\n    this.GTM_LEAD = { company: 'Nordlicht Fahrradmanufaktur', site: 'nordlicht-bikes.de', contact: 'Lena Brandt',\n      role: 'Inhaberin · 14 staff', email: 'lena@nordlicht-bikes.de', icp: '0.82', verify: 'deliverable',\n      angle: '11 product photos, no motion', source: 'icp-scanner · run #4821', country: 'DE · allow' };\n\n    this.GTM_DOCS = {\n      'cold-a-conversion.html': { label: 'Cold A · conversion angle', kind: 'Cold outreach',\n        preheader: 'One 30-second film built from nordlicht-bikes.de. Free preview, no account.',\n        vars: [\n          { key: '{{business_name}}', val: 'Nordlicht Fahrradmanufaktur', where: 'subject · line 1 · signature' },\n          { key: '{{site_url}}', val: 'nordlicht-bikes.de', where: 'subject · preheader · overline · footer' },\n          { key: '{{first_name}}', val: 'Lena', where: 'opening clause' },\n          { key: '{{conversion_angle}}', val: '11 product photos, no motion', where: 'headline · body 1' },\n          { key: '{{evidence_metric}}', val: '2.1%', where: 'proof line' },\n          { key: '{{activation_url}}', val: 'aaas.love/a/7f3c9', where: 'CTA href — click = activation' },\n          { key: '{{unsubscribe_url}}', val: 'aaas.love/u/7f3c9', where: 'footer' }\n        ] },\n      'cold-b-web-video.html': { label: 'Cold B · web-video service', kind: 'Cold outreach',\n        preheader: 'A 30-second infomercial for nordlicht-bikes.de, cut from your own photos.',\n        vars: [\n          { key: '{{business_name}}', val: 'Nordlicht Fahrradmanufaktur', where: 'subject · signature' },\n          { key: '{{site_url}}', val: 'nordlicht-bikes.de', where: 'subject · body · footer' },\n          { key: '{{first_name}}', val: 'Lena', where: 'opening clause' },\n          { key: '{{product_noun}}', val: 'e-cargo bikes', where: 'body · step 02' },\n          { key: '{{hero_asset_count}}', val: '11', where: 'body · step 01' },\n          { key: '{{activation_url}}', val: 'aaas.love/a/7f3c9', where: 'CTA href — click = cut starts' },\n          { key: '{{unsubscribe_url}}', val: 'aaas.love/u/7f3c9', where: 'footer' }\n        ] },\n      'delivery-ready.html': { label: 'Delivery · your film is ready', kind: 'Transactional',\n        preheader: '0:32, cut from your own photos. Watch it before you decide.',\n        vars: [\n          { key: '{{deliverable_kind}}', val: 'film', where: 'headline · body · spec block' },\n          { key: '{{duration}}', val: '0:32', where: 'overline · poster · spec' },\n          { key: '{{formats}}', val: 'MP4 · WebM · poster JPG', where: 'spec block' },\n          { key: '{{price}}', val: '€149', where: 'CTA note' },\n          { key: '{{delivery_url}}', val: 'aaas.love/d/7f3c9', where: 'CTA href → delivery page' },\n          { key: '{{expires_in}}', val: '7 days', where: 'CTA note' },\n          { key: '{{unsubscribe_url}}', val: 'aaas.love/u/7f3c9', where: 'footer' }\n        ] }\n    };\n\n    this.GTM_CHECKS = [\n      { label: 'SPF · DKIM · DMARC', val: 'pass', ok: true },\n      { label: 'Spam score', val: '0.8 / 5.0', ok: true },\n      { label: 'Links', val: '2 · both first-party', ok: true },\n      { label: 'Image weight', val: '0 KB · no images', ok: true },\n      { label: 'List-Unsubscribe', val: 'header + footer', ok: true },\n      { label: 'Send window', val: '09:00–11:00 CET', ok: true },\n      { label: 'Country gate', val: 'DE · allow', ok: true }\n    ];\n\n    this.ICONS = {\n      // WP-05 additions. Three fixed silent misses (search / person / arrow-up-right were\n      // referenced by STREAMS + KBT but absent, so those buttons rendered an empty glyph);\n      // the rest are the context-suite's Material Symbols set mapped onto this map\n      // (ligature -> key table in docs/TOKEN-MAP.md §6).\n      //\n      // PROVENANCE. The I0-I6 blocks carry no geometry — they render Lucide from CDN\n      // (`<i data-lucide=\"search\">`) and define name->Lucide mappings plus tone/a11y rules.\n      // So the authoritative geometry is Lucide v0.454.0, which the design system vendors at\n      // assets/icons/*.svg + packages/ui/src/icons.tsx (\"copied from lucide-icons/lucide\").\n      //   verbatim from the vendored SVGs: checkCircle (circle-check), hub (waypoints)\n      //   Lucide v0.454 source geometry: search, person (user), arrowUpRight, cut (scissors),\n      //     doc (file-text), edit (pencil), gavel, lock, swapVert (arrow-up-down), globe,\n      //     verified (badge-check), eye\n      //   composed here, no single Lucide glyph: syncProblem (refresh-cw + alert dot)\n      // Paths are authored on the 24x24 grid with round caps; stroke width comes from ic().\n      search: '<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>',\n      person: '<path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/>',\n      arrowUpRight: '<path d=\"M7 7h10v10\"/><path d=\"M7 17 17 7\"/>',\n      'arrow-up-right': '<path d=\"M7 7h10v10\"/><path d=\"M7 17 17 7\"/>',\n      checkCircle: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m9 12 2 2 4-4\"/>',\n      cut: '<circle cx=\"6\" cy=\"6\" r=\"3\"/><path d=\"M8.12 8.12 12 12\"/><path d=\"M20 4 8.12 15.88\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/><path d=\"M14.8 14.8 20 20\"/>',\n      doc: '<path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\"/><path d=\"M14 2v4a2 2 0 0 0 2 2h4\"/><path d=\"M10 9H8\"/><path d=\"M16 13H8\"/><path d=\"M16 17H8\"/>',\n      edit: '<path d=\"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z\"/><path d=\"m15 5 4 4\"/>',\n      gavel: '<path d=\"m14.5 12.5-8 8a2.119 2.119 0 1 1-3-3l8-8\"/><path d=\"m16 16 6-6\"/><path d=\"m8 8 6-6\"/><path d=\"m9 7 8 8\"/><path d=\"m21 11-8-8\"/>',\n      hub: '<path d=\"m10.586 5.414-5.172 5.172\"/><path d=\"m18.586 13.414-5.172 5.172\"/><path d=\"M6 12h12\"/><circle cx=\"12\" cy=\"20\" r=\"2\"/><circle cx=\"12\" cy=\"4\" r=\"2\"/><circle cx=\"20\" cy=\"12\" r=\"2\"/><circle cx=\"4\" cy=\"12\" r=\"2\"/>',\n      lock: '<rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\" ry=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"/>',\n      swapVert: '<path d=\"m21 16-4 4-4-4\"/><path d=\"M17 20V4\"/><path d=\"m3 8 4-4 4 4\"/><path d=\"M7 4v16\"/>',\n      syncProblem: '<path d=\"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8\"/><path d=\"M21 3v5h-5\"/><path d=\"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16\"/><path d=\"M8 16H3v5\"/><path d=\"M12 9v3\"/><path d=\"M12 15h.01\"/>',\n      globe: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20\"/><path d=\"M2 12h20\"/>',\n      verified: '<path d=\"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z\"/><path d=\"m9 12 2 2 4-4\"/>',\n      eye: '<path d=\"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>',\n      podcast: '<line x1=\"4\" y1=\"10\" x2=\"4\" y2=\"14\"/><line x1=\"8.5\" y1=\"6\" x2=\"8.5\" y2=\"18\"/><line x1=\"13\" y1=\"3\" x2=\"13\" y2=\"21\"/><line x1=\"17\" y1=\"8\" x2=\"17\" y2=\"16\"/><line x1=\"20.5\" y1=\"11\" x2=\"20.5\" y2=\"13\"/>',\n      palette: '<circle cx=\"12\" cy=\"12\" r=\"9\"/><circle cx=\"8.5\" cy=\"9\" r=\"1\" fill=\"currentColor\"/><circle cx=\"12\" cy=\"7.5\" r=\"1\" fill=\"currentColor\"/><circle cx=\"15.5\" cy=\"9\" r=\"1\" fill=\"currentColor\"/><circle cx=\"16.5\" cy=\"13\" r=\"1\" fill=\"currentColor\"/>',\n      select: '<rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"1.5\"/><rect x=\"9.5\" y=\"9.5\" width=\"5\" height=\"5\" rx=\"0.5\"/><line x1=\"9\" y1=\"3\" x2=\"9\" y2=\"6\"/><line x1=\"15\" y1=\"3\" x2=\"15\" y2=\"6\"/><line x1=\"9\" y1=\"18\" x2=\"9\" y2=\"21\"/><line x1=\"15\" y1=\"18\" x2=\"15\" y2=\"21\"/><line x1=\"3\" y1=\"9\" x2=\"6\" y2=\"9\"/><line x1=\"3\" y1=\"15\" x2=\"6\" y2=\"15\"/><line x1=\"18\" y1=\"9\" x2=\"21\" y2=\"9\"/><line x1=\"18\" y1=\"15\" x2=\"21\" y2=\"15\"/>',\n      intelligence: '<path d=\"M12 2.5l8 4.6v9.8l-8 4.6-8-4.6V7.1z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>',\n      grid: '<circle cx=\"6\" cy=\"6\" r=\"1.3\" fill=\"currentColor\"/><circle cx=\"12\" cy=\"6\" r=\"1.3\" fill=\"currentColor\"/><circle cx=\"18\" cy=\"6\" r=\"1.3\" fill=\"currentColor\"/><circle cx=\"6\" cy=\"12\" r=\"1.3\" fill=\"currentColor\"/><circle cx=\"12\" cy=\"12\" r=\"1.3\" fill=\"currentColor\"/><circle cx=\"18\" cy=\"12\" r=\"1.3\" fill=\"currentColor\"/><circle cx=\"6\" cy=\"18\" r=\"1.3\" fill=\"currentColor\"/><circle cx=\"12\" cy=\"18\" r=\"1.3\" fill=\"currentColor\"/><circle cx=\"18\" cy=\"18\" r=\"1.3\" fill=\"currentColor\"/>',\n      content: '<path d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z\"/><path d=\"M14 3v5h5\"/><line x1=\"9\" y1=\"13\" x2=\"15\" y2=\"13\"/><line x1=\"9\" y1=\"17\" x2=\"15\" y2=\"17\"/>',\n      costs: '<rect x=\"3\" y=\"6\" width=\"18\" height=\"13\" rx=\"2\"/><path d=\"M3 10h18\"/><circle cx=\"16.5\" cy=\"14\" r=\"1.2\" fill=\"currentColor\"/>',\n      finances: '<path d=\"M12 3l8 4.5H4z\"/><line x1=\"4\" y1=\"21\" x2=\"20\" y2=\"21\"/><line x1=\"5.5\" y1=\"21\" x2=\"5.5\" y2=\"11\"/><line x1=\"9.8\" y1=\"21\" x2=\"9.8\" y2=\"11\"/><line x1=\"14.2\" y1=\"21\" x2=\"14.2\" y2=\"11\"/><line x1=\"18.5\" y1=\"21\" x2=\"18.5\" y2=\"11\"/>',\n      ops: '<line x1=\"4\" y1=\"8\" x2=\"20\" y2=\"8\"/><line x1=\"4\" y1=\"16\" x2=\"20\" y2=\"16\"/><circle cx=\"9\" cy=\"8\" r=\"2.2\"/><circle cx=\"15\" cy=\"16\" r=\"2.2\"/>',\n      quality: '<path d=\"M12 3l7 3v5.5c0 4-3 6.8-7 8.5-4-1.7-7-4.5-7-8.5V6z\"/><path d=\"M9 12l2 2 4-4\"/>',\n      marketing: '<path d=\"M4 10v4h3l6 4V6L7 10H4z\"/><path d=\"M17 8a4 4 0 0 1 0 8\"/>',\n      product: '<circle cx=\"7.5\" cy=\"8\" r=\"3\"/><rect x=\"13\" y=\"5\" width=\"6\" height=\"6\" rx=\"1\"/><path d=\"M8 14.5l4 6H4z\"/>',\n      platform: '<circle cx=\"6\" cy=\"12\" r=\"2.4\"/><circle cx=\"18\" cy=\"6\" r=\"2.4\"/><circle cx=\"18\" cy=\"18\" r=\"2.4\"/><line x1=\"8.2\" y1=\"11\" x2=\"15.8\" y2=\"7\"/><line x1=\"8.2\" y1=\"13\" x2=\"15.8\" y2=\"17\"/>',\n      inbox: '<path d=\"M4 13h4l1.5 2.5h5L16 13h4\"/><path d=\"M5.5 5h13l2 8v5a1 1 0 0 1-1 1H4.5a1 1 0 0 1-1-1v-5z\"/>',\n      mail: '<rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m3.6 7 8.4 6 8.4-6\"/>',\n      send: '<path d=\"M21 3 10.4 13.6\"/><path d=\"M21 3l-6.8 18-3.7-7.5L3 9.8 21 3z\"/>',\n      columns: '<rect x=\"3\" y=\"4\" width=\"8\" height=\"16\" rx=\"1\"/><rect x=\"13\" y=\"4\" width=\"8\" height=\"16\" rx=\"1\"/>',\n      map: '<path d=\"M9 4 3 6.2v14L9 18l6 2 6-2.2V4L15 6 9 4z\"/><line x1=\"9\" y1=\"4\" x2=\"9\" y2=\"18\"/><line x1=\"15\" y1=\"6\" x2=\"15\" y2=\"20\"/>',\n      play: '<path d=\"M7 5l12 7-12 7z\" fill=\"currentColor\" fill-opacity=\"0.15\"/><path d=\"M7 5l12 7-12 7z\"/>',\n      book: '<path d=\"M5 4a2 2 0 0 1 2-2h12v18H7a2 2 0 0 0-2 2z\"/><line x1=\"9\" y1=\"6.5\" x2=\"15\" y2=\"6.5\"/>',\n      table: '<rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\"/><line x1=\"3\" y1=\"9.5\" x2=\"21\" y2=\"9.5\"/><line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/><line x1=\"10\" y1=\"4\" x2=\"10\" y2=\"20\"/>',\n      timeline: '<line x1=\"7\" y1=\"4\" x2=\"7\" y2=\"20\"/><circle cx=\"7\" cy=\"8\" r=\"2\"/><circle cx=\"7\" cy=\"15\" r=\"2\"/><line x1=\"11\" y1=\"8\" x2=\"19\" y2=\"8\"/><line x1=\"11\" y1=\"15\" x2=\"17\" y2=\"15\"/>',\n      chart: '<path d=\"M4 4v16h16\"/><polyline points=\"7 15 11 10 14 13 19 6\"/>',\n      drawer: '<rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\"/><line x1=\"14\" y1=\"4\" x2=\"14\" y2=\"20\"/>',\n      moon: '<path d=\"M20 14.5A8 8 0 1 1 9.5 4a6.5 6.5 0 0 0 10.5 10.5z\"/>',\n      sun: '<circle cx=\"12\" cy=\"12\" r=\"4\"/><line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"4\"/><line x1=\"12\" y1=\"20\" x2=\"12\" y2=\"22\"/><line x1=\"4\" y1=\"12\" x2=\"2\" y2=\"12\"/><line x1=\"22\" y1=\"12\" x2=\"20\" y2=\"12\"/><line x1=\"5\" y1=\"5\" x2=\"6.5\" y2=\"6.5\"/><line x1=\"17.5\" y1=\"17.5\" x2=\"19\" y2=\"19\"/><line x1=\"5\" y1=\"19\" x2=\"6.5\" y2=\"17.5\"/><line x1=\"17.5\" y1=\"6.5\" x2=\"19\" y2=\"5\"/>',\n      bolt: '<path d=\"M13 3L5 13h5l-1 8 8-11h-5z\" fill=\"currentColor\" fill-opacity=\"0.12\"/><path d=\"M13 3L5 13h5l-1 8 8-11h-5z\"/>',\n      apps: '<rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"/>',\n      chevronLeft: '<polyline points=\"15 6 9 12 15 18\"/>',\n      chevronRight: '<polyline points=\"9 6 15 12 9 18\"/>',\n      chevronDown: '<polyline points=\"6 9 12 15 18 9\"/>',\n      chevronUp: '<polyline points=\"6 15 12 9 18 15\"/>',\n      tree: '<rect x=\"9\" y=\"3\" width=\"6\" height=\"5\" rx=\"1\"/><rect x=\"3\" y=\"16\" width=\"6\" height=\"5\" rx=\"1\"/><rect x=\"15\" y=\"16\" width=\"6\" height=\"5\" rx=\"1\"/><path d=\"M12 8v4M12 12H6v4M12 12h6v4\"/>',\n      foundry: '<path d=\"M4 20V10l5 3V10l5 3V6l6 3v11z\"/><line x1=\"4\" y1=\"20\" x2=\"21\" y2=\"20\"/><line x1=\"8\" y1=\"16\" x2=\"8\" y2=\"16.5\"/><line x1=\"13\" y1=\"16\" x2=\"13\" y2=\"16.5\"/>',\n      trending: '<polyline points=\"3 17 9 11 13 15 21 6\"/><polyline points=\"15 6 21 6 21 12\"/>',\n      fit: '<path d=\"M4 9V5a1 1 0 0 1 1-1h4M20 9V5a1 1 0 0 0-1-1h-4M4 15v4a1 1 0 0 0 1 1h4M20 15v4a1 1 0 0 1-1 1h-4\"/>',\n      relayout: '<path d=\"M21 12a9 9 0 1 1-3-6.7\"/><polyline points=\"21 3 21 8 16 8\"/>',\n      download: '<path d=\"M12 3v12\"/><polyline points=\"7 11 12 16 17 11\"/><line x1=\"5\" y1=\"20\" x2=\"19\" y2=\"20\"/>',\n      refresh: '<path d=\"M3 12a9 9 0 0 1 15-6.7L21 8\"/><polyline points=\"21 3 21 8 16 8\"/><path d=\"M21 12a9 9 0 0 1-15 6.7L3 16\"/><polyline points=\"3 21 3 16 8 16\"/>',\n      sync: '<path d=\"M12 3a9 9 0 0 1 8.5 6\"/><path d=\"M12 21a9 9 0 0 1-8.5-6\"/><polyline points=\"21 3 20.5 9 15 8.5\"/><polyline points=\"3 21 3.5 15 9 15.5\"/>',\n      plus: '<line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\"/><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/>',\n      minus: '<line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/>',\n      check: '<polyline points=\"5 12 10 17 20 6\"/>',\n      x: '<line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>',\n      up: '<polyline points=\"6 15 12 9 18 15\"/>',\n      down: '<polyline points=\"6 9 12 15 18 9\"/>',\n      alert: '<path d=\"M12 3 2 20h20z\"/><line x1=\"12\" y1=\"10\" x2=\"12\" y2=\"14\"/><circle cx=\"12\" cy=\"17\" r=\"0.6\" fill=\"currentColor\"/>',\n      warnState: '<circle cx=\"12\" cy=\"12\" r=\"9\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"13\"/><circle cx=\"12\" cy=\"16\" r=\"0.6\" fill=\"currentColor\"/>',\n      compass: '<circle cx=\"12\" cy=\"12\" r=\"9\"/><polygon points=\"16 8 13 13 8 16 11 11\"/>',\n      gear: '<circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-2.7 1.1v.2a2 2 0 1 1-4 0v-.1A1.6 1.6 0 0 0 6 19.4a1.6 1.6 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.6 1.6 0 0 0-1.1-2.7H.2a2 2 0 1 1 0-4h.1A1.6 1.6 0 0 0 1.4 6 M4.6 4.6l.1.1a1.6 1.6 0 0 0 1.8.3H6.5A1.6 1.6 0 0 0 7.6 3.5V3.2a2 2 0 1 1 4 0v.1\"/>',\n      help: '<circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M9.2 9a2.8 2.8 0 0 1 5.4 1c0 1.9-2.8 2.5-2.8 2.5\"/><circle cx=\"12\" cy=\"17\" r=\"0.6\" fill=\"currentColor\"/>',\n      user: '<circle cx=\"12\" cy=\"8\" r=\"3.4\"/><path d=\"M5.5 20a6.5 6.5 0 0 1 13 0\"/>',\n      bell: '<path d=\"M6 9a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6z\"/><path d=\"M10 20a2 2 0 0 0 4 0\"/>',\n      link: '<path d=\"M9 15l6-6\"/><path d=\"M11 7l1-1a3.5 3.5 0 0 1 5 5l-1 1\"/><path d=\"M13 17l-1 1a3.5 3.5 0 0 1-5-5l1-1\"/>',\n      brush: '<path d=\"M4 20c0-2 1-3 3-3s2 2 4 2 3-3 3-5\"/><path d=\"M14 14l5-8a2 2 0 0 0-3-2l-6 5\"/>',\n      gift: '<rect x=\"4\" y=\"10\" width=\"16\" height=\"10\" rx=\"1\"/><path d=\"M4 10h16M12 10v10\"/><path d=\"M12 10S9 4 6.5 5.5 9 10 12 10zM12 10s3-6 5.5-4.5S15 10 12 10z\"/>',\n      trash: '<path d=\"M4 7h16M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2M6 7l1 13a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1l1-13\"/>',\n      target: '<circle cx=\"12\" cy=\"12\" r=\"9\"/><circle cx=\"12\" cy=\"12\" r=\"5\"/><circle cx=\"12\" cy=\"12\" r=\"1.4\" fill=\"currentColor\"/>',\n      spark: '<path d=\"M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8z\"/>',\n      flag: '<path d=\"M5 21V4M5 4h11l-2 4 2 4H5\"/>',\n      crosshair: '<circle cx=\"12\" cy=\"12\" r=\"8\"/><line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"6\"/><line x1=\"12\" y1=\"18\" x2=\"12\" y2=\"22\"/><line x1=\"2\" y1=\"12\" x2=\"6\" y2=\"12\"/><line x1=\"18\" y1=\"12\" x2=\"22\" y2=\"12\"/><circle cx=\"12\" cy=\"12\" r=\"1.5\" fill=\"currentColor\"/>',\n      arrowRight: '<line x1=\"4\" y1=\"12\" x2=\"19\" y2=\"12\"/><polyline points=\"13 6 19 12 13 18\"/>',\n      contacts: '<circle cx=\"9\" cy=\"8\" r=\"3.2\"/><path d=\"M3.5 20a5.5 5.5 0 0 1 11 0\"/><path d=\"M16 6.5a3 3 0 0 1 0 6\"/><path d=\"M17.5 20a5 5 0 0 0-3-4.6\"/>',\n      copy: '<rect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\"/><path d=\"M5 15V5a2 2 0 0 1 2-2h10\"/>',\n      radar: '<circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 12l6-3\"/><path d=\"M12 12a4 4 0 1 0 3 1.5\"/>',\n      gauge2: '<path d=\"M4 18a8 8 0 0 1 16 0\"/><line x1=\"12\" y1=\"18\" x2=\"16\" y2=\"11\"/><circle cx=\"12\" cy=\"18\" r=\"1.4\" fill=\"currentColor\"/><line x1=\"4\" y1=\"18\" x2=\"6\" y2=\"18\"/><line x1=\"20\" y1=\"18\" x2=\"18\" y2=\"18\"/>',\n      megaphone: '<path d=\"M4 10v4h3l7 4V6L7 10H4z\"/><path d=\"M17 9a4 4 0 0 1 0 6\"/>',\n      shield: '<path d=\"M12 3l7 3v5.5c0 4-3 6.8-7 8.5-4-1.7-7-4.5-7-8.5V6z\"/><path d=\"M9 12l2 2 4-4\"/>',\n      convert: '<path d=\"M4 8h11\"/><polyline points=\"11 4 15 8 11 12\"/><path d=\"M20 16H9\"/><polyline points=\"13 12 9 16 13 20\"/>',\n      power: '<path d=\"M12 3v9\"/><path d=\"M6.5 7a8 8 0 1 0 11 0\"/>',\n      display: '<rect x=\"3\" y=\"4\" width=\"18\" height=\"13\" rx=\"2\"/><line x1=\"8\" y1=\"21\" x2=\"16\" y2=\"21\"/><line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"21\"/>',\n      sparkles: '<path d=\"M12 4l1.4 4.1L17.5 9.5 13.4 11 12 15l-1.4-4L6.5 9.5 10.6 8.1z\"/><path d=\"M18 15l.7 2 2 .7-2 .7-.7 2-.7-2-2-.7 2-.7z\"/>'\n    };\n\n    const k = (label, value, delta, accent, goTab) => ({ label, value, delta: delta || null, accent: accent || 'circuit', goTab: goTab || null });\n    const subs = (arr, scaffold) => arr.map(l => ({ label: l, scaffold: (scaffold || []).indexOf(l) >= 0 }));\n\n    // ┌──────────────────────────────────────────────────────────────────────┐\n    // │ TEMPLATE ENTRY — copy this block to add a new dashboard stream.        │\n    // │ Then add the id to a RAIL_GROUPS group (and optionally MEGA).          │\n    // ├──────────────────────────────────────────────────────────────────────┤\n    // │ myStream: {                                                           │\n    // │   id: 'myStream',          // unique key — must match the property     │\n    // │   label: 'My Stream',      // Title Case — rail, subnav, hero          │\n    // │   icon: 'chart',           // key in the ICONS map (add SVG if new)    │\n    // │   group: 'operate',        // discover|ecosystem|money|operate|personal│\n    // │   tag: 'Terse subtitle',   // app-switcher tile                        │\n    // │   dot: false,              // optional gold attention dot in rail      │\n    // │   badge: null,             // optional red count pill, e.g. '2'        │\n    // │   desc: 'What it does, then who it is for — terse status clauses.',    │\n    // │   kpis: [                  // 3–6. accent: 'teal' clear · 'red' hot    │\n    // │     k('Label', '128', '+3'),          // (label, value, delta?)        │\n    // │     k('Cost · day', '€6', null, 'teal'),                              │\n    // │   ],                                                                   │\n    // │   actions2: [              // optional hero buttons                    │\n    // │     { label: 'Primary', icon: 'play' },                               │\n    // │   ],                                                                   │\n    // │   subs: subs(['Overview','Runs','Replays'], ['Replays']),             │\n    // │   // OPTIONAL — include `ref` ONLY for a filled reference screen;      │\n    // │   // omit it and the stream renders the six-slot scaffold instead.     │\n    // │   ref: {                                                               │\n    // │     contentCards: [ { title: '.thing', meta: 'short meta' } ],         │\n    // │     marketCards:  [ { title: '.thing', meta: 'short meta' } ],         │\n    // │     runs: [ { id:'#0001', agent:'my-agt', status:'ok',                 │\n    // │              dur:'12.4s', cost:'€0.04' } ], // status: ok|run|warn|fail│\n    // │   },                                                                   │\n    // │ },                                                                     │\n    // └──────────────────────────────────────────────────────────────────────┘\n    // D207 · Projects Work fixture — the ONE source the hero getter, the Work table, its board alternate and the note read.\n    // Rows verbatim from kinds-member.jsx:73-100 (W2). lane ∈ Draft · Running · Review · Done.\n    this.PRJ_ROWS = [\n      { title: 'Brand refresh — voice + visual worksheet', status: 'review', lane: 'Review', agent: 'brand-canvas', created: '2026-06-28', last: '2026-07-02 09:12', cost: '€ 0,42', meta: 'Awaiting your approval · € 0,42' },\n      { title: 'Cold-email pack — 40 prospects', status: 'running', lane: 'Running', agent: 'cold-email-personalizer', created: '2026-07-01', last: '2026-07-03 07:02 · live', cost: '€ 0,88', meta: 'Stage 3 of 5 · generating · € 0,88' },\n      { title: 'Blog series — autonomous finance (draft brief)', status: 'draft', lane: 'Draft', agent: '—', created: '2026-07-03', last: '—', cost: '€ 0,00', meta: 'Not started · pick an agent · € 0,00' },\n      { title: 'Podcast brief — weekly AI roundup', status: 'done', lane: 'Done', agent: 'podcast-brief', created: '2026-06-20', last: '2026-06-27 06:30', cost: '€ 0,31', meta: 'Delivered · downloaded · € 0,31' },\n      { title: 'Prospect intel — Helsing inbound', status: 'failed', lane: 'Running', agent: 'prospect-intel', created: '2026-06-14', last: '2026-06-14 11:48', cost: '€ 0,05', meta: 'OpenRouter 429 — replay available · € 0,05' }\n    ];\n    this.prjLanes = () => { const live = ((this.state && this.state.briefsSubmitted) || []).length; const n = (l) => this.PRJ_ROWS.filter(r => r.lane === l).length;\n      return { active: n('Running') + live, review: n('Review'), done: n('Done'), live }; };\n    const shell = this; // D198e: getters below run with `this` = the stream, so counts go through the shell explicitly\n    this.STREAMS = {\n      // ── Introduction · the public landing page + the value tour (Canvas Landing + Intro Tour handoffs) ──\n      onboardingV2: { id: 'onboardingV2', label: 'Introduction', icon: 'compass', group: 'archive', tag: 'Landing · connect · tour', kind: 'landing',\n        archived: true, archivedReason: 'Superseded — activation content moves to Account\\u2019s \"Getting started\" tab (D4, docs/DECISIONS.md).',\n        desc: 'The public landing route into the canvas — connect an API, let the engine contextualise it, then let agents run it.',\n        kpis: [k('Layers', '3'), k('Integrations', '18'), k('Tour steps', '5'), k('Time to first run', 'under 5 min', null, 'teal')],\n        actions2: [{ label: 'Take the tour', icon: 'play' }, { label: 'Connect an API', icon: 'link' }],\n        // 'Tour steps' added 2026-08-06 — Phase 2 Batch 3 migration target (docs/BLOCK-HOST-FINDING.md).\n        // Additive only: the existing 6 tabs still render via PaneIntro unchanged; this one tab is\n        // owned separately so ArchetypeHost mounts ONLY here, nothing else is touched.\n        ownsTabs: ['Tour steps'],\n        subs: subs(['Landing', 'Connect', 'Contextualize', 'Automate', 'Intro tour', 'First run', 'Tour steps'], []) },\n      growth: { id: 'growth', label: 'Growth Fleet', icon: 'trending', group: 'operate', tag: 'Inventory · send · ratchet manual', kind: 'growth',\n        // W-0 honesty pass: SD-11 analytics is PARTIAL — evaluateRound() takes decision/winnerId as\n        // caller-supplied parameters and there is no statistics code in the repo. The Experiment\n        // ratchet is operator-decided, never automated significance testing.\n        // W3 (D152): the source's dataContract is per kind and PARTIAL — no single endpoint exists. Was '/fleet/convert' (Convert's).\n        dataContract: { endpoint: null, status: 'planned', shape: 'items[]+heroKpis[]' },\n        ownsTabs: ['System inventory', 'Ops console', 'Send console', 'Analytics', 'Content calendar', 'Experiment ratchet'],\n        desc: 'The growth fleet — what exists, what is sending, what it converts, and which experiment ratchets next.',\n        // KPIs corrected 2026-08-06 against the source patch (kinds-inventory-growth.jsx). The\n        // previous set was fabricated: 'Live assets 18' (source counts 8 LIVE of 15 assets),\n        // 'Sends today 1,240' (source: 2.140, and en-US comma), 'Deliverability 98.2%'\n        // (source: 97,2 %), 'Budget spent €412' (source: € 184,20), '% greenfield 34%'\n        // (source derives 20% = 3 PLANNED/ABSENT of 15). See docs/PANE-AUDIT.md.\n        kpis: [k('LIVE assets', '8 / 15'), k('Sends today', '2.140', '+310'), k('Deliverability', '97,2 %', '+0,4', 'teal'), k('Budget spent', '€ 184,20'), k('% greenfield', '20 %')],\n        actions2: [{ label: 'Open send console', icon: 'megaphone' }],\n        subs: subs(['System inventory', 'Ops console', 'Send console', 'Analytics', 'Content calendar', 'Experiment ratchet'], []) },\n      agents: { id: 'agents', label: 'Agents', icon: 'intelligence', group: 'member', tag: 'Marketplace · favourites',\n        desc: 'Agents you can put on a project — what each one does, what it costs per run, and which services it needs.',\n        kpis: [k('Available', '12'), k('Connected', '6', null, 'teal'), k('Favourites', '4'), k('Avg cost / run', '3 cr')],\n        actions2: [{ label: 'Marketplace', icon: 'arrowRight' }],\n        subs: subs(['All', 'Favourites', 'Connected', 'Services'], []) },\n      // ── Member surface (from the Canvas 2.0 user/projects/deliverable handoffs) ──\n      projects: { id: 'projects', label: 'Projects', icon: 'columns', group: 'member', tag: 'Work · runs · credits',\n        dataContract: { endpoint: '/product/projects', status: 'stub', shape: 'items[]+heroKpis[]' },\n        desc: 'Every project you have running, the agents on it, and what each one has cost so far.',\n        // W2 (2026-09-23) · K.projects (kinds-member.jsx:52) is homed HERE, not ClientIndex — see fleet/W2-PaneClientIndex.md.\n        // heroKpis + primaryAction verbatim; 'Board view' is the Work tab's existing board alternate.\n        // D206 (P3c): Active / In review / Completed are counted off the Work rows (the same rows the tab renders),\n        // never retyped — the typed '11 completed' had no row behind it (1 Done). Credits spent stays a fixture figure (P4.4).\n        // D207: reads PRJ_ROWS directly (no first-render '—' any more).\n        get kpis() { const L = shell.prjLanes(); return [k('Active', String(L.active), L.live ? '+' + L.live : null, 'teal'), k('In review', String(L.review), null, 'red'), k('Completed', String(L.done), null, 'teal'), k('Credits spent', null, null, 'gold')]; },   // D210: fixture total, no rows to sum in the shell → modeled\n        actions2: [{ label: 'Submit a brief', icon: 'plus' }],\n        // D206: Runs → Platform ops › Runs (same D205 rule as Workspace); lens seed stays in IMPORT_PANES, unreachable.\n        moved: [['Runs', 'ops2', 'Runs']],\n        subs: subs(['Work', 'Library', 'Credits', 'Settings'], []) },\n      packages: { id: 'packages', label: 'Package Builder', icon: 'content', group: 'operate', kind: 'packages', tag: 'Catalog · tiers · quote',\n        dataContract: { endpoint: null, status: 'planned', shape: 'items[]+heroKpis[]' },\n        ownsTabs: ['Catalog', 'Playbook', 'Composer', 'Quote', 'Change review'],\n        desc: 'Define what you sell once — tiered packages with reusable line items that price themselves, match against incoming requests, and convert straight into a quote or invoice.',\n        kpis: [k('Live packages', '2'), k('Avg margin', '45%', '+3%', 'teal'), k('Quoted this month', '€38.4k'), k('Draft', '1', null, 'gold')],\n        actions2: [{ label: 'New package', icon: 'plus' }],\n        subs: subs(['Catalog', 'Playbook', 'Composer', 'Quote', 'Change review'], []) },\n      clientIndex: { id: 'clientIndex', label: 'Client Index', icon: 'content', group: 'proposals', kind: 'clientIndex', tag: 'One client · everything they see',\n        dataContract: { endpoint: null, status: 'planned', shape: 'items[]+heroKpis[]' },\n        ownsTabs: ['Index', 'Process', 'Approve', 'Brief', 'Ledger'],\n        desc: 'Everything a single client can see in one place — their proposals, their deliverables, their invoices. Read-only and scoped to that client.',\n        kpis: [k('Clients', '3'), k('Open proposals', '2', null, 'gold'), k('Open invoices', '2', null, 'gold'), k('Approved deliverables', '5', null, 'teal')],\n        subs: subs(['Index', 'Process', 'Approve', 'Brief', 'Ledger'], []) },\n      proposalRequest: { id: 'proposalRequest', label: 'Proposal Request', icon: 'content', group: 'operate', kind: 'proposalRequest', tag: 'Client intake · adaptive',\n        // PR-1 (2026-09-06): `mode: 'user'` restored from the source — a client filling in intake does not\n        // get the internal agent roster beside them. Suppressed, not hidden: the roster FAB appears.\n        mode: 'user',\n        dataContract: { endpoint: null, status: 'planned', shape: 'items[]+heroKpis[]' },\n        ownsTabs: ['Type', 'Brief', 'Context', 'Contact', 'Submitted'],\n        desc: 'Adaptive client intake — the workforce asks only what is missing for the chosen proposal type, pre-filled from the boutique walkthrough and business-email-domain enrichment.',\n        kpis: [k('Turnaround', '~2 days'), k('Completeness', '80%', '+20%', 'teal'), k('Matched offerings', '4', null, 'teal'), k('Est. range', '€ 48.000–72.000')],\n        actions2: [{ label: 'Save & finish later', icon: 'book' }],\n        subs: subs(['Type', 'Brief', 'Context', 'Contact', 'Submitted'], []) },\n      offeringMatch: { id: 'offeringMatch', label: 'Offering Match', icon: 'content', group: 'operate', kind: 'offeringMatch', tag: 'Request ⇄ catalog',\n        dataContract: { endpoint: null, status: 'planned', shape: 'items[]+heroKpis[]' },\n        ownsTabs: ['Grid', 'Diff'],\n        desc: 'Cross-alignment of the incoming request onto your product offerings — matched items, coverage, and the gaps that need a custom line or an explicit out-of-scope decline.',\n        kpis: [k('Coverage', '78%', null, 'teal', 'Grid'), k('Matched', '4', null, null, 'Grid'), k('Gaps', '1', null, 'red', 'Diff'), k('Est. margin', '41%', null, 'teal')],\n        actions2: [{ label: 'Push to Builder', icon: 'layers' }],\n        subs: subs(['Grid', 'Diff'], []) },\n      proposalBuilder: { id: 'proposalBuilder', label: 'Proposal Builder', icon: 'content', group: 'operate', kind: 'proposalBuilder', tag: 'Segment composer',\n        dataContract: { endpoint: null, status: 'planned', shape: 'items[]+heroKpis[]' },\n        ownsTabs: ['Segments', 'Preview'],\n        desc: 'Assemble a proposal from the shared segment library using a type preset — LLM-fill each segment from the request, offering-match, and client context; reorder, toggle, and preview live.',\n        kpis: [k('Segments', '7 / 9', null, null, 'Segments'), k('Preset', 'services'), k('Est. total', '€ 57.600', null, null, 'Preview'), k('Readiness', '72%', null, 'teal')],\n        actions2: [{ label: 'Preview as client', icon: 'content' }, { label: 'Send for review', icon: 'send' }],\n        subs: subs(['Segments', 'Preview'], []) },\n      proposalReview: { id: 'proposalReview', label: 'Proposal', icon: 'content', group: 'operate', kind: 'proposalReview', tag: 'Client review · e-sign', dot: true,\n        // PR-1 (2026-09-06): the client-facing proposal. Same `mode: 'user'` as the source.\n        mode: 'user',\n        dataContract: { endpoint: null, status: 'planned', shape: 'items[]+heroKpis[]' },\n        ownsTabs: ['Proposal'],\n        desc: 'The interactive client proposal — read it segment by segment, toggle optional scope with live price recalculation, ask questions, then accept and e-sign.',\n        kpis: [k('Total · live', '€ 54.000'), k('Valid until', 'Aug 15'), k('Options', '1'), k('Status', 'viewed', null, 'teal')],\n        actions2: [{ label: 'Download PDF', icon: 'download' }],\n        subs: subs(['Proposal'], []) },\n      proposalPipeline: { id: 'proposalPipeline', label: 'Proposal Pipeline', icon: 'content', group: 'operate', kind: 'proposalPipeline', tag: 'Deal flow · analytics', badge: '3',\n        dataContract: { endpoint: null, status: 'planned', shape: 'items[]+heroKpis[]' },\n        ownsTabs: ['Board', 'Funnel'],\n        desc: 'Deal flow from draft to won or lost — spot stalled deals, watch win/loss conversion, and read the section-dwell signal that drives follow-ups.',\n        kpis: [k('Open value', '€ 312.000', null, null, 'Board'), k('Win rate', '34%', '+3%', 'teal', 'Funnel'), k('Avg close', '11d'), k('Expiring', '3', null, 'red', 'Board')],\n        actions2: [{ label: 'New proposal', icon: 'plus' }, { label: 'Export', icon: 'download' }],\n        subs: subs(['Board', 'Funnel'], []) },\n      deliverables: { id: 'deliverables', label: 'Deliverables', icon: 'content', group: 'member', dot: true, tag: 'Review · approve · revise', kind: 'delivery', templateId: 'client',\n        dataContract: { endpoint: '/delivery', status: 'stub', shape: 'items[]+heroKpis[]' },\n        ownsTabs: ['Markdown', 'Text', 'Image', 'Code', 'Data', 'Web', 'Social', 'Audio', 'Video', 'PDF'],\n        desc: 'Agent output waiting on you — read it, approve it, or send it back with a note.',\n        kpis: [k('In review', '2', null, 'red', 'Markdown'), k('Approved', '18', '+3', 'teal'), k('Avg revisions', '1.4'), k('Turnaround', '18h')],\n        actions2: [{ label: 'Approve', icon: 'check' }, { label: 'Request changes', icon: 'alert' }],\n        subs: subs(['Markdown', 'Text', 'Image', 'Code', 'Data', 'Web', 'Social', 'Audio', 'Video', 'PDF'], []) },\n      // D151 (2026-09-23): Billing renders PaneAccount › Billing — same component + ACC_KINDS fixture, so the member\n      // rail and Account can never disagree. Stream kept for ROLE_RAIL/PERMISSIONS (Finance has billing, not account).\n      billing: { id: 'billing', label: 'Billing', icon: 'costs', group: 'member', tag: 'Credits · plan · invoices', kind: 'account', ownsTabs: ['Billing'],\n        desc: 'Credit balance, plan, and every charge — card entry is Stripe-hosted and never shown in-app.',\n        kpis: [k('Credit balance', '24,840'), k('This month', '€62.40'), k('Plan', 'Pro'), k('Included', '20,000 / mo', null, 'teal')],\n        actions2: [{ label: 'Buy credits', icon: 'plus' }, { label: 'Change plan', icon: 'refresh' }],\n        subs: subs(['Billing', 'Plan', 'Invoices'], []) },\n      // D151: Team renders PaneAccount › Team (same reason as billing above). Roles · Activity stay lens.\n      team: { id: 'team', label: 'Team', icon: 'user', group: 'member', tag: 'Members · seats · roles', kind: 'account', ownsTabs: ['Team'],\n        desc: 'Who is in this workspace, what they may do, and how many seats are left.',\n        kpis: [k('Members', '4'), k('Pending', '1', null, 'red'), k('Seats', '4 / 5'), k('Org credits', '24,840')],\n        actions2: [{ label: 'Invite member', icon: 'plus' }],\n        subs: subs(['Team', 'Roles', 'Activity'], []) },\n      protocol: { id: 'protocol', label: 'Protocol', icon: 'book', group: 'ecosystem', tag: 'Voice · guardrails · output', kind: 'protocol',\n        dataContract: { endpoint: null, status: 'planned', shape: 'items[]+heroKpis[]' },\n        desc: 'The contract every content run obeys — identity, audience, CTA policy, guardrails, and the self-audit gate.',\n        // D206 (P3c): '5 pillars' and '31 banned words' contradicted the pane one screen below (PaneProtocol.dc.html:289\n        // has 4 pillars, :293 has 8 words). The lists are pane-owned state the shell cannot read, so the two cells read\n        // '—' with the reason, not a retyped number. Freshness 90d = PaneProtocol freshness_days; gate pass is a fixture.\n        kpis: [k('Voice pillars', null, 'in pane', 'gold'), k('Banned words', null, 'in pane', 'gold'), k('Freshness', '90d'), k('Gate pass', null, null, 'gold')],   // D210: modeled treatment\n        actions2: [{ label: 'Assemble prompt', icon: 'spark' }],\n        // Tabs derived from the source screen's own 5-step spine (stepDefs) + 2 views onto real\n        // source artifacts (buildPrompt output, canonical_facts). docs/SCREEN-INTEGRATION-PROTOCOL.md\n        subs: subs(['Protocol', 'Assembled prompt', 'Registry'], []) },\n      context: { id: 'context', label: 'Context', icon: 'tree', group: 'discover', tag: 'Foundation · sources · assembly', kind: 'ctx',\n        // WP-11: all 4 modes render as the native `ctx` pane (docs/DATA-CONTRACTS.md — /context, stub).\n        // CM-E (2026-09-05): Overview/Company/History added — health rollup, live company scope block\n        // editor, version diff. All three read window.ContextStore where CM-D's boot loader populated it.\n        // CM-F (2026-09-06): Personal — the operator's own block plus field-level grants (D24 CM-7).\n        // CM-H (2026-09-06): Domains — the 8 block types, their depends_on chain and what is empty\n        // (native in PaneContext); Flows — strategic_gtm_flow through ArchetypeHost's `process`\n        // archetype, mounted shell-side like onboardingV2's 'Tour steps'. Both are declared here\n        // because ownsTabs is what makes a tab native at all, wherever it renders.\n        // T-6 §8/§10 Q3 (2026-09-08): declared at STREAM level, not per tab. PaneContext.dc.html:1036\n        // names `/context` as one flat literal, not keyed by tab — ten per-tab contracts would be\n        // invention. 10 of the 11 tabs read it; `Flows` names no endpoint in source.\n        dataContract: { endpoint: '/context', status: 'stub', shape: '14 segments · ~72 datapoints' },\n        ownsTabs: ['Foundation', 'Sources', 'Browse', 'Assembly', 'Overview', 'Company', 'History', 'Personal', 'Domains', 'Flows', 'Impact'],\n        desc: 'How a context package gets built — foundation facts, sources, browse, and the assembly that ships to an agent.',\n        // D210 (P4.4): no shell-readable fixture supplies these (ContextStore counts blocks, not packages) → modeled.\n        kpis: [k('Packages', null, null, 'gold'), k('Sources', null, null, 'gold'), k('Assembled · 24h', null, null, 'gold'), k('Avg size', null, null, 'gold')],\n        actions2: [{ label: 'Assemble package', icon: 'spark' }],\n        subs: subs(['Overview', 'Company', 'Personal', 'Domains', 'Flows', 'Impact', 'Foundation', 'Sources', 'Browse', 'Assembly', 'History'], []) },\n      // ── Developer role · Agent Workspace (AW-01..09) ──\n      agentRuns: { id: 'agentRuns', label: 'Agent Runs', icon: 'play', group: 'build', tag: 'Parallel runs · worktrees',\n        desc: 'N agents running in parallel, each in its own isolated git worktree — see and steer every run.',\n        kpis: [k('Active runs', '7'), k('Blocked', '2', null, 'red'), k('Awaiting approval', '3'), k('Worktrees', '9'), k('Done today', '14', '+14', 'teal'), k('Failed today', '1', null, 'red')],\n        actions2: [{ label: 'Start a run', icon: 'play' }],\n        subs: subs(['Board', 'Running', 'Blocked', 'Awaiting approval', 'Done', 'Failed', 'Replays'], ['Replays']) },\n      workspace: { id: 'workspace', label: 'Workspace', icon: 'display', group: 'build', tag: 'Side-by-side sessions', kind: 'wk',\n        // WK-3 (2026-09-05): Operator queue is native, ownsTabs-declared — every other tab stays lens\n        // (the source names no distinct structure for Panels/Tools/Files/Transcript beyond the shell's\n        // own invention, per the WK-1 diff).\n        ownsTabs: ['Swimlanes', 'Operator queue'],\n        desc: 'Side-by-side agent conversations as first-class panels — each a live session with tools, files, and status.',\n        kpis: [k('Open panels', '3'), k('Streaming', '1'), k('Awaiting input', '2', null, 'red'), k('Queued msgs', '4'), k('Session cost', '$1.84', null, 'teal')],\n        actions2: [{ label: 'New panel', icon: 'plus' }, { label: '4-up layout', icon: 'columns' }],\n        // D205 (NIGHT-PLAN P3b): a node of `platform-ops` (Runs). Swimlanes → Platform ops › Runs (the one board);\n        // Operator queue → Inbox (D198: same projection). Pane file + ownsTabs untouched.\n        templateId: 'platform-ops', templateNode: 'runs',\n        moved: [['Swimlanes', 'ops2', 'Runs'], ['Operator queue', 'inbox', 'Needs your input']],\n        subs: subs(['Panels', 'Tools', 'Files', 'Transcript'], ['Transcript']) },\n      automations: { id: 'automations', label: 'Automations', icon: 'bolt', group: 'build', tag: 'Cron · GitHub · webhook',\n        desc: 'Define, observe, and audit automations that trigger agent runs — cron, GitHub, Slack, webhook, poll.',\n        kpis: [k('Active', '8'), k('Paused', '2'), k('Fired 24h', '31', '+31', 'teal'), k('Failed 24h', '1', null, 'red'), k('Next fire', 'in 22m')],\n        actions2: [{ label: 'New automation', icon: 'plus' }, { label: 'Run history', icon: 'timeline' }],\n        // D205: node of `platform-ops` (Runs). History → Platform ops › Runs; Audit → Platform ops › Replay.\n        templateId: 'platform-ops', templateNode: 'runs',\n        moved: [['History', 'ops2', 'Runs'], ['Audit', 'ops2', 'Replay']],\n        subs: subs(['All', 'Triggers'], []) },\n      review: { id: 'review', label: 'Review', icon: 'quality', group: 'ship', tag: 'Diff · gates · PR', kind: 'rev',\n        // WK-2 (2026-09-05): Diff and PR are native (real split-diff + PR/checks/gates panel);\n        // Checks/Gates stay lens — the source names no distinct structure for them beyond a flat list.\n        // W1 (2026-09-23): Deliverable is native — the Canvas 2.0 Deliverable Review screen\n        // (pane-sources/A-Review.dc.html; member kind deliverableReview, D149 home = PaneReview).\n        ownsTabs: ['Diff', 'Deliverable', 'PR'],\n        desc: 'Review what an agent actually changed before it ships — diff, PR status, and quality-gate verdicts.',\n        kpis: [k('Awaiting review', '4'), k('Files changed', '23'), k('Checks green', '6/7', null, 'teal'), k('Gate verdicts', '2 / 1'), k('Merged today', '3', '+3', 'teal')],\n        actions2: [{ label: 'Approve & merge', icon: 'check' }, { label: 'Request changes', icon: 'alert' }],\n        // D205: node of `platform-ops` (Deploys — the human gate). Checks + Gates → Platform ops › Deploys.\n        templateId: 'platform-ops', templateNode: 'deploys',\n        moved: [['Checks', 'ops2', 'Deploys'], ['Gates', 'ops2', 'Deploys']],\n        subs: subs(['Diff', 'Deliverable', 'PR'], ['PR']) },\n      backends: { id: 'backends', label: 'Backends', icon: 'platform', group: 'ship', tag: 'Local · VM · Cloud',\n        desc: 'Choose where an agent run executes — Local, Remote VM, or Cloud — with honest state-preservation semantics.',\n        kpis: [k('Registered', '3'), k('Reachable', '3 / 3', null, 'teal'), k('Default', 'Local')],\n        actions2: [{ label: 'Add backend', icon: 'plus' }],\n        // D205: node of `platform-ops` (Infra). Capacity + Migration → Platform ops › Infra.\n        templateId: 'platform-ops', templateNode: 'infra',\n        moved: [['Capacity', 'ops2', 'Infra'], ['Migration', 'ops2', 'Infra']],\n        subs: subs(['Backends'], []) },\n      observability: { id: 'observability', label: 'Observability', icon: 'trending', group: 'observe', tag: 'Events · cost · replay',\n        desc: 'What are the agents doing right now, what did that run cost, and what exactly happened in run X.',\n        kpis: [k('Events/min', '214'), k('Active runs', '7'), k('Cost today', '$14.20', '+2.10', 'red'), k('Tier split', '28% opus'), k('Replays', '312', '+14', 'teal')],\n        actions2: [{ label: 'Open Costs suite', icon: 'chart' }],\n        // AW-7 · Feeds is native (kind 'feeds' + ownsTabs), so it bypasses the spec path the other\n        // three tabs use. Deliberately NOT given a dataContract of its own: it reads the shell's own\n        // records, so inventing an endpoint for it would be the first fabricated row in the table it\n        // renders. It shows up in its own \"no declaration\" group, which is the honest answer.\n        kind: 'feeds', ownsTabs: ['Feeds'],\n        subs: subs(['Event tail', 'Cost', 'Feeds', 'Replay'], ['Replay']) },\n      // You ARE the inbox — the agent console as a mail client. Every mail gesture is a delegation\n      // gesture; every agent is also a real SMTP address. Folders live in the Tier-2 subnav;\n      // Projects + Your staff live in the RIGHT roster panel (the shell already owns both).\n      inbox: { id: 'inbox', label: 'Inbox', icon: 'inbox', group: 'observe', tag: 'Commission · approve · deliver', get badge() { const c = window.AgentRegistry ? shell.agentCounts() : null; return c ? String(c.needs) : ''; }, dot: true, kind: 'inb',\n        // WP-06: ownsTabs makes ownership a declared fact instead of a runtime deletion ritual —\n        // see docs/PANE-RESOLUTION.md. All 5 folders render natively; nothing here is spec-backed.\n        // 4.5a (2026-09-06): this list is the ONLY ownsTabs on this stream. A second\n        // `ownsTabs` naming just 'Awaiting approval' sat 5 lines below it (added with PaneApprovals,\n        // Phase 4c) — a duplicate key in one object literal, so the later one won and 4 of the 5 folders\n        // silently lost `owns`, fell through to a DEV_PANES.inbox spec keyed All/Blocking/Permissions/\n        // Billing (labels that match no folder), and rendered no pane. 'Awaiting approval' stays in this\n        // list: `owns` is what makes kind='inb'. 2026-09-25 (NIGHT-PLAN P1): PaneInbox now renders this\n        // folder itself (approval/draft-state threads) instead of a shell-level swap to PaneApprovals.\n        ownsTabs: ['Inbox', 'Needs your input', 'In progress', 'Awaiting approval', 'Delivered & archived'],\n        // D198f · BUILD-PLAN §1.4 / inbox card I: the contract's named target, stub until bound — renders STUB · modeled.\n        dataContract: { endpoint: '/attention', status: 'stub', shape: 'Thread[] · INBOX-CONTRACT §3' },\n        crumb: 'Observe › Inbox › Thread',\n        desc: 'You are the inbox. Commission an agent the way you write a mail — reply is a revision, forward is a handoff, cc adds a reviewer, snooze schedules a re-run. Nothing leaves the building without your approval.',\n        // D182/D198e: tiles and badge read shell.agentCounts() — the thread-derived agent list, the same call the\n        // heartbeat and roster chips make. (Before D198e these called AR.counts() bare = the raw fixture state.)\n        get kpis() { const c = window.AgentRegistry ? shell.agentCounts() : null; return c\n          ? [k('Needs you', String(c.needs), null, 'red'), k('Stuck', String(c.stuck), null, 'gold'), k('Running', String(c.running)), k('Done · unseen', String(c.done), null, 'teal'), k('Agents', String(c.total))]\n          : [k('Needs you', '—'), k('Stuck', '—'), k('Running', '—'), k('Done · unseen', '—'), k('Agents', '—')]; },\n        actions2: [{ label: 'Commission an agent', icon: 'mail' }, { label: 'Triage', icon: 'check' }],\n        subs: subs(['Inbox', 'Needs your input', 'In progress', 'Awaiting approval', 'Delivered & archived']) },\n      library: { id: 'library', label: 'Library', icon: 'book', group: 'extend', tag: 'Skills · plugins · MCP',\n        desc: 'Browse and enable 5,272 skills, 315 plugins, and 36 MCP servers — search-first, equip an agent.',\n        kpis: [k('Skills', '5,272'), k('Plugins', '315'), k('MCP servers', '36'), k('Enabled', '12', '+12', 'teal'), k('Custom', '9')],\n        actions2: [{ label: 'Add custom', icon: 'plus' }],\n        subs: subs(['All', 'Skills', 'Plugins', 'MCP'], []) },\n      templates: { id: 'templates', label: 'Templates', icon: 'apps', group: 'extend', tag: 'First run in 5 min',\n        desc: 'Under-five-minute first run: detect the engine, guided connect, spawn a demo run — plus a template gallery.',\n        kpis: [k('Templates', '8'), k('First-run state', 'ready', null, 'teal'), k('Demo runs today', '2', '+2', 'teal')],\n        actions2: [{ label: 'Spawn demo run', icon: 'play' }],\n        subs: subs(['Gallery', 'Automations', 'Blueprints', 'Workflows'], ['Workflows']) },\n      podcast: { id: 'podcast', label: 'Podcast', icon: 'podcast', group: 'discover', tag: 'Daily AI brief', kind: 'pd',\n        dataContract: { endpoint: '/podcast/episodes', status: 'stub', shape: 'items[]+heroKpis[]' },\n        // WP-06: only these 4 tabs render the native pane; Generate/Episode/Publish/Runs stay lens.\n        ownsTabs: ['Overview', 'Sources', 'Prompts', 'Episodes', 'Trigger', 'Alerts'],\n        crumb: 'Podcast › Daily AI brief',\n        desc: 'The daily brief builds itself — five stages, one trigger. You approve and publish; nothing ships unreviewed.',\n        kpis: [k('Episodes · week', '7', '+2'), k('Avg gen time', '5:48', '−0:22', 'teal'), k('Sources tracked', '5', '4 live'), k('Cost · day', '€6', null, 'teal')],\n        subs: subs(['Overview', 'Sources', 'Prompts', 'Generate', 'Episode', 'Publish', 'Trigger', 'Alerts', 'Runs', 'Episodes', 'Replays'], ['Replays']) },\n      design: { id: 'design', label: 'Design', icon: 'palette', group: 'discover', tag: 'Tokens · components', kind: 'ds',\n        // WP-06: Catalog/Gallery/Templates/Building Blocks deliberately stay lens — they render the\n        // 57 gallery frames.\n        ownsTabs: ['Overview', 'Catalog', 'Studio', 'Tokens', 'Mirrors', 'PSI Gate', 'Plugin', 'Brand book', 'Logos', 'Voice + Tone', 'Palette', 'Pitch assets'],\n        crumb: 'Design › Design-system ops',\n        desc: 'Design tokens, components and mirrors. One source of truth across every AaaS surface — four gates between a token edit and a live surface.',\n        kpis: [k('Assets', '399'), k('Shipped', '160', '+8', 'teal'), k('Families', '57'), k('Tokens', '214'), k('Components', '11', '+1'), k('Coverage', '98.7%', null, 'teal')],\n        subs: [{ label: 'Overview' }, { label: 'Catalog' }, { label: 'Gallery' }, { label: 'Templates' }, { label: 'Building Blocks' }, { label: 'Videos' }, { label: 'Pitch assets' },\n          { label: 'CONTENT', bucket: true }, { label: 'Email' }, { label: 'Social' }, { label: 'Paid' }, { label: 'Marketing' },\n          { label: 'SYSTEM', bucket: true }, { label: 'Components' }, { label: 'Tokens' }, { label: 'Studio' }, { label: 'PSI Gate' }, { label: 'Plugin' },\n          { label: 'META', bucket: true }, { label: 'Mirrors' }, { label: 'Brand book' }, { label: 'Logos' }, { label: 'Voice + Tone' }, { label: 'Palette' }, { label: 'Replays', scaffold: true }] },\n      select: { id: 'select', label: 'Select', icon: 'select', group: 'discover', tag: 'Decision timeline', dot: true, kind: 'runtrace',\n        dataContract: { endpoint: '/select/runs', status: 'stub', shape: 'items[]+heroKpis[]' },\n        ownsTabs: ['Overview', 'Runs', 'Agents'],\n        crumb: 'Select › Run detail',\n        desc: 'The Zooey core — Decision-Timeline, Output, Logs, Costs. Click a decision step to expand the alternates it considered.',\n        kpis: [k('Run', '#4827'), k('Agent', 'AP-Reconciliation'), k('Duration', '12.4s'), k('Steps', '12'), k('Decisions', '3', null, 'teal'), k('Cost', '€0.042')],\n        actions2: [{ label: 'Export JSONL', icon: 'download' }, { label: 'Purge run', icon: 'x' }],\n        subs: subs(['Overview', 'Runs', 'Run detail', 'Needs review', 'Agents', 'Replays'], ['Replays']) },\n      services: { id: 'services', label: 'Services', icon: 'apps', group: 'ecosystem', tag: 'Toggle · features · audit', kind: 'svc',\n        crumb: 'Account › Services',\n        desc: 'Seventeen AaaS services in four categories. Toggle a service on and its features appear underneath. Every change is written to the audit log.',\n        kpis: [k('Services', '17'), k('Enabled', '7', null, 'teal'), k('Free', '10'), k('Freemium', '5'), k('Builder-only', '2', null, null), k('Audit · 30d', '42', '+8')],\n        actions2: [{ label: 'Enable recommended', icon: 'spark' }, { label: 'Reset to defaults', icon: 'refresh' }],\n        subs: subs(['Services', 'Audit']) },\n      intelligence: { id: 'intelligence', label: 'Intelligence', icon: 'intelligence', group: 'discover', tag: 'One ask · four sources', dot: true, kind: 'ask',\n        dataContract: { endpoint: '/intelligence/decisions', status: 'stub', shape: 'items[]+heroKpis[]' },\n        // I-3/I-4 (2026-09-05): every real tab already resolves natively (`ua.isGraph/isVault/isNb/\n        // isKb/isAudit/isCloseout/isPrompts` in the kind==='ask' block match the real labels below\n        // exactly) except Overview, which stays lens (no native branch handles it). The lens specs\n        // for the other 7 never matched any of them (near-miss keys 'Graph'/'NotebookLM'/'Audit' +\n        // 4 orphans 'Services'/'Runs'/'Agents'/'Replays' with no corresponding tab at all) and were\n        // dead code, reached by nothing. Deleted below rather than left as an accidental-absence\n        // dependency; ownsTabs declares the fact instead.\n        ownsTabs: ['Entity graph', 'Vault', 'Prompts', 'NotebookLM sync', 'KB', 'Query audit', 'Closeout · 14 briefs'],\n        crumb: 'Discover › Intelligence › Overview',\n        desc: 'Agentic database — four knowledge sources fused into one queryable surface. 7,960 entities, 5,412 skills, 5 megadocs and your own KB.',\n        kpis: [k('Entities', '7.960', '+142'), k('Edges', '24.180', '+1.2k'), k('Skills', '5.412', '+18'), k('Megadocs', '5'), k('KB docs', '342', '+12'), k('Queries · 24h', '184', '+22 %'), k('Cost · 24h', '€ 0,42'), k('Avg latency', '1,2 s')],\n        subs: subs(['Overview', 'Ask anything', 'Entity graph', 'Vault', 'Prompts', 'NotebookLM sync', 'KB', 'Query audit', 'Closeout · 14 briefs']) },\n      domains: { id: 'domains', label: 'Domains', icon: 'grid', group: 'ecosystem',\n        dataContract: { endpoint: null, status: 'planned', shape: 'items[]+heroKpis[]' },\n        /* tag · desc · kpis are DERIVED from this.DOMAINS — see the \"Domains counts: derived, never\n           typed\" block right after the DOMAINS object. They carried '15 domains', '15 AaaS sub-domains'\n           and 'Domains 15 / Live now 14 / In Canvas 4 / 15' as literals; every one was stale (15 keys,\n           all live) even before .store made it 16. Left empty rather than left wrong: a future editor\n           grepping \"15 domains\" now finds nothing here to \"fix\" with no visible effect. */\n        tag: '', desc: '', kpis: [],\n        actions2: [{ label: 'Two-Canvases plan (R1)', icon: 'columns' }, { label: 'Migration roadmap', icon: 'map' }],\n        subs: [{ label: 'Overview' }, { label: 'CONTENT', bucket: true }, { label: '.blog' }, { label: '.podcast' }, { label: '.pics' }, { label: '.academy' }, { label: '.today' }, { label: '.toolbox' }, { label: 'MARKET', bucket: true }, { label: '.select' }, { label: '.builders' }, { label: '.boutique' }, { label: '.store' }, { label: '.business' }, { label: '.diy' }, { label: 'COMMUNITY', bucket: true }, { label: '.forum' }, { label: '.quest' }, { label: '.canvas' }, { label: '.company' }, { label: 'PORTFOLIO', bucket: true }, { label: 'Products' }, { label: 'Workshop' }, { label: 'Digest' }],},\n      // D168: Websites = the domain estate replicated for a new vertical (sports founder). Template, not a live estate.\n      websites: { id: 'websites', label: 'Websites', icon: 'globe', group: 'ecosystem', tag: 'Every site · every vertical',\n        dataContract: { endpoint: null, status: 'stub', shape: 'frames[]' }, // D207: was undeclared; frames are files on disk\n        crumb: 'Content › Websites',\n        desc: 'Every website template in one place — the AaaS estate and each vertical re-cut from it. Same pages, new copy. Turn on Founder to work in the sports vertical.',\n        // D206 (P3c): counts come off this stream's own tabs (one tab per sports site) + DOMAIN_SITES; nothing retyped.\n        get kpis() { const sports = (this.subs || []).filter(s => /^sports\\./.test(s.label)).length; const aaas = (shell.DOMAIN_SITES || []).length;\n          return [k('Verticals', String((sports ? 1 : 0) + (aaas ? 1 : 0))), k('Sports sites', String(sports)), k('AaaS sites', String(aaas)), k('State', 'Draft', null, 'gold')]; },\n        actions2: [{ label: 'New vertical', icon: 'plus' }],\n        // D206: 'AaaS estate' duplicates Estate › Live (D203: frames over the same DOMAIN_SITES) → MOVED chip; lens seed stays.\n        moved: [['AaaS estate', 'estate2', 'Live']],\n        subs: subs(['Overview', 'sports.love', 'sports.diy', 'sports.blog', 'sports.builders', 'sports.store'], []) },\n      content: { id: 'content', label: 'Content', icon: 'content', group: 'ecosystem', tag: 'Every asset, shown', kind: 'cnt',\n        // W-0 honesty pass: composer (SD-5) is PARTIAL — per-record LLM cost is genuinely paid per\n        // lead; landing (SD-6) has no page-spec renderer yet.\n        dataContract: { endpoint: '/fleet/composer', status: 'planned', shape: 'items[]+heroKpis[]' },\n        desc: 'Every content surface — actually shown, not just listed. Email previews · landing pages in browser frames · social posts in channel cards · posters at print size.',\n        kpis: [k('Toolbox docs', '267', '+3'), k('Email tpl', '67', '+2'), k('Designs', '142', '+18'), k('Social posts', '212', '+24'), k('Decks', '18', '+1'), k('Brand book', 'v3.2')],\n        actions2: [{ label: 'Asset audit', icon: 'check' }, { label: 'Brand sync', icon: 'sync' }],\n        subs: [{ label: 'Overview' }, { label: 'Toolbox · 267' }, { label: 'Brand book' }, { label: 'Designs' }, { label: 'Email templates · 67' }, { label: 'Landing pages' }, { label: 'Blog drafts' }, { label: 'Documentation' }, { label: 'Social posts' }, { label: 'Banners' }, { label: 'Posters' }, { label: 'Presentations' }, { label: 'Videos' }, { label: 'Snippets' }, { label: 'Public KB articles' }] },\n      // ── D180 · v2 panes on the Process Engine recipe. ADDITIVE: the v1 streams stay untouched until the\n      // operator reassesses every v2 (ruling 2026-09-25). One template each, one archetype per tab.\n      content2: { id: 'content2', label: 'Content · v2', icon: 'content', group: 'ecosystem', tag: 'Brief → compose → publish · shelf', v2: true,\n        dataContract: { endpoint: '/fleet/composer', status: 'planned', shape: 'instances[]+assets[]' },\n        crumb: 'Produce › Content · v2',\n        desc: 'One production flow for every asset type — email, social, landing, blog, banner, poster. Brief it, compose it from a library template, review it against the Protocol, approve, publish. The shelf shows what came out.',\n        // D219 (P4.4): counts read assets/index.json at boot (shell.ASSET_COUNTS); '—' until it lands. In flight stays typed — no source yet.\n        get kpis() { const A = shell.ASSET_COUNTS, n = (c) => A ? String(A[c]) : '—';\n          return [k('Templates', n('templates')), k('In flight', '5'), k('Gallery', n('gallery')), k('Decks', n('decks')), k('Videos', n('videos'))]; },\n        actions2: [{ label: 'New brief', icon: 'plus' }],\n        subs: subs(['Overview', 'Brief', 'Compose', 'Review', 'Approve', 'Publish', 'Shelf'], []) },\n      podcast2: { id: 'podcast2', label: 'Podcast · v2', icon: 'podcast', group: 'discover', tag: 'daily-brief · 8 nodes', v2: true,\n        dataContract: { endpoint: '/podcast/episodes', status: 'stub', shape: 'instances[]+episodes[]' },\n        crumb: 'Produce › Podcast · v2',\n        desc: 'The daily brief as one process: Sources → Prompts → Generate → Review → Publish, on a 06:00 trigger. Publish is a human gate against the Protocol; failures raise Inbox threads.',\n        kpis: [k('Episodes · week', '7', '+2'), k('Avg gen time', '5:48', '−0:22', 'teal'), k('Sources', '5', '4 live'), k('Cost · day', '€6', null, 'teal')],\n        actions2: [{ label: 'Run now', icon: 'play' }],\n        subs: subs(['Overview', 'Sources', 'Prompts', 'Generate', 'Review', 'Publish', 'Trigger', 'Alerts', 'Episodes'], []) },\n      ads2: { id: 'ads2', label: 'Ads · v2', icon: 'megaphone', group: 'operate', tag: 'paid-campaign · 6 nodes', v2: true,\n        dataContract: { endpoint: '/ads/campaigns', status: 'planned', shape: 'instances[]' },\n        crumb: 'v2 › Ads · v2',\n        desc: 'One paid campaign as a process: brief → build → brand-safety gate → launch gate → measure → kill-switch. The five Ads panes become its nodes.',\n        kpis: [k('Nodes', '6'), k('Gates', '3'), k('v1 tabs folded', '8'), k('Live', 'no', null, 'gold')],\n        subs: subs(['Overview','Brief','Build','Brand safety','Launch','Measure','Kill-switch'], []) },\n      outbound2: { id: 'outbound2', label: 'Outbound · v2', icon: 'send', group: 'operate', tag: 'outbound · 7 nodes · one chain', v2: true,\n        dataContract: { endpoint: '/fleet/outbound', status: 'planned', shape: 'instances[]' },\n        crumb: 'v2 › Outbound · v2',\n        desc: 'The seven single-tab streams (ICP, Scout, Leads, Composer, Convert, Outreach, CRO) are one chain. One stream, seven nodes, the handoffs validated.',\n        kpis: [k('Nodes', '7'), k('Gates', '0'), k('v1 tabs folded', '13'), k('Live', 'no', null, 'gold')],\n        subs: subs(['Overview','ICP','Scout','Leads','Compose','Send','Convert','Score'], []) },\n      growth2: { id: 'growth2', label: 'Growth · v2', icon: 'trending', group: 'operate', tag: 'growth-loop · 6 nodes', v2: true,\n        dataContract: { endpoint: null, status: 'planned', shape: 'instances[]' },\n        crumb: 'v2 › Growth · v2',\n        desc: 'Inventory → send → measure → ratchet, with the run trace and ops thread beside it. Content calendar lives in Content · v2 › Publish (one surface).',\n        kpis: [k('Nodes', '6'), k('Gates', '1'), k('v1 tabs folded', '6'), k('Live', 'no', null, 'gold')],\n        subs: subs(['Overview','Inventory','Send','Measure','Ratchet','Run','Ops'], []) },\n      ops2: { id: 'ops2', label: 'Platform ops · v2', icon: 'gauge2', group: 'operate', tag: 'platform-ops · 6 nodes', v2: true,\n        dataContract: { endpoint: '/ops/runs', status: 'stub', shape: 'instances[]' },\n        crumb: 'v2 › Platform ops · v2',\n        desc: 'Runs, alerts, deploys, infra, cost and replay as one operations process. Folds Platform, Ops, Observability, Agent Runs and Backends; the six separate \"Replays\" tabs become one node.',\n        // D205: \"v1 tabs folded\" = the 18 folds named on the Overview node cards (D180) + every `moved` chip in\n        // STREAMS that points here — derived at read time, never retyped.\n        get kpis() { const S = shell.STREAMS || {}; const moved = Object.keys(S).reduce((n, id) => n + ((S[id].moved || []).filter(m => m[1] === 'ops2').length), 0);\n          return [k('Nodes', '6'), k('Gates', '1'), k('v1 tabs folded', String(18 + moved)), k('Live', 'no', null, 'gold')]; },\n        subs: subs(['Overview','Runs','Alerts','Deploys','Infra','Cost','Replay'], []) },\n      knowledge2: { id: 'knowledge2', label: 'Knowledge · v2', icon: 'intelligence', group: 'discover', tag: 'knowledge · 6 nodes', v2: true,\n        dataContract: { endpoint: '/intelligence/decisions', status: 'stub', shape: 'instances[]' },\n        crumb: 'v2 › Knowledge · v2',\n        desc: 'Ask, index, context, impact, decide and SOP as one process. Intelligence, KB, Context, Select and Protocol are its nodes — one Ask surface (also the roster bar).',\n        kpis: [k('Nodes', '6'), k('Gates', '1'), k('v1 tabs folded', '15'), k('Live', 'no', null, 'gold')],\n        subs: subs(['Overview','Ask','Index','Context','Impact','Decide','SOP'], []) },\n      money2: { id: 'money2', label: 'Money · v2', icon: 'chart', group: 'operate', tag: 'money · 6 nodes', v2: true,\n        dataContract: { endpoint: '/finance/ledger', status: 'stub', shape: 'instances[]', fixture: 'assets/data/finances-source.json' },\n        crumb: 'v2 › Money · v2',\n        desc: 'Invoices, costs, budgets, revenue recognition, reporting and billing as one ledger process. Costs (14 tabs) and Finances (10 tabs) fold into six nodes.',\n        kpis: [k('Nodes', '6'), k('Gates', '1'), k('v1 tabs folded', '21'), k('Live', 'no', null, 'gold')],\n        subs: subs(['Overview','Invoices','Costs','Budgets','Revenue','Report','Billing'], []) },\n      // socialpane (2026-10-05): the REAL social-publishing pipeline as a v2 process stream — the\n      // D180 recipe, additive (the MOCK Marketing › Social tabs above stay untouched per the\n      // charter's no-deletion constraint). One template (social-publishing, process-spec.js), one\n      // terminal-produced instance (inst-weekly-social) grounded in the review tools, the tenant\n      // manifest's review policy and the blocked Postiz seam. Blocked live stays shown, never faked.\n      social2: { id: 'social2', label: 'Social · v2', icon: 'radar', group: 'ecosystem', tag: 'social-publishing · 7 nodes', v2: true,\n        dataContract: { endpoint: 'state/social-review/<tenant>-groups.jsonl', status: 'stub', shape: 'groups[]+calendarItems[]' },\n        crumb: 'Marketing › Social · v2',\n        desc: 'The weekly social pipeline as one process: weekly slot → kit draft (Postiz, draft-only) → review email → holder review gate → publish (approve, or the tenant silence window) → review calendar. Draft creation and promote are blocked live until postiz-api-key is readable — shown as blocked, never faked.',\n        get kpis() { const P = window.ProcessSpec, I = P && P.instanceById ? P.instanceById('inst-weekly-social') : null;\n          const ids = I ? Object.keys(I.nodes || {}) : []; const blocked = ids.filter(n => I.nodes[n].state === 'blocked').length;\n          // #279 F3: the silence policy is data, not a constant — read the D6 mirror of the tenant's\n          // autoPublish at its real address: shell.MK_SUITE.review.autoPublish (review sits at SUITE\n          // depth — MK_SUITE.social.review does not exist; that dead path is why the Marketing\n          // badge's own read went dark). Same {enabled, days} shape and OFF fallback, ON · Nd / OFF.\n          const AP = (shell.MK_SUITE && shell.MK_SUITE.review && shell.MK_SUITE.review.autoPublish) || { enabled: false, days: 7 };\n          return [k('Nodes', String(ids.length || '—')), k('Blocked live', String(blocked), null, 'gold'), k('Review gate', 'human'), k('Silence window', AP.enabled ? 'ON · ' + AP.days + 'D' : 'OFF')]; },\n        actions2: [],\n        subs: subs(['Process'], []) },\n      estate2: { id: 'estate2', label: 'Estate · v2', icon: 'globe', group: 'ecosystem', tag: 'estate · 6 nodes', v2: true,\n        dataContract: { endpoint: null, status: 'stub', shape: 'instances[]' },\n        crumb: 'v2 › Estate · v2',\n        desc: 'Every public surface — domains, design system, decks, video, voice — as one publish process: source → tokens → build → review → publish gate → live shelf. Websites (the verticals) is its template node.',\n        kpis: [k('Nodes', '6'), k('Gates', '1'), k('v1 tabs folded', '22'), k('Live', 'no', null, 'gold')],\n        subs: subs(['Overview','Source','Tokens','Build','Review','Publish','Live'], []) },\n      account2: { id: 'account2', label: 'Account · v2', icon: 'gear', group: 'personal', tag: 'account · 6 nodes', v2: true,\n        dataContract: { endpoint: '/account', status: 'planned', shape: 'instances[]' },\n        crumb: 'v2 › Account · v2',\n        desc: 'Identity, plan, people, agents, projects and start as one account process. Settings, Team, Billing, Home, Projects, Agents and both onboardings are its nodes.',\n        kpis: [k('Nodes', '6'), k('Gates', '0'), k('v1 tabs folded', '22'), k('Live', 'no', null, 'gold')],\n        subs: subs(['Overview','Identity','Plan','People','Agents','Org chart','Profile','Governance','Connectors','Secrets','Activity','Runs','Timeline','Tasks','Routines','Artifacts','Export','Adapters','Projects','Start'], []) },\n      foundry2: { id: 'foundry2', label: 'Foundry · v2', icon: 'foundry', group: 'discover', tag: 'foundry · 5 nodes', v2: true,\n        dataContract: { endpoint: '/foundry/queue', status: 'planned', shape: 'instances[]' },\n        crumb: 'v2 › Foundry · v2',\n        desc: 'Build, score, publish and install agents, skills and templates as one process. Foundry, Library, Templates, Toolbox and the Process Tree are its nodes.',\n        kpis: [k('Nodes', '5'), k('Gates', '1'), k('v1 tabs folded', '13'), k('Live', 'no', null, 'gold')],\n        subs: subs(['Overview','Compose','Score','Publish','Install','Docs'], []) },\n      design2: { id: 'design2', label: 'Design · v2', icon: 'palette', group: 'discover', tag: 'template library → edit → save to Content', v2: true,\n        dataContract: { endpoint: null, status: 'stub', shape: 'templates[]+instances[]' },\n        crumb: 'v2 › Design · v2',\n        desc: 'Design is the template library. Pick a template, edit its slots and brand, save — the instance lands in Content · v2 › Shelf. Tokens, mirrors and the PSI gate stay in Estate · v2.',\n        get kpis() { const A = shell.ASSET_COUNTS, n = (c) => A ? String(A[c]) : '—', saved = (shell.state.contentInstances || []).length;   // D219: index-backed\n          return [k('Templates', n('templates')), k('Gallery', n('gallery')), k('Saved to Content', String(saved), null, saved ? 'circuit' : 'gold'), k('Live', 'no', null, 'gold')]; },\n        actions2: [{ label: 'New from template', icon: 'plus' }],\n        subs: subs(['Overview', 'Library', 'Gallery', 'Edit', 'Save to Content'], []) },\n      // ── Pitches (PT-1) · the deck shelf. D30: this is an INTEGRATION of the material already on\n      // the drive (assets/decks/found-invest + assets/gallery/objects/aaas-hero-objects), not a build.\n      // Reuse works at both deck and slide level, so Slide library is a peer tab, not a detail view.\n      pitches: { id: 'pitches', label: 'Pitches', icon: 'content', group: 'ecosystem', kind: 'pitch',\n        tag: 'Decks · slides · objects · video',\n        // 'stub', not 'planned'. The W-0 marker renders 'planned' as \"every figure here is modeled\",\n        // which would sit inches above a pane whose every count is read from <section data-label>,\n        // Video Timing.md and the SRT — the badge would contradict the plane, and undercut the\n        // RULE IT banner that asks the operator to rule on the strength of those counts. 'stub'\n        // renders as SNAPSHOT / \"not reading live data\", which describes pitch-inventory.js exactly:\n        // real counts, snapshotted at author time. ('planned' stays true of the Send & track and\n        // Compose TABS, which say so themselves.)\n        dataContract: { endpoint: null, status: 'stub', shape: 'decks[]+slides[]+objects[]' },\n        ownsTabs: ['Overview', 'Deck detail', 'Slide library', 'Objects', 'Send & track', 'Compose'],\n        crumb: 'Ecosystem › Pitches › Deck',\n        desc: 'Every pitch we own — open one, send it, lift its slides, or turn it into an animated video. Decks, their voice-over and the 3D hero objects are one production, not three exports.',\n        // Counts are what is on disk today (pitchdecks/INDEX.md + the two imported folders), not targets.\n        // Derived from pitch-inventory.js — the same module PanePitches reads. Hand-typing these\n        // is what let the strip say 24 slides / 3 zips while the shelf showed 48 / 2. The `Slides`\n        // label is scoped ('ingested') because the bare number under 'Decks 6' read as a total.\n        kpis: (function () { var T = window.PitchInventory.totals(); return [\n          k('Decks', String(T.decks), T.conflicts ? T.conflicts + ' in conflict' : T.superseded + ' superseded', T.conflicts ? 'red' : 'teal'),\n          k('Slides · ingested', String(T.slidesIngested), T.distinct + ' distinct'),\n          k('Objects', String(T.objects), 'styles', 'teal'),\n          k('Video', T.runtime, T.scenes + ' scenes'),\n          k('Awaiting ingest', String(T.awaiting), T.awaitingZips + ' zips + ' + T.awaitingVariants + ' variants', 'gold'),\n          k('Deck kit', String(T.kitDecks), T.kitSlides + ' slides · ' + T.motion + ' motion', 'teal')\n        ]; })(),\n        actions2: [{ label: 'Compose a deck', icon: 'plus' }, { label: 'Deck → video', icon: 'play' }],\n        subs: subs(['Overview', 'Deck detail', 'Slide library', 'Objects', 'Send & track', 'Compose'], []) },\n      // ── VO Studio (PT-3) · reads the productions' own cue files, three schemas and all.\n      // 'stub' not 'planned' (the D31 rule): the cue files are real and fetched at mount.\n      voice: { id: 'voice', label: 'VO Studio', icon: 'content', group: 'ecosystem', kind: 'vox',\n        tag: 'Reels · cues · drift',\n        dataContract: { endpoint: null, status: 'stub', shape: 'vo_cues[]+srt' },\n        ownsTabs: ['Reels', 'Cues', 'Drift'],\n        crumb: 'Ecosystem › VO Studio › Reel',\n        desc: 'Every voice-over we have, read from its own cue file — per-scene timing, measured words-per-minute, and the places the reels disagree with each other.',\n        // No counts typed here: the plane fetches the cue files and counts them, and a KPI that\n        // restated them would be the pitch-inventory mistake again in a new stream.\n        kpis: [k('Reels', '4', '3 cue schemas', 'red'), k('Source', 'on disk', 'assets/decks'), k('Editing', 'read-only', 'srt is generated', 'gold'), k('Per-scene', 'yes', 'stitched preview owed')],\n        actions2: [{ label: 'New reel', icon: 'plus' }],\n        subs: subs(['Reels', 'Cues', 'Drift'], []) },\n      // ── Video (PT-4) · the plane that brings deck + voice + objects together. Reads each\n      // animation's own OM_SCENES literal; no scene or duration is typed here.\n      video: { id: 'video', label: 'Video', icon: 'play', group: 'ecosystem', kind: 'vid',\n        tag: 'Reels · scene alignment',\n        dataContract: { endpoint: null, status: 'stub', shape: 'OM_SCENES[]+vo_cues[]' },\n        ownsTabs: ['Reels', 'Alignment'],\n        crumb: 'Ecosystem › Video › Reel',\n        desc: 'Four animated reels and the three-way alignment behind each one — scenes against the voice sections that time them and the deck they came from.',\n        kpis: [k('Reels', '4', 'animations_v3'), k('Source', 'OM_SCENES', 'read at mount'), k('Deck ≠ video', 'always', 'not a storyboard', 'red'), k('Export', 'host timeline', 'no render farm', 'gold')],\n        actions2: [{ label: 'Open reel', icon: 'play' }],\n        subs: subs(['Reels', 'Alignment'], []) },\n      costs: { id: 'costs', label: 'Costs', icon: 'costs', group: 'money', tag: 'Stripe · LLM · GCP', kind: 'cs',\n        ownsTabs: ['Overview', 'Budgets', 'Rev-rec'],\n        crumb: 'Costs › System usage',\n        desc: 'Stripe, LLM and GCP spend in one ledger. What the system costs to run — budgets, forecasts and per-run attribution.',\n        kpis: [k('MTD spend', '€4,182', '+18%'), k('Run-rate close', '€6,040', 'modeled'), k('Biggest mover', 'LLM', '+28%', 'red'), k('Budget headroom', '€3,318', '44% left', 'teal'), k('Days left', '9')],\n        subs: subs(['Overview', 'Sources', 'AP inbox', 'Ledger', 'LLM', 'GCP', 'Stripe', 'FX', 'Budgets', 'Rev-rec', 'Subscriptions', 'Free users', 'Forecast', 'Replays'], ['Replays']) },\n      finances: { id: 'finances', label: 'Finances', icon: 'finances', group: 'money', tag: 'Invoices · clients · EUR', dot: true, kind: 'fin',\n        dataContract: { endpoint: '/finance/dashboard', status: 'stub', shape: 'items[]+heroKpis[]' },\n        ownsTabs: ['EÜR report', 'Gutschriften', 'Invoice detail'],\n        desc: 'Invoices, clients, and expenses in EUR. Cash position and runway at a glance. Books kept EÜR, cash basis, de-DE.',\n        // KPI strip corrected 2026-09-04 (D19). It previously read `Revenue MTD €18.2k · Outstanding\n        // €7.1k · Expenses MTD €18.4k · Cash position €254.8k · Result MTD −€0.2k · Runway 14mo`, and\n        // every value traced to a lens spec — but three labels misdescribed what they held:\n        // `€18,200` is the MRR row, `€18,400` is the Burn·month row, `€7,140` is the Overdue row. So\n        // \"Result MTD −€0.2k\" was MRR minus burn: two different accounting concepts subtracted, printing\n        // a LOSS headline directly above an EÜR tab reporting a surplus. Labels now say what the values\n        // are; the derived Result is GONE rather than recomputed, because a real one has to come from\n        // the invoice and expense ledgers (D20) and typing a replacement is the Costs incident.\n        // NB the same defect had a SECOND copy in `IMPORT2_PANES._finances2.Overview` (the card that\n        // actually renders, since _finances2 is merged over DEV_PANES.finances) — fixed in the same\n        // pass after a source-wide grep. Editing one site and declaring it done was the error.\n        kpis: [k('MRR', '€18.2k', '+4%', 'teal'), k('Overdue', '€7.1k', '2 invoices', 'red'), k('Burn · month', '€18.4k', 'flat'), k('Cash position', '€254.8k'), k('Runway', '14mo')],\n        subs: subs(['Overview', 'Invoices', 'Invoice detail', 'Clients', 'Expenses', 'Gutschriften', 'Subscriptions', 'EÜR report', 'Settings', 'Replays'], ['Replays']) },\n      ops: { id: 'ops', label: 'Ops', icon: 'ops', group: 'operate', tag: 'Runbook · alerts', badge: '1',\n        dataContract: { endpoint: '/ops/schedule-presets', status: 'stub', shape: 'items[]+heroKpis[]' },\n        desc: 'Runbook, alerts, and the deploy queue. Everything keeping the workforce online.',\n        // D219: Agents read the roster counter (was a typed '9' against the heartbeat's live 17).\n        get kpis() { const c = window.AgentRegistry ? shell.agentCounts() : null;\n          return [k('Alerts', '1', null, 'red'), k('DLQ', '0', null, 'teal'), k('Uptime', '99.9%'), k('Agents', c ? String(c.total) : '—')]; },\n        subs: subs(['Overview', 'Alerts', 'Queue', 'Deploys', 'Onboarding', 'Impersonate', 'Replays'], ['Replays']) },\n      quality: { id: 'quality', label: 'Quality', icon: 'quality', group: 'operate', tag: 'Evals · guardrails', kind: 'ql',\n        dataContract: { endpoint: '/quality/delivery', status: 'stub', shape: 'items[]+heroKpis[]' },\n        // WP-08: Delivery health / Review queue / Product health render as the native `ql` pane\n        // (docs/DATA-CONTRACTS.md — /quality/delivery, /quality/queue, /quality/product, all stub).\n        // Overview/Support chat/Consolidation/Evals/Guardrails/Regressions stay lens — no reference\n        // for them yet.\n        ownsTabs: ['Delivery health', 'Review queue', 'Product health'],\n        desc: 'Evals, guardrails, and regression tracking. The bar every agent has to clear.',\n        kpis: [k('Error budget', '82%', null, 'teal'), k('Open queue', '14'), k('Deploy freq', '3.1/day'), k('MTTR', '38m'), k('NPS', '61', '+4', 'teal'), k('PSI mobile', '92')],\n        subs: subs(['Overview', 'Delivery health', 'Review queue', 'Product health', 'Evals', 'Guardrails', 'Regressions'], []),\n        // D195: Support chat → client Support · Consolidation → Knowledge › Index · Replays → Platform ops › Replay\n        moved: [['Support chat', 'csupport', ''], ['Consolidation', 'knowledge2', 'Index'], ['Replays', 'ops2', 'Replay']] },\n      marketing: { id: 'marketing', label: 'Marketing', icon: 'marketing', group: 'operate', tag: 'Campaigns · syndication', badge: '3', kind: 'mk',\n        dataContract: { endpoint: '/analytics/ga4-funnel', status: 'stub', shape: 'items[]+heroKpis[]' },\n        // W3 (D152): Social · Calendar · Queue become pane tabs — the SQUR social suite upgrades them (were lens/generic).\n        ownsTabs: ['Overview', 'Funnel', 'Leads', 'AI citations', 'Audience', 'Social', 'Calendar', 'Queue'],\n        tabViews: { 'Overview': ['chart', 'table', 'cards'], 'Funnel': ['chart', 'table'],\n                    'Leads': ['board', 'table'], 'AI citations': ['cards', 'table'] },\n        crumb: 'Marketing › Command centre',\n        desc: 'One funnel across 18 surfaces. Draft, schedule and replay from one board — nothing publishes until a human approves it.',\n        kpis: [k('Audience', '4,342', '+312 mtd'), k('Published · MTD', '18', 'on pace', 'teal'), k('Pipeline · leads', '42', '3 demos'), k('Needs action', '3', 'HITL', 'red')],\n        subs: subs(['Overview', 'Funnel', 'Leads', 'AI citations', 'Audience', 'Social', 'Email'], []),\n        // D195: Campaigns → Ads · Channels → Growth › Inventory · Calendar → Content › Publish · Queue → Inbox · Replays → Platform ops › Replay\n        moved: [['Campaigns', 'ads2', 'Build'], ['Channels', 'growth2', 'Inventory'], ['Calendar', 'content2', 'Publish'], ['Queue', 'inbox', 'Needs your input'], ['Replays', 'ops2', 'Replay']] },\n      product: { id: 'product', label: 'Product', icon: 'product', group: 'operate', tag: 'Roadmap · specs', kind: 'pdt',\n        dataContract: { endpoint: '/product/pipelines', status: 'stub', shape: 'items[]+heroKpis[]' },\n        // WP-09: all 7 reference views render as the native `pdt` pane (docs/DATA-CONTRACTS.md —\n        // /product/pipelines, /agents, /work, /projects, /sales, /orders, /threads, all stub).\n        ownsTabs: ['Pipelines', 'Agents', 'Work', 'Projects', 'Sales', 'Orders', 'Threads'],\n        // tabViews deliberately NOT declared yet — PaneProduct already uses `view` internally for its\n        // tab and implements 7 branches; offering archetypes it cannot render would put dead icons in\n        // the strip. Declare here once the pane implements them (docs/CANVAS-IMPROVEMENT-PLAN.md #1).\n        desc: 'Roadmap, specs, and shipped work. What the platform is becoming next.',\n        kpis: [k('Active agents', '6'), k('Items in flight', '19'), k('Blocked', '3', null, 'red'), k('Spend today', '€41'), k('Shipped', '5', '+2', 'teal')],\n        subs: subs(['Overview', 'Roadmap', 'Specs', 'Shipped', 'Sales', 'Orders'], []),\n        // D195: Pipelines → Platform ops › Runs · Agents → Account › Agents · Work/Projects → Projects · Threads → Inbox · Replays → Platform ops › Replay\n        moved: [['Pipelines', 'ops2', 'Runs'], ['Agents', 'account2', 'Agents'], ['Work', 'projects', 'Work'], ['Projects', 'account2', 'Projects'], ['Threads', 'inbox', 'Inbox'], ['Replays', 'ops2', 'Replay']] },\n      platform: { id: 'platform', label: 'Platform', icon: 'platform', group: 'operate', tag: 'Infra · deploys', kind: 'pl',\n        dataContract: { endpoint: '/integrations/status', status: 'stub', shape: 'items[]+heroKpis[]' },\n        // WP-10: Overview + Integrations render as the native `pl` pane (docs/DATA-CONTRACTS.md —\n        // /integrations/status, stub). Infra/Environments/Deploys stay lens — no reference for them.\n        ownsTabs: ['Overview', 'Integrations'],\n        desc: 'Infrastructure, environments, and deploys. The ground the whole system stands on.',\n        kpis: [k('Services up', '11 / 11', null, 'teal'), k('Open alerts', '1', null, 'red'), k('Integrations', '8'), k('Deploys today', '3'), k('p95', '210ms')],\n        subs: subs(['Overview', 'Infra', 'Environments', 'Integrations', 'Deploys', 'Replays'], ['Replays']) },\n      outreach: { id: 'outreach', label: 'Outreach', icon: 'mail', group: 'operate', tag: 'GTM email agent · live send OFF', badge: '2', kind: 'mail',\n        // W-0 honesty pass (docs/GTM-FLEET-MASTER-PLAN.md §A.6, GTM-6): SD-7 send and SD-8 compliance\n        // are both MISSING upstream. OUTREACH_LIVE_SEND is default-OFF; nothing here dispatches.\n        dataContract: { endpoint: '/fleet/outreach', status: 'planned', shape: 'items[]+heroKpis[]' },\n        crumb: 'Go-to-market › Outreach › Email agent',\n        desc: 'Cold mail to delivery in one thread. Every draft here is the file the sender ships — no second copy.',\n        kpis: [k('In queue', '12'), k('Sent · 24h', '84'), k('Opened', '41%'), k('Clicked', '17%', '+4', 'teal'), k('Replies', '9'), k('Bounced', '1', null, 'red')],\n        actions2: [{ label: 'Approve queue', icon: 'play' }, { label: 'Templates', icon: 'palette' }],\n        subs: subs(['Overview', 'Queue', 'Cold A', 'Cold B', 'Delivery emails', 'Delivery page', 'Sent', 'Templates', 'Deliverability', 'Variants', 'Follow-up ladder']) },\n      // ══ MVP · the customer-facing canvas. Same shell, four streams, nothing else. ══\n      // The client here is the receiver of an AaaS offer — they see their project, not the platform.\n      // D204 (NIGHT-PLAN P3a): the six MVP streams are the nodes of the `client` template (process-spec.js) —\n      // `templateId` binds each one; the rail is unchanged, the Overview strip renders the order.\n      cdelivery: { id: 'cdelivery', label: 'Your script', icon: 'play', group: 'client', tag: 'Six steps · context to film', kind: 'client', templateId: 'client',\n        crumb: 'Your project › Script',\n        desc: 'Six steps from your own pages to a finished film. Context first, then the script, the storyboard, a five-second preview, review and delivery.',\n        kpis: [k('Step', '2 of 6'), k('Length', '0:45'), k('Words', '118'), k('Script', '€149'), k('Full film', '€49', null, 'teal'), k('Revisions', '2 left', null, 'teal')],\n        actions2: [{ label: 'Ask for a change', icon: 'mail' }],\n        subs: subs(['Overview', 'Context', 'Script', 'Storyboard', 'Snippet', 'Review', 'Delivery']) },\n      cbrief: { id: 'cbrief', label: 'Your brief', icon: 'book', group: 'client', tag: 'What the agent knows', badge: '5', kind: 'client', templateId: 'client',\n        crumb: 'Your project › Brief',\n        desc: 'Everything the agent used to write your script, pulled from your own site — fifteen topics. Open any one to read the detail, edit it, and confirm it.',\n        kpis: [k('Topics', '15'), k('Sources read', '7'), k('Last sweep', '2 h ago'), k('Words of context', '4,180')],\n        actions2: [{ label: 'Correct something', icon: 'mail' }],\n        subs: subs(['Brief', 'Sources']) },\n      cbilling: { id: 'cbilling', label: 'Billing', icon: 'finances', group: 'client', tag: 'Invoices · plan', templateId: 'client',\n        crumb: 'Account › Billing',\n        desc: 'One line per delivery. No subscription, no seats — you pay for what you keep.',\n        kpis: [k('Outstanding', '€149', null, 'red'), k('Paid · 2026', '€298'), k('Next charge', 'none', null, 'teal')],\n        actions2: [{ label: 'Download invoices', icon: 'download' }],\n        subs: subs(['Invoices', 'Plan']) },\n      csupport: { id: 'csupport', label: 'Support', icon: 'inbox', group: 'client', tag: 'One thread, a human at the end', templateId: 'client',\n        crumb: 'Account › Support',\n        desc: 'One thread with the people who wrote your script. Replies inside a working day.',\n        kpis: [k('Open', '1'), k('Answered', '4', null, 'teal'), k('Median reply', '3 h')],\n        actions2: [{ label: 'Write a message', icon: 'mail' }],\n        subs: subs(['Messages']) },\n      home: { id: 'home', label: 'Home', icon: 'inbox', group: 'personal', tag: 'Your inbox across streams', kind: 'home',\n        // O5 (D15 = GO): Overview/Services/Threads/Tasks are the native PaneHome, ported verbatim from\n        // canvas/30-audits/pane-sources/A-Personal Home.dc.html + its home.css. Inbox and Saved keep\n        // their generic lens specs below — ownsTabs bypasses the spec for the four owned tabs only (WP-06).\n        ownsTabs: ['Overview', 'Services', 'Threads', 'Tasks'],\n        dataContract: { endpoint: null, status: 'planned', shape: 'items[]+heroKpis[]' },\n        desc: 'Your inbox across every stream — runs, replies, and decisions that need you.',\n        // KPIs deliberately NOT retyped to the pane's own 2/3/1. These are stream-level counts across\n        // every stream ('7 unread' inbox-wide); the pane's '3 unread' is threads with your own agents in\n        // the personal workspace — a different denominator, same class as the Services-overlap finding\n        // in PLAN-personal-home.md. Flagged in PANE-AUDIT rather than silently reconciled.\n        // D206 (P3c): 'Awaiting you' = the Inbox projection's needs count (same number as the rail badge, heartbeat and\n        // roster — D198e). Unread / Saved stay typed until P4.4 (no shell-readable fixture: PaneHome owns its threads).\n        get kpis() { const c = window.AgentRegistry ? shell.agentCounts() : null; return [k('Unread', null, null, 'gold'), k('Awaiting you', c ? String(c.needs) : '—', null, 'red'), k('Saved', null, null, 'gold')]; },   // D210: Unread/Saved are pane-owned → modeled\n        // D206: the 'Inbox' lens tab duplicated the Inbox stream (one queue, INBOX-CONTRACT §1) → MOVED chip; seed stays.\n        moved: [['Inbox', 'inbox', 'Inbox']],\n        subs: subs(['Overview', 'Services', 'Threads', 'Tasks', 'Saved'], []) },\n\n      // ── Personal · onboarding + account ──\n      // 4.5c (2026-09-06) — source attribution corrected. This said \"(proposals design package)\", which is\n      // false: `_surfaces/proposals/aaas-proposal-suite/project/Proposal Suite.dc.html` declares no\n      // `onboarding` and no `account` stream (grepped: 0 matches for either, or for isOnboarding/isAccount).\n      // Real authorities — `account`: the member patch's AW/4.8 `accountSettings` kind in\n      // `05_Canvas/_surfaces/home-account/_recovered-canvas2.0-registry/kinds-member.jsx` (its\n      // `workspaceLabel` IS \"Account & Settings\"); `onboarding`: `_surfaces/_unmapped/\n      // AaaS-Onboarding-Web+Mobile.html` + \"AaaS Onboarding — Flow & Emails.pdf\" (diff owed, item 4.5c-2).\n      // See canvas/30-audits/LOSS-LEDGER.md § 4.5c.\n      onboarding: { id: 'onboarding', label: 'Onboarding', icon: 'compass', group: 'personal', tag: 'First-run walkthrough', dot: true, kind: 'onboarding',\n        crumb: 'Personal › Onboarding',\n        desc: 'Welcome to your canvas — a permanent, always-revisitable home for the product walkthrough. Start anytime; every step is saved.',\n        kpis: [k('Steps done', '2 / 6'), k('Setup tasks', '1 / 4'), k('Est. time', '4 min', null, 'teal'), k('Agents spawned', '0', null, 'red')],\n        actions2: [{ label: 'Start walkthrough', icon: 'play' }, { label: 'Skip for now', icon: 'x' }],\n        // Explainers (2026-09-03): the CX click-through tours live here as a second tab — each row opens\n        // CanvasTour.dc.html on that track, driven by its recorded voice-over. Also surfaced under\n        // Design › Videos for the production view; Onboarding is where a new operator meets them.\n        // W2 · servicesWizard (kinds-services.jsx:149, D149 → PaneOnboarding, D150 → process). Additive tab;\n        // the shell renders it (ArchetypeHost `process`), PaneOnboarding keeps Walkthrough.\n        subs: subs(['Walkthrough', 'Getting started', 'Explainers'], []) },\n      account: { id: 'account', label: 'Settings', icon: 'gear', group: 'personal', tag: 'Configure your canvas', kind: 'account',\n        dataContract: { endpoint: '/account', status: 'stub', shape: 'items[]+heroKpis[]' }, // D207: matches account2; fixture ACC_KINDS\n        crumb: 'Personal › Account & Settings',\n        desc: 'The one place to configure your canvas — profile, where it opens, notifications, connected services, brand, and more.',\n        // D207 (verifier r2): stream-level kpis are DEAD here — SUBHERO.account defines all three tabs and the hero builder\n        // takes sh.kpis first. The derived values live there ('services connected' from ACC_SERVICES since D151; 'members' /\n        // 'seats used' from orgTeam.mockItems since D207). Kept empty so nobody retypes a hero that never renders.\n        kpis: [],\n        actions2: [{ label: 'Save changes', icon: 'check' }],\n        // W2 · PaneAccount (D149: billing · orgTeam · accountSettings). Per-tab hero in SUBHERO.account.\n        ownsTabs: ['Billing', 'Team', 'Settings'],\n        subs: subs(['Billing', 'Team', 'Settings'], []) },\n\n      // ── Canvas-native migrations (apps/canvas top-3) ──────────────────────\n      zui: { id: 'zui', label: 'Process Tree', icon: 'tree', group: 'canvas', tag: 'ZUI whiteboard · graph', dot: true, kind: 'zui', templateId: 'client',\n        dataContract: { endpoint: null, status: 'planned', shape: 'items[]+heroKpis[]' },\n        crumb: 'Projects › AP-Reconciliation › Process Tree',\n        desc: 'Zoomable process tree for an autonomous run — every step, agent, and artifact as nodes; dependencies and data-flow as edges. Read-first; mutating actions hold-to-confirm.',\n        // D219: counted off ZUI_NODES, the seed the plane draws (was typed '142' against a 14-node tree).\n        get kpis() { const N = shell.ZUI_NODES || [], by = (st) => N.filter(x => x.state === st).length, f = by('failed');\n          return [k('Nodes', String(N.length)), k('Depth', String(N.length ? Math.max(...N.map(x => x.row)) + 1 : 0)), k('Running', String(by('running')), null, 'teal'), k('Blocked', String(by('blocked')), null, 'red'), k('Failed', String(f), null, f ? 'red' : 'teal')]; },\n        actions2: [{ label: 'Fit to view', icon: 'fit' }, { label: 'Re-layout', icon: 'relayout' }, { label: 'Export PNG', icon: 'download' }],\n        subs: subs(['Whiteboard'], []) },\n      foundry: { id: 'foundry', label: 'Foundry', icon: 'foundry', group: 'canvas', tag: 'Worker orchestration', badge: '6', kind: 'foundry',\n        dataContract: { endpoint: null, status: 'planned', shape: 'items[]+heroKpis[]' },\n        crumb: 'Fleet › Foundry',\n        desc: 'Worker Orchestration — a maturity roster (F1–F6), the task queue each worker is draining, and the escalation inbox of items needing a human. Promote/pause and approve/reject hold-to-confirm.',\n        kpis: [k('Workers', '24'), k('Running', '9', null, 'teal'), k('Queued tasks', '137'), k('Blocked', '4', null, 'red'), k('Mean maturity', 'F3.2'), k('Escalations', '6', null, 'red')],\n        actions2: [{ label: 'Refresh', icon: 'refresh' }, { label: 'Export', icon: 'download' }],\n        subs: subs(['Roster', 'Queue'], []) },\n      icp: { id: 'icp', label: 'ICP Builder', icon: 'crosshair', group: 'gtm', tag: 'Ideal customer profile · country gate planned', kind: 'icp',\n        // W-0 honesty pass: SD-2 is MISSING and no `country_gate` table exists in any migration\n        // (GTM-D13) — nothing mechanically blocks outbound to a disallowed country today.\n        dataContract: { endpoint: '/fleet/icp', status: 'planned', shape: 'items[]+heroKpis[]' },\n        crumb: 'Go-to-market › ICP Builder',\n        desc: 'Define your ideal-customer profile — firmographics, target segments, buying personas, and a compliance-critical country gate. Everything downstream (lead lists, outreach) inherits from here.',\n        kpis: [k('Segments', '3'), k('Personas', '4'), k('TAM', '$4.2B', null, 'teal'), k('Allowed geos', '18'), k('Consent-req', '6', null, null), k('Blocked', '2', null, 'red')],\n        actions2: [{ label: 'Build ICP', icon: 'spark' }, { label: 'Export', icon: 'download' }],\n        subs: subs(['Builder'], []) },\n      leads: { id: 'leads', label: 'Leads', icon: 'contacts', group: 'gtm', tag: 'Discovered lead list · 3-bucket verify', kind: 'leads',\n        // W-0 honesty pass: SD-3 provider wiring is real but has zero callers; SD-4 verify ships a\n        // 3-bucket taxonomy (safe/risky/blocked) per GTM-D11 — not SD-4's proposed 4.\n        dataContract: { endpoint: '/fleet/leads', status: 'live', shape: 'items[]+heroKpis[]' },\n        crumb: 'Fleet › Leads',\n        desc: 'Verified, ICP-scored contacts discovered from your ideal-customer profile. Approve the good ones; discovery keeps the list fresh.',\n        kpis: [k('Leads', '412'), k('Verified valid', '71%', null, 'teal'), k('Risky / catch-all', '18%', null, null), k('Avg ICP', '0.74', null, 'teal')],\n        actions2: [{ label: 'Run discovery', icon: 'radar' }, { label: 'Export', icon: 'download' }],\n        subs: subs(['Leads'], []) },\n      scout: { id: 'scout', label: 'Scout', icon: 'radar', group: 'gtm', tag: 'Idea discovery · demand/fit/buildability scoring', kind: 'scout',\n        dataContract: { endpoint: '/fleet/scout', status: 'planned', shape: 'items[]+heroKpis[]' },\n        crumb: 'Fleet › Scout',\n        desc: 'Scans market and usage signals for new product ideas, scores each on demand, fit, buildability, and differentiation, and flags duplicates before anything reaches Composer.',\n        kpis: [k('Candidates', '6', '+2', 'teal'), k('Signals · 30d', '41', '+9'), k('Top composite', '0.81', '+0.06', 'teal'), k('Seeded', '1')],\n        actions2: [{ label: 'Run scan', icon: 'radar' }, { label: 'Export', icon: 'download' }],\n        subs: subs(['Scout'], []) },\n      composer: { id: 'composer', label: 'Composer', icon: 'spark', group: 'gtm', tag: 'Sequence drafting · variants + compliance review', kind: 'composer',\n        dataContract: { endpoint: '/fleet/composer', status: 'planned', shape: 'items[]+heroKpis[]' },\n        crumb: 'Fleet › Composer',\n        desc: 'Drafts and A/B-tests outbound sequences from approved leads — stage, variant count, and compliance review tracked before a send goes live.',\n        kpis: [k('Sequences', '7', '+2', 'teal'), k('In review', '2'), k('Live', '3', '+1', 'teal'), k('Avg variants', '2.4', '+0.2')],\n        actions2: [{ label: 'New sequence', icon: 'spark' }, { label: 'Export', icon: 'download' }],\n        subs: subs(['Composer'], []) },\n      convert: { id: 'convert', label: 'Convert', icon: 'trending', group: 'gtm', tag: 'Stripe-hosted checkout · reply to paid', kind: 'convert',\n        dataContract: { endpoint: '/fleet/convert', status: 'planned', shape: 'items[]+heroKpis[]' },\n        crumb: 'Fleet › Convert',\n        desc: 'Tracks Stripe-hosted checkouts from outreach reply through to paid subscription — plan, MRR, and close date per customer. No raw card fields ever pass through Canvas.',\n        kpis: [k('Conversions · 30d', '9', '+3', 'teal'), k('MRR', '€ 4.180', '+620', 'teal'), k('Checkout → paid', '62%', '+5'), k('Avg deal', '€ 464', '+18')],\n        actions2: [{ label: 'Refresh', icon: 'refresh' }, { label: 'Export', icon: 'download' }],\n        subs: subs(['Convert'], []) },\n      kb: { id: 'kb', label: 'Knowledge Base', icon: 'book', group: 'gtm', tag: 'Content grounding · fabrication flagged before ship', kind: 'kb',\n        dataContract: { endpoint: '/fleet/kb', status: 'planned', shape: 'items[]+heroKpis[]' },\n        crumb: 'Fleet › Knowledge Base',\n        desc: 'Grounds every generated content piece against source material before it feeds Composer or Social — flags fabrication instead of shipping it.',\n        kpis: [k('Pieces', '22', '+5', 'teal'), k('Grounded', '94%', '+2', 'teal'), k('In review', '3'), k('Flagged', '1', '-1', 'red')],\n        actions2: [{ label: 'Ingest', icon: 'spark' }, { label: 'Export', icon: 'download' }],\n        subs: subs(['Knowledge Base'], []) },\n      growthRun: { id: 'growthRun', label: 'GrowthRun', icon: 'radar', group: 'gtm', tag: 'Orchestration spine · stage FSM + human gates', kind: 'growthrun',\n        dataContract: { endpoint: '/growth-runs', status: 'planned', shape: 'items[]' },\n        crumb: 'Fleet › GrowthRun',\n        desc: 'The 7-stage conductor that sequences every other GTM stage — defining through done — with durable human-gate pauses and a replayable event log.',\n        kpis: [k('Active runs', '2', '+1', 'teal'), k('Waiting on human', '1'), k('Failed', '0'), k('Avg stage time', '4m')],\n        actions2: [{ label: 'New run', icon: 'radar' }, { label: 'Export', icon: 'download' }],\n        subs: subs(['GrowthRun'], []) },\n      // ── Toolbox · the tenant's toolset pack (F2, 2026-09-03 · canvas/20-plans/TOOLBOX-STREAM.md) ──\n      // Reads tenants/<active>/toolset/toolbox-manifest.json. Counts come from the manifest at load\n      // (this.loadToolset), never typed here — the repo documents eight drifted hand-typed counts.\n      // Lens-rendered by default (HANDOVER §2); phases are the Tier-2 subnav.\n      toolbox: { id: 'toolbox', label: 'Toolbox', icon: 'apps', group: 'operate', tag: 'Toolset pack · business templates by phase',\n        dataContract: { endpoint: 'tenants/<id>/toolset/toolbox-manifest.json', status: 'stub', shape: 'phases[].templates[]' }, // stub = SNAPSHOT badge (DATA-CONTRACTS vocabulary: live · stub · planned)\n        crumb: 'Control room › Toolbox',\n        desc: 'Every business template the tenant owns, organised by startup phase. Open one in the Studio: its context fields are predetermined by the template schema, you edit them, the document optimises itself against the company context.',\n        kpis: [k('Templates', '—'), k('Phases', '—'), k('Context-mapped', '—'), k('Finalised', '0')],\n        actions2: [{ label: 'Open in Studio', icon: 'spark' }, { label: 'Export index', icon: 'download' }],\n        // Studio = the document studio (canvas/20-plans/TOOLBOX-DOCUMENT-STUDIO.md): context fields left,\n        // live template centre, optimise/finalise right. Native branch, Opus O1b.\n        subs: subs(['Overview', 'Studio'], []) },\n      // ── Ads · one command centre, six surfaces (replaces the four single-tab ads streams) ──\n      ads: { id: 'ads', label: 'Ads', icon: 'megaphone', group: 'ads', tag: 'Google Ads command centre', dot: true,\n        crumb: 'Ads › Command centre',\n        desc: 'Eight specs, three channel types, one gate. Everything ships PAUSED — nothing spends until a human enables it at the launch gate.',\n        kpis: [k('Campaigns', '8'), k('Spend today', '€48 / €70'), k('Conversions · 14d', '6'), k('Blended CPL', '€16', null, 'teal'), k('Blockers', '3', null, 'red'), k('Create mode', 'PAUSED', null, 'red')],\n        actions2: [{ label: 'Recheck signals', icon: 'refresh' }, { label: 'Docs', icon: 'book' }],\n        subs: subs(['Launch Readiness', 'Campaign Builder', 'Assets & Brand Safety', 'Conversions', 'Budget & Kill-Switch', 'Account Overview']) },\n      cro: { id: 'cro', label: 'CRO', icon: 'trending', group: 'canvas', tag: 'Conversion scores', kind: 'cro',\n        dataContract: { endpoint: null, status: 'planned', shape: 'items[]+heroKpis[]' },\n        crumb: 'Growth › CRO',\n        desc: 'Conversion-Rate Optimization scores by dimension — an overall score, a per-dimension breakdown, and a funnel showing where conversion leaks. Read-only.',\n        kpis: [k('CRO score', '72 / 100'), k('Conv. rate', '3.4%', '+0.3', 'teal'), k('Sessions', '48.2K'), k('Top leak', 'Checkout', null, 'red'), k('Last sync', '06:00 UTC')],\n        actions2: [{ label: 'Sync from API', icon: 'sync' }, { label: 'Export', icon: 'download' }],\n        subs: subs(['Scores'], []) }\n    };\n\n    // The MVP rail — four client streams, nothing from the platform.\n    this.MVP_IDS = ['cdelivery', 'cbrief', 'zui', 'deliverables', 'cbilling', 'csupport', 'onboarding'];\n    this.MVP_RAIL = [\n      // D165: Process Tree and Deliverables belong to the new user's project.\n      { id: 'clientwork', label: 'Your project', ids: ['cdelivery', 'cbrief', 'zui', 'deliverables'] },\n      { id: 'clientacct', label: 'Account', ids: ['cbilling', 'csupport'] },\n      { id: 'start', label: 'Get started', ids: ['onboarding'] }\n    ];\n    // FQ-2 (D155): the client brand is the tenant's — read from tenants/brand-tokens.js by offer id at\n    // render time. The former MVP_CLIENT / CLIENT_TOKENS literals named a client in the shell and had no reader.\n    // The six steps of the project, in the client's words. Each one is a tab.\n    this.CLIENT_STEPS = [\n      { n: '01', tab: 'Context', icon: 'book', tone: 'teal', state: 'signed off', title: 'Context',\n        body: 'Everything the agent read from client-site.example, held as fifteen topics. Open one, correct it, confirm it. Nothing gets written from a topic you have not seen.' },\n      { n: '02', tab: 'Script', icon: 'play', tone: 'circuit', state: 'with you now', title: 'Script',\n        body: 'A 45-second spoken read. Two lines play free; the rest unlocks when you approve. Two revision rounds are included in the price.' },\n      { n: '03', tab: 'Storyboard', icon: 'display', tone: 'circuit', state: 'next', title: 'Storyboard',\n        body: 'Pick a visual style, then see the script cut into six shots on your own brand — before a single frame is rendered.' },\n      { n: '04', tab: 'Snippet', icon: 'sparkles', tone: 'muted', state: 'free', title: 'Snippet',\n        body: 'A five-second moving preview of the opening shot in the style you picked. It exists so you never buy the film blind.' },\n      { n: '05', tab: 'Review', icon: 'quality', tone: 'muted', state: '6 checks', title: 'Review',\n        body: 'Claims, licences, captions and brand safety checked in one pass, then signed off by a human before anything is released.' },\n      { n: '06', tab: 'Delivery', icon: 'download', tone: 'muted', state: '€49', title: 'Delivery',\n        body: 'Masters, captions and cutdowns for every placement you named. Yours outright — perpetual, commercial, unlimited.' }\n    ];\n\n    // ── Context · the fifteen topics the agent holds about you ──────────────\n    // Every topic opens a deep-dive page: editable fields, provenance, and one\n    // big confirm button. Confirmed state lives in state.briefOK.\n    this.CLIENT_BRIEF = [\n      { id: 'product', grp: 'WHAT IT IS', title: 'Product', ok: true,\n        line: 'Autonomous pentesting in 24 hours for European SMEs. Web and API.',\n        src: 'client-site.example — home, /pentest', why: 'Sets the first seven words. Everything else hangs off it.',\n        used: 'Line 2 — \"Client Co points an AI pentester at your URL\"',\n        fields: [\n          { k: 'One line', v: 'Autonomous pentesting in 24 hours for European SMEs. Web and API.' },\n          { k: 'What it actually does', v: 'An AI pentester is pointed at a URL, handed a login, and attempts real exploitation — not signature or pattern matching. A second agent independently re-tests every finding before it reaches you.' },\n          { k: 'Scope', v: 'Web applications and REST APIs, authenticated and unauthenticated. No network layer, no physical, no social engineering.' },\n          { k: 'What it is not', v: 'Not a vulnerability scanner, not a bug-bounty programme, not a managed SOC.' }\n        ] },\n      { id: 'problem', grp: 'WHAT IT IS', title: 'Problem', ok: true,\n        line: 'Nobody has counted the doors. Manual pentests are quoted in weeks and priced in thousands.',\n        src: 'client-site.example — /attack-surface, /pricing', why: 'The opening line has to name a pain the viewer already feels.',\n        used: 'Line 1 — \"Your website has doors nobody has counted.\"',\n        fields: [\n          { k: 'The symptom', v: 'An SME ships weekly but tests annually. Between those two clocks sits an unmeasured attack surface nobody owns.' },\n          { k: 'What it costs them', v: 'A manual pentest is quoted at 3–6 weeks and €8,000–€25,000, so it gets done once a year — or once, before an audit.' },\n          { k: 'Why now', v: 'NIS2 pulled security evidence forward for suppliers who were previously out of scope.' }\n        ] },\n      { id: 'audience', grp: 'WHO IT IS FOR', title: 'Audience', ok: true,\n        line: 'SME security leads and CTOs in the EU who answer to an auditor.',\n        src: 'client-site.example — /compliance, /pricing', why: 'Decides vocabulary, pace, and how much is explained.',\n        used: 'Whole read · 138 wpm, no jargon glossary',\n        fields: [\n          { k: 'Primary', v: 'Head of security or CTO at a 50–500 person EU company. Technical enough to want detail, busy enough to hate padding.' },\n          { k: 'Secondary', v: 'The compliance lead who has to hand the report to an auditor, and the founder who signs the invoice.' },\n          { k: 'What they already tried', v: 'A scanner that produced 400 findings nobody triaged, and one manual pentest that is now eleven months old.' }\n        ] },\n      { id: 'trigger', grp: 'WHO IT IS FOR', title: 'Buying trigger', ok: false,\n        line: 'What makes them act this week rather than next quarter — we guessed, confirm it.',\n        src: 'inferred — not stated on your site', why: 'A film with no trigger gets agreed with and forgotten.',\n        used: 'Not yet in the read — waiting on you',\n        fields: [\n          { k: 'Trigger we assumed', v: 'An enterprise customer sent a security questionnaire and a pentest report is one of the line items.' },\n          { k: 'Other candidates', v: 'A NIS2 supplier audit. A funding round with technical due diligence. A near-miss or a disclosed incident at a competitor.' },\n          { k: 'Question for you', v: 'Which of these actually shows up in your inbound? Name the one you hear most and the script closes on it.' }\n        ] },\n      { id: 'objections', grp: 'WHO IT IS FOR', title: 'Objections', ok: false,\n        line: 'Four reasons they say no. We can only pre-empt two in 45 seconds — pick which.',\n        src: 'inferred from /pricing FAQ', why: 'Handling the wrong objection wastes eight seconds of a 45-second film.',\n        used: 'Line 3 handles \"AI findings are noise\"',\n        fields: [\n          { k: 'Objection 1', v: '\"AI findings are noise.\" — answered in line 3 by the second-agent re-test.' },\n          { k: 'Objection 2', v: '\"An auditor will not accept a machine report.\" — not currently answered.' },\n          { k: 'Objection 3', v: '\"You will break production.\" — not currently answered.' },\n          { k: 'Objection 4', v: '\"We already have a scanner.\" — not currently answered.' },\n          { k: 'Question for you', v: 'Which second objection earns the eight seconds? Our recommendation is the auditor one.' }\n        ] },\n      { id: 'claim', grp: 'WHY BELIEVE IT', title: 'Claim', ok: true,\n        line: 'Verified vulnerabilities, zero noise. Compliance-ready report.',\n        src: 'client-site.example — home hero', why: 'The one sentence the viewer should be able to repeat afterwards.',\n        used: 'Line 3 — \"what reaches you is exploitable — zero noise\"',\n        fields: [\n          { k: 'Headline claim', v: 'Verified vulnerabilities, zero noise. Compliance-ready report.' },\n          { k: 'How it is substantiated', v: 'Every finding is re-tested by an independent second agent before it is written to the report. Findings that cannot be reproduced are dropped, not downgraded.' },\n          { k: 'Wording we must keep', v: '\"Verified\" and \"exploitable\". Legal signed both; \"guaranteed\" and \"complete\" are not available to us.' }\n        ] },\n      { id: 'proof', grp: 'WHY BELIEVE IT', title: 'Proof points', ok: true,\n        line: 'Four numbers, all sourced from your own pages. Numerals stay numerals in the read.',\n        src: 'client-site.example — /pentest, /pricing', why: 'Numbers are the only thing a 45-second film can prove.',\n        used: 'Lines 2, 4 and 5',\n        fields: [\n          { k: 'Time', v: '24 hours from URL to report. Stated on /pentest.' },\n          { k: 'Coverage', v: 'Authenticated and unauthenticated, web and API. Stated on /pentest.' },\n          { k: 'Retest', v: 'One-click retesting after remediation, unlimited within the engagement. Stated on /pricing.' },\n          { k: 'Origin', v: 'Built and hosted in Europe. Stated on /compliance.' }\n        ] },\n      { id: 'edge', grp: 'WHY BELIEVE IT', title: 'Differentiator', ok: true,\n        line: 'The second agent. It is the only thing on the page a scanner cannot claim.',\n        src: 'client-site.example — /pentest', why: 'A film needs exactly one thing the competitor cannot say.',\n        used: 'Line 3 — the whole line exists for this',\n        fields: [\n          { k: 'The difference', v: 'A second, independent agent re-tests every finding the first one produced. Two agents have to agree before anything reaches your inbox.' },\n          { k: 'Why it matters to them', v: 'It converts \"400 findings to triage\" into \"six findings to fix\" — the difference between a report and a to-do list.' },\n          { k: 'Runner-up we did not use', v: 'The 24-hour turnaround. Strong, but a competitor can buy their way to it; the second agent is architectural.' }\n        ] },\n      { id: 'rivals', grp: 'WHY BELIEVE IT', title: 'Competitors', ok: false,\n        line: 'Who they are comparing you against, and how directly we are allowed to say so.',\n        src: 'inferred — nothing comparative on your site', why: 'Framing changes if the viewer is leaving a scanner rather than a consultancy.',\n        used: 'Not in the read — needs your call',\n        fields: [\n          { k: 'Assumed alternative', v: 'A boutique pentest consultancy, quoted per engagement, booked six weeks out.' },\n          { k: 'Second alternative', v: 'A DAST scanner already in the CI pipeline that nobody reads the output of.' },\n          { k: 'How far we may go', v: 'We have written it category-level — \"pattern matching\" rather than a product name. Confirm that is the line, or name names and we re-cut.' }\n        ] },\n      { id: 'cta', grp: 'WHAT IT ASKS', title: 'Primary CTA', ok: false,\n        line: 'Start a pentest — or should the film close on the free 60-second scan?',\n        src: 'client-site.example — home, /attack-surface', why: 'The last four seconds are the only part of a film with a job.',\n        used: 'Line 5 — \"Start at client-site.example.\"',\n        fields: [\n          { k: 'Currently written', v: '\"Twenty-four hours. Built in Europe. Start at client-site.example.\"' },\n          { k: 'Alternative', v: '\"Twenty-four hours. Built in Europe. Run the free 60-second scan at client-site.example.\" — lower friction, softer intent.' },\n          { k: 'Question for you', v: 'Which one does your funnel actually want? If the scan converts better, we re-time the last four seconds at no cost.' },\n          { k: 'Destination', v: 'client-site.example — confirm whether a campaign path or UTM should be spoken or only shown on screen.' }\n        ] },\n      { id: 'tone', grp: 'HOW IT SOUNDS', title: 'Tone of voice', ok: true,\n        line: 'Technical, plain, no fear-selling. Numbers over adjectives.',\n        src: 'client-site.example — copy across all pages', why: 'Tone decides read speed, pause length and how the voice lands.',\n        used: 'Whole read · 138 wpm, held pause before the CTA',\n        fields: [\n          { k: 'Register', v: 'Peer-to-peer technical. The viewer is assumed competent; nothing is explained twice.' },\n          { k: 'Pace', v: '138 words per minute with a held beat before the call to action.' },\n          { k: 'Explicitly avoided', v: 'Fear-selling, breach imagery, countdown urgency, and any sentence that would embarrass an engineer.' }\n        ] },\n      { id: 'words', grp: 'HOW IT SOUNDS', title: 'Vocabulary', ok: true,\n        line: 'The words we use, and the words the read will never contain.',\n        src: 'client-site.example — copy + your press kit', why: 'One wrong noun and the film sounds like it is about somebody else.',\n        used: 'Whole read',\n        fields: [\n          { k: 'Always', v: 'Pentest. Findings. Exploitable. Remediation. Report. Agent.' },\n          { k: 'Never', v: 'Hacker. Cyberattack. Bulletproof. Military-grade. Unhackable. Peace of mind.' },\n          { k: 'House style', v: '\"AI pentester\" not \"AI-powered pentesting\".' }\n        ] },\n      { id: 'brand', grp: 'HOW IT SOUNDS', title: 'Brand and visual', ok: true,\n        line: 'Logo, type and two brand colours, pulled from your own assets.',\n        src: 'client-site.example — brand assets', why: 'The storyboard styles are all rendered on these tokens.',\n        used: 'Storyboard · all six styles',\n        fields: [\n          { k: 'Colours', v: 'Cyan #35C6E8 as the lead, a signal red for alerts and press states. Deep teal-navy grounds.' },\n          { k: 'Type', v: 'Inter for anything read as language, JetBrains Mono for anything read as a system.' },\n          { k: 'Logo use', v: 'Wordmark only, end frame, no animation on the mark itself.' },\n          { k: 'Motion', v: 'Cuts, not transitions. Nothing eases longer than 300ms.' }\n        ] },\n      { id: 'limits', grp: 'RULES', title: 'Off limits', ok: false,\n        line: 'No client logos on screen — confirm we can name the sector instead.',\n        src: 'client-site.example — /compliance', why: 'Cheaper to know now than after the storyboard is rendered.',\n        used: 'Blocks a shot in the storyboard',\n        fields: [\n          { k: 'Hard no', v: 'No customer logos, no named customers, no screenshots containing real host names.' },\n          { k: 'Proposed workaround', v: 'Name the sector instead — \"a Dutch payments platform\", \"a German logistics SaaS\". Needs your yes.' },\n          { k: 'Also check', v: 'Whether any finding shown on screen has to be fictional. We assume yes and will synthesise them.' }\n        ] },\n      { id: 'market', grp: 'RULES', title: 'Market, language and length', ok: true,\n        line: 'EU market, English read, 45 seconds, cut down to 30 and 15 on delivery.',\n        src: 'client-site.example — /pricing, footer', why: 'Length is decided before the first word, never after.',\n        used: 'Whole read · 0:45',\n        fields: [\n          { k: 'Market', v: 'European Union. Netherlands, Germany and the Nordics named as priority.' },\n          { k: 'Language', v: 'English, EU-neutral accent. No localised versions in this order.' },\n          { k: 'Length', v: '45 seconds master. 30 and 15-second cutdowns produced at delivery from the same take.' },\n          { k: 'Placement', v: 'Site hero, LinkedIn paid, and the top of the outbound sequence.' }\n        ] }\n    ];\n\n    // ── Storyboard · six visual styles, each a draft rendered on your tokens ──\n    this.SB_STYLES = [\n      { id: 'photo', name: 'Photographic', spec: 'Live plates · 6 shots · graded to your cyan', price: 'included',\n        body: 'Real footage, shot dark and cool: a server aisle, a hand on a keyboard, your interface on a screen in frame. The most literal read, and the most expensive to change once shot.',\n        bg: 'radial-gradient(130% 110% at 26% 12%, rgb(var(--basalt-bright)) 0%, rgb(var(--basalt-deep)) 68%)',\n        deco: 'linear-gradient(196deg, rgb(var(--lead, var(--circuit-glow)) / 0.14), transparent 46%)', decoOp: '1',\n        title: 'Your website has doors nobody has counted.', titleFont: 'var(--font-inter)', titleSize: 'clamp(0.95rem,2.6vw,1.85rem)',\n        titleWeight: '300', titleTrack: '-0.01em', titleCase: 'none', titleColor: 'rgb(var(--text))', titleMax: '58%',\n        justify: 'flex-end', sub: 'PLATE 01 · SERVER AISLE · 35MM · F2.0' },\n      { id: 'type', name: 'Animated type', spec: 'Type on ground · 6 cuts · no footage', price: 'included',\n        body: 'No footage at all. The words are the picture — set in your display weight, cut hard on the beat, on a flat brand ground. Fastest to change, strongest on a muted feed.',\n        bg: 'rgb(var(--basalt-deep))',\n        deco: 'linear-gradient(92deg, rgb(var(--lead, var(--circuit-glow)) / 0.16), transparent 62%)', decoOp: '1',\n        title: 'DOORS NOBODY HAS COUNTED.', titleFont: 'var(--font-inter)', titleSize: 'clamp(1.2rem,4.4vw,3.1rem)',\n        titleWeight: '900', titleTrack: '-0.05em', titleCase: 'uppercase', titleColor: 'rgb(var(--text))', titleMax: '82%',\n        justify: 'center', sub: 'CUT 01 OF 06 · HOLD 1.4S' },\n      { id: 'flat', name: 'Illustrated', spec: 'Flat vector · 6 scenes · 2 brand colours', price: 'included',\n        body: 'Flat vector scenes in two brand colours — a building with doors, a queue of findings, one report. Warmer than the rest, and the only style that can show an attack without showing a person.',\n        bg: 'linear-gradient(158deg, rgb(var(--lead, var(--circuit-glow)) / 0.26) 0%, rgb(var(--basalt-bright)) 62%, rgb(var(--basalt-deep)) 100%)',\n        deco: 'radial-gradient(42% 68% at 82% 24%, rgb(var(--accent-red) / 0.18), transparent 70%)', decoOp: '1',\n        title: 'Eleven doors. Nobody counted them.', titleFont: 'var(--font-inter)', titleSize: 'clamp(1rem,3vw,2.1rem)',\n        titleWeight: '700', titleTrack: '-0.02em', titleCase: 'none', titleColor: 'rgb(var(--text))', titleMax: '62%',\n        justify: 'flex-end', sub: 'SCENE 01 · FACADE · 2 COLOURS' },\n      { id: 'ui', name: 'Screen capture', spec: 'Product UI in motion · 6 states · real data shape', price: 'included',\n        body: 'Your own interface, recorded and re-timed: the scan starting, findings arriving, the second agent confirming. Nothing to imagine — but it lives or dies on the product looking finished.',\n        bg: 'rgb(var(--basalt-surface))',\n        deco: 'repeating-linear-gradient(0deg, rgb(var(--text) / 0.045) 0 1px, transparent 1px 36px), repeating-linear-gradient(90deg, rgb(var(--text) / 0.045) 0 1px, transparent 1px 36px)', decoOp: '1',\n        title: 'scanning client-site.example — 11 surfaces found', titleFont: 'var(--font-jetbrains)', titleSize: 'clamp(0.8rem,2vw,1.25rem)',\n        titleWeight: '500', titleTrack: '0', titleCase: 'none', titleColor: 'var(--app-accent)', titleMax: '70%',\n        justify: 'center', sub: 'STATE 01 · DISCOVERY · 00:00–00:07' },\n      { id: 'data', name: 'Data motion', spec: 'Counters and graphs · 6 beats · numerals lead', price: 'included',\n        body: 'The numbers do the work: a surface count climbing, 400 findings collapsing to six, a 24-hour clock. Best fit for a claim built on numerals, weakest at carrying emotion.',\n        bg: 'radial-gradient(95% 130% at 78% 104%, rgb(var(--lead, var(--circuit-glow)) / 0.24), rgb(var(--basalt-deep)) 62%)',\n        deco: 'linear-gradient(0deg, rgb(var(--basalt-deep)) 0%, transparent 44%)', decoOp: '1',\n        title: '24h', titleFont: 'var(--font-inter)', titleSize: 'clamp(2rem,7vw,5rem)',\n        titleWeight: '900', titleTrack: '-0.055em', titleCase: 'none', titleColor: 'rgb(var(--text))', titleMax: '100%',\n        justify: 'center', sub: 'BEAT 01 · URL TO REPORT · COUNT UP' },\n      { id: 'mono', name: 'Mono terminal', spec: 'Terminal-first · 6 frames · one accent', price: 'included',\n        body: 'A single terminal, typed in real time, with one cyan accent. The most credible style for this audience and the cheapest to revise — it will not work anywhere the viewer is not technical.',\n        bg: 'linear-gradient(180deg, rgb(var(--basalt-deep)) 0%, rgb(var(--basalt-deep)) 100%)',\n        deco: 'radial-gradient(120% 90% at 50% 0%, rgb(var(--lead, var(--circuit-glow)) / 0.10), transparent 62%)', decoOp: '1',\n        title: '[client] 11 surfaces · 6 verified · 0 noise', titleFont: 'var(--font-jetbrains)', titleSize: 'clamp(0.75rem,1.9vw,1.15rem)',\n        titleWeight: '500', titleTrack: '0', titleCase: 'none', titleColor: 'var(--app-accent)', titleMax: '78%',\n        justify: 'flex-start', sub: 'FRAME 01 · TYPED AT 22 CPS' }\n    ];\n\n    // Six shots, cut from the script lines above.\n    this.SB_SHOTS = [\n      { n: '01', t: '0:00–0:07', title: 'The doors', dir: 'Wide, static. Facade or facade-equivalent in the chosen style. No motion but the type.', vo: 'Your website has doors nobody has counted.' },\n      { n: '02', t: '0:07–0:13', title: 'Point it at a URL', dir: 'Push in on the address. The URL is the only sharp thing in frame.', vo: 'Client Co points an AI pentester at your URL…' },\n      { n: '03', t: '0:13–0:18', title: 'Trying the handles', dir: 'Fast cuts, four in two seconds. Real exploitation, not a progress bar.', vo: '…hands it a login, and tries the handles.' },\n      { n: '04', t: '0:18–0:29', title: 'The second agent', dir: 'Split frame. Two passes, one verdict. This is the shot the film is for.', vo: 'A second agent re-tests every finding independently.' },\n      { n: '05', t: '0:29–0:38', title: 'Six things to fix', dir: 'Findings collapse from many to few. Numerals on screen, held.', vo: 'Step-by-step remediation, one-click retesting, a compliance-ready report.' },\n      { n: '06', t: '0:38–0:45', title: 'End frame', dir: 'Wordmark, one line, the URL. Cut to black on the last syllable.', vo: 'Twenty-four hours. Built in Europe. Start at client-site.example.' }\n    ];\n\n    this.REVIEW_CHECKS = [\n      { title: 'Claim substantiation', meta: 'Every spoken number traced to a page on client-site.example', chip: 'passed', tone: 'teal' },\n      { title: 'Comparative language', meta: 'Category-level only — no competitor named on screen or in the read', chip: 'passed', tone: 'teal' },\n      { title: 'Brand safety', meta: 'No customer logos, no real host names, findings synthesised', chip: 'blocked', tone: 'red' },\n      { title: 'Music and voice licence', meta: 'Perpetual commercial, worldwide, no revenue cap', chip: 'passed', tone: 'teal' },\n      { title: 'Captions and burn-ins', meta: 'EN captions, 45/30/15 versions, contrast checked at 4.5:1', chip: 'passed', tone: 'teal' },\n      { title: 'Human sign-off', meta: 'Waiting on you — the last gate before the masters are cut', chip: 'waiting', tone: 'circuit' }\n    ];\n\n    // ══ Select · own stream data (S-2…S-4 build, 2026-09-05) — 8 runs, 6-agent roster with a\n    // 3-level autonomy ladder. Distinct from DT_RUN/DT_TRACE below, which is the generic Zooey\n    // trace fixture Run detail drills into — S-1 (that trace should be Select's own run) is a\n    // separate, larger data-repointing item, not fixed here.\n    this.SELECT_RUNS = [\n      { id: '#4827', agent: 'AP-Reconciliation', status: 'running', started: '2 min ago', dur: '—', decisions: 1, cost: '€0.01', live: true },\n      { id: '#4826', agent: 'AP-Reconciliation', status: 'success', started: '18 min ago', dur: '12.4 s', decisions: 3, cost: '€0.042' },\n      { id: '#4825', agent: 'Lead-Qualifier', status: 'success', started: '41 min ago', dur: '4.1 s', decisions: 2, cost: '€0.008' },\n      { id: '#4824', agent: 'AP-Reconciliation', status: 'fail', started: '1 h ago', dur: '3.9 s', decisions: 1, cost: '€0.006' },\n      { id: '#4823', agent: 'Content-Drafter', status: 'success', started: '2 h ago', dur: '22.7 s', decisions: 4, cost: '€0.091' },\n      { id: '#4822', agent: 'Lead-Qualifier', status: 'success', started: '3 h ago', dur: '3.8 s', decisions: 2, cost: '€0.007' },\n      { id: '#4821', agent: 'SEO-Auditor', status: 'awaiting', started: '5 h ago', dur: '—', decisions: 0, cost: '€0.002' },\n      { id: '#4820', agent: 'AP-Reconciliation', status: 'success', started: '7 h ago', dur: '11.9 s', decisions: 3, cost: '€0.039' }\n    ];\n    this.SELECT_AGENTS = [\n      { name: 'AP-Reconciliation', autonomy: 'Supervised', runs: 214, successRate: '96%' },\n      { name: 'Lead-Qualifier', autonomy: 'Autonomous', runs: 1042, successRate: '99%' },\n      { name: 'Content-Drafter', autonomy: 'HITL', runs: 88, successRate: '92%' },\n      { name: 'SEO-Auditor', autonomy: 'Supervised', runs: 156, successRate: '94%' },\n      { name: 'Review-Agent', autonomy: 'HITL', runs: 61, successRate: '89%' },\n      { name: 'Finance-Ops', autonomy: 'HITL', runs: 19, successRate: '100%' }\n    ];\n    // ══ Decisions · Zooey run trace (ported from kinds-decisions.jsx + the dt- CSS spec) ══\n    // Schema aaas-trace-v1. Step types: plan · llm_call · tool_call · decision_branch · self_critique · phase.\n    // S-1 (2026-09-05): this used to be one fixture shown for every SELECT_RUNS row regardless of which\n    // was clicked — #4827 even carried conflicting data (labeled 'ok/3 decisions' here, 'running/1\n    // decision' in SELECT_RUNS). Re-keyed: the rich 12-step trace below actually matches #4826's own\n    // numbers (success · 3 decisions · 12.4s · €0.042) exactly, so it moves there un-touched; the other\n    // 7 runs get their own real, shorter traces sized to their own decisions/dur/cost.\n    this.DT_RUN = { label: '#4826', agent: 'AP-Reconciliation', owner: 'jor@gmail.com', tier: 'Business',\n      status: 'ok', statusLabel: 'run complete · 12 steps · 3 decisions', schema: 'v1',\n      started: '28.05.2026 17:24', duration: '12.4 s', cost: '0.042', tokIn: 4180, tokOut: 2640, retention: '04.06.2026' };\n    this.DT_TRACE = [\n      { id: 's01', type: 'plan', tone: 'soft', icon: 'map', label: 'Plan · 5 strategies weighed', dur: 800, cost: '0.001',\n        summary: 'The agent sketches possible match strategies against the last 30 days of reconciliation data. Sample size 312.' },\n      { id: 's02', type: 'llm_call', tone: 'llm', icon: 'spark', label: 'LLM · gemini-2.5-flash', dur: 1200, cost: '0.003', tin: 412, tout: 289,\n        summary: 'Classifies incoming invoices by structural pattern.',\n        pin: 'You are the AP-Reconciliation agent. Classify these 12 invoices …',\n        pout: '{ \"structure_hint\": \"tabular\", \"currency\": \"EUR\", \"candidates\": 12 }' },\n      { id: 's03', type: 'tool_call', tone: 'tool', icon: 'bolt', label: 'Tool · db_query(\"invoices WHERE status…\")', dur: 400, cost: '0.000',\n        summary: 'Pulls 12 open invoices from the database.',\n        pin: \"SELECT * FROM invoices WHERE status IN ('ISSUED','OVERDUE') LIMIT 50\", pout: '[ … 12 rows … ]' },\n      { id: 's04', type: 'decision_branch', tone: 'decision', icon: 'relayout', label: 'Decision · pick match strategy', dur: 100, cost: '0.000',\n        conf: 0.87, chosen: 'fuzzy_match',\n        alts: [\n          { label: 'fuzzy_match', score: 0.87, why: '12 candidates found at tolerance 0.8. Scales linearly, accuracy acceptable for this dataset.' },\n          { label: 'exact_match', score: 0.12, why: 'Strict equality — the fastest path if the data is clean.', rejected: 'Strict equality returned 0 matches when tested against last month.' },\n          { label: 'embedding_similarity', score: 0.71, why: 'Semantic match over vector similarity — robust against typos.', rejected: 'High accuracy, but 8× the cost of fuzzy_match at comparable recall.' }\n        ] },\n      { id: 's05', type: 'tool_call', tone: 'tool', icon: 'bolt', label: 'Tool · write_match_results', dur: 300, cost: '0.000',\n        summary: 'Writes 12 matches back to the database.', pout: 'OK · 12 rows updated' },\n      { id: 's06', type: 'self_critique', tone: 'critique', icon: 'quality', label: 'Self-critique · round 1', dur: 1100, cost: '0.002',\n        crit: { round: 1, accepted: true,\n          before: 'Processing complete. 12 invoices matched.',\n          critique: 'Output too short — the customer expects per-match reasoning. Expand to \"why this match\".',\n          after: 'Processing complete. 12 invoices matched. Per-match reasoning appended.' } },\n      { id: 's07', type: 'llm_call', tone: 'llm', icon: 'spark', label: 'LLM · gemini-2.5-flash (refinement)', dur: 1800, cost: '0.004', tin: 612, tout: 488,\n        summary: 'Generates extended reasoning for the 12 matches.' },\n      { id: 's08', type: 'decision_branch', tone: 'decision', icon: 'relayout', label: 'Decision · delivery channel', dur: 60, cost: '0.000',\n        conf: 0.94, chosen: 'email_summary',\n        alts: [\n          { label: 'email_summary', score: 0.94, why: 'The customer opened 87% of reports by email over the last 30 days.' },\n          { label: 'canvas_only', score: 0.42, why: 'No email load on the recipient.', rejected: 'Last 14 days: 3 of 5 canvas-only updates went unread.' }\n        ] },\n      { id: 's09', type: 'tool_call', tone: 'tool', icon: 'bolt', label: 'Tool · send_email(\"jor@gmail.com\")', dur: 240, cost: '0.000',\n        summary: 'Match report sent by email.', pout: '202 Accepted · resend_id=msg_28a1f' },\n      { id: 's10', type: 'tool_call', tone: 'tool', icon: 'bolt', label: 'Tool · canvas_update(threadId, \"AP-Recon done\")', dur: 180, cost: '0.000',\n        summary: 'Canvas thread updated with a status tile.' },\n      { id: 's11', type: 'decision_branch', tone: 'decision', icon: 'relayout', label: 'Decision · suggest follow-up?', dur: 80, cost: '0.000',\n        conf: 0.61, chosen: 'no_followup',\n        alts: [\n          { label: 'no_followup', score: 0.61, why: 'All 12 invoices matched without conflicts.' },\n          { label: 'schedule_review', score: 0.39, why: 'Routine review after 7 days.', rejected: 'Auto-follow-up threshold (>10 invoices at low confidence) not reached.' }\n        ] },\n      { id: 's12', type: 'phase', tone: 'soft', icon: 'check', label: 'Deliver', dur: 200, cost: '0.001',\n        summary: 'Delivery complete. Run finished.' }\n    ];\n    // The other 7 SELECT_RUNS rows — each its own run and owner, sized to its own decisions/dur/cost.\n    this.DT_RUNS_OTHER = {\n      '#4827': { run: { label: '#4827', agent: 'AP-Reconciliation', owner: 'jor@gmail.com', tier: 'Business',\n          status: 'running', statusLabel: 'running · step 3 of ~9 · 1 decision so far', schema: 'v1',\n          started: '2 min ago', duration: '—', cost: '0.010', tokIn: 890, tokOut: 410, retention: '—' },\n        trace: [\n          { id: 'a01', type: 'plan', tone: 'soft', icon: 'map', label: 'Plan · match strategy for today\\u2019s batch', dur: 700, cost: '0.001', summary: '9 open invoices queued for reconciliation.' },\n          { id: 'a02', type: 'tool_call', tone: 'tool', icon: 'bolt', label: 'Tool · db_query(\"invoices WHERE status…\")', dur: 380, cost: '0.000', summary: 'Pulls 9 open invoices.', pin: \"SELECT * FROM invoices WHERE status='ISSUED' LIMIT 50\", pout: '[ … 9 rows … ]' },\n          { id: 'a03', type: 'decision_branch', tone: 'decision', icon: 'relayout', label: 'Decision · pick match strategy', dur: 90, cost: '0.000', conf: 0.83, chosen: 'fuzzy_match',\n            alts: [\n              { label: 'fuzzy_match', score: 0.83, why: '9 candidates at tolerance 0.8, consistent with today\\u2019s batch shape.' },\n              { label: 'exact_match', score: 0.18, why: 'Fastest if data is clean.', rejected: 'Returned 0 matches on this batch when probed.' }\n            ] },\n          { id: 'a04', type: 'llm_call', tone: 'llm', icon: 'spark', label: 'LLM · gemini-2.5-flash (in progress)', dur: 600, cost: '0.002', tin: 210, tout: 0, summary: 'Classifying remaining 9 invoices — running now.' }\n        ] },\n      '#4825': { run: { label: '#4825', agent: 'Lead-Qualifier', owner: 'maya@intrinsic.com.de', tier: 'Business',\n          status: 'ok', statusLabel: 'run complete · 6 steps · 2 decisions', schema: 'v1',\n          started: '41 min ago', duration: '4.1 s', cost: '0.008', tokIn: 980, tokOut: 540, retention: '11.06.2026' },\n        trace: [\n          { id: 'b01', type: 'plan', tone: 'soft', icon: 'map', label: 'Plan · score inbound lead', dur: 200, cost: '0.000', summary: 'New form submission from moorlicht-kerzen.de.' },\n          { id: 'b02', type: 'tool_call', tone: 'tool', icon: 'bolt', label: 'Tool · enrich_company(\"moorlicht-kerzen.de\")', dur: 900, cost: '0.001', summary: 'Pulls firmographic data.', pout: '{ employees: 8, industry: \"retail/candles\", revenue_band: \"€100k-500k\" }' },\n          { id: 'b03', type: 'decision_branch', tone: 'decision', icon: 'relayout', label: 'Decision · fit tier', dur: 60, cost: '0.000', conf: 0.74, chosen: 'tier_b',\n            alts: [\n              { label: 'tier_b', score: 0.74, why: 'Revenue band and industry match the tier-B ICP profile.' },\n              { label: 'tier_a', score: 0.31, why: 'Reserved for >€500k revenue.', rejected: 'Revenue band too low for tier-A routing.' }\n            ] },\n          { id: 'b04', type: 'llm_call', tone: 'llm', icon: 'spark', label: 'LLM · gemini-2.5-flash', dur: 1400, cost: '0.004', tin: 340, tout: 180, summary: 'Drafts a personalized qualification note for the CRM.' },\n          { id: 'b05', type: 'decision_branch', tone: 'decision', icon: 'relayout', label: 'Decision · route to sequence', dur: 50, cost: '0.000', conf: 0.88, chosen: 'cold_b_web_video',\n            alts: [\n              { label: 'cold_b_web_video', score: 0.88, why: 'Retail/candle vertical responds best to the web-video angle historically.' },\n              { label: 'cold_a_conversion', score: 0.46, why: 'General conversion angle.', rejected: 'Lower reply rate for this vertical over the last quarter.' }\n            ] },\n          { id: 'b06', type: 'phase', tone: 'soft', icon: 'check', label: 'Deliver', dur: 100, cost: '0.000', summary: 'Lead queued into Cold B sequence.' }\n        ] },\n      '#4824': { run: { label: '#4824', agent: 'AP-Reconciliation', owner: 'jor@gmail.com', tier: 'Business',\n          status: 'fail', statusLabel: 'run failed · 4 steps · 1 decision', schema: 'v1',\n          started: '1 h ago', duration: '3.9 s', cost: '0.006', tokIn: 610, tokOut: 220, retention: '04.06.2026' },\n        trace: [\n          { id: 'c01', type: 'plan', tone: 'soft', icon: 'map', label: 'Plan · match strategy', dur: 300, cost: '0.000', summary: '4 open invoices queued.' },\n          { id: 'c02', type: 'tool_call', tone: 'tool', icon: 'bolt', label: 'Tool · db_query(\"invoices WHERE status…\")', dur: 400, cost: '0.000', summary: 'Query timed out after 3.2s — connection pool exhausted.', pout: 'ERROR: query_timeout after 3200ms' },\n          { id: 'c03', type: 'decision_branch', tone: 'decision', icon: 'relayout', label: 'Decision · retry or abort', dur: 80, cost: '0.000', conf: 0.55, chosen: 'abort_and_alert',\n            alts: [\n              { label: 'abort_and_alert', score: 0.55, why: 'Second timeout this hour — escalate rather than retry blind.' },\n              { label: 'retry_once', score: 0.44, why: 'Transient errors usually clear on retry.', rejected: 'Already retried once at 0:58 — pattern suggests pool exhaustion, not a blip.' }\n            ] },\n          { id: 'c04', type: 'phase', tone: 'critique', icon: 'x', label: 'Fail', dur: 200, cost: '0.000', summary: 'Run aborted. Alert raised on #ap-reconciliation-alerts.' }\n        ] },\n      '#4823': { run: { label: '#4823', agent: 'Content-Drafter', owner: 'maya@intrinsic.com.de', tier: 'Team',\n          status: 'ok', statusLabel: 'run complete · 9 steps · 4 decisions', schema: 'v1',\n          started: '2 h ago', duration: '22.7 s', cost: '0.091', tokIn: 6420, tokOut: 3980, retention: '11.06.2026' },\n        trace: [\n          { id: 'd01', type: 'plan', tone: 'soft', icon: 'map', label: 'Plan · nis2-compliance-digest draft', dur: 900, cost: '0.001', summary: 'Weekly compliance digest, 4 sections planned.' },\n          { id: 'd02', type: 'tool_call', tone: 'tool', icon: 'bolt', label: 'Tool · vault_search(\"NIS2 deadlines\")', dur: 1100, cost: '0.001', summary: 'Pulls 6 relevant vault documents.' },\n          { id: 'd03', type: 'decision_branch', tone: 'decision', icon: 'relayout', label: 'Decision · lead angle', dur: 70, cost: '0.000', conf: 0.79, chosen: 'deadline_urgency',\n            alts: [\n              { label: 'deadline_urgency', score: 0.79, why: 'October reporting deadline is 6 weeks out — timely hook.' },\n              { label: 'case_study', score: 0.52, why: 'A completed client audit as proof.', rejected: 'Less timely than the approaching deadline.' }\n            ] },\n          { id: 'd04', type: 'llm_call', tone: 'llm', icon: 'spark', label: 'LLM · claude-sonnet-4.5', dur: 8200, cost: '0.052', tin: 3100, tout: 2400, summary: 'Drafts the full digest body across 4 sections.' },\n          { id: 'd05', type: 'self_critique', tone: 'critique', icon: 'quality', label: 'Self-critique · round 1', dur: 3400, cost: '0.011',\n            crit: { round: 1, accepted: true, before: 'Digest drafted, 4 sections, generic tone.', critique: 'Too generic for the boutique-agency audience — add a concrete SME example.', after: 'Digest revised with a specific 12-person-agency example in section 2.' } },\n          { id: 'd06', type: 'decision_branch', tone: 'decision', icon: 'relayout', label: 'Decision · CTA placement', dur: 60, cost: '0.000', conf: 0.81, chosen: 'end_of_digest',\n            alts: [\n              { label: 'end_of_digest', score: 0.81, why: 'Readers who finish the digest are the highest-intent segment.' },\n              { label: 'inline_mid', score: 0.44, why: 'Catches skimmers.', rejected: 'Tested lower CTR in the last 3 sends.' }\n            ] },\n          { id: 'd07', type: 'llm_call', tone: 'llm', icon: 'spark', label: 'LLM · claude-sonnet-4.5 (refinement)', dur: 5800, cost: '0.021', tin: 1620, tout: 1380, summary: 'Applies the critique and finalizes copy.' },\n          { id: 'd08', type: 'decision_branch', tone: 'decision', icon: 'relayout', label: 'Decision · send now or schedule', dur: 50, cost: '0.000', conf: 0.68, chosen: 'schedule',\n            alts: [\n              { label: 'schedule', score: 0.68, why: 'No approved send window yet — needs a human pick, per the marketing queue.' },\n              { label: 'send_now', score: 0.29, why: 'Fastest path.', rejected: 'Skips the approval step this list requires.' }\n            ] },\n          { id: 'd09', type: 'phase', tone: 'soft', icon: 'check', label: 'Deliver', dur: 200, cost: '0.001', summary: 'Draft queued in Marketing → Queue, awaiting a schedule pick.' }\n        ] },\n      '#4822': { run: { label: '#4822', agent: 'Lead-Qualifier', owner: 'maya@intrinsic.com.de', tier: 'Business',\n          status: 'ok', statusLabel: 'run complete · 6 steps · 2 decisions', schema: 'v1',\n          started: '3 h ago', duration: '3.8 s', cost: '0.007', tokIn: 920, tokOut: 510, retention: '11.06.2026' },\n        trace: [\n          { id: 'e01', type: 'plan', tone: 'soft', icon: 'map', label: 'Plan · score inbound lead', dur: 200, cost: '0.000', summary: 'New form submission from seilerei-nord.de.' },\n          { id: 'e02', type: 'tool_call', tone: 'tool', icon: 'bolt', label: 'Tool · enrich_company(\"seilerei-nord.de\")', dur: 850, cost: '0.001', summary: 'Pulls firmographic data.', pout: '{ employees: 14, industry: \"manufacturing/rope\", revenue_band: \"€500k-1M\" }' },\n          { id: 'e03', type: 'decision_branch', tone: 'decision', icon: 'relayout', label: 'Decision · fit tier', dur: 60, cost: '0.000', conf: 0.86, chosen: 'tier_a',\n            alts: [\n              { label: 'tier_a', score: 0.86, why: 'Revenue band clears the tier-A threshold.' },\n              { label: 'tier_b', score: 0.22, why: 'Default for smaller accounts.', rejected: 'Revenue band exceeds the tier-B ceiling.' }\n            ] },\n          { id: 'e04', type: 'llm_call', tone: 'llm', icon: 'spark', label: 'LLM · gemini-2.5-flash', dur: 1300, cost: '0.004', tin: 320, tout: 170, summary: 'Drafts a personalized qualification note.' },\n          { id: 'e05', type: 'decision_branch', tone: 'decision', icon: 'relayout', label: 'Decision · route to sequence', dur: 50, cost: '0.000', conf: 0.91, chosen: 'cold_a_conversion',\n            alts: [\n              { label: 'cold_a_conversion', score: 0.91, why: 'Manufacturing accounts convert best on the direct-ROI angle.' },\n              { label: 'cold_b_web_video', score: 0.33, why: 'Media-forward angle.', rejected: 'Lower reply rate for manufacturing over the last quarter.' }\n            ] },\n          { id: 'e06', type: 'phase', tone: 'soft', icon: 'check', label: 'Deliver', dur: 100, cost: '0.000', summary: 'Lead queued into Cold A sequence — replied within 2 days.' }\n        ] },\n      '#4821': { run: { label: '#4821', agent: 'SEO-Auditor', owner: 'maya@intrinsic.com.de', tier: 'Team',\n          status: 'running', statusLabel: 'awaiting approval · 2 steps · 0 decisions', schema: 'v1',\n          started: '5 h ago', duration: '—', cost: '0.002', tokIn: 240, tokOut: 90, retention: '—' },\n        trace: [\n          { id: 'f01', type: 'plan', tone: 'soft', icon: 'map', label: 'Plan · audit aaas.blog for AI-citation gaps', dur: 400, cost: '0.001', summary: 'Compares indexed pages against the 49 canonical context topics.' },\n          { id: 'f02', type: 'phase', tone: 'soft', icon: 'clock', label: 'Awaiting approval', dur: 100, cost: '0.001', summary: 'Found 3 pages worth rewriting — awaiting sign-off before drafting changes (Business-tier requires review here).' }\n        ] },\n      '#4820': { run: { label: '#4820', agent: 'AP-Reconciliation', owner: 'jor@gmail.com', tier: 'Business',\n          status: 'ok', statusLabel: 'run complete · 10 steps · 3 decisions', schema: 'v1',\n          started: '7 h ago', duration: '11.9 s', cost: '0.039', tokIn: 3890, tokOut: 2410, retention: '04.06.2026' },\n        trace: [\n          { id: 'g01', type: 'plan', tone: 'soft', icon: 'map', label: 'Plan · match strategy for today\\u2019s batch', dur: 750, cost: '0.001', summary: '11 open invoices queued.' },\n          { id: 'g02', type: 'llm_call', tone: 'llm', icon: 'spark', label: 'LLM · gemini-2.5-flash', dur: 1150, cost: '0.003', tin: 398, tout: 271, summary: 'Classifies incoming invoices by structural pattern.' },\n          { id: 'g03', type: 'tool_call', tone: 'tool', icon: 'bolt', label: 'Tool · db_query(\"invoices WHERE status…\")', dur: 390, cost: '0.000', summary: 'Pulls 11 open invoices.', pout: '[ … 11 rows … ]' },\n          { id: 'g04', type: 'decision_branch', tone: 'decision', icon: 'relayout', label: 'Decision · pick match strategy', dur: 90, cost: '0.000', conf: 0.85, chosen: 'fuzzy_match',\n            alts: [\n              { label: 'fuzzy_match', score: 0.85, why: '11 candidates at tolerance 0.8.' },\n              { label: 'exact_match', score: 0.14, why: 'Strict equality.', rejected: 'Returned 1 of 11 matches when tested.' }\n            ] },\n          { id: 'g05', type: 'tool_call', tone: 'tool', icon: 'bolt', label: 'Tool · write_match_results', dur: 280, cost: '0.000', summary: 'Writes 11 matches back to the database.', pout: 'OK · 11 rows updated' },\n          { id: 'g06', type: 'self_critique', tone: 'critique', icon: 'quality', label: 'Self-critique · round 1', dur: 1000, cost: '0.002',\n            crit: { round: 1, accepted: true, before: 'Processing complete. 11 invoices matched.', critique: 'Add per-match reasoning.', after: 'Processing complete. 11 invoices matched, reasoning appended.' } },\n          { id: 'g07', type: 'llm_call', tone: 'llm', icon: 'spark', label: 'LLM · gemini-2.5-flash (refinement)', dur: 1700, cost: '0.004', tin: 590, tout: 460, summary: 'Generates extended reasoning for the 11 matches.' },\n          { id: 'g08', type: 'decision_branch', tone: 'decision', icon: 'relayout', label: 'Decision · delivery channel', dur: 60, cost: '0.000', conf: 0.92, chosen: 'email_summary',\n            alts: [\n              { label: 'email_summary', score: 0.92, why: 'Owner opened 87% of reports by email over the last 30 days.' },\n              { label: 'canvas_only', score: 0.4, why: 'No email load.', rejected: 'Unread 3 of last 5 canvas-only updates.' }\n            ] },\n          { id: 'g09', type: 'tool_call', tone: 'tool', icon: 'bolt', label: 'Tool · send_email(\"jor@gmail.com\")', dur: 230, cost: '0.000', summary: 'Match report sent by email.', pout: '202 Accepted · resend_id=msg_2091c' },\n          { id: 'g10', type: 'phase', tone: 'soft', icon: 'check', label: 'Deliver', dur: 190, cost: '0.001', summary: 'Delivery complete. Run finished.' }\n        ] }\n    };\n    // ══ Intelligence · Unified Ask (ua- spec from the handoff) ══\n    // One question, four sources, a cited answer. A classifier routes each query\n    // across blog / vault / notebooklm / workspace-kb and weights the mix.\n    this.UA = {\n      question: 'Which LLM is best for GDPR-compliant invoice processing in Germany?',\n      latency: '1.2 s', cost: '€ 0,004', model: 'gemini-2.5-flash · classifier + synthesis',\n      answer: [\n        { t: 'For GDPR-compliant invoice processing in the EU, EU-hosted models with a DPA are the strongest fit. Three options rank highest.', em: false },\n        { t: 'Claude Sonnet 4.5 via the Vertex AI europe-west endpoint — data residency confirmed, and the default model for the gold-tier ap-reconciliation skill.', em: true },\n        { t: 'Gemini 2.5 Flash via europe-west — cheapest per invoice, GDPR DPA in place, weaker at unstructured line-item extraction.', em: true },\n        { t: 'Mistral Large on EU infrastructure — fully EU-owned, but no gold-tier reconciliation skill exists for it yet.', em: true },\n        { t: 'The decision trace plus 30-day retention already satisfies the EU AI Act high-risk documentation requirement, so no extra logging work is needed.', em: false }\n      ],\n      sources: [\n        { id: 'blog', label: 'aaas.blog', weight: '48%', on: true },\n        { id: 'notebooklm', label: 'NotebookLM', weight: '22%', on: true },\n        { id: 'vault', label: 'Skill vault', weight: '18%', on: true },\n        { id: 'user_kb', label: 'Workspace KB', weight: '12%', on: true }\n      ],\n      citations: [\n        { n: '1', src: 'blog', title: 'Claude Sonnet 4.5 · Anthropic', conf: '0.94', url: 'aaas.blog/claude-sonnet-4-5',\n          quote: 'EU data residency via Vertex AI europe-west confirmed.' },\n        { n: '2', src: 'blog', title: 'Gemini 2.5 Flash · Google', conf: '0.91', url: 'aaas.blog/gemini-2-5-flash',\n          quote: 'europe-west endpoint available under a GDPR DPA.' },\n        { n: '3', src: 'vault', title: 'ap-reconciliation/match', conf: '0.88', url: 'vault/ap-reconciliation',\n          quote: 'Gold-tier skill · 4,820 successful runs · default model claude-sonnet-4.5.' },\n        { n: '4', src: 'notebooklm', title: 'MEGADOC part 2 · §4.3 Compliance', conf: '0.86', url: 'notebooklm.google.com/notebook/337118fc',\n          quote: 'Decision trace plus 30-day retention satisfies EU AI Act high-risk requirements.' },\n        { n: '5', src: 'user_kb', title: 'gdpr-compliance.md · Workspace KB', conf: '0.92', url: '/kb/gdpr-compliance.md',\n          quote: 'All customer data stays in the EU. Anthropic via the Vertex EU endpoint, never US-direct.' }\n      ],\n      recent: [\n        { q: 'Which LLM is best for GDPR-compliant invoice processing?', ago: '2 min', cost: '0,004' },\n        { q: 'Best practice for a decision branch below 0.8 confidence', ago: '14 min', cost: '0,003' },\n        { q: 'Which skills exist for Podcast-Brief?', ago: '38 min', cost: '0,002' },\n        { q: 'How does the vault manifest cache strategy work?', ago: '1 h', cost: '0,002' },\n        { q: 'Which EU hardware supports Sonnet 4.5 inference?', ago: '2 h', cost: '0,003' }\n      ],\n      suggested: [\n        'How does Decisions v1 differ from v0?',\n        'What happens when the admin-bridge cookie expires?',\n        'Which skills are marked gold publishable?'\n      ],\n      budget: { used: 64, of: 100, label: 'queries today · Pro tier' }\n    };\n\n    // ══ Work · agent swimlanes (sl- / bd- spec from the handoff) ══\n    // Each lane is one live agent run in its own git worktree. 12 cells = the run's step strip.\n    this.WK_LANES = [\n      { id: 'run-5012', agent: 'feat-builder', model: 'sonnet-4.5', tier: 'pro', status: 'running',\n        task: 'Add worktree-diff endpoint to engine control-plane',\n        worktree: 'agent/feat-diff-endpoint', dirty: '4 files WIP', last: 'writing tests · cache_test.py',\n        cost: '0,84', started: '19:02', dur: '8 min', pr: '#2331', project: 'engine',\n        cells: ['ok','ok','ok','ok','ok','ok','warn','warn','empty','empty','empty','empty'],\n        steps: [{ l: 'Plan', s: 'done' }, { l: 'Route', s: 'done' }, { l: 'Tests', s: 'active' }, { l: 'PR', s: 'todo' }],\n        msgs: [\n          { r: 'agent', t: 'Adding GET /worktree/{id}/diff — unified and split modes. Wiring gh PR status through the same call.' },\n          { r: 'tool', t: 'write_file · src/routes/worktree.ts (+142 −0)' },\n          { r: 'agent', t: 'Now the test suite. Covering empty-diff, binary, and rename cases.' },\n          { r: 'tool', t: 'run · pnpm test worktree · 12 pass · 1 pending' }\n        ] },\n      { id: 'run-5011', agent: 'review-agent', model: 'opus-4.7', tier: 'team', status: 'awaiting',\n        task: 'Review PR #2329 — engine API surface',\n        worktree: 'agent/review-2329', dirty: 'clean', last: 'awaiting operator · scope question',\n        cost: '1,12', started: '18:48', dur: '22 min', pr: '#2329', project: 'engine',\n        cells: ['ok','ok','ok','ok','ok','warn','warn','warn','empty','empty','empty','empty'],\n        steps: [{ l: 'Fetch', s: 'done' }, { l: 'Analyze', s: 'done' }, { l: 'Verdict', s: 'active' }],\n        msgs: [\n          { r: 'agent', t: 'The API exposes agent_logs raw. Should I flag PII-redaction as a blocker, or is redaction handled downstream?' },\n          { r: 'system', t: 'awaiting operator input' }\n        ] },\n      { id: 'run-5009', agent: 'crm-enrich', model: 'gemini-2.5-flash', tier: 'free', status: 'running',\n        task: 'Enrich 142 leads · Acme pipeline',\n        worktree: 'agent/crm-batch-4', dirty: '1 file WIP', last: 'batch 3 of 7 · 61 enriched',\n        cost: '0,32', started: '19:05', dur: '5 min', pr: '—', project: 'crm',\n        cells: ['ok','ok','ok','ok','warn','empty','empty','empty','empty','empty','empty','empty'],\n        steps: [{ l: 'Fetch', s: 'done' }, { l: 'Enrich', s: 'active' }, { l: 'Write', s: 'todo' }],\n        msgs: [\n          { r: 'agent', t: 'Pulling company signals from LinkedIn and the blog. 61 done, no rate-limit hits.' },\n          { r: 'tool', t: 'linkedin_scrape · 20 profiles · 4.1s' }\n        ] },\n      { id: 'run-5007', agent: 'podcast-brief', model: 'gemini-2.5-flash', tier: 'free', status: 'success',\n        task: 'Daily brief · Ep 185 script and notes',\n        worktree: 'agent/ep-185', dirty: 'clean', last: 'delivered · script.md + notes.md',\n        cost: '0,04', started: '18:12', dur: '18 min', pr: '#2330', project: 'podcast',\n        cells: ['ok','ok','ok','ok','ok','ok','ok','ok','ok','ok','ok','ok'],\n        steps: [{ l: 'Rank', s: 'done' }, { l: 'Write', s: 'done' }, { l: 'Deliver', s: 'done' }],\n        msgs: [\n          { r: 'agent', t: 'Script done · 5:12 runtime. Six chapters. Notes and RSS pushed.' },\n          { r: 'tool', t: 'canvas_update · thread_ep185 · status_tile ok' }\n        ] },\n      { id: 'run-5004', agent: 'migrate-agent', model: 'sonnet-4.5', tier: 'pro', status: 'fail',\n        task: 'Migrate .diy module to /select/diy',\n        worktree: 'agent/migrate-diy', dirty: '8 files WIP', last: 'FAILED · type error in route map',\n        cost: '0,48', started: '18:30', dur: '12 min', pr: '—', project: 'platform',\n        cells: ['ok','ok','ok','ok','ok','ok','fail','fail','empty','empty','empty','empty'],\n        steps: [{ l: 'Copy', s: 'done' }, { l: 'Wire', s: 'done' }, { l: 'Build', s: 'failed' }],\n        msgs: [\n          { r: 'agent', t: 'Route map wired but the static export fails — Next type mismatch on the dynamic segment.' },\n          { r: 'tool', t: 'run · pnpm build · ERR TS2345 · exit 1' },\n          { r: 'system', t: 'run halted · replay available' }\n        ] },\n      { id: 'run-5001', agent: 'doc-writer', model: 'gemini-2.5-flash', tier: 'free', status: 'blocked',\n        task: 'Draft handover · agent-workspace package',\n        worktree: 'agent/doc-workspace', dirty: '2 files WIP', last: 'blocked · waiting on run-5012 PR',\n        cost: '0,00', started: '19:00', dur: '10 min', pr: '—', project: 'docs',\n        cells: ['ok','ok','ok','warn','empty','empty','empty','empty','empty','empty','empty','empty'],\n        steps: [{ l: 'Outline', s: 'done' }, { l: 'Draft', s: 'active' }, { l: 'Finalize', s: 'todo' }],\n        msgs: [\n          { r: 'agent', t: 'Cannot finalise the endpoint section until feat-builder\\u2019s PR #2331 lands. Holding.' },\n          { r: 'system', t: 'blocked · dependency run-5012' }\n        ] }\n    ];\n    // WK-3 (2026-09-05): operatorInbox — every outstanding item needing a human, one priority-sorted\n    // queue, type-coded. The shell's `inbox` stream is a different, unrelated design (mail console);\n    // this is Work's own queue, native on the Operator queue tab.\n    // D151 · K.operatorInbox (kinds-agent-workspace.jsx:222) is homed HERE. Its 6 loops (lp-*) are verbatim; the 5 oq-*\n    // loops stay (they drive the swimlanes). group = the source's three groups; age drives 'oldest'.\n    this.WK_QUEUE_SRC = [\n      { id: 'lp-1', group: 'Blocking now', title: 'Run #360 wants: run destructive migration', type: 'permission', tone: 'fail', age: '12m', preview: 'Type      permission\\nRun       r-360 · codex\\nScope     run_migration (destructive)\\nPolicy    never always-allowed for destructive ops' },\n      { id: 'lp-2', group: 'Blocking now', title: 'Run #372 wants: push to origin', type: 'permission', tone: 'warn', age: '4m', preview: 'Type      permission\\nRun       r-372 · claude-code\\nScope     git push origin feat/372-context-loop' },\n      { id: 'lp-3', group: 'Needs decision', title: 'Grant request: read access to billing table', type: 'grant-request', tone: 'warn', age: '1h', preview: 'Type      grant-request\\nRequested by  ops-responder\\nDelta     + SELECT on billing.invoices' },\n      { id: 'lp-4', group: 'Needs decision', title: 'canvas#358 labeled human-prio', type: 'human-prio', tone: 'accent', age: '3h', preview: 'Type      human-prio\\nSource    GitHub issue #358\\nAsk       Confirm scope before triage-agent proceeds' },\n      { id: 'lp-5', group: 'FYI / aging', title: 'Payment retry failed — inbox-04 card declined', type: 'billing', tone: 'fail', age: '2d', preview: 'Type      billing\\nAccount   inbox-04@squr.ai\\nAction    Update payment method' },\n      { id: 'lp-6', group: 'FYI / aging', title: 'Pipeline node blocked: ULW step 3 idle 6h', type: 'blocked-node', tone: 'faint', age: '6h', preview: 'Type      blocked-node\\nNode      ULW step 3\\nReason    Awaiting upstream artifact' }\n    ];\n    this.WK_QUEUE = [\n      { id: 'oq-1', age: '46m', type: 'blocker', priority: 1, title: 'Approve destructive migration on outbox schema', meta: 'run-5012 · feat-builder · blocking the run · 46m', lane: 'run-5012' },\n      { id: 'oq-2', type: 'human-prio', priority: 2, title: 'Review PR #2329 — engine API surface', meta: 'run-5011 · review-agent · awaiting your call on scope', lane: 'run-5011' },\n      { id: 'oq-3', age: '4h', type: 'permission', priority: 3, title: 'Confirm MCP server allow-list change', meta: 'playwright-mcp · requested by feat-builder · 4h', lane: null },\n      { id: 'oq-4', age: '2h', type: 'grant-request', priority: 4, title: 'Grant repo write access for release automation', meta: 'automations · requested by ops-agt · 2h', lane: null },\n      { id: 'oq-5', age: '40m', type: 'stalled', priority: 5, title: 'run-5010 idle 40 minutes — no tool calls since 18:22', meta: 'ap-fixer · dependency wait, no timeout set', lane: 'run-5010' }\n    ];\n\n    // ══ Services · 17 services, 4 categories (sg- / svc- spec from the handoff) ══\n    // pricing tiers: free · freemium · builder_only. alwaysOn services cannot be toggled off.\n    this.SVC_CATS = [\n      { id: 'use', label: 'USE', tag: 'The surfaces you work in' },\n      { id: 'discover', label: 'DISCOVER', tag: 'Reading, listening, learning' },\n      { id: 'build', label: 'BUILD', tag: 'Making and publishing' },\n      { id: 'manage', label: 'MANAGE', tag: 'Account, org and community' }\n    ];\n    this.SVC_CATALOG = [\n      { id: 'select', name: 'AaaS Select', domain: 'aaas.select', cat: 'use', icon: 'select', tier: 'free', on: true, alwaysOn: true,\n        desc: 'The agent marketplace — G2 for AI agents.', features: [] },\n      { id: 'canvas', name: 'AaaS Canvas', domain: 'aaas-canvas.web.app', cat: 'use', icon: 'columns', tier: 'freemium', on: true,\n        desc: 'Visual builder and project boards. R1 merge in Q3 2026.',\n        features: [{ id: 'projects', label: 'Project boards', on: true }, { id: 'collab', label: 'Realtime collab', on: false, tier: 'freemium', limit: '2 collaborators free' }] },\n      { id: 'pics', name: 'AaaS Pics', domain: 'aaas.pics', cat: 'use', icon: 'display', tier: 'freemium', on: false, limit: '1 pack/month on Free',\n        desc: 'AI images — Headshot Pack €19, Team Pack €99.',\n        features: [{ id: 'headshots', label: 'Headshot Pack', on: true }, { id: 'team', label: 'Team Pack', on: false, tier: 'freemium' }] },\n      { id: 'quest', name: 'AaaS Quest', domain: 'aaas.quest', cat: 'use', icon: 'user', tier: 'free', on: false,\n        desc: 'CV and career assistant, multilingual.', features: [] },\n      { id: 'boutique', name: 'AaaS Boutique', domain: 'aaas.boutique', cat: 'use', icon: 'gift', tier: 'free', on: false,\n        desc: 'Premium bundles — Brand-30 €3k, Launch-90 €8k.', features: [] },\n      { id: 'academy', name: 'AaaS Academy', domain: 'aaas.academy', cat: 'discover', icon: 'sparkles', tier: 'freemium', on: false, limit: '3 courses free',\n        desc: 'Courses and certificates — daily capability upgrade.',\n        features: [{ id: 'courses', label: 'Course library', on: true }, { id: 'certs', label: 'Certificates', on: false, tier: 'freemium' }] },\n      { id: 'today', name: 'AaaS Today', domain: 'aaas.today', cat: 'discover', icon: 'content', tier: 'free', on: true,\n        desc: 'Daily AI picks, numbered and dated.', features: [] },\n      { id: 'blog', name: 'AaaS Blog', domain: 'aaas.blog', cat: 'discover', icon: 'book', tier: 'free', on: true,\n        desc: 'Long-form essays, weekly.', features: [] },\n      { id: 'podcast', name: 'AaaS Podcast', domain: 'podcast.aaas.today', cat: 'discover', icon: 'podcast', tier: 'free', on: true,\n        desc: 'Daily five-minute AI briefing.', features: [] },\n      { id: 'toolbox', name: 'AaaS Toolbox', domain: 'aaas-toolbox.web.app', cat: 'discover', icon: 'apps', tier: 'free', on: true,\n        desc: '267 templates across 21 categories, OSS-auditable.', features: [] },\n      { id: 'builders', name: 'AaaS Builders', domain: 'aaas.builders', cat: 'build', icon: 'foundry', tier: 'builder_only', on: false,\n        desc: 'Studio, drafts, published. Become a Builder.',\n        features: [{ id: 'studio', label: 'Studio (editor)', on: true }, { id: 'publish', label: 'Publish to Select', on: false, tier: 'builder_only' }, { id: 'revenue', label: 'Revenue split 70%', on: false, tier: 'builder_only' }] },\n      { id: 'diy', name: 'AaaS DIY', domain: 'aaas.diy', cat: 'build', icon: 'bolt', tier: 'freemium', on: false, limit: '10 packages free',\n        desc: 'Free · Pro $12 · Team $29 — template and package marketplace.',\n        features: [{ id: 'free-packs', label: 'Free packages', on: true }, { id: 'pro-packs', label: 'Pro packages', on: false, tier: 'freemium' }, { id: 'team-mcp', label: 'Team MCP server', on: false, tier: 'freemium' }] },\n      { id: 'design', name: 'AaaS Design', domain: 'aaas-design.web.app', cat: 'build', icon: 'palette', tier: 'builder_only', on: false,\n        desc: 'Design deliveries, token mirrors, PSI gate.', features: [] },\n      { id: 'company', name: 'AaaS Company', domain: 'aaas.company', cat: 'manage', icon: 'platform', tier: 'free', on: false,\n        desc: 'Organization account, teams, seats.', features: [] },\n      { id: 'business', name: 'AaaS Business', domain: 'aaas.business', cat: 'manage', icon: 'finances', tier: 'free', on: false,\n        desc: 'Enterprise tool marketplace and B2B leads.', features: [] },\n      { id: 'forum', name: 'AaaS Forum', domain: 'aaas.forum', cat: 'manage', icon: 'inbox', tier: 'free', on: false,\n        desc: 'Threads, voting, moderation.', features: [] },\n      { id: 'name', name: 'AaaS Name', domain: 'aaas.name', cat: 'manage', icon: 'user', tier: 'free', on: false,\n        desc: 'Identity profile and public card.', features: [] }\n    ];\n    // W2 · Home › Services brief (2026-09-23): the hero counts are DERIVED from the catalog, with the source's\n    // labels verbatim (kinds-services.jsx K.servicesToggle.heroKpis). They were typed (Free 10 · Freemium 5 ·\n    // Builder-only 2) and matched neither this catalog nor the source. 'Enabled' is kept — live-only, derived.\n    {\n      const C = this.SVC_CATALOG, n = (f) => String(C.filter(f).length), sk = (l, v, d, a) => ({ label: l, value: v, delta: d || null, accent: a || 'circuit', goTab: null });\n      this.STREAMS.services.kpis = [sk('Available', String(C.length)), sk('Enabled', n(x => x.on), null, 'teal'), sk('Free', n(x => x.tier === 'free')),\n        sk('Freemium', n(x => x.tier === 'freemium')), sk('Builder', n(x => x.tier === 'builder_only')), sk('Audit 30 d', '42', '+8')];\n    }\n    this.SVC_AUDIT = [\n      { ts: '28.05.2026 14:32', actor: 'self', action: 'enable', target: 'pics', tone: 'ok' },\n      { ts: '28.05.2026 14:31', actor: 'self', action: 'enable', target: 'today', tone: 'ok' },\n      { ts: '28.05.2026 12:18', actor: 'self', action: 'bulk_enable_recommended', target: 'all services', tone: 'ok' },\n      { ts: '27.05.2026 18:14', actor: 'self', action: 'feature_enable', target: 'academy / certificates', tone: 'ok' },\n      { ts: '27.05.2026 18:14', actor: 'self', action: 'enable', target: 'academy', tone: 'ok' },\n      { ts: '24.05.2026 09:22', actor: 'admin · jorian', action: 'enable', target: 'design', tone: 'warn' },\n      { ts: '22.05.2026 11:08', actor: 'self', action: 'disable', target: 'forum', tone: 'fail' }\n    ];\n\n    // ══ Finance · EÜR grid + credits + invoice form (fin- spec from the handoff) ══\n    this.FIN_EUR = { year: '2026',\n      rows: [\n        { m: 'January', rev: '4.200,00', exp: '148,12', pro: '4.051,88' },\n        { m: 'February', rev: '3.900,00', exp: '162,40', pro: '3.737,60' },\n        { m: 'March', rev: '4.350,00', exp: '184,12', pro: '4.165,88' },\n        { m: 'April', rev: '4.290,00', exp: '216,52', pro: '4.073,48' },\n        { m: 'May', rev: '4.320,00', exp: '707,87', pro: '3.612,13' },\n        { m: 'June', rev: '—', exp: '—', pro: '—' }, { m: 'July', rev: '—', exp: '—', pro: '—' },\n        { m: 'August', rev: '—', exp: '—', pro: '—' }, { m: 'September', rev: '—', exp: '—', pro: '—' },\n        { m: 'October', rev: '—', exp: '—', pro: '—' }, { m: 'November', rev: '—', exp: '—', pro: '—' },\n        { m: 'December', rev: '—', exp: '—', pro: '—' }\n      ],\n      // D20 (2026-09-05): totals removed as a typed field — `fin.eurTotals` is now computed live\n      // from these rows in the kind==='fin' block below, so the D19 mislabel-then-subtract class of\n      // contradiction becomes structurally impossible rather than merely fixed once.\n    };\n    // D18 (2026-09-05): the platform's own prepaid-credit balance (money OUT) already has a home —\n    // the `billing` stream (group 'member', Credits/Plan/Invoices/Usage) — so nothing moves there;\n    // Finances' `Credits` tab stops duplicating it and becomes Gutschriften (client credit notes,\n    // money IN) instead, per the D18 direction split. FIN_CREDITS is retired, not renamed in place,\n    // since its content (Stripe top-ups) was never a Finances concept — it was always `billing`'s.\n    this.FIN_GUTSCHRIFTEN = [\n      { number: 'G-2026-003', client: 'Studio Nord', issued: '28.05.2026', amt: '420,00', reason: 'Leistungskürzung Mai', ref: '2026-109' },\n      { number: 'G-2026-002', client: 'Helsing GmbH', issued: '10.05.2026', amt: '150,00', reason: 'Rabatt Treue-Kunde', ref: '2026-118' },\n      { number: 'G-2026-001', client: 'Kanzlei Steuer', issued: '15.03.2026', amt: '90,00', reason: 'Korrektur Stundensatz', ref: '2026-098' }\n    ];\n    // D21 (2026-09-05): the invoice shown here is one of the numbers Client Index and Home already\n    // cite (2026-114, Kanzlei Steuer) rather than an unmounted, unreferenced number — a number\n    // another surface already cites is load-bearing in a way this pane's own invention was not.\n    this.FIN_INVOICE = { number: '2026-114', client: 'Kanzlei Steuer', issued: '30.04.2026', due: '30.05.2026', delivered: '30.04.2026',\n      state: 'OVERDUE', net: '2.470,59', vat: '469,41', gross: '2.940,00', rate: '19,00',\n      lines: [{ d: 'Retainer — April 2026', q: '1', up: '2.470,59', vat: '19,00', t: '2.470,59' }],\n      issuer: ['AaaS Love UG (haftungsbeschränkt)', 'Hafenstraße 12, 20359 Hamburg', 'VAT DE355 812 447', 'Reverse charge does not apply'],\n      chips: ['de-DE', 'EUR', 'Small-business §19 off', 'Reverse charge off'] };\n\n    this.DT_OUTPUT = '12 invoices matched · 0 exceptions\\n\\n' +\n      'INV-2026-0412  → PO-8871   fuzzy 0.94   \"Nordlicht GmbH\" / \"Nordlicht G.m.b.H.\"\\n' +\n      'INV-2026-0413  → PO-8872   fuzzy 0.91   VAT match, name drift on line 2\\n' +\n      'INV-2026-0418  → PO-8880   fuzzy 0.88   amount and date match, vendor alias\\n' +\n      '… 9 more lines …\\n\\n' +\n      'Report emailed to jor@gmail.com · canvas thread updated.';\n    this.DT_LOGS = [\n      { t: '17:42:01.204', tag: '[ctx]', msg: 'Loaded vendor ledger + 12 skills', tone: 'accent' },\n      { t: '17:42:02.008', tag: '[llm]', msg: 'gemini-2.5-flash · 412 in / 289 out · 1.2s', tone: 'soft' },\n      { t: '17:42:02.412', tag: '[tool]', msg: 'db_query returned 12 rows in 400ms', tone: 'soft' },\n      { t: '17:42:02.512', tag: '[dec]', msg: 'match strategy: fuzzy_match (0.87) over embedding_similarity (0.71)', tone: 'accent' },\n      { t: '17:42:03.914', tag: '[crit]', msg: 'self-critique round 1 accepted — reasoning expanded', tone: 'warn' },\n      { t: '17:42:05.774', tag: '[dec]', msg: 'delivery channel: email_summary (0.94)', tone: 'accent' },\n      { t: '17:42:06.014', tag: '[tool]', msg: 'send_email 202 Accepted · msg_28a1f', tone: 'success' },\n      { t: '17:42:12.400', tag: '[aud]', msg: 'PII redaction applied at write-time · trace stored', tone: 'lav' }\n    ];\n\n    this.DELIVERY_FILES = [\n      { name: 'client-film-45s-master.mp4', spec: 'H.264 · 1920×1080 · 24 fps', size: '68 MB', state: 'on delivery' },\n      { name: 'client-film-30s.mp4', spec: 'Cutdown · 1920×1080', size: '44 MB', state: 'on delivery' },\n      { name: 'client-film-15s-9x16.mp4', spec: 'Vertical cutdown · 1080×1920', size: '26 MB', state: 'on delivery' },\n      { name: 'client-script-read.wav', spec: 'Voice master · 48 kHz', size: '7.9 MB', state: 'on approval' },\n      { name: 'client-script.md', spec: 'Markdown · 118 words', size: '3 KB', state: 'preview' },\n      { name: 'client-captions.srt', spec: 'EN · 45/30/15', size: '4 KB', state: 'on delivery' }\n    ];\n\n    this.CLIENT_SCRIPT = { duration: '0:45', words: '118', voice: 'EN-EU · calm, technical',\n      lines: [\n        { t: '0:00', text: 'Your website has doors nobody has counted.', locked: false },\n        { t: '0:07', text: 'Client Co points an AI pentester at your URL, hands it a login, and tries the handles — real exploitation, not pattern matching.', locked: false },\n        { t: '0:18', text: 'A second agent re-tests every finding independently, so what reaches you is exploitable — zero noise.', locked: true },\n        { t: '0:29', text: 'You get step-by-step remediation for each finding, one-click retesting, and a compliance-ready report.', locked: true },\n        { t: '0:38', text: 'Twenty-four hours. Built in Europe. Start at client-site.example.', locked: true }\n      ] };\n\n    // ── White-label token sets — the canvas wears whichever brand is selected ──\n    // Same shell, same components; only the token strings change.\n    // F3 (2026-09-03): the literals below are the FALLBACK. At boot, applyTenantPacks() reads\n    // tenants/<id>/<id>.json (repo schema: brand.tokens / brand.tokensLight in --app-* space) and\n    // rewrites BRANDS[id].dark/light through appTokensToShellVars(). Adding a tenant = adding a\n    // manifest file; the shell learns it without an edit here. TOKEN-MAP §1 documents the two shapes.\n    this.BRANDS = {\n      aaas: { name: 'AAAS', tag: 'CURATED AI AGENTS', swatch: 'rgb(0 243 255)', tokens: null },\n      squr: { name: 'SQUR', tag: 'AUTONOMOUS PENTESTING · 24H', swatch: '#35C6E8',\n        dark: '--basalt-deep:6 22 27;--basalt-surface:13 36 43;--basalt-bright:19 50 59;--text:226 240 243;--text-muted:rgba(226,240,243,0.55);--border:rgba(120,200,215,0.12);--circuit-glow:53 198 232;--circuit-dim:rgba(53,198,232,0.16);--accent-red:244 85 106;--accent-red-dim:rgba(244,85,106,0.16);--accent-teal:47 191 165',\n        light: '--basalt-deep:238 245 246;--basalt-surface:255 255 255;--basalt-bright:224 236 239;--text:8 30 37;--text-muted:rgba(8,30,37,0.6);--border:rgba(8,30,37,0.10);--circuit-glow:13 130 163;--circuit-dim:rgba(13,130,163,0.14);--accent-red:196 44 66;--accent-red-dim:rgba(196,44,66,0.12);--accent-teal:0 130 112' },\n      nike: { name: 'NIKE', tag: 'JUST DO IT', swatch: '#D7FF3E',\n        dark: '--basalt-deep:8 8 8;--basalt-surface:20 20 20;--basalt-bright:28 28 28;--text:245 245 245;--text-muted:rgba(245,245,245,0.55);--border:rgba(255,255,255,0.10);--circuit-glow:215 255 62;--circuit-dim:rgba(215,255,62,0.16);--accent-red:255 90 40;--accent-red-dim:rgba(255,90,40,0.16);--accent-teal:120 220 120',\n        light: '--basalt-deep:245 245 245;--basalt-surface:255 255 255;--basalt-bright:232 232 232;--text:17 17 17;--text-muted:rgba(17,17,17,0.6);--border:rgba(0,0,0,0.10);--circuit-glow:90 110 0;--circuit-dim:rgba(90,110,0,0.14);--accent-red:214 60 10;--accent-red-dim:rgba(214,60,10,0.12);--accent-teal:20 130 60' },\n      // D209 (P3d) · the sports vertical as a tenant: nike's demo tokens renamed. Literal FALLBACK — tenants/sports/sports.json\n      // rewrites it at boot like squr. Founder = brandKey 'sports'. nike stays a demo brand, unchanged.\n      sports: { name: 'STRIDE', tag: 'EVERY SPORT · EVERY LEVEL', swatch: '#D7FF3E',\n        dark: '--basalt-deep:8 8 8;--basalt-surface:20 20 20;--basalt-bright:28 28 28;--text:245 245 245;--text-muted:rgba(245,245,245,0.55);--border:rgba(255,255,255,0.10);--circuit-glow:215 255 62;--circuit-dim:rgba(215,255,62,0.16);--accent-red:255 90 40;--accent-red-dim:rgba(255,90,40,0.16);--accent-teal:120 220 120',\n        light: '--basalt-deep:245 245 245;--basalt-surface:255 255 255;--basalt-bright:232 232 232;--text:17 17 17;--text-muted:rgba(17,17,17,0.6);--border:rgba(0,0,0,0.10);--circuit-glow:90 110 0;--circuit-dim:rgba(90,110,0,0.14);--accent-red:214 60 10;--accent-red-dim:rgba(214,60,10,0.12);--accent-teal:20 130 60' },\n      coke: { name: 'COCA-COLA', tag: 'REAL MAGIC', swatch: '#FF3B41',\n        dark: '--basalt-deep:22 6 8;--basalt-surface:38 12 15;--basalt-bright:52 16 20;--text:255 240 238;--text-muted:rgba(255,240,238,0.58);--border:rgba(255,120,120,0.14);--circuit-glow:255 59 65;--circuit-dim:rgba(255,59,65,0.16);--accent-red:255 209 102;--accent-red-dim:rgba(255,209,102,0.16);--accent-teal:255 150 120',\n        light: '--basalt-deep:250 240 238;--basalt-surface:255 255 255;--basalt-bright:243 226 224;--text:38 8 10;--text-muted:rgba(38,8,10,0.6);--border:rgba(38,8,10,0.10);--circuit-glow:214 0 8;--circuit-dim:rgba(214,0,8,0.12);--accent-red:150 90 0;--accent-red-dim:rgba(150,90,0,0.12);--accent-teal:170 60 40' }\n    };\n\n    // An unknown brandKey must NEVER impersonate tenant-0 (the default tenant's marks): every brand consumer\n    // resolves through here instead of falling back to the default tenant's entry. A key the\n    // registry has not learned yet renders under its OWN name with an honest not-loaded tag and\n    // warns once (the placeholder caches itself, so the warn fires once per unknown key per page);\n    // applyTenantPacks() upgrades it to the real pack the moment the manifest lands. That is what\n    // makes the F3 promise above true end to end: a manifest tenant is learned at boot, and until\n    // then it still never renders as tenant-0.\n    this.brandOrPlaceholder = (key) => {\n      if (!key) return this.BRANDS.aaas;   // no selection = the default tenant, not an unknown brand\n      if (this.BRANDS[key]) return this.BRANDS[key];\n      console.warn('[brand] unknown brandKey \"' + key + '\" — no tenant pack loaded yet; rendering a neutral placeholder, not tenant-0');\n      return (this.BRANDS[key] = { name: String(key).toUpperCase(), tag: 'BRAND PACK NOT LOADED', swatch: '#888' });\n    };\n\n    // F3 adapter · --app-* (tenant manifest) → the shell's DS-layer vars. Only the keys the shell\n    // consumes are mapped; anything else in the manifest is passed through untouched as --app-*.\n    const toTriplet = (v) => { // '#35C6E8' | 'rgb(6,22,27)' | 'rgb(6 22 27)' → '53 198 232'\n      if (!v) return null; const s = String(v).trim();\n      let m = s.match(/^#([0-9a-f]{6})$/i); if (m) return [1, 3, 5].map(i => parseInt(m[1].slice(i - 1, i + 1), 16)).join(' ');\n      m = s.match(/^rgba?\\(\\s*(\\d+)[\\s,]+(\\d+)[\\s,]+(\\d+)/i); if (m) return m[1] + ' ' + m[2] + ' ' + m[3];\n      return null;\n    };\n    this.appTokensToShellVars = (tok) => {\n      if (!tok) return null;\n      const out = [];\n      const put = (k, v) => { if (v) out.push(k + ':' + v); };\n      put('--basalt-deep', toTriplet(tok['--app-bg'])); put('--basalt-surface', toTriplet(tok['--app-surface'])); put('--basalt-bright', toTriplet(tok['--app-elevated']));\n      put('--text', toTriplet(tok['--app-ink'])); put('--text-muted', tok['--app-ink-soft']); put('--border', tok['--app-rule']);\n      put('--circuit-glow', toTriplet(tok['--app-accent'])); put('--circuit-dim', tok['--app-accent-dim']);\n      put('--accent-red', toTriplet(tok['--app-signal'])); put('--accent-red-dim', tok['--app-signal-dim']); put('--accent-teal', toTriplet(tok['--app-success']));\n      Object.keys(tok).forEach(kk => { if (/^--app-/.test(kk)) out.push(kk + ':' + tok[kk]); });\n      return out.join(';');\n    };\n    // G1 · The tenant set is READ off the tree, never enumerated here. A browser cannot list a\n    // directory, so tenants/index.json is the generated stand-in for that listing: tools/tenants-index.mjs\n    // writes it from `git ls-files tenants/`, and `npm test` fails when it drifts from the tree.\n    // It is declared as an ext-resource-dependency in <head> beside ctx-pack-default, so the publish\n    // bundler inlines it as a blob: and window.__resources['tenants-index'] resolves in the offline\n    // build. Without that meta this fetch 404s in the export and takes the tenant AND context packs\n    // with it — the declaration is load-bearing, not decoration. Memoised on window: both pack\n    // loaders below ask one question.\n    // DELIBERATE, and the cost is real: a failed index resolves [] once and stays [], so ONE transient\n    // failure costs every tenant for the page's life. That is a fan-in the per-manifest fetches below\n    // do not have (they lose one tenant each). Accepted, not overlooked, because (a) the published\n    // build reads a blob: that cannot 404, (b) in dev it is a same-origin static file with exactly the\n    // reachability of the manifests it indexes, and (c) a retry HERE would not run: applyContextPacks\n    // burns __dcContextLoaded above before it ever reaches this gate, so nothing re-enters. Making a\n    // retry reachable means moving that flag burn — a re-entrancy change to this function, outside the\n    // G1 defect and needing its own brief. Failing open to empty matches the honest-empty posture the\n    // pack fetches already take, and leaves this.BRANDS on its built-in defaults rather than adding a\n    // second source of truth here.\n    this.tenantIds = () => (window.__dcTenantIdsP || (window.__dcTenantIdsP =\n      fetch((window.__resources && window.__resources['tenants-index']) || this.rootPrefix + 'tenants/index.json')\n        .then(r => r.ok ? r.json() : null)\n        .then(j => Array.isArray(j && j.tenants) ? j.tenants : [])\n        .catch(() => [])));\n    this.applyTenantPacks = () => {\n      if (window.__dcTenantsLoaded) return; window.__dcTenantsLoaded = true;\n      this.tenantIds().then(ids => Promise.all(ids.map(id => fetch(this.rootPrefix + 'tenants/' + id + '/' + id + '.json').then(r => r.ok ? r.json() : null).catch(() => null))).then(list => {\n        let changed = false;\n        list.forEach((tn, i) => {\n          if (!tn || !tn.brand) { console.warn('[brand] tenants/index.json lists \"' + ids[i] + '\" but its manifest (tenants/' + ids[i] + '/' + ids[i] + '.json) is missing or brand-less — the shell cannot learn this tenant; a direct request renders a neutral placeholder, never tenant-0'); return; }\n          (this._tenantPacks = this._tenantPacks || {})[ids[i]] = tn;\n          const id = ids[i], b = this.BRANDS[id] || (this.BRANDS[id] = { name: id.toUpperCase(), tag: '', swatch: '#888' });\n          if (tn.brand.wordmark) b.name = String(tn.brand.wordmark).toUpperCase();\n          if (tn.brand.tagline) b.tag = String(tn.brand.tagline).toUpperCase();\n          if (tn.brand.tokens && tn.brand.tokens['--app-accent']) b.swatch = tn.brand.tokens['--app-accent'];\n          // AaaS is tenant-0: its tokens ARE the bound _ds, so tokens:null stays (the DS renders itself).\n          if (id !== 'aaas') {\n            const d = this.appTokensToShellVars(tn.brand.tokens), l = this.appTokensToShellVars(tn.brand.tokensLight);\n            if (d) b.dark = d; if (l) b.light = l;\n          }\n          b.fromManifest = 'tenants/' + id + '/' + id + '.json'; changed = true;\n        });\n        if (changed) { this.forceUpdate(); setTimeout(() => this.broadcastBrandToFrames(), 30); }\n      }));\n    };\n    if (!window.__dcTenantsScheduled) { window.__dcTenantsScheduled = true; setTimeout(() => this.applyTenantPacks(), 0); }\n\n    // ── CM-D (CONTEXT-MANAGEMENT-PLAN.md §7): load tenant context/*.json into ContextStore scope\n    // blocks at boot, same one-shot-flag pattern as applyTenantPacks() above. Company scope only this\n    // pass (account/personal/domain packs don't exist on disk yet) — PaneContext reads the loaded block\n    // by a stable per-tenant id so re-navigating never re-fetches or duplicates versions.\n    this.applyContextPacks = () => {\n      if (window.__dcContextLoaded) return;\n      // In dev the helmet's context-store.js is still loading when this constructor runs, so the\n      // original order (burn the one-shot flag, THEN check the dependency) meant the overlay never\n      // loaded at all outside the standalone build — a fail-shut boot, found live 2026-09-06 (4.3).\n      // Retry briefly instead, and only claim the flag once there is something to load into.\n      if (!window.ContextStore) {\n        this._ctxTries = (this._ctxTries || 0) + 1;\n        if (this._ctxTries <= 40) setTimeout(() => this.applyContextPacks(), 60);\n        return;\n      }\n      window.__dcContextLoaded = true;\n      const CS = window.ContextStore;\n\n      /* CM-F · the operator's own personal block. There is no personal pack on disk and inventing a\n         person is forbidden (WP-02), so it is seeded from fixtures.js's single cast — Fixtures.OPERATOR.\n         Seeded ONCE and never overwritten: grants live on this block, so re-seeding on every reload\n         would silently revoke whatever the operator granted. Personal blocks carry scope[] = [] and are\n         readable only through grants (D24 CM-7) — never by tenant scope. */\n      if (window.Fixtures && !CS.store.getById('ctx-personal-operator', CS.INTERNAL)) {\n        const op = window.Fixtures.OPERATOR;\n        const pb = CS.createPersonalBlock({\n          identity: { name: op.name, handle: op.handle, role: op.role },\n          contact: { email: op.addr + '.com.de', phone: '+49 151 2345 6789' },\n          work: { plan: op.plan, hours: 'Mo–Fr 09:00–18:00 CET', timezone: 'Europe/Berlin' },\n          preferences: { comms: 'email digest, no pings', locale: 'de-DE' },\n          grants: []\n        });\n        pb.id = 'ctx-personal-operator';\n        pb.source.owner = op.addr;\n        CS.store.save(pb);\n      }\n\n      // CM-G: trigger registration moved to componentDidMount (see the note there) — it does not\n      // belong to pack loading, and living here meant it never ran at all.\n\n      this.tenantIds().then(ids => ids.forEach((id) => {\n        const curl = (window.__resources && window.__resources['ctx-pack-' + id]) || (this.rootPrefix + 'tenants/' + id + '/context/company-context.json');\n        fetch(curl).then(r => r.ok ? r.json() : null).then((raw) => {\n          if (!raw) return; // e.g. squr has no context pack yet — honest empty, never fabricated\n          const block = CS.createCompanyBlock({\n            identity: { name: raw.company?.name, legal_entity: raw.company?.legal_form, hq_location: raw.company?.address?.city,\n              mission: raw.branding?.tagline, values: [] },\n            structure: { business_model: raw.product?.type },\n            economics: { revenue_band: null, profitability_status: raw.product?.stage, funding_stage: raw.funding?.stage, runway_months: raw.financials?.runway_months },\n            environment: { industry: raw.industry?.primary, sub_industry: raw.industry?.secondary, geo_focus: [], regulatory_constraints: [] }\n          }, [id]);\n          block.id = `ctx-company-${id}`; // stable id — re-fetch on reload re-saves as a new version, never a duplicate block\n          // 4.3: the pack lives in an explicit registry, NOT as a block.raw sidecar. A ContextBlock is a\n          // validated, versioned, cloned record; hanging a non-envelope key off it made the pack's survival\n          // depend on every code path that copies a block, and it did not survive — Company/Overview read\n          // block.raw and silently showed their empty state while the mapped structured_data was correct.\n          // One registry, one owner, greppable: window.__dcCtxPacks[tenantId].\n          (window.__dcCtxPacks = window.__dcCtxPacks || {})[id] = raw;\n          /* Save only on a genuine pack change. save() is non-destructive, so an unconditional write\n             here appended one identical version per page load (17 by the time it was caught) and buried\n             real changes — including CM-F's grant history, which 4.4 relies on as THE audit surface — in\n             boot noise, growing localStorage without bound. The defect was latent in CM-D: its fetch\n             404'd, so the save never ran; 4.3 fixed the path and switched it on. This is what\n             \"re-fetch on reload re-saves as a new version, never a duplicate\" was meant to mean, and it\n             matches the personal block's own seed-only-when-absent rule. */\n          const prev = CS.store.getById(block.id, CS.INTERNAL);\n          if (prev && CS.diffStructuredData(prev, block).length === 0) return; // pack unchanged — no version\n          CS.store.save(block);\n        }).catch(() => {}); // no pack on disk = honest empty state, not a fetch error surfaced to the operator\n      }));\n    };\n    if (!window.__dcContextScheduled) { window.__dcContextScheduled = true; setTimeout(() => this.applyContextPacks(), 0); }\n\n\n    /* ── CM-I · the T-1 coverage matrix (136 raw Toolbox keys -> template count + phases). The Impact tab\n       relates datapoints to consumers, and the document half of that relation is only countable from this\n       file. Own boot flag, own failure mode: no file -> window.__dcCoverage stays null and Impact says\n       \"none declared\" for documents instead of implying zero consumers. */\n    this.applyToolsetCoverage = () => {\n      if (window.__dcCoverageLoaded) return; window.__dcCoverageLoaded = true;\n      const url = (window.__resources && window.__resources['toolset-coverage']) ||\n        this.rootPrefix + 'tenants/aaas/toolset/context-coverage.json';\n      fetch(url).then(r => r.ok ? r.json() : null).then((cov) => {\n        if (!cov || !cov.coverage) return;\n        window.__dcCoverage = cov;\n        this.forceUpdate(); // Impact is coverage-derived; repaint once it lands\n      }).catch(() => {});\n    };\n    if (!window.__dcCoverageScheduled) { window.__dcCoverageScheduled = true; setTimeout(() => this.applyToolsetCoverage(), 0); }\n\n    // ── P4 · agent grounding (CANVAS-INDEX-PLAN.md §2 P4) ─────────────────────────────────────────\n    // canvas.index.json is the canvas's own account of itself: 42 planes (one per source tab, with its\n    // verdict and what it reads/writes), 42 needs, 297 datapoints. Loading it at boot lets the agent\n    // answer FROM the record instead of paraphrasing the screen — and lets \"Gaps\" be a query, not a doc.\n    // Same one-shot-flag boot pattern as applyTenantPacks/applyContextPacks. Failure is silent and\n    // honest: no index → planeBrief() returns null → the agent says it has no record for this plane\n    // rather than inventing one.\n    this.applyIndex = () => {\n      if (window.__dcIndexLoaded) return; window.__dcIndexLoaded = true;\n      const url = (window.__resources && window.__resources['canvas-index']) || '../../canvas.index.json';\n      fetch(url).then(r => r.ok ? r.json() : null).then((idx) => {\n        if (!idx || !idx.planes) return;\n        const planes = {}, byTab = {}, needsByStream = {}, dps = {};\n        // planes: keyed by plane id (unique). byTab: every row on a stream/tab — a tab may serve more\n        // than one need (intelligence/Entity graph, marketing/Queue), and keying only on the pair\n        // dropped one row of each (42 rows -> 40 keys, caught 2026-09-06).\n        idx.planes.forEach(p => {\n          planes[p.id] = p;\n          (byTab[p.stream + '/' + p.tab] = byTab[p.stream + '/' + p.tab] || []).push(p);\n        });\n        (idx.needs || []).forEach(n => { (needsByStream[n.stream] = needsByStream[n.stream] || []).push(n); });\n        (idx.datapoints || []).forEach(d => { dps[d.id] = d; });\n        window.__dcIndex = { raw: idx, planes, byTab, needsByStream, dps,\n          benchmark: idx.needsBenchmark || null, version: idx.version };\n        this.forceUpdate(); // the chat seed and Gaps command are index-derived; repaint once it lands\n      }).catch(() => {});\n    };\n    if (!window.__dcIndexScheduled) { window.__dcIndexScheduled = true; setTimeout(() => this.applyIndex(), 0); }\n\n    /* The grounded brief for one plane. Every field comes from the index row or the ContextStore —\n       nothing is authored here, which is the whole point: a hand-written per-stream blurb would drift\n       from the ledger the moment a build item shipped, and drift is the failure this index exists to\n       stop. Returns null when the index has no row for stream/tab. */\n    this.planeBrief = (streamId, tabLabel) => {\n      const IX = window.__dcIndex;\n      if (!IX) return null;\n      const rows = (IX.byTab && IX.byTab[streamId + '/' + tabLabel]) || [];\n      if (!rows.length) return null;\n      const p = rows.find(r => r.id === streamId + '/' + tabLabel) || rows[0];\n      const extra = rows.filter(r => r !== p);\n      const CS = window.ContextStore;\n      const reads = (p.reads || []).map((id) => {\n        const d = IX.dps[id];\n        if (!d) return { id, meaning: null, tenant: 'endpoint', note: 'stub endpoint — no live read yet' };\n        let tenant = 'missing';\n        if (CS) {\n          try {\n            const tid = this.state.brandKey || 'aaas';\n            const blk = CS.store.getById('ctx-company-' + tid, CS.INTERNAL);\n            const pk = (window.__dcCtxPacks || {})[tid];\n            const path = String(d.schemaPath || id.replace(/^ctx\\.[a-z]+\\./, '')).split('.');\n            let v = pk || (blk && blk.structured_data);\n            path.forEach(k => { v = (v && typeof v === 'object') ? v[k] : undefined; });\n            if (v === undefined && pk) { // one-level scan: schemaPath and pack shape differ by nesting\n              const leaf = path[path.length - 1];\n              const hit = Object.values(pk).find(o => o && typeof o === 'object' && o[leaf] !== undefined);\n              if (hit) v = hit[leaf];\n            }\n            if (v !== undefined && v !== null && v !== '') tenant = 'present';\n          } catch (e) { tenant = 'missing'; }\n        }\n        return { id, meaning: d.type ? (d.scope + ' · ' + d.type) : d.scope, tenant, note: null };\n      });\n      const ids = [p.id].concat(extra.map(r => r.id));\n      const need = (IX.needsByStream[streamId] || []).find(n => (n.planes || []).some(x => ids.indexOf(x) >= 0)) || null;\n      return {\n        plane: p, need,\n        purpose: p.purpose, stage: p.stage, verdict: p.verdict, resolves: p.resolves,\n        reads, readsScopes: p.readsScopes || [],\n        writes: [...new Set((p.writes || []).concat(...extra.map(r => r.writes || [])))],\n        alsoServes: extra.map(r => r.id),\n        buildItem: p.buildItem || (extra.find(r => r.buildItem) || {}).buildItem || null, reason: p.reason,\n        sourceFile: (p.source && p.source.file) || null\n      };\n    };\n\n    /* CM-F · the only writer of personal grants. Kept shell-side, next to the other store writes, so a\n       pane never mutates the store directly. Every change is a new block version (the store is\n       non-destructive), so History diffs a grant exactly like any other context change — that IS the\n       audit row, not a second log to keep in sync. */\n    this.ctxGrant = (fields, days, companyId) => {\n      const CS = window.ContextStore; if (!CS) return;\n      const blk = CS.store.getById('ctx-personal-operator', CS.INTERNAL); if (!blk) return;\n      const next = JSON.parse(JSON.stringify(blk));\n      next.grants = (next.grants || []).slice();\n      next.grants.push({\n        id: 'g-' + Date.now().toString(36), company_id: companyId || 'aaas',\n        fields: fields.slice(),\n        granted_at: new Date().toISOString(),\n        until: days ? new Date(Date.now() + days * 864e5).toISOString() : null,\n        granted_by: (window.Fixtures && window.Fixtures.OPERATOR.addr) || 'operator'\n      });\n      CS.store.save(next); this.forceUpdate();\n      // CM-G: a grant changes what a consumer receives, so it IS a context change. It is also the only\n      // live context write today (the Company tab renders read-only rows), so it is the only trigger\n      // source until CM-E's editor lands — at which point that editor fans out for free by calling\n      // `ctxEdited`, with no change to the machinery below.\n      this.ctxEdited('ctx-personal-operator', fields.slice());\n    };\n    /* CM-H · the only writer of a derived domain block. The mapping itself lives in\n       ProcessSpec.DOMAIN_MODEL[].derive (one place, read by both the Domains rows and the Flows\n       instance), so this handler only decides WHETHER to write and files the consequences.\n       CM-6 makes an agent write a proposal, and the acceptance queue that should hold it (Sources) is\n       still fixture — so the honest interim is: write it, stamp it `agent:flow-strategic-gtm` so\n       isAgentOrigin() can find it, and raise the approval-state thread that routes it for review.\n       Recorded as this item's open gap rather than described as accepted. */\n    this.ctxDerive = (type) => {\n      const CS = window.ContextStore, PS = window.ProcessSpec;\n      if (!CS || !PS || typeof PS.deriveDomainBlock !== 'function') return;\n      const tid = 'aaas';\n      const pack = (window.__dcCtxPacks || {})[tid] || null;\n      const blk = PS.deriveDomainBlock(CS, type, pack, tid);\n      if (!blk) { console.warn('[CM-H] nothing derivable for block type ' + type); return; }\n      const prev = CS.store.getById(blk.id, CS.INTERNAL);\n      if (prev && CS.diffStructuredData(prev, blk).length === 0) return; // same derivation — no version\n      CS.store.save(blk);\n      this.forceUpdate();\n      // The ids a consumer would declare for this block, so CM-5's fan-out matches by prefix exactly as\n      // it does for company edits — a derived block going live is a context change like any other.\n      // The dictionary's canonical domain id is ctx.domain.<type>.<path> (canvas.index.json datapoints,\n      // 53 domain rows) — NOT ctx.<type>.<path>, which the first cut emitted and which would have matched\n      // no declared read at all. Top-level keys are enough: ctxFanOut matches by prefix in both directions.\n      const fields = Object.keys(blk.structured_data).map(k => 'ctx.domain.' + type + '.' + k);\n      this.ctxEdited(blk.id, fields);\n      const at = new Date().toISOString();\n      this.ctxRaise({\n        id: 'cmh-derive-' + blk.id + '-v' + (((prev && prev.source.version) || 0) + 1),\n        category: 'plans', sourceStream: 'context', sourceStatus: 'pending_approval', state: 'approval',\n        who: 'gtm-flow', initials: 'GT', addr: '<flow@yourco.aaas.love>',\n        at: at.slice(11, 16), unread: true,\n        subject: (PS.domainEntry(type) || {}).label + ' block derived from company intake — accept or reject',\n        tag: 'Context proposal',\n        snip: 'Origin ' + blk.source.origin + ' · confidence ' + blk.source.confidence +\n          ' · depends on ' + (blk.relationships.depends_on || []).join(', '),\n        msgs: [{ from: 'gtm-flow', when: '· ' + at.slice(11, 16) + ' · strategic_gtm_flow',\n          body: 'The ' + type + ' block was mapped from company-scope intake and saved as version ' +\n            (((prev && prev.source.version) || 0) + 1) + ' with an agent origin. CM-6 makes an agent write a ' +\n            'proposal, so it is not an accepted fact until a Director accepts it in Sources.' }],\n        route: ['context', 'Review in Sources']\n      });\n    };\n    this.ctxRevoke = (grantId) => {\n      const CS = window.ContextStore; if (!CS) return;\n      const blk = CS.store.getById('ctx-personal-operator', CS.INTERNAL); if (!blk) return;\n      const next = JSON.parse(JSON.stringify(blk));\n      // Revoking marks the grant revoked; it neither deletes it nor rewrites its window. Back-dating\n      // `until` (the first cut) destroyed the very thing the kept record is for — the row then showed\n      // granted/until/revoked as three identical stamps and the real 30-day window survived only inside\n      // an older block version. `revoked_at` alone carries the early end; readers test it first.\n      next.grants = (next.grants || []).map(g => g.id === grantId\n        ? Object.assign({}, g, { revoked_at: new Date().toISOString() })\n        : g);\n      const gone = (blk.grants || []).filter(g => g.id === grantId)[0];\n      CS.store.save(next); this.forceUpdate();\n      if (gone) this.ctxEdited('ctx-personal-operator', (gone.fields || []).slice());\n    };\n\n    /* ══ CM-G · stale fan-out (CM-5) — `context.updated{block, fields}` → every consumer whose\n       `reads ∩ fields ≠ ∅` marks those fields stale, plus ONE Inbox thread per affected consumer.\n       Hangs off `context-store.js`'s own triggers registry, which has exported `registerTrigger` /\n       `dispatchEvent` since CM-D and had **no caller at all** until now. */\n    this.ctxConsumers = () => {\n      const out = [];\n      // Consumers that declare their reads in the index (CM-4). Only `ctx.*` reads can go stale;\n      // endpoint reads (`/automations`, growth feeds) are a different contract.\n      const IX = window.__dcIndex;\n      if (IX && IX.planes) Object.keys(IX.planes).forEach(id => {\n        const p = IX.planes[id] || {};\n        const reads = (p.reads || []).filter(r => String(r).indexOf('ctx.') === 0);\n        if (reads.length) out.push({ id: id, kind: 'plane', label: id, reads: reads });\n      });\n      // Toolbox templates declare theirs in their own schema's `fields` OBJECT MAP (field_id → def,\n      // each with an optional `context_map`) — see tenants/_schema/toolbox-template.schema-spec.json.\n      // A section's own `fields` is a separate thing: an ARRAY of field ids pointing into that map.\n      // (CR-6, 2026-09-10 — the old `fields[].context_map` wording contradicted the code below it.)\n      const SC = window.__dcSchemas || {};\n      Object.keys(SC).forEach(slug => {\n        const s = SC[slug];\n        if (!s || !s.fields) return;\n        const reads = Object.keys(s.fields).map(k => s.fields[k] && s.fields[k].context_map).filter(Boolean);\n        if (reads.length) out.push({ id: slug, kind: 'doc', label: s.name || slug, reads: reads });\n      });\n      return out;\n    };\n    /* The one vocabulary bridge in CM-G, stated rather than buried: the index says `ctx.company.name`,\n       a template schema says `company.name`. Both normalise by dropping a leading `ctx.`, and a read\n       matches a changed field when either is a prefix of the other — so editing `company.address`\n       marks a consumer reading `company.address.city` stale, and vice versa. */\n    this.ctxFanOut = (ev) => {\n      const fields = (ev && ev.fields) || [];\n      if (!fields.length) return { consumers: 0, fields: 0 };\n      const norm = (x) => String(x).replace(/^ctx\\./, '');\n      const hit = (read) => fields.some(f => {\n        const a = norm(read), b = norm(f);\n        return a === b || a.indexOf(b + '.') === 0 || b.indexOf(a + '.') === 0;\n      });\n      const affected = this.ctxConsumers()\n        .map(c => ({ c: c, matched: c.reads.filter(hit) }))\n        .filter(x => x.matched.length);\n      if (!affected.length) return { consumers: 0, fields: 0 };\n      const at = new Date().toISOString();\n      this.setState(s => {\n        const next = Object.assign({}, s.ctxStale);\n        affected.forEach(x => {\n          const prev = next[x.c.id] || { fields: [] };\n          const add = x.matched.filter(m => prev.fields.indexOf(m) < 0);\n          next[x.c.id] = { label: x.c.label, kind: x.c.kind, at: at,\n            block: (ev && ev.block) || null, fields: prev.fields.concat(add) };\n        });\n        return { ctxStale: next };\n      });\n      // CM-5 says one thread per affected CONSUMER — never one per field, which is how a single\n      // address edit would bury the Inbox under a dozen identical threads.\n      affected.forEach(x => this.ctxRaise({\n        id: 'cm5-' + String(x.c.id).replace(/[^a-z0-9]+/gi, '-').toLowerCase() + '-' + Date.now().toString(36),\n        category: 'questions', sourceStream: 'context', sourceStatus: 'pending_approval', state: 'needs',\n        who: 'context-agt', initials: 'CX', addr: '<context@yourco.aaas.love>',\n        at: at.slice(11, 16), unread: true,\n        subject: x.c.label + ' · ' + x.matched.length + (x.matched.length === 1 ? ' field' : ' fields') + ' stale after a context change',\n        tag: x.c.kind === 'doc' ? 'Toolbox document' : 'Plane',\n        snip: 'Changed: ' + x.matched.join(', ') + ' — re-optimise or accept the current value.',\n        msgs: [{ from: 'context-agt', when: '· ' + at.slice(11, 16) + ' · CM-5 fan-out',\n          body: 'A context change touched ' + x.matched.join(', ') + '. This consumer declares those reads, so its fields are marked stale until you re-optimise or accept them.' }],\n        route: ['context', 'Review in Sources']\n      }));\n      return { consumers: affected.length, fields: affected.reduce((a, x) => a + x.matched.length, 0) };\n    };\n    /* The one call any future context writer needs. Goes through the store's dispatcher when present\n       (so the EventBus history records the event too), direct fan-out when not. */\n    this.ctxEdited = (block, fields) => {\n      const CS = window.ContextStore;\n      const ev = { type: 'context.updated', block: block, fields: fields || [], at: new Date().toISOString() };\n      if (CS && typeof CS.dispatchEvent === 'function') {\n        // D202: was `CS.store.all` (no such method) — every dispatch passed `[]` as the block set.\n        const blocks = (CS.store && typeof CS.store.getAll === 'function') ? CS.store.getAll(CS.INTERNAL) : [];\n        CS.dispatchEvent(ev, blocks);\n      } else {\n        this.ctxFanOut(ev);\n      }\n      return ev;\n    };\n    /* The Inbox's real API is `raiseThread` (it normalises and RETURNS a thread; it stores nothing —\n       \"Inbox projects, it never owns\", INBOX-CONTRACT §1). Two call sites in this shell called a\n       `push()` that has never existed on it, each behind a `typeof … === 'function'` guard, so both\n       silently filed nothing: this one, and `finaliseDoc` — whose comment has claimed \"finalising files\n       a thread\" since it shipped. Both now go through here. `raiseThread` THROWS on a missing required\n       field (id/category/sourceStream/subject/who/at), so the catch reports rather than swallows. */\n    this.ctxRaise = (t) => {\n      const IT = window.InboxThreads;\n      if (!IT || typeof IT.raiseThread !== 'function') { console.warn('[CM-G] InboxThreads.raiseThread unavailable'); return null; }\n      // D191 · gate 2 (authority) on raise: the agent must be in the one cast, and — when it has a declared\n      // write scope — that scope must include the stream the thread claims to own. Undeclared = warn, allow\n      // (the engine's own rule is \"omitted key throws\"; on the inbox path we log instead of dropping work).\n      const AR = window.AgentRegistry, PI = window.ProcessInstantiate || window.PI;\n      const ag = AR && t && (AR.byName(t.who) || AR.byId(t.who));\n      if (AR && !ag) { console.warn('[D191] raiseThread: who \"' + (t && t.who) + '\" is not in AgentRegistry — thread dropped'); return null; }\n      if (ag && PI && PI.AGENT_WRITE && Object.prototype.hasOwnProperty.call(PI.AGENT_WRITE, ag.id)) {\n        const scope = PI.AGENT_WRITE[ag.id];\n        if (Array.isArray(scope) && scope.indexOf(t.sourceStream) < 0) { console.warn('[D191] raiseThread: ' + ag.id + ' may not write ' + t.sourceStream + ' (AGENT_WRITE) — thread dropped'); return null; }\n      } else if (ag && PI && PI.AGENT_WRITE) console.warn('[D191] raiseThread: ' + ag.id + ' has no AGENT_WRITE entry — allowed, undeclared');\n      let thread = null;\n      try { thread = IT.raiseThread(t); }\n      catch (e) { console.warn('[CM-G] raiseThread rejected a thread:', e && e.message); return null; }\n      this.setState(s => ({ ctxThreads: (s.ctxThreads || []).concat([thread]) }));\n      return thread;\n    };\n    this.ctxRegisterTriggers = () => {\n      const CS = window.ContextStore;\n      if (!CS || typeof CS.registerTrigger !== 'function' || this._ctxTrig) return;\n      this._ctxTrig = true;\n      CS.registerTrigger({ id: 'cm5-stale-fanout', event_type: 'context.updated',\n        condition: () => true, action: (ev) => this.ctxFanOut(ev) });\n      // Observable proof that the trigger is armed. The first cut of CM-G registered from inside\n      // `applyContextPacks` and silently never ran; without a flag like this the only symptom was\n      // \"no threads appear\", which looks identical to \"nothing matched\".\n      window.__cm5Armed = { at: new Date().toISOString(), consumersAtArm: this.ctxConsumers().length };\n      // consumersAtArm is usually 0: the index and the template schemas load AFTER mount. Consumers are\n      // recomputed per event in ctxFanOut, so this number says nothing about coverage — only that the\n      // trigger is armed. Probed live: one change to company.name fans out to 5 consumers.\n    };\n\n    /* Gaps for a stream — needs whose status is not shipped, plus planes still partial/lost. Output is\n       a pane brief in prompts/canvas-dashboard-brief.md's own shape, so a gap converts straight into a\n       build item instead of a conversation. An empty result is a real answer, not a failure. */\n    this.streamGaps = (streamId) => {\n      const IX = window.__dcIndex;\n      if (!IX) return null;\n      const needs = (IX.needsByStream[streamId] || []).filter(n => n.status !== 'shipped');\n      const planes = Object.values(IX.planes).filter(p => p.stream === streamId &&\n        ['partial', 'lost'].indexOf(p.verdict) >= 0);\n      const stages = new Set(Object.values(IX.planes).filter(p => p.stream === streamId).map(p => p.stage));\n      const missingStages = [1, 2, 3, 4, 5, 6, 7].filter(n => !stages.has(n));\n      return { needs, planes, missingStages, total: needs.length + planes.length };\n    };\n\n    // ── Ads · launch gate + account overview (the two surfaces the Ads handoff adds) ──\n    this.ADS_PREREQS = [\n      { label: '€25 Fix-Kit tier live + Stripe tested', state: 'ok', note: 'checkout verified 06:12Z' },\n      { label: 'Fix-kit landing page live', state: 'ok', note: '/fix-kit · 200' },\n      { label: '/start?focus=… variants live', state: 'ok', note: '4 of 4 variants' },\n      { label: 'Anti-abuse on /start', state: 'warn', note: 'rate limit set, captcha pending' },\n      { label: 'Conversion tracking green', state: 'fail', note: 'auto · 6 actions REPLACE_ME' },\n      { label: 'Consent Mode v2 audit', state: 'ok', note: 'pass · 07:40Z' },\n      { label: 'Budget ceiling agreed', state: 'ok', note: '€5,000 phase-1' },\n      { label: 'Kill-switch thresholds signed off', state: 'ok', note: '3 tiers armed' },\n      { label: 'Negative keyword baseline', state: 'warn', note: '112 of ~180' },\n      { label: 'Landing-page PSI ≥ 85 mobile', state: 'fail', note: '78 · LCP 3.1s' }\n    ];\n    this.ADS_GOLIVE = [\n      { label: 'Enable at 09:00 CET · brand only', applied: false },\n      { label: 'First-hour spend check · ≤ €6', applied: false },\n      { label: 'First-day spend check · ≤ €70', applied: false },\n      { label: 'Overspend auto-pause armed', applied: false },\n      { label: 'Day-1 review scheduled', applied: true }\n    ];\n    this.ADS_ACCOUNT = [\n      { name: 'Search-Brand', ch: 'SEARCH', status: 'enabled', spend: '€4,80', conv: '2', cpl: '€2,40', ctr: '8,1%' },\n      { name: 'Search-Compliance-LT', ch: 'SEARCH', status: 'enabled', spend: '€24,10', conv: '3', cpl: '€8,03', ctr: '3,4%' },\n      { name: 'Search-Fix-Kit', ch: 'SEARCH', status: 'enabled', spend: '€18,60', conv: '1', cpl: '€18,60', ctr: '2,9%' },\n      { name: 'Display-Remarketing', ch: 'DISPLAY', status: 'enabled', spend: '€0,50', conv: '0', cpl: '—', ctr: '0,4%' },\n      { name: 'Search-ASM', ch: 'SEARCH', status: 'staged', spend: '—', conv: '—', cpl: '—', ctr: '—' },\n      { name: 'Search-Pentest', ch: 'SEARCH', status: 'staged', spend: '—', conv: '—', cpl: '—', ctr: '—' },\n      { name: 'Search-Competitor', ch: 'SEARCH', status: 'staged', spend: '—', conv: '—', cpl: '—', ctr: '—' },\n      { name: 'DemandGen-EU', ch: 'DEMAND_GEN', status: 'staged', spend: '—', conv: '—', cpl: '—', ctr: '—' }\n    ];\n\n    // ── Domains · the sub-domains mounted in one shell (handoff: app/kinds-domains.jsx) ──\n    // Count is derived below (see \"Domains counts: derived, never typed\"); do not restate it here.\n    // Each domain owns an accent, a bucket, a real content surface and an operator overlay.\n    // Dual-mode: overlay rows render only when the OPS toggle is on — same components either way.\n    const AC = 'var(--app-accent)', RD = 'var(--app-fail)', TL = 'var(--app-success)',\n          LV = 'rgb(var(--pastel-lavender, 147 154 255))', GD = 'rgb(var(--pastel-gold, 248 217 116))';\n    // .pics · consumer LoRA photo studio · 7-screen flow (upstream pics-views.jsx, .pa-/.pl-/.pc-/.ps-/.pt-/.psd-/.pr-/.pb- CSS)\n    this.PICS_FLOW = [\n      { id: 'landing', label: 'Welcome', icon: 'grid' },\n      { id: 'connect', label: 'Connect', icon: 'sync' },\n      { id: 'select', label: 'Select', icon: 'display' },\n      { id: 'train', label: 'Train', icon: 'sparkles' },\n      { id: 'studio', label: 'Studio', icon: 'palette' },\n      { id: 'results', label: 'Results', icon: 'apps' },\n      { id: 'billing', label: 'Billing', icon: 'costs' }\n    ];\n    this.PICS = {\n      landTags: ['classic', 'street', 'studio', 'warm', 'golden', 'noir', 'editorial', 'candid'],\n      landSteps: [\n        ['Connect Google Photos', 'We auto-pick 20 photos that work for training.'],\n        ['Confirm + train', '~6 minutes. We notify you when ready.'],\n        ['Pick a vibe + generate', 'Dropdown axes — never a blank prompt box.']\n      ],\n      trust: ['Photos never leave EU once trained', 'First pack ~6 min', 'Free preview · €19 / pack'],\n      perms: [\n        ['ok', 'Read photos taken in the last 24 months'],\n        ['ok', 'Read photo metadata (date, location)'],\n        ['no', 'No write access. No upload. No deletes.']\n      ],\n      why: ['You already have photos there. We skip the upload step.', 'We pull only what we need — no library copies.', \"You revoke access in Google's UI any time.\"],\n      trainStages: [\n        { label: 'Queued', done: true },\n        { label: 'Embeddings', done: true },\n        { label: 'Fine-tuning LoRA', active: true, pct: '64 %' },\n        { label: 'Snapshot · save' },\n        { label: 'Ready' }\n      ],\n      trainCards: [['Steps', '1,420 / 2,200'], ['Loss', '0.084'], ['ETA', '3 min 14 s']],\n      axes: {\n        background: ['white seamless', 'street, NYC', 'golden field', 'warm interior', 'beach, golden hour', 'studio noir'],\n        composition: ['headshot', '3/4 portrait', 'full body', 'candid mid-shot', 'close-up'],\n        camera: ['85mm f/1.4', '35mm f/2', '50mm f/1.8', 'phone wide', 'medium format'],\n        lighting: ['soft natural', 'rim light', 'golden hour', 'high-key studio', 'low-key dramatic']\n      },\n      packs: [\n        { name: 'Headshot', credits: '100 credits', price: '€ 19', per: '€ 0,19 per credit' },\n        { name: 'Team pack', credits: '500 credits', price: '€ 79', per: '€ 0,16 per credit', pop: true },\n        { name: 'Studio pack', credits: '2000 credits', price: '€ 249', per: '€ 0,12 per credit' }\n      ]\n    };\n    // .diy · package marketplace · 4 tabs, 5 format panes, 3 source players (upstream diy-views.jsx, .dy- CSS)\n    this.DIY_TINT = { youtube: '#cc0000', article: '#3a3a3e', podcast: 'oklch(0.5 0.18 290)', reddit: '#ff4500', github: '#24292e', newsletter: '#0080ff' };\n    this.DIY = {\n      packages: [\n        { id: 'pkg-prompt-cache', name: 'Anthropic prompt caching deep-dive', creator: 'benjamin', st: 'youtube',\n          duration: '43:21', mode: 'Implementation', generated: '27.05.2026 17:42', installs: '4,182', nFmt: 5,\n          src: { type: 'youtube', duration: '43:21', title: 'Prompt caching for 90% cost reduction · Anthropic dev talk',\n            channel: 'Anthropic · 28 K subscribers',\n            transcript: [['00:00', \"Welcome — today we're walking through prompt caching, a server-side optimization we just shipped.\"],\n              ['00:24', 'The headline: 90 % token-cost reduction for agents with stable system prompts and variable user inputs.'],\n              ['00:48', \"Let's start with the cost math, then walk through the implementation, then the gotchas.\"],\n              ['01:12', 'A 50K-token system prompt costs €0.15 per turn at Sonnet 4.5 rates. Cached: €0.014.'],\n              ['01:38', \"That math holds at any prompt size — it's about turn-count, not absolute cost.\"]] },\n          formats: [\n            { id: 'ide_package', label: 'IDE Package',\n              files: ['CLAUDE.md', 'README.md', 'prompts/system.md', 'prompts/cache_template.md', 'code/cache_call.py', 'tests/cache_test.py'],\n              activeFile: 'README.md',\n              readme: '# Anthropic Prompt Caching Deep-Dive\\n\\nA working implementation of prompt caching with 90% cost reduction.\\n\\n## Setup\\n\\n1. claude mcp add anthropic-prompt-cache\\n2. Set ANTHROPIC_API_KEY env var\\n3. Run: python code/cache_call.py\\n\\n## Files\\n\\n- prompts/system.md       — the cacheable system prompt\\n- prompts/cache_template.md — the dynamic user prompt template\\n- code/cache_call.py       — the cached completion handler\\n- tests/cache_test.py      — verification suite\\n\\n## Cost savings\\n\\nMeasured: 92.4 % token-cost reduction on conversations >5 turns.\\nSource: Anthropic announcement (2026-05-24, 43-min talk).' },\n            { id: 'briefing', label: 'Briefing',\n              paras: ['**Problem:** Long-context agents re-pay full prompt tokens on every turn. At Claude Sonnet 4.5 rates, a 50K-token system prompt costs ~€0.15 per turn.',\n                '**Approach:** Anthropic ships a server-side prompt cache. Mark the cacheable prefix; first call writes, subsequent calls reuse for ~5 min. Cost on cache hits drops to ~10 % of full call.',\n                '**Key insights:** 1. Cache breakpoints must be explicit — no auto-detection. 2. Cache TTL is 5 min by default; extendable to 1 hour on enterprise. 3. Cache survives across users — verify isolation requirements.',\n                '**TL;DR:** Drop-in optimization for agents with stable system prompts + variable user inputs. 90 % cost reduction at production scale.'] },\n            { id: 'podcast_notes', label: 'Show Notes',\n              notes: [['00:00', 'Intro — what changes when system prompts are cached'],\n                ['04:18', 'Live demo — cache hit on second turn, latency drop 220→48 ms'],\n                ['12:40', 'Cost math — €0.15 → €0.014 per turn at 50K system prompt'],\n                ['24:50', 'Edge case — cache key collision across users (and how to avoid it)'],\n                ['38:12', 'Production checklist — what to verify before rolling out']] },\n            { id: 'social_content', label: 'Social',\n              variants: [\n                { p: 'LinkedIn', chars: '1248/3000', top: '#0a66c2', text: \"Anthropic just shipped prompt caching — and the numbers are wild.\\n\\n92.4 % token-cost reduction on multi-turn agent conversations. We tested it on AaaS Decisions runs over the weekend; the drop in our LLM bill was instant.\\n\\nThree things worth knowing:\\n\\n1. Cache breakpoints are explicit. You mark the cacheable prefix yourself — no magic auto-detection.\\n2. TTL is 5 min by default. For sticky agents, extend to 1 hour.\\n3. Cache survives across users by default. Make sure your prompt structure doesn't leak per-user context above the breakpoint.\" },\n                { p: 'X', chars: '268/280', top: 'var(--app-ink)', text: 'Anthropic prompt caching = 92 % cost cut on multi-turn agents.\\n\\nMark cacheable prefix → cache hits drop calls to ~10 % of full cost.\\nTTL 5 min default, 1 hour on enterprise.\\n\\nIDE package + tests below ↓' },\n                { p: 'Mastodon', chars: '412/500', top: '#6364ff', text: 'Anthropic prompt caching ships — 92 % cost cut on multi-turn agents.\\n\\nMark the cacheable prefix explicitly; cache hits cost ~10 % of a fresh call. TTL 5 min default.\\n\\nWe tested it on AaaS Decisions runs over the weekend, full drop in LLM bill immediately.' }\n              ] },\n            { id: 'mcp_server', label: 'MCP Server',\n              mcp: { tools: ['cache_breakpoint', 'cache_status', 'cache_metrics'],\n                resources: ['cache_keys', 'cache_metrics_24h'],\n                prompts: ['cache_optimization_review', 'cache_breakpoint_suggest'] } }\n          ] },\n        { id: 'pkg-vector-rerank', name: 'Vector reranking · production patterns', creator: 'gauri', st: 'article',\n          mode: 'Briefing', generated: '24.05.2026', installs: '1,820', proOnly: true, nFmt: 0, formats: [],\n          src: { type: 'article', readMin: '12', title: 'Why your reranker matters more than your embedder', domain: 'gauri.dev/blog',\n            lead: \"When your retrieval relevance plateaus at ~0.78, the next gain comes from reranking — not from a better embedder. Here's how to wire one in without breaking your latency budget.\" } },\n        { id: 'pkg-finetune-econ', name: 'Fine-tuning economics in 2026', creator: 'sarah', st: 'podcast',\n          duration: '54:18', mode: 'Briefing', generated: '20.05.2026', installs: '2,840', nFmt: 0, formats: [],\n          src: { type: 'podcast', duration: '54:18', title: 'The fine-tuning calculator · Practical AI #318', channel: 'changelog.com/practicalai' } },\n        { id: 'pkg-mcp-intro', name: 'MCP servers · the missing intro', creator: 'jorian', st: 'youtube',\n          duration: '18:42', mode: 'Implementation', generated: '14.05.2026', installs: '6,820', nFmt: 0, formats: [],\n          src: { type: 'youtube', duration: '18:42', title: 'MCP in 18 minutes', channel: 'AaaS · 12 K subscribers', transcript: [] } },\n        { id: 'pkg-langgraph', name: 'LangGraph 0.2 parallel branches', creator: 'patrick', st: 'github',\n          mode: 'Extract', generated: '12.05.2026', installs: '942', proOnly: true, nFmt: 0, formats: [],\n          src: { type: 'article', readMin: '6', title: 'LangGraph 0.2 release notes', domain: 'github.com/langchain-ai/langgraph', lead: 'Native fan-out for decision-branch patterns.' } },\n        { id: 'pkg-eval-frameworks', name: 'Eval frameworks · 2026 landscape', creator: 'demir', st: 'newsletter',\n          mode: 'Compare', generated: '08.05.2026', installs: '1,142', proOnly: true, nFmt: 0, formats: [],\n          src: { type: 'article', readMin: '8', title: 'Comparing 12 eval frameworks side-by-side', domain: 'demir.substack.com', lead: 'MT-Bench, MMLU, HumanEval, GAIA, and 8 others — when to use which.' } }\n      ],\n      modes: [['implementation'], ['briefing'], ['extract', 'PRO'], ['study', 'PRO'], ['compare', 'PRO']],\n      fmtOpts: [['ide_package', 'IDE Package'], ['checklist', 'Checklist'], ['slide_deck', 'Slide Deck'],\n        ['email_course', 'Email Course'], ['notion', 'Notion Page'], ['obsidian', 'Obsidian Vault'],\n        ['rag_chunks', 'RAG Chunks'], ['mcp_server', 'MCP Server', 'TEAM'], ['social_content', 'Social Content'],\n        ['podcast_notes', 'Podcast Notes']],\n      runs: [\n        { id: 'rn-018', stage: 'generate', source: 'youtube.com/watch?v=xyz123 · 43m', cost: '0,028' },\n        { id: 'rn-017', stage: 'transcribe', source: 'changelog.com/practicalai/318', cost: '0,012' },\n        { id: 'rn-016', stage: 'render', source: 'gauri.dev/blog/reranking-patterns', cost: '0,021' }\n      ]\n    };\n    // Content · 15 surfaces, actually shown (upstream kinds-content-presentation.jsx + .dg-/.sb-/.bb- CSS)\n    this.CNT = {\n      cards: [\n        ['267 toolbox docs · 21 categories', 'Authoring via PR · OSS-style audit', '267', '24 deprecated', 'ok'],\n        ['142 designs', 'Figma + exported PNGs · brand-aligned', '142', '18 last 30d', 'ok'],\n        ['67 email templates', 'MJML + React-Email; live preview', '67', '7 last 30d', 'ok'],\n        ['12 landing pages', 'Rendered in browser frames', '12', '4 in draft', 'ok'],\n        ['38 blog drafts', 'LLM-generated · awaiting review', '38', '12 ready', 'warn'],\n        ['84 documentation pages', 'developer + customer docs', '84', 'API · CLI · UX', 'ok'],\n        ['212 social posts', 'LinkedIn · X · Mastodon', '212', '38 scheduled', 'ok'],\n        ['38 banners', '4 ratios · 1080 / 1200 / 1600 / 1920', '38', '6 ratios live', 'ok'],\n        ['24 posters', 'A1 · A2 · A3 · print + screen', '24', '2 A1 · 8 A2', 'ok'],\n        ['18 presentations', 'Investor · sales · all-hands', '18', '4 investor', 'ok'],\n        ['12 videos', 'Demos · explainers · social', '12', '90s avg', 'ok'],\n        ['Brand book · v3.2', 'type · color · voice · logo', 'v3.2', 'updated 24.05', 'ok']\n      ],\n      designs: [\n        ['Decisions launch hero', 'marketing', 'hero', '16:9', '26.05.2026', 'cy'],\n        ['Zooey OG image', 'social', 'OG', '1200×630', '24.05.2026', 'cy'],\n        ['Finances dashboard mock', 'product', 'screen', '16:10', '22.05.2026', 'gd'],\n        ['Brand book — type spread', 'brand', 'spread', '3:2', '20.05.2026', 'red'],\n        ['AP-Recon explainer · panel', 'marketing', 'explainer', '1:1', '18.05.2026', 'lav'],\n        ['Pricing page · plan grid', 'product', 'screen', '16:9', '14.05.2026', 'tl'],\n        ['Quest CV wizard · step 4', 'product', 'screen', '9:16', '12.05.2026', 'cy'],\n        ['Podcast cover art template', 'social', 'cover', '1:1', '10.05.2026', 'lav'],\n        ['Footer redesign — 3 variants', 'marketing', 'fragment', '16:3', '08.05.2026', 'gd'],\n        ['Builder workshop · empty state', 'product', 'screen', '16:10', '04.05.2026', 'tl'],\n        ['Cost-by-tenant heatmap mock', 'product', 'chart', '4:3', '02.05.2026', 'cy'],\n        ['.diy Pro paywall', 'marketing', 'screen', '16:9', '28.04.2026', 'gd']\n      ],\n      posts: [\n        { ch: 'LinkedIn', top: '#0a66c2', author: 'Jorian Lundqvist', handle: 'jorian-lundqvist', av: 'JL',\n          when: '4h ago', live: true,\n          body: 'Wir haben heute Decisions/Zooey für alle AaaS-Pläne ausgerollt. 30-Tage-Retention für Pro+; 7-Tage für Free.\\n\\nKein \"Black-Box-Agent\". Jede Entscheidung, jede verworfene Alternative, jede Selbstkritik — alles sichtbar.\\n\\nKurzes Video unten zeigt einen AP-Reconciliation-Run · 12 Belege · 3 Decision-Branches · 3,8 s.',\n          media: 'Video · 1:24', stats: '142 likes · 18 comments · 24 reposts' },\n        { ch: 'X', top: 'var(--app-ink)', author: 'AaaS', handle: '@AaaS_Love', av: 'A', when: '6h ago', live: true,\n          body: 'Decisions ships today. See what your agent considered. Not just what it did.\\n\\n→ aaas.select/decisions',\n          media: 'OG image · 1200×630', stats: '84 likes · 12 comments · 38 reposts' },\n        { ch: 'Mastodon', top: '#6364ff', author: 'AaaS', handle: 'aaas@social', av: 'A', when: '1d ago', live: true,\n          body: 'EU-hosted AI agents · GDPR-native · German-first.\\n\\nFree tier includes 7-day decision retention. Pro+ extends to 30 days.\\n\\n#AIAct #AaaS',\n          stats: '22 likes · 4 comments · 11 reposts' },\n        { ch: 'LinkedIn', top: '#0a66c2', author: 'Jorian Lundqvist', handle: 'jorian-lundqvist', av: 'JL',\n          when: 'scheduled · 01.06 09:00', live: false,\n          body: \"June podcast lineup · 30 episodes pre-prepped.\\n\\nThis was the part that scared me about going daily — the prep load. Solved by AaaS Podcast-Brief agent.\\n\\nMore in tomorrow's 5-min Folge 185.\",\n          media: 'Carousel · 5 frames' }\n      ]\n    };\n    this.CNT_SUB = {\n      'Overview': ['CONTENT · OVERVIEW', 'CONTENT', 'Every content surface — actually shown, not just listed. Email previews · landing pages in browser frames · social posts in channel cards · posters at print size.',\n        [['Toolbox docs', '267', '+3'], ['Email tpl', '67', '+2'], ['Designs', '142', '+18'], ['Landing pages', '12', '+1'], ['Blog drafts', '38', '+5'], ['Docs', '84', '+4']],\n        'Author new asset', [['Asset audit', 'check'], ['Brand sync', 'sync']]],\n      'Toolbox · 267': ['TOOLBOX · 267 DOCS', 'TOOLBOX', 'Authoring via PR, OSS-style. 21 categories, 24 deprecated pages flagged for the next audit.',\n        [['Docs', '267', '+3'], ['Categories', '21'], ['Deprecated', '24', '+4'], ['Contributors', '8']], 'New doc', [['Audit deprecated', 'check']]],\n      'Brand book': ['BRAND BOOK · V3.2', 'BRAND BOOK', 'Type, colour, voice and logo in one versioned document. Updated 24.05.2026.',\n        [['Version', 'v3.2'], ['Sections', '6'], ['Updated', '24.05'], ['Open questions', '2']], 'Propose change', [['Version history', 'clock']]],\n      'Designs': ['DESIGNS · 142', 'DESIGNS', 'Figma + PNG exports. Brand-aligned visuals, grouped by surface (marketing · product · social).',\n        [['Designs', '142', '+18'], ['Surfaces', '5'], ['Last 30d', '18', '+18'], ['In Figma', '142']], 'New design', [['Open Figma', 'external'], ['Export pack', 'download']]],\n      'Email templates · 67': ['EMAIL TEMPLATES · 67', 'EMAIL TEMPLATES', 'MJML + React-Email. Live email-client preview · de-DE + en-US variants · send-test inline.',\n        [['Templates', '67', '+2'], ['Open rate', '38 %', '+2 %'], ['Locales', '2'], ['Last 30d', '7', '+7']], 'Send test', [['Locale switch', 'globe'], ['Spam check', 'shield']]],\n      'Landing pages': ['LANDING PAGES · 12', 'MARKETING PAGES', 'Landing · pricing · about · legal pages in their actual browser frame.',\n        [['Pages', '12', '+1'], ['In draft', '4'], ['Lighthouse', '98'], ['Conversion', '3.4 %', '+0.2 %']], 'New page', [['Lighthouse run', 'zap'], ['Publish queue', 'clock']]],\n      'Blog drafts': ['BLOG DRAFTS · 38', 'BLOG DRAFTS', 'LLM-generated essays awaiting human review. Click any draft to read the full article inline.',\n        [['Drafts', '38', '+5'], ['Ready', '12'], ['Awaiting review', '26'], ['Published 30d', '9', '+9']], 'Approve & publish', [['Reject', 'x'], ['Regenerate', 'sync']]],\n      'Documentation': ['DOCUMENTATION · 84 PAGES', 'DOCUMENTATION', 'Developer + customer-facing documentation. API · CLI · UX guides.',\n        [['Pages', '84', '+4'], ['API', '32'], ['CLI', '18'], ['UX guides', '34']], 'New doc page', [['Link check', 'check']]],\n      'Social posts': ['SOCIAL POSTS · 212', 'SOCIAL POSTS', 'LinkedIn · X · Mastodon — every post rendered in its native channel card.',\n        [['Posts 7d', '38', '+8'], ['Scheduled', '12', '+4'], ['Engagement', '4.2 %', '+0.4 %'], ['Channels', '3']],\n        'New post · multi-channel', [['Schedule queue', 'clock'], ['Postiz settings', 'settings']]],\n      'Banners': ['BANNERS · 38', 'BANNERS', 'Web banners + social ratios. Same campaign rendered across every required size.',\n        [['Banners', '38', '+6'], ['Ratios', '6'], ['Campaigns', '8', '+1'], ['Brand pass', '100 %']], 'New campaign', [['Export pack', 'download'], ['Brand check', 'check']]],\n      'Posters': ['POSTERS · 24', 'POSTERS', 'A0 / A1 / A2 / A3 print + screen. Conference · trade-show · office posters.',\n        [['Posters', '24', '+2'], ['A1', '2'], ['A2', '8'], ['Print-ready', '20']], 'New poster', [['Print spec', 'file']]],\n      'Presentations': ['PRESENTATIONS · 18 DECKS', 'PRESENTATIONS', 'Investor · sales · all-hands · workshop decks. 12-slide thumbnails per deck.',\n        [['Decks', '18', '+1'], ['Investor', '4'], ['Sales', '6'], ['Slides', '216']], 'New deck', [['Export PPTX', 'download']]],\n      'Videos': ['VIDEOS · 12', 'VIDEOS', 'Demos · explainers · social shorts. Click thumbnail to play inline.',\n        [['Videos', '12', '+1'], ['Avg length', '90s'], ['Views 30d', '18.4k', '+2.1k'], ['Captions', '12']], 'Upload video', [['Caption check', 'check']]],\n      'Snippets': ['SNIPPETS · REUSABLE COPY', 'SNIPPETS', 'Reusable copy blocks — CTAs, boilerplate, legal lines. One source, every surface.',\n        [['Snippets', '48', '+6'], ['In use', '44'], ['Orphaned', '4'], ['Locales', '2']], 'New snippet', [['Find orphans', 'search']]],\n      'Public KB articles': ['PUBLIC KB · 24 ARTICLES', 'PUBLIC KB ARTICLES', 'Customer-facing knowledge base. Published from Documentation, tracked for search hits.',\n        [['Articles', '24', '0'], ['Search hits 30d', '4.2k', '+380'], ['Zero-result', '18'], ['Stale', '3']], 'New article', [['Search report', 'chart']]]\n    };\n    // Five domain surfaces, native (upstream scaffold-views.jsx · .dp-/.bw-/.cw-/.eo-/.vb- CSS)\n    this.SCAF = {\n      picks: [\n        ['Anthropic ships Sonnet 4.5 · 18% faster tool calls', 'Anthropic blog', '4', 'EN', '4,2k', '184', 'Drop-in upgrade for any agent stack using Sonnet 4.x.'],\n        ['OpenAI o5 preview · 30% cheaper inference', 'OpenAI dev day', '6', 'EN', '2,8k', '142', 'Pricing pressure on Anthropic premium tiers.'],\n        ['EU AI Act · Q3 enforcement details published', 'europa.eu', '8', 'DE', '1,4k', '98', 'Decision-Logging wird ab Q3 für High-Risk verpflichtend.'],\n        ['LangGraph 0.2 ships parallel branches', 'LangChain blog', '5', 'EN', '892', '64', 'Native fan-out for our Decision-Branch pattern.'],\n        ['Cohere embedding-v4 · multilingual default', 'Cohere', '3', 'EN', '412', '28', 'Could replace text-embedding-3 in EU surfaces.'],\n        ['GitHub Copilot Workspace · replay-from-here', 'GitHub Next', '7', 'EN', '1,1k', '88', 'Mirror of Zooey replay UX — validates the pattern.']\n      ],\n      shop: {\n        status: 'draft v3', activeFile: 'agent.system.md',\n        files: [['agent.system.md', true, true], ['tools.json', false, false], ['README.md', false, false], ['tests/case1.json', false, false], ['tests/case2.json', false, false]],\n        code: 'Du bist AP-Reconciliation-Agent.\\n\\nTask:\\n  Match incoming invoices automatically. Source = Stripe Invoice API.\\n  Strategy = fuzzy_match (default) | exact_match | embedding.\\n\\nVorgaben:\\n  - Antworte auf Deutsch.\\n  - Logge jeden Decision-Branch in window.__decisionsLog.\\n  - Bei < 0,8 Confidence → \"needs human review\" + ablegen.\\n\\nToolset:\\n  - stripe.invoices.list\\n  - decisions.branch.log\\n  - email.send',\n        tests: [['fuzzy_match · 12 invoices · 0,87 confidence', 'pass', '1,2 s'], ['exact_match · 0 hits → fallback', 'pass', '0,8 s'],\n          ['embedding · 8× cost vs fuzzy', 'fail', '4,4 s'], ['edge: empty Stripe response', 'pass', '0,2 s']],\n        history: [['8a4f12c', 'add embedding fallback (rejected — cost)', '17:32'], ['b1c298e', 'fuzzy threshold 0.8 → 0.85', '15:18'], ['a01142e', 'add German system prompt', '12:48']]\n      },\n      wizard: [\n        { label: 'Persönlich', title: 'Persönliche Daten', tag: 'Was auf jedem CV oben steht.',\n          fields: [['Name', 'Jane Doe', 1], ['Rolle gesucht', 'Senior Frontend Engineer', 1], ['Stadt', 'Berlin', 1]] },\n        { label: 'Erfahrung', title: 'Berufserfahrung', tag: 'Die letzten 3 Stationen genügen.',\n          fields: [['Letzte Station', '2023–2026 · Lead Frontend @ Acme · React + Next.js', 3], ['Davor', '2020–2023 · Senior Engineer @ Beta Co · Vue.js', 3]] },\n        { label: 'Skills', title: 'Hauptskills', tag: '5–8 Stichworte. Mehr nicht.',\n          fields: [['Skills', 'React · TypeScript · Next.js · Design Systems · A/B Testing · Web Performance · Accessibility', 2]] },\n        { label: 'Highlights', title: 'Highlights · 3 Bullets', tag: 'Konkret. Mit Zahlen. \"+18% Conversion durch …\"',\n          fields: [['#1', 'Led redesign of acme.com checkout, +18% conversion, € 4,2M ARR uplift.', 3],\n            ['#2', 'Built Design System used by 6 teams; reduced shipping time 40%.', 3],\n            ['#3', 'Speaker · React Berlin 2025 · \"Edge-rendered React at scale\".', 3]] },\n        { label: 'Anschreiben', title: 'Anschreiben-Generator', tag: 'AaaS-Quest erstellt Variante pro Job-Posting.',\n          previewLabel: 'Auszug · Variante 1',\n          preview: 'Liebe Hiring Manager,\\n\\nIhre Stellenausschreibung für Senior Frontend liest sich wie eine Brücke zwischen unseren Welten — A/B-Testing, EU-Hosting, Design-System-Ownership. Genau die drei Hebel, die ich bei Acme zuletzt bewegt habe.\\n\\n…\\n\\nViele Grüße,\\nJane' },\n        { label: 'Export', title: 'CV-Export · PDF + Markdown', tag: 'Mehrsprachig · ATS-konform · Quest-Watermark optional.',\n          fields: [['Sprache', 'de-DE / en-US (auto)', 1], ['Format', 'PDF + Markdown + JSON', 1]] }\n      ],\n      entity: {\n        id: 'org-intrinsic', kind: 'org', label: 'Intrinsic GmbH', tagline: '14 seats · Business tier · Berlin · DE',\n        badges: ['Business tier', 'EU hosting', 'GDPR-verified', 'SSO enabled'],\n        actions: [['Invite member', true], ['Manage seats', false], ['Billing portal', false]],\n        metrics: [['Seats', '14 / 20', '+2'], ['MRR', '€ 1.392', '+€ 198'], ['Runs 30d', '4.182', '+8 %'], ['Agents on', '6 / 12', null]],\n        activity: [['17:32', 'jorian invited finance@partner.de as Finance role', 'ok'],\n          ['15:18', 'Seat 14 of 20 used — 30% headroom remaining', 'ok'],\n          ['12:48', 'Decisions retention upgraded · 7d → 30d (Business)', 'ok'],\n          ['yesterday', 'auto-renewal · € 1.392 charged to Stripe', 'ok'],\n          ['24.05', 'patrick@intrinsic exceeded run quota · throttled', 'warn']],\n        members: [['Jorian Lundqvist', 'JL', 'Owner', 'jor@gmail.com', 'active'],\n          ['Patrick', 'PT', 'Operator', 'patrick@intrinsic.com.de', 'active'],\n          ['finance@partner', 'FP', 'Finance', 'finance@partner.de', 'pending'],\n          ['Reyes', 'RY', 'Builder', 'reyes@intrinsic.com.de', 'active']],\n        related: [['4 active agents', 'agent'], ['142 toolbox templates', 'toolbox'], ['12 KB articles', 'kb'], ['3 .diy Pro subscriptions', 'sub']]\n      },\n      projects: [\n        ['AP-Reconciliation flow', '14', '17:32', ['JL']],\n        ['Daily podcast pipeline', '24', 'yesterday', ['JL', 'PT']],\n        ['Brand-30 onboarding', '8', '24.05', ['JL', 'RY', 'BV']],\n        ['Lead enrichment', '12', '22.05', ['PT']],\n        ['Decisions training set', '18', '20.05', ['JL']],\n        ['Headshot pipeline · v2', '22', '14.05', ['RY']]\n      ]\n    };\n    // Twelve bespoke lenses (upstream content-presentation-views.jsx + scaffold-views.jsx ·\n    // .ep-/.pp-/.md-/.bb-/.pb-/.db-/.vb-/.ar-/.sc-/.rl-/.fe-/.im- CSS)\n    this.LENS = {\n      email: {\n        templates: [['welcome', 'Onboarding', '64 %'], ['invoice-issued', 'Transactional', '88 %'],\n          ['subscription-renew', 'Transactional', '72 %'], ['magic-link', 'Auth', '92 %'],\n          ['agent-finished', 'Product', '58 %']],\n        sample: { from: 'AaaS · jor@gmail.com', to: 'jane@cvbuilder.io',\n          subject: 'Deine Rechnung 2026-0042 · € 290,00', preheader: 'Zahlbar bis 25.06.2026 · IBAN auf der PDF',\n          heroLabel: 'Rechnung 2026-0042', heroAmount: '€ 290,00',\n          body: ['Hi Jane,', 'attached is the May invoice for your SQUR Pro subscription. Due by 25.06.2026; all bank details are on the attached PDF.', 'If anything is unclear — just reply to this email.', 'Best from Berlin,', 'Jorian'],\n          cta: 'Open invoice PDF', footer: 'Intrinsic GmbH · Mustergasse 1 · 10115 Berlin · DE 123 456 789' }\n      },\n      pages: {\n        list: [['Landing', '/', '26.05.2026', 'live'], ['Pricing', '/pricing', '22.05.2026', 'live'],\n          ['About', '/about', '12.05.2026', 'live'], ['Agents', '/agents', '18.05.2026', 'live'],\n          ['Launch', '/launch-2026-06', '27.05.2026', 'draft']],\n        sample: { url: 'agents-as-a-service.com/', heroEyebrow: 'CONTEXT ENGINE · ONLINE',\n          heroTitle: ['AUTONOMOUS', 'AI WORKFORCE'],\n          heroSub: 'Curated AI agents, automated end-to-end. Builders deploy in minutes; operators stay in the loop with Decisions.',\n          ctas: [['Free context package · 2 min', true], ['Browse agents', false]],\n          stats: [['5.400+', 'structured skills'], ['268', 'toolbox templates'], ['98,7 %', 'compliance score']],\n          features: ['Autonomous agents', 'Decision transparency', 'multiple sub-domains', 'GDPR-native', 'Free + Pro + Team', 'EU hosting'] }\n      },\n      docs: {\n        tree: [['Getting started', ['Install the CLI', 'Your first agent', 'Connect a service']],\n          ['API reference', ['Authentication', 'Runs', 'Decisions', 'Webhooks']],\n          ['CLI reference', ['aaas run', 'aaas deploy', 'aaas vault']],\n          ['UX guides', ['Writing prompts', 'Reading a decision trace']]],\n        active: 'Reading a decision trace',\n        md: [['h1', 'Reading a decision trace'],\n          ['p', 'Every agent run writes a decision trace. The trace is not a log — it records what the agent chose AND what it rejected, with the confidence attached to each branch.'],\n          ['h2', 'Anatomy of a step'],\n          ['p', 'A step has a kind (llm_call, tool_call, decision_branch), a duration, a cost, and — for decision branches — an alternates array.'],\n          ['code', 'aaas runs get run-4827 --format=jsonl \\\\\\n  | jq \\'select(.kind==\"decision_branch\")\\''],\n          ['h2', 'Confidence thresholds'],\n          ['p', 'Below 0.80 the agent stops and asks. Between 0.80 and 0.92 it proceeds but flags the step for review. Above 0.92 it proceeds silently.'],\n          ['ul', ['Green — proceeded, no flag', 'Amber — proceeded, flagged for review', 'Red — stopped, needs a human']],\n          ['h2', 'Replaying from a step'],\n          ['p', 'Replay-from-step re-runs the trace from any decision branch with a different choice, so you can see the counterfactual without re-running the whole job.']]\n      },\n      banners: [\n        { label: 'Decisions launch · 28.05.2026', eyebrow: 'NEW · DECISIONS', title: 'See what your AI considered.',\n          sub: 'Not just what it did.', cta: 'Try it free', accent: 'cy',\n          formats: [['16:9', 'Web hero', '1920×1080'], ['1200×630', 'OG · LinkedIn', '1200×630'],\n            ['1:1', 'IG · square', '1080×1080'], ['9:16', 'Story · vertical', '1080×1920'],\n            ['16:3', 'Footer leaderboard', '1920×360'], ['3:1', 'Forum header', '1500×500']] },\n        { label: 'Podcast Folge 184 · Sonnet 4.5', eyebrow: '.podcast · 28.05', title: 'Folge 184',\n          sub: 'Anthropic Sonnet 4.5 + Genkit 1.4', cta: 'Hör rein · 5 Min', accent: 'lav',\n          formats: [['16:9', 'Web hero', '1920×1080'], ['1:1', 'Cover', '1080×1080'], ['9:16', 'Story', '1080×1920']] }\n      ],\n      posters: [\n        ['A1', 'portrait', 'CONTEXT ENGINE', ['AUTONOMOUS', 'AI WORKFORCE.'], 'aaas — Curated AI agents, automated end-to-end.', 'cy', 'Conference 06.2026'],\n        ['A2', 'portrait', '.podcast', ['TÄGLICH 5 MIN.', 'AI BRIEF.'], 'podcast.aaas.today', 'cy', 'TUM info-day'],\n        ['A3', 'portrait', 'DECISIONS', ['SEE WHAT YOUR', 'AI CONSIDERED.'], 'Not just what it did. · aaas.select/decisions', 'red', 'Office wall'],\n        ['A2', 'landscape', '.diy', ['BUILD YOUR OWN', 'AGENT IN 30 MIN.'], 'Free · Pro $12 · Team $29', 'gd', 'Hackathon'],\n        ['A3', 'portrait', '.academy', ['DAILY', 'CAPABILITY', 'UPGRADE.'], 'aaas.academy', 'gd', 'Berlin office'],\n        ['A1', 'portrait', '.boutique', ['BRAND-30.', '€ 3.000.'], 'From brand DNA to live tenant in 30 days.', 'red', 'Sales kit']\n      ],\n      decks: [\n        { title: 'Investor update · Q2 2026', kind: 'investor', slides: '18', updated: '26.05.2026',\n          thumbs: [['Cover', 'cy'], ['TL;DR', 'cy'], ['MRR', 'gd'], ['Churn', 'red'], ['Roadmap', 'cy'], ['Team', 'lav'], ['Asks', 'gd'], ['Q&A', 'cy']] },\n        { title: 'Sales pitch · enterprise · v3', kind: 'sales', slides: '14', updated: '20.05.2026',\n          thumbs: [['Cover', 'cy'], ['Problem', 'red'], ['Solution', 'cy'], ['Demo', 'gd'], ['Pricing', 'cy'], ['Case', 'lav'], ['Next', 'cy']] },\n        { title: 'All-hands · May 2026', kind: 'all-hands', slides: '12', updated: '14.05.2026',\n          thumbs: [['Hello', 'cy'], ['Numbers', 'gd'], ['Shipped', 'cy'], ['Next', 'tl'], ['Hires', 'lav'], ['Thanks', 'cy']] }\n      ],\n      videos: [\n        ['AP-Reconciliation · 90s demo', '1:24', 'demo', 'cy', '2,1k'],\n        ['Decisions · explainer', '2:48', 'explainer', 'cy', '1,4k'],\n        ['Podcast trailer · 30s', '0:30', 'trailer', 'lav', '4,2k'],\n        ['Brand-30 boutique pitch', '1:18', 'pitch', 'red', '684'],\n        ['Daily AaaS · workflow short', '0:48', 'short', 'gd', '12,4k'],\n        ['Headshot-Pack · before-after', '0:32', 'short', 'lav', '8,2k']\n      ],\n      drafts: {\n        list: [['Why we killed the chat window', 'ready', '1,840'], ['The cost of an idle agent', 'ready', '1,240'],\n          ['Prompt caching, measured', 'review', '2,110'], ['EU AI Act · what changes in Q3', 'legal', '1,680'],\n          ['Reranking beats embedding', 'draft', '940'], ['Context engineering in practice', 'draft', '1,420']],\n        article: { title: 'Why we killed the chat window', author: 'blog-agent · reviewed by jorian',\n          meta: 'draft · 1,840 words · 7 min read · generated 26.05.2026',\n          body: [['p', 'The chat window is a great interface for a conversation and a terrible one for work. We shipped one anyway, watched people use it for six months, and then took it out.'],\n            ['h2', 'What we saw'],\n            ['p', 'Builders did not want to converse with an agent. They wanted to see a queue, approve a thing, and get on with their day. The chat window made every task feel like a negotiation.'],\n            ['quote', 'The best agent interface is the one where you never have to explain yourself twice.'],\n            ['p', 'Our own operators had stopped using it internally two months before we admitted the problem — they went straight to the runs table instead. That was the signal.'],\n            ['h2', 'What replaced it'],\n            ['p', 'A decision trace and an inbox. The trace shows what the agent chose and what it rejected. The inbox holds anything that needs a human. Neither one asks you to type a sentence.'],\n            ['p', 'Conversation still exists where it earns its place: the ask surface, where a question genuinely is the unit of work. Everywhere else it was ceremony.']],\n          outline: ['What we saw', 'What replaced it'],\n          checks: [['Sources cited', 'ok'], ['Brand voice', 'ok'], ['Banned words', 'ok'], ['Claim check', 'warn']] }\n      },\n      support: {\n        snippets: ['/refund', '/key-rotation', '/cancel-confirm', '/escalate', '/gdpr-export'],\n        threads: [\n          { customer: 'ACME GmbH', email: 'info@acme.de', tier: 'Business', ago: '12 min', opened: 'today · 17:18',\n            prio: 'p2', tag: 'billing', preview: 'Stripe key rotation — does the bridge cookie need re-issuing?',\n            messages: [['them', 'Ana · ACME', 'AA', '17:18', 'Hi! We rotated our Stripe live key this morning. Does the admin-bridge cookie need to be re-issued, or does it pick the new key up automatically?'],\n              ['us', 'Jorian · AaaS', 'JL', '17:24', 'Hi Ana — the bridge cookie is bound to the uid, not the Stripe key, so it survives rotation. Your next billing-svc call will use the new key automatically (~60s cache TTL).'],\n              ['them', 'Ana · ACME', 'AA', '17:28', 'Perfect. Will keep an eye on the next invoice run.']] },\n          { customer: 'KoruFM Ltd', email: 'reyes@korufm.com', tier: 'Pro', ago: '2 h', opened: 'today · 15:12',\n            prio: 'p3', tag: 'product', preview: 'Replay-from-step is greyed out — paid tier required?',\n            messages: [['them', 'Reyes', 'RY', '15:12', 'I see \"Ab hier wiederholen\" disabled in Decisions — is that a Phase-2 feature or a Pro+ gating?'],\n              ['us', 'Patrick · AaaS', 'PT', '15:48', \"Phase 2 — coming Q3. We'll ping you in the changelog when it ships. The tier gating in the brief is \\\"Pro+ for replay\\\" but final pricing isn't locked.\"]] },\n          { customer: 'Codeligence', email: 'demo@codeligence.de', tier: 'Business', ago: 'yesterday', opened: 'yesterday · 11:02',\n            prio: 'p4', tag: 'feedback', preview: 'Idea: per-agent kill-switch with reason logging.',\n            messages: [['them', 'Demo · Codeligence', 'DC', 'yesterday 11:02', 'Would love a per-agent kill-switch — when an agent goes sideways we want to stop just that one, not the whole rail.']] }\n        ]\n      },\n      replays: [\n        ['rp-001', 'run-4827', 's04 · decision_branch', 'fuzzy → embedding', 'ok', 'completed', '+€ 0,028'],\n        ['rp-002', 'run-4823', 's08 · decision_branch', 'canvas → email', 'ok', 'completed', '−€ 0,004'],\n        ['rp-003', 'run-4825', 's02 · llm_call', 'gpt-4 → claude-sonnet', 'warn', 'human review', '+€ 0,184'],\n        ['rp-004', 'run-4824', 's03 · tool_call', 'longer-timeout', 'ok', 'completed', '+€ 0,002']\n      ],\n      flow: {\n        variants: [['Baseline · 6-step', '64'], ['A/B · 4-step compressed', '71']],\n        steps: [['Erzählen Sie uns kurz von sich', 'context-form', 'Rolle · Branche · Ziel — 90 Sekunden.', ['role', 'goal', 'industry']],\n          ['Wählen Sie Ihren ersten Agent', 'agent-picker', 'Empfehlungen basierend auf Ihrem Kontext.', ['suggestions[5]', 'custom']],\n          ['Lassen Sie den Agent laufen', 'live-task', 'Erste konkrete Aufgabe.', ['task-prompt', 'run-button']],\n          ['Verbinden Sie die nötigen Zugänge', 'vault-form', 'API-Keys + Credentials.', ['dynamic-from-agent']],\n          ['Willkommen im AaaS-Workspace', 'confirmation', 'Setup abgeschlossen.', ['confetti', 'redirect-cta']],\n          ['Welche AaaS-Dienste möchten Sie aktivieren?', 'services-wizard', 'Sie können das jederzeit ändern.', ['services-grid', 'bulk-recommended']]]\n      },\n      imp: {\n        policy: [['Max duration', '60 min'], ['Auto-redact', 'on'], ['Writes', 'allowed with double-confirm']],\n        reasons: ['Customer asked for live walkthrough', 'Reproducing reported bug', 'Investigating refund request', 'GDPR data-export verification', 'Other (typed)'],\n        recent: [['u-acme · info@acme.de', 'jorian', '14 min', 'Reproducing bug', 'yesterday 11:02'],\n          ['u-reyes · reyes@korufm.com', 'patrick', '8 min', 'Customer walkthrough', '22.05.2026'],\n          ['u-demo · demo@codeligence.de', 'jorian', '4 min', 'GDPR export verification', '18.05.2026']]\n      }\n    };\n    this.DOM_RULES = [\n      ['Server-rendered embeds, NOT iframes.', 'Preserves \\u2318K, auth and page transitions.'],\n      ['One component library.', '@supraforge/ui · canvas sub-export.'],\n      ['Two render contexts.', 'mode=\"operator\" | \"user\" on every surface.'],\n      ['Per-domain accent only.', 'Header underline, active tab, one CTA. Nothing else.'],\n      ['Unified search.', 'Typesense over Algolia — single index, domain facet.'],\n      ['Single NotificationCenter.', 'Per-domain unread badges in the rail.']\n    ];\n    this.DOM_BUCKETS = [\n      { id: 'CONTENT', label: 'Content', tagline: 'Consumption surfaces · read · watch · listen' },\n      { id: 'MARKET', label: 'Market', tagline: 'Marketplaces · creators · commerce' },\n      { id: 'COMMUNITY', label: 'Community', tagline: 'Relational surfaces · interact · profile' }\n    ];\n    this.DOMAINS = {\n      '.blog': { tld: 'aaas.blog', tagline: 'Long-form essays · weekly', accent: AC, bucket: 'CONTENT', state: 'live · in canvas',\n        desc: 'Separate repo · consumed via JSON API · cached 60 s.', view: 'article',\n        kpis: [['Articles', '142', '+3'], ['Views 7d', '12,4k', '+18 %'], ['AI citations', '184', '+38'], ['Avg read', '4,2 min', '+8 s']],\n        ops: [['Views 7d', '4.820'], ['Sources', 'organic 62% · LinkedIn 18% · X 14%'], ['Top citation', 'Perplexity · \"AI agent platforms 2026\"'], ['Co-author', 'blog-writer-agt · v1.3'], ['Search rank', 'pos 2 · \"autonomous finance Germany\"']],\n        article: { title: 'Autonomous finance · what changes when the agent owns the ledger',\n          author: 'Jorian Lundqvist', avatar: 'JL', published: '24.05.2026', read: '8 min',\n          tags: ['autonomy', 'finance', 'de-DE'],\n          lead: 'For 40 years, bookkeeping was a pull job: humans pulled invoices out of email, app, bank. In 2026 that flips. Agents pull, push, reconcile — humans only confirm.',\n          sections: [\n            { h: 'What actually changes', b: 'Three shifts: (1) invoices are actively requested, not waited for. (2) Reconciliation runs continuously, not monthly. (3) Your accountant receives a finished P&L, not a data dump.' },\n            { h: 'Why now', b: 'GPT-4 could already read invoices in 2023. What 2026 adds: reliable tool calls plus decision tracing. You can trust the agent because you see what it considered.' },\n            { h: 'The plan for SMEs', b: 'Run in parallel for six months. Observe first, then auto-confirm one entry per week. After 90 days, full autonomy if score > 0.9.' },\n            { h: 'What stays human', b: 'Edge cases. Negotiations. Empathy with late payers. The accountant becomes a strategist, not a data-entry clerk.' }\n          ],\n          related: [{ t: 'EU AI Act · 8 changes for SMEs', m: '6 min' }, { t: 'Zooey · see every agent step', m: '5 min' }, { t: 'Agents or tools? The line in 2026', m: '7 min' }] } },\n\n      '.podcast': { tld: 'podcast.aaas.today', tagline: 'Daily AI brief · 5-min episodes', accent: AC, bucket: 'CONTENT', state: 'live · in canvas',\n        desc: 'Daily 5-min AI brief. Trigger lives in admin /canvas/podcast/trigger.', view: 'podcast',\n        kpis: [['Episodes', '184', '+1'], ['Listens 7d', '6.842', '+12 %'], ['Completion', '64%', '+2%'], ['Avg minutes', '4.2 min', '0']],\n        ops: [['Listen time avg', '3:48'], ['Completion', '64%'], ['Top cited moment', '00:42 · Sonnet 4.5 announcement'], ['Trigger status', 'today · 06:00 · shipped'], ['Next run in', '12h 12min']],\n        podcast: { title: 'Episode 184 · Anthropic Sonnet 4.5 + Genkit update', duration: '5:24', published: '28.05.2026 · 06:00', pct: '38%', at: '2:04',\n          chapters: [['00:00', 'Intro · what matters today'], ['00:42', 'Sonnet 4.5 · what is new'], ['02:18', 'Genkit · tool calls get stable'], ['03:48', 'What it means for our stack'], ['04:56', 'Outro · Patrick takes tomorrow']],\n          transcript: [['00:00', 'Jorian', 'Good morning — today is episode 184. Two stories: Sonnet 4.5 and a quiet Genkit update.'], ['00:42', 'Jorian', 'Sonnet 4.5. Anthropic released it overnight. Three hard numbers.'], ['01:14', 'Jorian', 'One: 18% faster on tool calls. Two: same price. Three: same context window.'], ['02:18', 'Jorian', 'Genkit. The 1.4 release fixed the tool-call instability that bit us last week.'], ['03:48', 'Jorian', 'What this means for our stack: Sonnet stays our default for decision branches.']],\n          recent: [['ep-183', 'OpenAI o5 · cost cut by a third', '27.05.2026 · 4:48'], ['ep-182', 'LangGraph 0.2 release', '26.05.2026 · 5:12'], ['ep-181', 'EU AI Act Q3 update', '25.05.2026 · 6:02']] } },\n\n      '.pics': { tld: 'aaas.pics', tagline: 'Business photography · €19 / €99 / €249', accent: LV, bucket: 'CONTENT', state: 'live',\n        desc: 'Live front door sells three photography packs; this plane is the /showcase LoRA studio — 7 screens from landing to billing.', view: 'picsApp',\n        kpis: [['Packs · 24h', '38', '+4'], ['Images · 24h', '912', '+118'], ['Revenue 24h', '€488', '+€88'], ['Cost / pack', '€1,20', '−€0,08'], ['Margin', '93%', '+1%']],\n        ops: [['Owner tier', 'Pro · trial day 4'], ['Generation cost', '€0,48'], ['Owner lifetime', '€19,00 (1 pack)'], ['Regen rate', '14%'], ['Favorite rate', '32%']],\n        gallery: { pack: 'pack-2042', owner: 'jane@cvbuilder.io', model: 'flux-1.5-pro', generated: '17:42 · 4 s/image',\n          prompt: 'Professional headshot · subtle blue background · warm but serious · suit',\n          tiles: [['classic', '0.92'], ['classic', '0.89'], ['classic', '0.91'], ['casual', '0.85'], ['casual', '0.87'], ['studio', '0.94'], ['studio', '0.90'], ['environment', '0.82'], ['environment', '0.86'], ['studio', '0.88'], ['classic', '0.90'], ['casual', '0.84']] } },\n\n      '.academy': { tld: 'aaas.academy', tagline: 'Daily capability upgrade', accent: GD, bucket: 'CONTENT', state: 'live',\n        desc: 'Course catalog + lesson player. Freemium funnel.', view: 'video',\n        kpis: [['Courses', '12', '+1'], ['Enrolled', '418', '+12'], ['Completion 30d', '38%', '+4%'], ['Avg watch', '14 min', '+2 min']],\n        ops: [['Enrolled', '142'], ['Completed this lesson', '38 / 142'], ['Dropoff step', 'Lesson 6 · self-critique loops'], ['Avg watch', '74%']],\n        video: { course: 'Zooey · agent decisions you can trust', progress: '3 / 8 done · 38%',\n          title: 'Lesson 4 · Alternates considered · how the AI shows its hand', duration: '8:42', at: '00:32',\n          chapters: [['00:00', 'Recap · why decisions matter'], ['01:14', 'The shape of an alternate'], ['03:32', 'Reading rejection reasons'], ['06:18', 'Replay-from-step (Phase 2)']],\n          transcript: 'Recap: every agent run is a chain of decisions. Some are obvious (which tool to call), some are subtle (which result to trust). Last lesson we mapped the trace. This one opens up what was not chosen — the part most trace tools hide. We do not. Watch the right-hand panel as I click step 4.',\n          modules: [\n            { label: 'Module 1 · The trace anatomy', lessons: [['done', '1 · The 6 step types', '6:12'], ['done', '2 · Reading the timeline', '7:48'], ['done', '3 · Phase vs llm-call vs tool', '5:32']] },\n            { label: 'Module 2 · Alternates + critique', lessons: [['now', '4 · Alternates considered', '8:42'], ['next', '5 · Reading rejection reasons', '6:18'], ['next', '6 · Self-critique loops', '9:14']] },\n            { label: 'Module 3 · Acting on traces', lessons: [['next', '7 · When to retry', '5:40'], ['next', '8 · Auto-confirm thresholds', '7:20']] }\n          ] } },\n\n      '.today': { tld: 'aaas.today', tagline: 'Daily AI picks', accent: TL, bucket: 'CONTENT', state: 'live',\n        desc: 'Three picks a day, sourced and linked. Every edition stays on file with its desk tags.', view: 'dailyPicks',\n        kpis: [['Picks today', '12', '0'], ['Views 24h', '4.2k', '+12%'], ['Click-through', '18%', '+1%'], ['Streak', '184 d', '+1']],\n        ops: [['Curator', 'today-agt + human confirm'], ['Engagement peak', '07:10 CET'], ['Archive depth', '184 editions'], ['Top desk', 'Platform · 41% of clicks']],\n        browser: { filters: [['All', '12'], ['Today', '3'], ['Yesterday', '3'], ['Archive', '184']],\n          cats: [['Platform', '38'], ['Agents', '46'], ['Money', '24'], ['Field notes', '76']],\n          items: [\n            ['Context packages ship to every surface', 'Today', 'platform', '06:05', 'One context package now hydrates every surface at once — no per-app refetch.', true],\n            ['Relay handoff chain goes GA', 'Today', 'agents', '06:05', 'Agents hand a run to each other with the full transcript attached. GA today.', true],\n            ['Pricing experiment: usage floors', 'Today', 'money', '06:05', 'Three tiers tested against a usage floor instead of a seat count.', false],\n            ['Decision timelines land in Select', 'Yesterday', 'agents', '06:05', 'Every listed agent now ships a readable decision timeline.', false],\n            ['EU AI Act · Q3 changes for SMEs', 'Yesterday', 'platform', '06:05', 'Eight changes that touch anyone shipping agents into the EU.', false],\n            ['Cost per run drops below €0,02', 'Yesterday', 'money', '06:05', 'Batching plus a smaller default model. Same output scores.', false],\n            ['The archive keeps every edition', 'Archive', 'field notes', '184 editions', 'Masthead: Today / Yesterday / Archive. Each edition keeps its desk tags.', false]\n          ] } },\n\n      '.toolbox': { tld: 'aaas-toolbox.web.app', tagline: '267 docs · 21 categories', accent: AC, bucket: 'CONTENT', state: 'live · in canvas',\n        desc: '267 toolbox docs in 21 categories. User-portal view of Content / Toolbox.', view: 'browser',\n        kpis: [['Docs', '267', '+3'], ['Used last 7d', '418', '+42'], ['Categories', '21']],\n        ops: [['Edits this month', '14'], ['PRs open', '3'], ['Top used doc', 'Brand-identity worksheet · 84 runs'], ['Deprecated', '24']],\n        browser: { filters: [['All', '267'], ['Template', '142'], ['Canvas', '38'], ['Prompt', '41'], ['Framework', '46']],\n          cats: [['00 · Dream-to-Idea', '7'], ['02 · Brand identity', '18'], ['04 · Product def.', '16'], ['06 · Growth engine', '19'], ['20 · AI · Autonomous', '7']],\n          items: [\n            ['Brand-identity worksheet', 'template', '02', '24.05.2026', 'Map mission, audience, voice, visual cues. Pairs with brand-canvas agent.', true],\n            ['Idea-stress-test', 'framework', '00', '12.05.2026', '5 questions to kill bad ideas in 30 minutes.', false],\n            ['Cofounder-fit canvas', 'canvas', '00', '08.05.2026', 'Detect alignment and friction with a potential cofounder.', true],\n            ['Logo-brief generator', 'prompt', '02', '20.05.2026', 'Feed your brand answers, get a designer-ready brief.', false],\n            ['Voice ladder', 'framework', '02', '14.05.2026', 'Pin your voice between 5 polar opposites.', false],\n            ['AP-Reconciliation playbook', 'template', '20', '24.05.2026', 'Operator playbook for the AP-Reconciliation agent.', false],\n            ['Pricing experiments', 'framework', '06', '28.05.2026', '3 A/B variants for SaaS pricing.', false]\n          ] } },\n\n      '.select': { tld: 'aaas.select', tagline: 'G2 for AI agents', accent: AC, bucket: 'MARKET', state: 'live · in canvas',\n        desc: 'The canonical Canvas hub — agent marketplace plus reviews.', view: 'listing',\n        kpis: [['Agents', '142', '+8'], ['Installs 7d', '8.2k', '+12%'], ['Avg rating', '4.6', '+0.1'], ['Revenue 30d', '€28.4k', '+€4.2k']],\n        ops: [['Publisher cut', '70%'], ['Installs 30d', '8.420'], ['Revenue 30d', '€28.420'], ['Top review', '4★ · \"AP-Recon saved me 6 hours/week\"']],\n        listing: { title: 'AP-Reconciliation', publisher: 'AaaS Studio', verified: true, rating: '4.8', reviews: '142 reviews', installs: '4.8k installs',\n          price: 'Free tier · €0.001/run', priceChip: 'Free · Pro features',\n          tagline: 'Match invoices automatically — fuzzy match · embedding similarity · exact match',\n          desc: 'Operator-grade agent for AP reconciliation. Pulls 12 invoices, decides the match strategy, sends an annotated summary. The decision timeline lets you audit every choice.',\n          caps: ['12 invoices/minute', 'EUR-only', 'GDPR-compliant', 'Stripe + Datev export'],\n          perms: ['Read Stripe invoices', 'Write to Decision Log', 'Send email summary'],\n          recent: [['Meeting-Prep', 'AaaS Studio', '4.6', '2.1k', 'Free'], ['Headshot-Pack', '.pics', '4.7', '1.4k', '€19'], ['Podcast-Brief', 'KoruFM', '4.5', '892', 'Free'], ['CRM-Enrich', 'AaaS Studio', '4.3', '684', 'Pro+']] } },\n\n      '.builders': { tld: 'aaas.builders', tagline: 'Studio · drafts · published', accent: TL, bucket: 'MARKET', state: 'live',\n        desc: 'Builder workshop — editor, preview, test runner, draft/published toggle.', view: 'builderWorkshop',\n        kpis: [['Builders', '38', '+2'], ['Agent drafts', '142', '+18'], ['Published', '64', '+4'], ['Pending review', '8', '+1']],\n        ops: [['Review SLA', '18 h median'], ['Rejected 30d', '11']],\n        hint: 'BuilderWorkshop ships in P2. Today it renders as a queue of drafts plus a promote-to-published action; the full editor lives at aaas.builders.' },\n\n      '.boutique': { tld: 'aaas.boutique', tagline: 'Premium bundles · €3K / €8K', accent: RD, bucket: 'MARKET', state: 'live',\n        desc: 'Premium bundles and €3K / €8K consulting deposits via Stripe Checkout.', view: 'listing',\n        kpis: [['Packages', '8', '+1'], ['Deposits 30d', '4', '+1'], ['GMV 30d', '€22k', '+€3k'], ['In delivery', '3', '+1']],\n        ops: [['Deposit pipeline', '€11.000 pending'], ['Avg delivery', '88 days'], ['Revenue recognized', '€22.000 YTD'], ['Active', '3 projects in flight']],\n        listing: { title: 'Launch-90 · 90 days to a live AaaS tenant', publisher: 'AaaS Boutique', verified: true, rating: '5.0', reviews: '8 reviews', installs: '4 active',\n          price: '€8.000 deposit · 90-day delivery · weekly status', priceChip: '€8.000 deposit',\n          tagline: 'From brand core to a live AaaS tenant with your own agents in 90 days.',\n          desc: 'Fully-scoped Boutique engagement. Jorian and team, 12 weeks. At the end you get a live tenant with your agents, your brand, your data.',\n          caps: ['12 weeks · 4 phases', 'Weekly status update', 'Full source handover', 'Operator training included'],\n          perms: [],\n          recent: [['Brand-30 · identity in 30 days', 'AaaS Boutique', '4.9', '11 done', '€3.000'], ['Podcast-10 · your daily brief', 'AaaS Boutique', '4.8', '6 done', '€3.000']] } },\n\n      /* .store — the 18th domain. Field shape copied from the '.boutique' sibling above, not from the\n         handover's prose: `accent` is the const AC (the circuit lead — RD is the red one boutique uses),\n         and `bucket` is MARKET, the same bucket '.select' sits in. Every number below is read from\n         assets/domains/store/data.jsx, never typed: 14 CATALOG entries, mode split 9 buy / 3 partner /\n         2 provider, 14 of 14 carrying `totalCostLine`, 5 of 5 partner+provider carrying `disclosure`.\n         The `recent` rows are all 14 items; the renderer's 5 columns are fixed by the sibling shape\n         (title · publisher · rating · installs · price), so mode rides in the third slot and the total-\n         cost line in the fifth — the four fields the handover asked for, in the columns that exist. */\n      '.store': { tld: 'store.aaas.love', tagline: 'Buy, compare, or go to the provider. The mode is on the card.', accent: AC, bucket: 'MARKET', state: 'live',\n        desc: 'One catalog, three honest modes: we sell it, a partner sells it, or a provider delivers it. Total cost and the disclosure are on the card, before any click.', view: 'listing',\n        /* kpis · ops · listing.recent/installs/priceChip/reviews are ALL written by applyStoreCatalog()\n           from assets/domains/store/data.jsx. Left empty rather than left wrong: these carried six typed\n           counts and a typed '+14' delta that the derivation overwrote every render — the same trap the\n           STREAMS.domains literals were stripped for, where a future editor greps a number, \"fixes\" it\n           and sees no effect, or deletes the derivation believing the literal is truth. */\n        kpis: [],\n        ops: [],   // see the note above — written by applyStoreCatalog(), including these three rows\n        listing: { title: 'AaaS Store · one catalog, three modes', publisher: 'AaaS Store', verified: true, rating: '—', reviews: 'provider reviews', installs: 'items',\n          price: 'Mode is on the card · total cost shown before any click', priceChip: '',\n          tagline: 'Buy, compare, or go to the provider. The mode is on the card.',\n          desc: 'Nine items we sell and deliver ourselves, three a partner bills, two a vetted provider delivers off-platform. The card says which, what it really costs, and why we would send you elsewhere.',\n          caps: ['Buy · our own checkout', 'Partner · outbound, disclosure inline', 'Provider · redirect with a stated reason', 'Total cost before any click'],\n          perms: [],\n          /* Filled by applyStoreCatalog() from assets/domains/store/data.jsx. Left EMPTY on purpose:\n             this held all 14 catalog rows typed into the shell, which is the defect the domain-count\n             sequence spent four rounds removing. If the fetch fails the pane says so and shows nothing,\n             rather than showing a copy that may already be wrong. */\n          recent: [] } },\n\n      '.business': { tld: 'aaas.business', tagline: 'Enterprise tool marketplace', accent: AC, bucket: 'MARKET', state: 'live',\n        desc: 'Enterprise tool marketplace. Lead funnel, per-tool detail, request-demo.', view: 'soon',\n        kpis: [['Tools', '24', '+2'], ['Leads 30d', '142', '+18'], ['Demos booked', '18', '+4'], ['Conversion', '12%', '+2%']],\n        ops: [['Close rate', '31% demo → deal'], ['Avg deal', '€14.200']],\n        hint: 'Marketplace listing variant (B2B). Lead funnel inline; demo-request to close rate per tool.' },\n\n      '.diy': { tld: 'aaas.diy', tagline: 'Free · Pro $12 · Team $29', accent: GD, bucket: 'MARKET', state: 'live',\n        desc: 'Source-to-package generator. Paste a URL, pick a mode, get 10 formats.', view: 'diyApp',\n        kpis: [['Packages', '34', '+2'], ['Free users', '4.182', '+82'], ['Pro users', '184', '+12'], ['Team users', '24', '+2'], ['MRR', '$4.504', '+$304']],\n        ops: [['Installs 30d', '1.842'], ['Top package', 'AP-Reconciliation · 412 installs'], ['Churn', '4.2%'], ['Upgrade rate', '3.4% free → Pro']],\n        browser: { filters: [['All', '34'], ['Free', '12'], ['Pro $12', '14'], ['Team $29', '8']],\n          cats: [['Finance', '6'], ['Content', '11'], ['Sales', '8'], ['Design', '9']],\n          items: [\n            ['AP-Reconciliation pack', 'Free', 'finance', '24.05.2026', '12 invoices/min. Free tier. Includes EU CSV export.', true],\n            ['Podcast-Brief pack', 'Free', 'content', '20.05.2026', 'Daily 5-min brief generator.', true],\n            ['Pricing experiments pack', 'Pro', 'sales', '28.05.2026', '3 A/B variants plus revenue impact.', false],\n            ['Headshot Pack', 'Pro', 'design', '24.05.2026', '12 headshots per pack. €19/pack add-on.', true],\n            ['Brand-identity workshop', 'Team', 'design', '14.05.2026', '4-week guided workshop.', false],\n            ['CRM-Enrich pack', 'Team', 'sales', '08.05.2026', 'Auto-enrich CRM contacts.', false],\n            ['Meeting-Prep pack', 'Free', 'content', '12.05.2026', 'Daily meeting prep agent.', false]\n          ] } },\n\n      '.forum': { tld: 'aaas.forum', tagline: 'Threads · vote · moderate', accent: LV, bucket: 'COMMUNITY', state: 'live',\n        desc: 'Public-read threads, auth-write replies, operator moderation overlay.', view: 'thread',\n        kpis: [['Threads', '184', '+8'], ['Posts 7d', '418', '+42'], ['Mod queue', '3', '+1'], ['Active', '142', '+12']],\n        ops: [['Mod risk score', '2 / 10 · low'], ['Spam flags', '0'], ['Reports', '0'], ['Thread velocity', 'fast · 4 replies/hour'], ['Suggested action', 'pin to /decisions tag']],\n        thread: { title: 'How are you handling 30-day retention on Pro plans for agent traces?',\n          author: 'reyes_korufm', avatar: 'RK', flair: 'Pro', created: '28.05.2026 · 14:32',\n          stats: '142 views · 12 votes · 8 replies', tags: ['decisions', 'retention', 'pro-tier'],\n          body: 'We just upgraded to Pro for the 30-day retention on decision traces. Question: does the retention window restart when you replay-from-step, or does it count from the original run timestamp? The docs say \"30 days from completion\" but replay is officially Phase 2 — just want to confirm before I plan around it.',\n          posts: [\n            { name: 'jorian', avatar: 'JL', flair: 'Operator', at: '14:48', votes: '8', op: true, body: 'Original run timestamp — confirmed. Replay-from-step (Phase 2) will inherit the parent run retention. We are leaning toward \"new run = new clock\" for replays once they ship; that is the more useful behavior for the audit story. Open to pushback if it breaks your model.' },\n            { name: 'reyes_korufm', avatar: 'RK', flair: 'Pro', at: '15:02', votes: '3', op: false, body: 'Makes sense for replays. Thanks for the confirm — that is what I needed.' },\n            { name: 'ana_acme', avatar: 'AA', flair: 'Business', at: '15:18', votes: '5', op: false, body: 'For what it is worth, we would prefer \"new run = new clock\" too. Lets us re-explore decision branches without burning the parent window.' },\n            { name: 'patrick', avatar: 'PT', flair: 'Operator', at: '15:42', votes: '12', op: true, body: 'Adding to Jorian: the Phase 2 replay UI will show both parent run and replay separately. The \"burn the parent window\" concern is real and we want to design against it.' }\n          ] } },\n\n      '.quest': { tld: 'aaas.quest', tagline: 'CV · career wizard', accent: TL, bucket: 'COMMUNITY', state: 'live',\n        desc: 'CV and career wizard — multi-step form, output preview, download.', view: 'ceuWizard',\n        kpis: [['Sessions today', '38', '+8'], ['Complete', '64%', '+4%'], ['Revenue 30d', '€1.418', '+€318'], ['Dropoff step', 'Step 4']],\n        ops: [['Wizard host', 'MultiStepWizard'], ['Abandon point', 'step 4 · work history']],\n        hint: 'Wizard shell renders the Quest 9-step CV builder. MultiStepWizard is the host component.' },\n\n      '.canvas': { tld: 'aaas-canvas.web.app', tagline: 'Visual builder · R1 merge planned', accent: AC, bucket: 'COMMUNITY', state: 'live',\n        desc: 'Visual builder SPA — R1 merge into /select/canvas planned in P3.', view: 'visualBuilder',\n        kpis: [['Projects', '418', '+12'], ['Active editors', '14', '+2'], ['R1 migration', 'P3'], ['Modules', '7']],\n        ops: [['Merge batches', '7 modules, one at a time'], ['Redirect', 'old SPA 301s after each batch']],\n        hint: 'Vite + React SPA. R1 merge plan: migrate 7 modules (diy, builders, today, gallery, portal, canvas, zui, marketplace) one at a time into the platform app; the old SPA 301-redirects after each batch.' },\n\n      '.company': { tld: 'aaas.company', tagline: 'Account · team · seats', accent: AC, bucket: 'COMMUNITY', state: 'live',\n        desc: 'Org dashboard — company profile, team, seats. Borders subscription seat management.', view: 'entityOverview',\n        kpis: [['Org seats', '14 / 20', '+2'], ['Teams', '4', '0'], ['Admins', '3', '0'], ['Tier', 'Business']],\n        ops: [['MRR contribution', '€1.240 / mo'], ['Seat delta 30d', '+2']],\n        hint: 'EntityOverview view target. Per-org MRR contribution and seat-count delta are the operator overlay.' }\n    };\n\n    /* ── Domains counts: derived, never typed ─────────────────────────────────────────────────────\n       Adding `.store` left the Domains stream contradicting itself in one viewport: the KPI strip and\n       hub header said 15 while the hub's own bucket counts — which ARE auto-derived from DOMAINS —\n       summed to 16. The typed literals had in fact drifted BEFORE this change too ('Live now 14' with\n       15 entries, all of them `state: 'live'`). So the fix is not a bump: the three counts below are\n       computed from `this.DOMAINS` itself, here rather than in the STREAMS literal above, because\n       DOMAINS is defined after it. The next domain needs no edit here.\n       Two denominators exist and they are NOT the same number, so both are named rather than blended:\n       **16 domains mounted in this shell** (`DOMAINS` keys) vs **18 domain assets pulled into\n       `assets/domains/`** (`assets/index.json` `domains.count`). The KPI reports what the shell mounts\n       and says so; the gap is 2 pulled-but-unmounted assets, which is a real, readable fact.\n       Same class as `.toolbox`'s annotated `267` vs its own `275 templates` — a denominator note\n       beats a silent third number. */\n    (() => {\n      const D = this.DOMAINS, ks = Object.keys(D);\n      const mounted = ks.length;\n      const live = ks.filter(k => /live/i.test(D[k].state || '')).length;\n      const inCanvas = ks.filter(k => /in canvas/i.test(D[k].state || '')).length;\n      // `pulled` is the ONE count this shell cannot compute from itself: it lives in\n      // assets/index.json (`classes.domains.count`) — domain assets on disk, not streams mounted here.\n      // 18 is a FALLBACK for the moment before that file resolves, not the source of truth:\n      // `_loadGalleryIndex()` already fetches the same index and overwrites this, then re-applies the\n      // strings. If the fetch fails the fallback shows and the pane still reads coherently.\n      this._domCounts = { mounted: mounted, live: live, inCanvas: inCanvas, pulled: 18, pulledFrom: 'fallback' };\n      this.applyDomainCounts();\n    })();\n\n\n    // ── Intro tour (handoff: app/intro-tour.jsx) — Connect → Contextualize → Automate ──\n    // Same contract as the handoff: first visit, ?intro=1, and the ? help affordance re-open it.\n    this.TOUR_PLATFORMS = ['Google Analytics', 'Google Ads', 'Stripe', 'HubSpot', 'Shopify', 'Notion', 'PostHog', 'Meta Ads', 'Salesforce', 'Custom REST'];\n    this.TOUR_PIPELINE = [{ label: 'Connect', icon: 'link' }, { label: 'Contextualize', icon: 'tree' }, { label: 'Automate', icon: 'bolt' }];\n    this.TOUR_STEPS = [\n      { eyebrow: 'CANVAS 2.0 · VISUAL BUSINESS TOOLBOX', glyph: 'apps', kind: 'pipeline',\n        title: 'Connect any API. The engine does the rest.',\n        body: 'You are already inside the product. Point the canvas at any source through a visual front-end — the contextual engine gathers every signal and puts AI to work. Three layers, one loop.' },\n      { eyebrow: 'LAYER 01 · CONNECT', glyph: 'link', kind: 'platforms',\n        title: 'Point the canvas at any API.',\n        body: 'No glue code. The front-end you see becomes the control surface for anything with an API — analytics, ads, commerce, product, finance.' },\n      { eyebrow: 'LAYER 02 · CONTEXTUALIZE', glyph: 'tree', kind: 'central',\n        title: 'The engine combines everything.',\n        body: 'A contextual-engineering machine merges every connected source into one live context — deduplicated, cross-referenced, always current. No tab-hopping, no exports.' },\n      { eyebrow: 'LAYER 03 · AUTOMATE', glyph: 'bolt', kind: 'agents',\n        title: 'AI runs the operation.',\n        body: 'Steer agents against that combined context and watch them run — content, outreach, ops — each in its own worktree, with full cost and decision transparency.' },\n      { eyebrow: 'INTEGRATE & START', glyph: 'spark', kind: 'final',\n        title: 'Connect your first API.',\n        body: 'Connect · Contextualize · Automate — that is the whole loop. Connect a source and watch the operation come online across every surface of the shell.',\n        cta: { label: 'Connect an API', go: ['platform', 'Integrations'] } }\n    ];\n    this.TOUR_AGENTS = [{ name: 'content-agt', pct: '72%' }, { name: 'outreach-agt', pct: '40%' }, { name: 'ops-agt', pct: '88%' }];\n\n    // Floating agent chat — ported from the canvas repo (overlay/vendor-app/agent-support.jsx):\n    // collapsed tab → panel, live dot, 3 rate-limited demo turns, canned replies.\n    this.CHAT_REPLIES = [\n      { m: /price|cost|pay|€|invoice/i, o: 'The script is €149 — charged only if you keep it. Two revision rounds are included, and nothing recurs.' },\n      { m: /own|licence|license|right/i, o: 'You own it outright on payment: perpetual, commercial, unlimited placements. We keep no rights to the recording.' },\n      { m: /short|cut|length|shorter|longer/i, o: 'A 30-second cut is one revision, not a new order. Say the word in the Revisions tab and the agent re-times the read.' },\n      { m: /after|next|then|deliver/i, o: 'Pay and the clean read unlocks here — MP3, WAV and the script as Markdown. Revisions stay open for 14 days.' },\n      { m: /voice|read|accent|speaker/i, o: 'The read is EN-EU, calm and technical. German and Dutch reads are available as a revision at no extra cost.' },\n      { m: /vault|skill|agent|how/i, o: 'One agent writes from your pages, a second reads it aloud, a third checks the claims against your site. You only see the result.' }\n    ];\n    // W2 · PaneAccount fixture — the three member kinds, verbatim from\n    // canvas/30-audits/pane-sources/A-Member-kinds.jsx (K.billing :233 · K.orgTeam :276 · K.accountSettings :316).\n    // State: Modeled (B3). Held here rather than assets/data/account.json, per the W1 Review precedent\n    // (a runtime fetch would need a new head resource declaration for the offline bundle).\n    // W2 · Submit a brief — the 6 curated agents verbatim from\n    // 05_Canvas/_surfaces/_unmapped/Canvas 2.0 Projects List/Projects.dc.html :465 (est = credits).\n    this.BRIEF_AGENTS = [\n      { id: 'research-agent', label: 'research-agent', skills: 34, desc: 'Deep research, competitive teardowns, synthesis.', est: 1800 },\n      { id: 'content-agent', label: 'content-agent', skills: 28, desc: 'Long & short-form copy, rewrites, variants.', est: 620 },\n      { id: 'crm-agent', label: 'crm-agent', skills: 19, desc: 'Lead enrichment, list ops, pipeline sync.', est: 2100 },\n      { id: 'design-agent', label: 'design-agent', skills: 22, desc: 'Brand systems, figma exports, asset audits.', est: 940 },\n      { id: 'seo-agent', label: 'seo-agent', skills: 16, desc: 'On-page refresh, metadata, internal links.', est: 780 },\n      { id: 'ops-agent', label: 'ops-agent', skills: 41, desc: 'Reconciliation, digests, workflow automation.', est: 540 }\n    ];\n    this.ACC_KINDS = {\n      billing: {\n        id: 'billing', label: 'Billing', workspaceLabel: 'Billing & Credits', defaultView: 'financeCredits',\n        description: 'Your credit balance and usage. Buy credits or change plan — card entry is Stripe-hosted.',\n        heroKpis: [\n          { label: 'credit balance', value: '24.840', delta: '+500', trend: 'up' },\n          { label: 'spend this month', value: '€ 62,40', delta: '+12,1', trend: 'down' },\n          { label: 'plan', value: 'Pro', delta: '', trend: 'flat' },\n          { label: 'included / mo', value: '20.000', delta: '', trend: 'flat' }\n        ],\n        balance: { eur: '248,40', credits: '24.840', rate: '1 credit = € 0,01 · Pro plan' },\n        topups: [\n          { id: 'tu-4', date: '2026-06-30', pack: 'Pro monthly (20.000 cr)', amount: '199,00', method: 'Visa ••42' },\n          { id: 'tu-3', date: '2026-06-12', pack: 'Top-up · 5.000 cr', amount: '49,00', method: 'Visa ••42' },\n          { id: 'tu-2', date: '2026-05-30', pack: 'Pro monthly (20.000 cr)', amount: '199,00', method: 'Visa ••42' },\n          { id: 'tu-1', date: '2026-05-18', pack: 'Referral bonus', amount: '0,00', method: '+500 cr · AAAS-J7X2' }\n        ],\n        year: 2026,\n        rows: [\n          { month: 'Jan', revenue: '48,20', expenses: '0,00', profit: '48,20' },\n          { month: 'Feb', revenue: '52,80', expenses: '0,00', profit: '52,80' },\n          { month: 'Mar', revenue: '61,40', expenses: '0,00', profit: '61,40' },\n          { month: 'Apr', revenue: '58,10', expenses: '0,00', profit: '58,10' },\n          { month: 'May', revenue: '64,90', expenses: '0,00', profit: '64,90' },\n          { month: 'Jun', revenue: '62,40', expenses: '0,00', profit: '62,40' }\n        ],\n        totals: { revenue: '347,80', expenses: '0,00', profit: '347,80' }\n      },\n      orgTeam: {\n        id: 'orgTeam', label: 'Team', workspaceLabel: 'Team', defaultView: 'table', defaultDetail: 'drawer',\n        description: 'Manage your organisation — members, roles, and invites — so you can share access.',\n        heroKpis: [\n          { label: 'members', value: '4', delta: '+1', trend: 'up' },\n          { label: 'pending invites', value: '1', delta: '', trend: 'flat' },\n          { label: 'seats used', value: '4 / 5', delta: '', trend: 'flat' },\n          { label: 'org credits', value: '24.840', delta: '', trend: 'flat' }\n        ],\n        schema: [\n          { key: 'name', label: 'Member', wide: true },\n          { key: 'email', label: 'Email', mono: true },\n          { key: 'role', label: 'Role', mono: false },\n          { key: 'status', label: 'Status', kind: 'status' },\n          { key: 'joinedAt', label: 'Joined', mono: true }\n        ],\n        mockItems: [\n          { id: 'mb-1', name: 'Jorian Lundqvist', title: 'Jorian Lundqvist', email: 'jorian@aaas.love', role: 'Owner', status: 'ok', statusLabel: 'active', joinedAt: '2026-01-04',\n            subtitle: 'Owner · full access', preview: 'Role      Owner\\nEmail     jorian@aaas.love\\nAccess    full canvas + billing + team\\nJoined    2026-01-04' },\n          { id: 'mb-2', name: 'Tobias Reell', title: 'Tobias Reell', email: 'tobias@aaas.love', role: 'Admin', status: 'ok', statusLabel: 'active', joinedAt: '2026-02-11',\n            subtitle: 'Admin · manage agents + projects', preview: 'Role      Admin\\nEmail     tobias@aaas.love\\nAccess    projects · agents · runs\\nJoined    2026-02-11' },\n          { id: 'mb-3', name: 'Mira Sundqvist', title: 'Mira Sundqvist', email: 'mira@aaas.love', role: 'Member', status: 'ok', statusLabel: 'active', joinedAt: '2026-04-22',\n            subtitle: 'Member · submit briefs', preview: 'Role      Member\\nEmail     mira@aaas.love\\nAccess    submit briefs · view deliverables\\nJoined    2026-04-22' },\n          { id: 'mb-4', name: 'Bookkeeper (Steuerberater)', title: 'Bookkeeper', email: 'kanzlei@steuer.de', role: 'Finance', status: 'ok', statusLabel: 'active', joinedAt: '2026-05-02',\n            subtitle: 'Finance · billing read-only', preview: 'Role      Finance\\nEmail     kanzlei@steuer.de\\nAccess    billing (read-only)\\nJoined    2026-05-02' },\n          { id: 'mb-5', name: 'Invited — designer', title: 'Pending invite', email: 'designer@studio.co', role: 'Member', status: 'idle', statusLabel: 'invited', joinedAt: '—',\n            subtitle: 'Invite sent 2026-07-01 · revoke', preview: 'Role      Member (pending)\\nEmail     designer@studio.co\\nStatus    invited 2026-07-01\\nAction    Resend · Revoke invite' }\n        ]\n      },\n      accountSettings: {\n        id: 'accountSettings', label: 'Account', workspaceLabel: 'Account & Settings', defaultView: 'financeForm',\n        description: 'Profile, email preferences, connected services, referral — and the danger zone.',\n        heroKpis: [\n          { label: 'plan', value: 'Pro', delta: '', trend: 'flat' },\n          { label: 'credits', value: '24.840', delta: '', trend: 'flat' },\n          { label: 'services connected', value: '6 / 9', delta: '', trend: 'flat' }\n        ]\n      }\n    };\n\n    // ══ Intelligence · 9 sub-surfaces, each with its own hero, KPIs and actions ══\n    const ik = (label, value, delta, accent) => ({ label, value, delta: delta || null, accent: accent || null });\n    this.SUBHERO = {\n      quality: {\n        'Support chat': { crumb: 'Operate › Quality › Support chat', over: 'SUPPORT · 3 OPEN THREADS', title: 'SUPPORT CHAT',\n          desc: 'Customer threads with tier, priority and SLA. Canned snippets for the five requests that make up most of the volume.',\n          kpis: [ik('Open', '3'), ik('SLA breach', '0', null, 'teal'), ik('First response', '38 m'), ik('CSAT', '4.6 / 5', '+0.2', 'teal')],\n          actions2: [{ label: 'Escalate', icon: 'alert' }, { label: 'Close thread', icon: 'check' }] }\n      },\n      ops: {\n        'Onboarding': { crumb: 'Operate › Ops › Onboarding', over: 'FLOW EDITOR · 6 STEPS', title: 'ONBOARDING FLOW',\n          desc: 'The signup-to-first-run flow, editable step by step. Two variants running as an A/B; completion rate per variant.',\n          kpis: [ik('Variants', '2'), ik('Baseline', '64 %'), ik('A/B', '71 %', '+7 pt', 'teal'), ik('Drop-off step', '4')],\n          actions2: [{ label: 'Publish variant', icon: 'check' }] },\n        'Impersonate': { crumb: 'Operate › Ops › Impersonate', over: 'IMPERSONATE · AUDITED', title: 'IMPERSONATE',\n          desc: 'View the canvas as a customer sees it. Every session is logged with actor, target, reason and duration; the customer is notified.',\n          kpis: [ik('Sessions 30d', '3'), ik('Max duration', '60 m'), ik('Auto-redact', 'on', null, 'teal'), ik('Writes', 'double-confirm')],\n          actions2: [{ label: 'Audit log', icon: 'timeline' }] }\n      },\n      select: {\n        'Replays': { crumb: 'Discover › Select › Replays', over: 'REPLAYS · PHASE 2', title: 'REPLAYS',\n          desc: 'Re-run a trace from any decision branch with a different choice. The counterfactual, without re-running the whole job.',\n          kpis: [ik('Replays', '4'), ik('Completed', '3', null, 'teal'), ik('Needs review', '1', null, 'red'), ik('Δ cost', '+€ 0,21')],\n          actions2: [{ label: 'New replay', icon: 'refresh' }] }\n      },\n      content: (() => { const m = {}; Object.keys(this.CNT_SUB).forEach(k => { const s = this.CNT_SUB[k];\n        m[k] = { crumb: 'Content › ' + k.split(' · ')[0], over: s[0], title: s[1], desc: s[2],\n          kpis: s[3].map(x => ik(x[0], x[1], x[2] || null)),\n          actions2: (s[5] || []).map(a => ({ label: a[0], icon: a[1] })) }; }); return m; })(),\n      intelligence: {\n        'Ask anything': { crumb: 'Intelligence › Ask anything › Unified ask', over: 'UNIFIED ASK · ASK ANYTHING', title: 'Unified ask',\n          desc: 'One question, four sources, a cited answer. A Gemini classifier routes each query across blog / vault / notebooklm / kb.',\n          actions2: [{ label: 'Frage stellen', icon: 'search' }, { label: 'Historie', icon: 'refresh' }, { label: 'Budget · Pro', icon: 'costs' }],\n          kpis: [ik('Queries 24 h', '184', '+22 %'), ik('Cost 24 h', '€ 0,42', '+€ 0,08'), ik('Avg latency', '1,2 s', '−0,1 s', 'teal'), ik('Budget · day', '64 / 100'), ik('Top source', 'aaas.blog · 48 %')] },\n        'Entity graph': { crumb: 'Intelligence › Entity graph › aaas.blog', over: 'AAAS.BLOG GRAPH · ENTITY GRAPH', title: 'aaas.blog graph',\n          desc: '7,960 entities · 24,180 edges · 5 predicates. Live via MCP + /llms-full.txt.',\n          actions2: [{ label: 'Trigger pipeline', icon: 'spark' }, { label: 'Open in aaas.blog ↗', icon: 'arrow-up-right' }, { label: 'HITL queue · 24', icon: 'quality' }],\n          kpis: [ik('Entities', '7.960', '+142'), ik('Edges', '24.180', '+1.2k'), ik('Collections', '11'), ik('Ground rate', '87 %', '+2 %', 'teal'), ik('HITL pending', '24', '+4', 'red'), ik('Last run', '04:30 Z')] },\n        Vault: { crumb: 'Intelligence › Vault › Skill library', over: 'VAULT · VAULT · SKILL LIBRARY', title: 'Vault',\n          desc: 'Skill library, not secrets. 5,412 skills across gold / silver / bronze, fetched live from the aaas-vault index.',\n          actions2: [{ label: 'Promote → gold (PR)', icon: 'spark' }, { label: 'Open in GitHub ↗', icon: 'arrow-up-right' }, { label: 'Refresh manifest', icon: 'refresh' }],\n          kpis: [ik('Skills', '5.412', '+18'), ik('Gold', '412', '+4'), ik('Silver', '1.840', '+8'), ik('Bronze', '3.160', '+6'), ik('Publishable', '4.218', '+12'), ik('Context-enhanced', '824', '+24'), ik('Manifest sync', 'OK · 14 min ago', null, 'teal')] },\n        Prompts: { crumb: 'Intelligence › Prompts › Prompt library', over: 'PROMPT LIBRARY · PROMPTS', title: 'Prompt library',\n          desc: 'Versioned registry of every prompt every agent uses. Diff, A/B, rollback, RBAC-gated.',\n          actions2: [{ label: 'New prompt', icon: 'plus' }, { label: 'Import', icon: 'content' }, { label: 'Audit log', icon: 'refresh' }],\n          kpis: [ik('Prompts', '8'), ik('Active A/B', '2'), ik('Runs · 30 d', '63,612', '+8 %'), ik('Languages', 'de · en · multi'), ik('Tokens · total', '3,419t')] },\n        'NotebookLM sync': { crumb: 'Intelligence › NotebookLM sync', over: 'NOTEBOOKLM · NOTEBOOKLM SYNC', title: 'NotebookLM',\n          desc: '5 megadocs, 2 notebooks, domain-wide-delegation sync every 15 minutes. Operator-only, Google-account gated.',\n          actions2: [{ label: 'Trigger sync now', icon: 'refresh' }, { label: 'Drive folder ↗', icon: 'arrow-up-right' }, { label: 'LaunchAgent logs', icon: 'content' }],\n          kpis: [ik('Megadocs', '5'), ik('Sync status', 'OK', null, 'teal'), ik('Last sync', '12 min ago'), ik('DWD token', '47 d left'), ik('Errors 24 h', '0', null, 'teal'), ik('Notebooks', '2')] },\n        KB: { crumb: 'Intelligence › KB › Personal · Workspace · Project', over: 'KNOWLEDGE BASE · KB · PERSONAL · WORKSPACE · PROJECT', title: 'Knowledge base',\n          desc: 'Per-user, per-workspace and per-project knowledge. Markdown, tags, Gemini embeddings for vector retrieval.',\n          actions2: [{ label: 'New doc', icon: 'plus' }],\n          kpis: [ik('Docs', '342', '+12'), ik('Folders', '38'), ik('Reindex', '06:00')] },\n        'Query audit': { crumb: 'Intelligence › Query audit', over: 'QUERY AUDIT · AUDIT', title: 'Query audit',\n          desc: 'Who asked what, which sources were consulted, what came back. Retention is tier-gated.',\n          actions2: [{ label: 'Export CSV', icon: 'download' }, { label: 'Filter user', icon: 'search' }],\n          kpis: [ik('Events 30 d', '4.182', '+8 %'), ik('Unique users', '38', '+4'), ik('Avg cost', '€ 0,003'), ik('Retention', '30 d')] },\n        'Closeout · 14 briefs': { crumb: 'Intelligence › Closeout › 14-brief sweep', over: 'CLOSEOUT · CLOSEOUT · 14-BRIEF SWEEP', title: 'Closeout',\n          desc: 'Final completeness sweep across the 14-brief Canvas series · 17 themes · 40+ surfaces · P1/P2/P3 phasing · memory updates.',\n          actions2: [{ label: 'Open the full brief', icon: 'arrow-up-right' }, { label: 'Memory updates', icon: 'intelligence' }, { label: 'Export to PDF', icon: 'download' }],\n          kpis: [ik('Briefs', '14'), ik('Themes', '17'), ik('Surfaces', '40+'), ik('Open Qs', '180'), ik('Memory fix', '3', null, 'red')] }\n      }\n    };\n    // W2 · PaneAccount per-tab hero — heroKpis + actions verbatim from kinds-member.jsx (K.billing :233,\n    // K.orgTeam :276, K.accountSettings :316). trend up → teal, down → red, flat → no accent.\n    {\n      const hk = (arr) => arr.map(x => ik(x.label, x.value, x.delta || null, x.trend === 'up' ? 'teal' : x.trend === 'down' ? 'red' : null));\n      const AK = this.ACC_KINDS;\n      this.SUBHERO.account = {\n        'Billing': { crumb: 'Personal › Account & Settings › Billing', over: 'BILLING · MODELED', title: AK.billing.workspaceLabel.toUpperCase(),\n          desc: AK.billing.description, kpis: hk(AK.billing.heroKpis),\n          actions2: [{ label: 'Buy credits', icon: 'plus' }, { label: 'Change plan', icon: 'refresh' }, { label: 'Spend over time', icon: 'chart' }] },\n        'Team': { crumb: 'Personal › Account & Settings › Team', over: 'TEAM · MODELED', title: AK.orgTeam.workspaceLabel.toUpperCase(),\n          // D207 · members / pending invites / seats used are counted off orgTeam.mockItems (the rows the tab renders);\n          // the fixture's typed '4 · 1 · 4 / 5' disagreed with its own 5 rows. Seat cap 5 stays the fixture's.\n          desc: AK.orgTeam.description, kpis: hk(AK.orgTeam.heroKpis.map(x => { const R = AK.orgTeam.mockItems || []; const pend = R.filter(r => r.statusLabel !== 'active').length;\n            return x.label === 'members' ? Object.assign({}, x, { value: String(R.length) }) : x.label === 'pending invites' ? Object.assign({}, x, { value: String(pend) })\n              : x.label === 'seats used' ? Object.assign({}, x, { value: R.length + ' / ' + String(x.value).split('/')[1].trim() }) : x; })),\n          actions2: [{ label: 'Invite member', icon: 'user' }] },\n        'Settings': { crumb: 'Personal › Account & Settings › Settings', title: AK.accountSettings.workspaceLabel.toUpperCase(),\n          desc: AK.accountSettings.description, kpis: hk(AK.accountSettings.heroKpis),\n          actions2: [{ label: 'Save changes', icon: 'check' }, { label: 'Cancel', icon: 'x' }] }\n      };\n      // D151 · 'services connected' is DERIVED from the list it summarises (was typed 6 / 9 against a 10-row list).\n      const accSettingsKpis = this.SUBHERO.account.Settings.kpis;\n      Object.defineProperty(this.SUBHERO.account.Settings, 'kpis', { get: () => accSettingsKpis.map(x => x.label !== 'services connected' ? x\n        : Object.assign({}, x, { value: (this.ACC_SERVICES || []).filter(v => v.state === 'ok').length + ' / ' + (this.ACC_SERVICES || []).length })) });\n      const ageMin = (a) => { const m = String(a || '').match(/^(\\d+)\\s*([mhd])/); return m ? +m[1] * ({ m: 1, h: 60, d: 1440 })[m[2]] : 0; };\n      const wkq = () => this.WK_QUEUE_SRC.map(q => ({ group: q.group, tone: q.tone, age: q.age }))\n        .concat(this.WK_QUEUE.map(q => ({ group: q.type === 'blocker' ? 'Blocking now' : q.type === 'stalled' ? 'FYI / aging' : 'Needs decision', tone: q.type === 'blocker' ? 'fail' : 'warn', age: q.age })));\n      this.SUBHERO.workspace = Object.assign(this.SUBHERO.workspace || {}, { 'Operator queue': {\n        crumb: 'Agent workspace › Workspace › Operator queue', over: 'OPERATOR INBOX · ONE QUEUE', title: 'OPERATOR INBOX',\n        desc: 'One queue for everything that needs a human — permission gates, blocked nodes, human-prio issues, grant requests, billing.',\n        get kpis() { const L = wkq(); const oldest = L.reduce((a, x) => ageMin(x.age) > ageMin(a) ? x.age : a, '—');\n          return [ik('open loops', String(L.length)), ik('blocking a run', String(L.filter(x => x.group === 'Blocking now').length), null, 'red'),\n            ik('critical', String(L.filter(x => x.group === 'Blocking now' && x.tone === 'fail').length), null, 'red'), ik('oldest', oldest),\n            ik('cleared today', String(9 + (wkState().wkCleared || 0)), '+' + (9 + (wkState().wkCleared || 0)), 'teal')]; },\n        actions2: [{ label: 'Clear queue', icon: 'check' }] } });\n      const wkState = () => this.state || {};\n      this.SUBHERO.billing = { 'Billing': Object.assign({}, this.SUBHERO.account.Billing, { crumb: 'Your work › Billing' }) };\n      this.SUBHERO.team = { 'Team': Object.assign({}, this.SUBHERO.account.Team, { crumb: 'Your work › Team' }) };\n      this.STREAMS.billing.kpis = hk(AK.billing.heroKpis); this.STREAMS.billing.actions2 = this.SUBHERO.account.Billing.actions2;\n      this.STREAMS.team.kpis = hk(AK.orgTeam.heroKpis); this.STREAMS.team.actions2 = this.SUBHERO.account.Team.actions2;\n      // W3 · K.<kind>.heroKpis + primaryAction verbatim per Growth tab (kinds-inventory-growth.jsx). Inventory counts derived.\n      {\n        const GA = [['LIVE', 8], ['BUILT', 2], ['SCAFFOLDED', 2], ['PLANNED', 1], ['ABSENT', 2]].reduce((a, x) => (a[x[0]] = x[1], a), {});\n        const gp = (lbl, icon) => [{ label: lbl, icon }];\n        this.SUBHERO.growth = {\n          'System inventory': { crumb: 'Operate › Growth Fleet › System inventory', over: 'SYSTEM & ASSET INVENTORY · MODELED', title: 'SYSTEM & ASSET INVENTORY',\n            desc: 'Everything that exists in the AaaS ecosystem — GCP/platform side + Docker execution layer — tagged by maturity and mapped to a requirement. Legend: LIVE · BUILT · SCAFFOLDED · PLANNED · ABSENT.',\n            kpis: hk([{ label: 'LIVE', value: String(GA.LIVE), trend: 'up' }, { label: 'BUILT', value: String(GA.BUILT) }, { label: 'SCAFFOLDED', value: String(GA.SCAFFOLDED) },\n              { label: 'PLANNED / ABSENT', value: String(GA.PLANNED + GA.ABSENT), trend: 'down' }, { label: '% greenfield', value: Math.round(100 * (GA.PLANNED + GA.ABSENT) / 15) + '%' }]),\n            actions2: gp('Table view', 'table') },\n          'Ops console': { crumb: 'Operate › Growth Fleet › Ops console', over: 'A0 · OPS CONSOLE · MODELED', title: 'A0 · OPS CONSOLE',\n            desc: 'Fleet-status home — audience-building, sending, converting, feedback — at a glance.',\n            kpis: hk([{ label: 'active agents', value: '9', delta: '+2', trend: 'up' }, { label: 'sends today', value: '2.140', delta: '+310', trend: 'up' },\n              { label: 'deliverability', value: '97,2 %', delta: '+0,4', trend: 'up' }, { label: 'budget spent', value: '€ 184,20', delta: '+22', trend: 'down' }]),\n            actions2: gp('Swimlanes view', 'columns') },\n          'Send console': { crumb: 'Operate › Growth Fleet › Send console', over: 'A9 · SEND CONSOLE · MODELED', title: 'A9 · SEND CONSOLE',\n            desc: 'Warmup ramp, throughput headroom, and the raw send log.',\n            kpis: hk([{ label: 'inboxes warming', value: '6' }, { label: 'sends today', value: '2.140', delta: '+310', trend: 'up' },\n              { label: 'bounce rate', value: '0,8 %', delta: '-0,1', trend: 'up' }, { label: 'throughput cap', value: '78 % used', delta: '+6', trend: 'down' }]),\n            actions2: gp('Send log', 'table') },\n          'Analytics': { crumb: 'Operate › Growth Fleet › Analytics', over: 'A10 · ANALYTICS · MODELED', title: 'A10 · ANALYTICS',\n            desc: 'Funnel conversion and weekly retention cohorts across the growth fleet.',\n            kpis: hk([{ label: 'visit → signup', value: '18 %', delta: '+1,2', trend: 'up' }, { label: 'signup → activate', value: '31 %', delta: '+2,0', trend: 'up' },\n              { label: 'W4 retention', value: '42 %', delta: '-3', trend: 'down' }, { label: 'A/B lift', value: '+6,4 %', trend: 'up' }]),\n            actions2: gp('Cohorts view', 'chart') },\n          'Content calendar': { crumb: 'Operate › Growth Fleet › Content calendar', over: 'A11 · CONTENT CALENDAR · MODELED', title: 'A11 · CONTENT CALENDAR',\n            desc: 'Scheduled content across the fleet, and the review-gate queue before anything sends.',\n            kpis: hk([{ label: 'scheduled this week', value: '24', delta: '+6', trend: 'up' }, { label: 'awaiting review', value: '5' },\n              { label: 'approved today', value: '9', delta: '+3', trend: 'up' }, { label: 'rejected this week', value: '1' }]),\n            actions2: gp('Review gate', 'columns') },\n          'Experiment ratchet': { crumb: 'Operate › Growth Fleet › Experiment ratchet', over: 'A17 · EXPERIMENT RATCHET · OPERATOR-DECIDED', title: 'A17 · EXPERIMENT RATCHET',\n            desc: 'The decision trace behind each Lighthouse/variant promotion, and the diff between the current and challenger variant.',\n            kpis: hk([{ label: 'active experiments', value: '3' }, { label: 'promoted this month', value: '2', delta: '+1', trend: 'up' },\n              { label: 'avg lift', value: '+6,4 %', trend: 'up' }, { label: 'Lighthouse score', value: '96', delta: '+4', trend: 'up' }]),\n            actions2: gp('Variant diff', 'content') }\n        };\n      }\n      // W3 · SQUR email suite E1/E2/E3/E5 (canvas/30-audits/pane-sources/squr2/email-*.html) — values verbatim,\n      // Modeled. Tenant data held here (ACC_KINDS precedent) until the tenant-pack fixture loader lands.\n      // Counts that the source shows as totals are DERIVED from the rows they total.\n      this.OUT_SUITE = {\n        overview: { source: 'Firestore sent-emails + GA4', refresh: 'last refresh 06:00 UTC',\n          senders: [{ addr: 'adam@squr-ai.com', tone: 'ok' }, { addr: 'niall@squr-ai.com', tone: 'ok' }, { addr: 'hello@squr-ai.com', tone: 'warn', note: 'warming' }],\n          trend: [{ label: 'Sent · 7d', value: '557', points: '3.0,13.6 22.0,3.0 41.0,7.3 60.0,12.1 79.0,9.3 98.0,11.1 117.0,31.0' },\n            { label: 'Delivered · 7d', value: '557', points: '3.0,13.6 22.0,3.0 41.0,7.3 60.0,12.1 79.0,9.3 98.0,11.1 117.0,31.0' },\n            { label: 'Human Opens · 7d', value: '5', points: '3.0,17.0 22.0,31.0 41.0,3.0 60.0,17.0 79.0,17.0 98.0,31.0 117.0,31.0' }],\n          note: 'No scan_started or demo_booked events in window. This is honest, not missing — the funnel has not converted past human-click yet.' },\n        reputation: { domain: 'squr-ai.com', region: 'region: eu-west-1', source: 'Resend Domains API + DNS', tracking: 'HMAC click-tracking ON',\n          auth: [{ k: 'DMARC', v: 'PASS', d: 'p=reject' }, { k: 'DKIM', v: 'PASS', d: 'selector: resend' }, { k: 'SPF', v: 'PASS', d: '~all (softfail)' }],\n          dns: [{ type: 'DMARC', host: '_dmarc.squr-ai.com', value: 'v=DMARC1; p=reject; rua=mailto:dmarc@squr-ai.com', status: 'pass' },\n            { type: 'SPF', host: 'squr-ai.com', value: 'v=spf1 include:amazonses.com include:spf.resend.com ~all', status: 'pass' },\n            { type: 'DKIM', host: 'resend._domainkey.squr-ai.com', value: 'p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7…k2bQIDAQAB', status: 'pass' },\n            { type: 'MX', host: 'squr-ai.com', value: 'feedback-smtp.eu-west-1.amazonses.com (10)', status: 'pending' }],\n          keyNote: 'Bounce / complaint reputation needs a FULL-ACCESS Resend API key. DNS + auth posture above is resolved independently and stays live.' },\n        variants: { source: 'Firestore sent-emails · ab_variant', filter: 'bot-filtered',\n          rows: [{ id: 'cold-email-b', subject: 'Your external attack surface — 1 finding', sent: 165, hOpen: '1.82%', hClick: '1.82%', scan: '0%', demo: 0, status: 'winner', win: true },\n            { id: 'cold-email-a', subject: 'Quick question on your pentest cadence', sent: 252, hOpen: '0.40%', hClick: '0.40%', scan: '0%', demo: 0, status: 'ok' },\n            { id: 'cold-email-c', subject: 'Cut pentest turnaround from weeks to days', sent: 252, hOpen: '0.40%', hClick: '0.40%', scan: '0%', demo: 0, status: 'ok' },\n            { id: 'test', subject: '[internal] deliverability probe', sent: 88, hOpen: '0.00%', hClick: '0.00%', scan: '0%', demo: 0, status: 'no-send' }],\n          h2h: { title: 'cold-email-b vs cold-email-a', sub: 'human open-rate · 50 / 50 split', flag: 'needs n',\n            arms: [{ label: 'B · attack surface', score: '1.82%', meta: '3 / 165 opens', pct: 100, win: true }, { label: 'A · pentest cadence', score: '0.40%', meta: '1 / 252 opens', pct: 22 }],\n            delta: 'Δ +1.42 pp', sig: 'significance: needs n (n too low)' } },\n        ladder: { source: 'Firestore · t2_live / t3_live flags', refresh: 'last refresh 06:00 UTC', cap: '250',\n          stages: [{ id: 'T1', title: 'T1 · Cold open', live: true, k: 'cohort', v: '4,342', rule: 'Trigger on cohort import · sends immediately within daily cap', foot: '757 delivered to date', action: 'Pause' },\n            { id: 'T2', title: 'T2 · First follow-up', live: false, k: 'eligible', v: '752', rule: 'Trigger +3 days after T1 with no human open · gated by t2_live flag', foot: 'flag t2_live = OFF', action: 'Enable T2' },\n            { id: 'T3', title: 'T3 · Breakup', live: false, k: 'eligible', v: '0', rule: 'Trigger +7 days after T2 with no human click · gated by t3_live flag', foot: 'flag t3_live = OFF · upstream T2 not live', action: 'Enable T3' }] }\n      };\n      {\n        const OS = this.OUT_SUITE, V = OS.variants.rows, L = OS.ladder.stages;\n        const best = V.reduce((a, r) => parseFloat(r.hOpen) > parseFloat(a.hOpen) ? r : a, V[0]);\n        this.SUBHERO.outreach = {\n          'Overview': { crumb: 'Go-to-market › Outreach › Overview', over: 'E2 · CAMPAIGN OVERVIEW · MODELED', title: 'CAMPAIGN OVERVIEW',\n            desc: 'Cohort, sends and human engagement across the active window. Several metrics read 0 by design — daily quota is unset and scan/demo conversion has not fired yet.',\n            kpis: [ik('Cohort size', '4,342', 'T1'), ik('Sent today', '0', 'quota unset'), ik('Delivered', '757', 'window'), ik('Human-Open Rate', '0.11%'),\n              ik('Human-Click Rate', '0.11%'), ik('Scan Started', '0'), ik('Demo Booked', '0'), ik('Daily cap', OS.ladder.cap + ' /day', '→ 2026-07-10')] },\n          'Deliverability': { crumb: 'Go-to-market › Outreach › Deliverability', over: 'E1 · EMAIL DELIVERABILITY · MODELED', title: 'EMAIL DELIVERABILITY',\n            desc: 'Auth posture and DNS records for the sending domain. Bounce and complaint rates require a full-access Resend key — the current key is send-only.',\n            kpis: [ik('Bounce rate', 'pending', 'needs full-access Resend key'), ik('Complaint rate', 'pending', 'needs full-access Resend key'),\n              ik('Auth pass', OS.reputation.auth.filter(a => a.v === 'PASS').length + ' / ' + OS.reputation.auth.length, 'all-clear', 'teal'), ik('Click tracking', 'HMAC ON')],\n            actions2: [{ label: 'Re-check DNS', icon: 'refresh' }] },\n          'Variants': { crumb: 'Go-to-market › Outreach › Variants', over: 'E3 · A/B/C VARIANT PERFORMANCE · MODELED', title: 'A/B/C VARIANT PERFORMANCE',\n            desc: 'Cold-email subject-line tests. Winner is decided on human open-rate with bot traffic filtered out. Promoting pushes the subject to the live sequence behind a HITL gate.',\n            kpis: [ik('Variants tested', String(V.length)), ik('Best H-OR', best.hOpen, best.id, 'teal'), ik('Total sent', String(V.reduce((a, r) => a + r.sent, 0))), ik('Best scan %', '0%')],\n            actions2: [{ label: 'New variant', icon: 'plus' }] },\n          'Follow-up ladder': { crumb: 'Go-to-market › Outreach › Follow-up ladder', over: 'E5 · FOLLOW-UP LADDER · MODELED', title: 'FOLLOW-UP LADDER',\n            desc: 'Three-stage sequence T1 → T2 → T3. Only T1 is live; T2 and T3 are flag-gated and stay locked until promoted. Enabling a stage is a mutating action behind a HITL gate.',\n            kpis: [ik('Stages live', L.filter(x => x.live).length + ' / ' + L.length), ik('T2 eligible', L[1].v), ik('T3 eligible', L[2].v), ik('Daily cap', OS.ladder.cap + ' /day')],\n            actions2: [{ label: 'Sequence settings', icon: 'gear' }] }\n        };\n      }\n      // W3 · SQUR §2 funnel + E4 persona + social suite → Marketing (D152). Verbatim from\n      // canvas/30-audits/pane-sources/squr2/{funnel-dashboard,email-funnel-personas,social-*}.html · Modeled (Postiz MOCK).\n      this.MK_SUITE = {\n        funnel: { source: 'source: bigquery · squr-487620.analytics_535158695', filter: 'bot-filtered', refresh: 'last refresh 06:00 UTC',\n          steps: [{ label: 'Sent', ev: 'email_sent', human: 757, conv: '100%' }, { label: 'Delivered', ev: 'delivered', human: 757, conv: '100.0%' },\n            { label: 'Human Open', ev: 'human_opened', human: 5, conv: '0.66%' }, { label: 'Human Click', ev: 'human_clicked', human: 5, bot: 218, count: '5 human · 218 bot', conv: 'CTOR 100%' },\n            { label: 'Scan Started', ev: 'scan_started', human: 0, conv: '0%' }, { label: 'Scan Done', ev: 'scan_done', human: 0, conv: '0%' }, { label: 'Demo Booked', ev: 'demo_booked', human: 0, conv: '0%' }],\n          variants: [['cold-email-b', 165, 3, 3, 64, '100%', 'winner'], ['cold-email-a', 252, 1, 1, 96, '100%', 'ok'], ['cold-email-c', 252, 1, 1, 58, '100%', 'ok'], ['test', 88, 0, 0, 0, '—', 'no-send']],\n          personas: [['SOC Lead', 'cold-email-b', 165, 3, 3, 0, 0, 'best'], ['CISO', 'cold-email-a', 210, 1, 1, 0, 0], ['Head of Security', 'cold-email-c', 200, 1, 1, 0, 0],\n            ['Pentest Manager', 'cold-email-a', 94, 0, 0, 0, 0], ['Internal / test', 'test', 88, 0, 0, 0, 0]] },\n        social: { source: 'source: postiz · MOCK — api.postiz.com not wired', refresh: 'snapshot 06:00 UTC',\n          channels: [{ code: 'in', name: 'LinkedIn', cards: [{ text: 'CISA added CVE-2026-3491 to the KEV catalog — actively exploited in the wild. Federal patch deadline: 2026-07-11.', tpl: 'kev-watch', st: 'needs approval' },\n              { text: 'Breach autopsy: one exposed .env file → full tenant compromise. The 4 controls that would have stopped it.', tpl: 'breach-autopsy', st: 'draft' }] },\n            { code: 'X', name: 'X', cards: [{ text: 'LOLBin of the week: certutil.exe. Living-off-the-land download + decode in one line. Detection logic in thread.', tpl: 'lolbin-of-the-week', st: 'needs approval' },\n              { text: 'EPSS mover: CVE-2026-2210 jumped 0.04 → 0.71 this week. Exploitation probability now top-decile.', tpl: 'epss-mover', st: 'draft' }] },\n            { code: 'bs', name: 'Bluesky', cards: [{ text: 'CWE-79 still tops the 2026 weakness list. Why reflected XSS refuses to die.', tpl: 'cwe-of-the-week', st: 'draft' }] },\n            { code: 'th', name: 'Threads', cards: [{ text: 'Defense spotlight: canary tokens. The cheapest tripwire you are not deploying yet.', tpl: 'defense-spotlight', st: 'draft' }] },\n            { code: 'ma', name: 'Mastodon', cards: [{ text: 'Actor dossier: Scattered Spider TTP map across MITRE ATT&CK. Initial access → exfil.', tpl: 'actor-dossier', st: 'draft' }] }],\n          templates: [['kev-watch', 'weekly', '2026-06-16', 'active'], ['lolbin-of-the-week', 'weekly', '2026-06-15', 'active'], ['breach-autopsy', 'biweekly', '2026-06-09', 'active'],\n            ['cwe-of-the-week', 'weekly', '2026-06-14', 'active'], ['actor-dossier', 'monthly', '2026-05-28', 'active'], ['control-map', 'biweekly', '2026-06-02', 'active'],\n            ['epss-mover', 'weekly', '2026-06-16', 'active'], ['crosswalk-insight', 'monthly', '—', 'paused'], ['defense-spotlight', 'biweekly', '—', 'draft']],\n          schedule: [['2026-06-23 09:00', 'in', 'LinkedIn', 'kev-watch', 'scheduled', true], ['2026-06-24 12:00', 'X', 'X', 'lolbin-of-the-week', 'scheduled'],\n            ['2026-06-25 09:00', 'bs', 'Bluesky', 'cwe-of-the-week', 'draft'], ['2026-06-26 15:00', 'th', 'Threads', 'defense-spotlight', 'draft'],\n            ['2026-06-27 10:00', 'ma', 'Mastodon', 'actor-dossier', 'needs approval'], ['2026-06-30 09:00', 'in', 'LinkedIn', 'breach-autopsy', 'draft']],\n          pending: [{ id: 'kev-watch', code: 'in', channel: 'LinkedIn', when: 'scheduled 2026-06-23 09:00', text: 'CISA added CVE-2026-3491 to the KEV catalog — actively exploited in the wild. Federal patch deadline: 2026-07-11. Full advisory + affected versions on kb.squr.ai.' },\n            { id: 'actor-dossier', code: 'ma', channel: 'Mastodon', when: 'scheduled 2026-06-27 10:00', text: 'Actor dossier: Scattered Spider TTP map across MITRE ATT&CK — initial access via help-desk social engineering through to data exfil. Detections mapped to D3FEND.' }],\n          published: 3, target: 5,\n          campaignQueue: [{ title: 'nis2-compliance-digest send window', meta: 'awaiting a schedule pick', chip: 'pending' },\n            { title: 'Replay Postiz syndication · 05-27', meta: 'awaiting OpenRouter quota · 2h', chip: 'held' },\n            { title: 'x syndication · relay-launch clip', meta: 'rate-limited by platform · 3 retries exhausted', chip: 'failed' }] },\n          // D6 (charter socialreview) · review state in exactly the shape the pane consumes it:\n          // the ledger-to-calendar bridge's output (tools/social/calendar/ledger-to-calendar.mjs;\n          // render-calendar.mjs writes the same object to assets/data/social-review-<tenant>-calendar.json).\n          // MODELED snapshot of the D3 groups ledger as of 2026-07-01 — that stated snapshot day is\n          // why breach-autopsy's deadline is overdue and actor-dossier's is not. autoPublish is the\n          // D1 silence-consent default (7 days); the tenant's own manifest value overrides it.\n          review: {\n            autoPublish: { enabled: true, days: 7 },\n            items: [\n              { id: 'kev-watch-2026-06-23', date: '2026-06-23', title: 'kev-watch', class: 'scheduled', reviewState: 'approved' },\n              { id: 'lolbin-of-the-week-2026-06-24', date: '2026-06-24', title: 'lolbin-of-the-week', class: 'scheduled', reviewState: 'approved' },\n              { id: 'cwe-of-the-week-2026-06-25', date: '2026-06-25', title: 'cwe-of-the-week', class: 'published', reviewState: 'published' },\n              { id: 'control-map-2026-06-22', date: '2026-06-22', title: 'control-map', class: 'published', reviewState: 'auto-published after the silence window' },\n              { id: 'breach-autopsy-2026-06-30', date: '2026-06-30', title: 'breach-autopsy', class: 'draft', reviewState: 'awaiting review', deadline: '2026-06-30', overdue: true },\n              { id: 'epss-mover-2026-07-02', date: '2026-07-02', title: 'epss-mover', class: 'draft', reviewState: 'edit requested' },\n              { id: 'actor-dossier-2026-07-03', date: '2026-07-03', title: 'actor-dossier', class: 'draft', reviewState: 'awaiting review', deadline: '2026-07-03', overdue: false }\n            ],\n            rejected: [\n              { id: 'defense-spotlight-2026-06-26', date: '2026-06-26', title: 'defense-spotlight', reviewState: 'rejected' }\n            ] }\n      };\n      {\n        const M = this.MK_SUITE, F = M.funnel, SO = M.social;\n        const cards = SO.channels.reduce((a, c) => a.concat(c.cards), []);\n        // D6 · hero KPIs derive from the same review items the pane's Calendar/Queue render —\n        // one source per number, so the hero cannot contradict the pane body.\n        const RV = SO.review || null, RVI = RV && Array.isArray(RV.items) ? RV.items : [];\n        const RVAP = RV && RV.autoPublish ? RV.autoPublish : { enabled: false, days: 7 };\n        const RVAW = RVI.filter(i => i.reviewState === 'awaiting review');\n        const RVSCH = RVI.filter(i => i.class === 'scheduled').sort((a, b) => a.date.localeCompare(b.date));\n        const autoLabel = RVAP.enabled ? 'ON · ' + RVAP.days + 'D' : 'OFF';\n        const bestP = F.personas.reduce((a, p) => (p[3] / p[2]) > (a[3] / a[2]) ? p : a, F.personas[0]);\n        const pct = (n) => (Math.round(n * 10000) / 100).toFixed(2) + '%';\n        this.SUBHERO.marketing = Object.assign(this.SUBHERO.marketing || {}, {\n          'Funnel': { crumb: 'Marketing › Funnel', over: '§2 · CONVERSION FUNNEL · E4 BY PERSONA · MODELED', title: 'CONVERSION FUNNEL',\n            desc: 'Sent → demo booked, bot-filtered. Human engagement is split from bot/unknown traffic on the click steps so the real signal isn\\'t masked by scanners.',\n            kpis: [ik('Sent', String(F.steps[0].human)), ik('Human Opens', String(F.steps[2].human), '0.11% · low', 'red') /* verbatim: source hero says 0.11%, its own funnel row says 0.66% (5/757) — source defect, logged in W3-PaneMarketing.md */, ik('Human Clicks', String(F.steps[3].human), 'CTOR 100%'),\n              ik('Bot / Unknown Clicks', String(F.steps[3].bot), '98%', 'red'), ik('Scan Started', '0'), ik('Demo Booked', '0'), ik('Best persona H-OR', pct(bestP[3] / bestP[2]), bestP[0], 'teal')] },\n          'Social': { crumb: 'Marketing › Social', over: 'SOCIAL QUEUE · AUTO-PUBLISH OFF · MOCK', title: 'SOCIAL QUEUE',\n            desc: 'Drafts staged across five channels. Nothing publishes itself — every card moves to Approvals before it can go live. Mock data until the Postiz API is wired.',\n            kpis: [ik('Drafts pending', String(cards.length)), ik('Channels active', String(SO.channels.length)), ik('Approval queue', String(SO.pending.length), 'review', 'red'), ik('Published this week', SO.published + ' / ' + SO.target + ' target')],\n            actions2: [{ label: 'New draft', icon: 'plus' }] },\n          'Calendar': { crumb: 'Marketing › Calendar', over: 'SOCIAL SCHEDULE · AUTO-PUBLISH ' + autoLabel + ' · MOCK', title: 'SOCIAL SCHEDULE',\n            desc: 'The social review calendar (D6): drafts, scheduled and published posts from the review ledger, each with its review state. Auto-publish follows the tenant policy — under the D1 silence-consent default a draft with no response publishes itself after ' + RVAP.days + ' days.',\n            kpis: [ik('Calendar items', String(RVI.length)), ik('Next publish', RVSCH.length ? RVSCH[0].date : '—'), ik('Awaiting review', String(RVAW.length), 'gate', 'red'), ik('Rejected', String(RV && Array.isArray(RV.rejected) ? RV.rejected.length : 0))] },\n          'Queue': { crumb: 'Marketing › Queue', over: 'SOCIAL APPROVALS · THE PUBLISH GATE · MOCK', title: 'SOCIAL APPROVALS',\n            desc: 'The review gate. Silence consents: a draft with no response publishes itself after ' + RVAP.days + ' days (tenant policy) — approve, reject or request an edit before the deadline. ' + (RVAW.length ? RVAW.length + ' items are waiting on you.' : 'The queue is clear.'),\n            kpis: [ik('Pending review', String(RVAW.length), 'action', 'red'), ik('Published this week', SO.published + ' / ' + SO.target + ' target'), ik('Auto-publish', autoLabel), ik('Cadence target', SO.target + ' /week')],\n            actions2: [{ label: 'Approval log', icon: 'timeline' }] }\n        });\n      }\n      // socialpane (2026-10-05): point the MOCK Social subhero at the live process stream — an\n      // additive append (the shipped desc string and the mock itself stay byte-identical; the\n      // charter's no-deletion constraint).\n      if (this.SUBHERO.marketing && this.SUBHERO.marketing['Social']) this.SUBHERO.marketing['Social'].desc += ' The real pipeline is the Marketing › Social · v2 stream — one Process tab over the social-publishing template, its instance terminal-produced from the review tools, the tenant manifest and the blocked Postiz seam.';\n      // K.servicesWizard.heroKpis — labels verbatim, values derived from the live catalog exactly as the source derives them.\n      const SC = this.SVC_CATALOG || [];\n      this.SUBHERO.onboarding = {\n        'Getting started': { crumb: 'Personal › Onboarding › Getting started', over: 'ONBOARDING · SERVICES TOGGLE', title: 'GETTING STARTED',\n          desc: 'The Canvas first-step. Six steps from signup to an activated Canvas surface.',\n          kpis: [ik('Onboarding steps', '6'), ik('Services', String(SC.length)), ik('Categories', String((this.SVC_CATS || []).length)), ik('Default enabled', String(SC.filter(x => x.on).length))],\n          actions2: [] }\n      };\n    }\n\n    // Entity graph — 11 collections, 12 nodes, 9 edges, 3 in the HITL queue\n    this.IG = {\n      collections: [\n        { id: 'tools', label: 'Tools', count: 680, tint: 'cy' },\n        { id: 'models', label: 'Models', count: 560, tint: 'cy' },\n        { id: 'agents', label: 'Agents', count: 240, tint: 'cy' },\n        { id: 'papers', label: 'Papers', count: 1200, tint: 'lav' },\n        { id: 'skills', label: 'Skills', count: 1840, tint: 'gd' },\n        { id: 'scripts', label: 'Scripts', count: 680, tint: 'gd' },\n        { id: 'benchmarks', label: 'Benchmarks', count: 420, tint: 'tl' },\n        { id: 'datasets', label: 'Datasets', count: 580, tint: 'tl' },\n        { id: 'providers', label: 'Providers', count: 320, tint: 'red' },\n        { id: 'hardware', label: 'Hardware', count: 180, tint: 'red' },\n        { id: 'integrations', label: 'Integrations', count: 260, tint: 'lav' }\n      ],\n      nodes: [\n        { id: 'claude-sonnet-4.5', label: 'Claude Sonnet 4.5', collection: 'models', x: 50, y: 30, edges: 24 },\n        { id: 'anthropic', label: 'Anthropic', collection: 'providers', x: 25, y: 18, edges: 38 },\n        { id: 'langchain', label: 'LangChain', collection: 'agents', x: 72, y: 25, edges: 42 },\n        { id: 'mt-bench', label: 'MT-Bench', collection: 'benchmarks', x: 55, y: 62, edges: 12 },\n        { id: 'mmlu', label: 'MMLU', collection: 'benchmarks', x: 38, y: 70, edges: 18 },\n        { id: 'h100', label: 'NVIDIA H100', collection: 'hardware', x: 80, y: 55, edges: 14 },\n        { id: 'crewai', label: 'CrewAI', collection: 'agents', x: 85, y: 38, edges: 8 },\n        { id: 'attention-is-all', label: 'Attention Is All You Need', collection: 'papers', x: 28, y: 50, edges: 32 },\n        { id: 'hf-datasets', label: 'HuggingFace Datasets', collection: 'datasets', x: 18, y: 75, edges: 22 },\n        { id: 'stripe-slack-bridge', label: 'Stripe↔Slack', collection: 'integrations', x: 65, y: 80, edges: 6 },\n        { id: 'pytorch', label: 'PyTorch', collection: 'tools', x: 42, y: 38, edges: 28 },\n        { id: 'gpt-5', label: 'GPT-5', collection: 'models', x: 60, y: 14, edges: 19 }\n      ],\n      edges: [\n        { src: 'claude-sonnet-4.5', tgt: 'anthropic', predicate: 'made_by', confidence: 1.0 },\n        { src: 'gpt-5', tgt: 'anthropic', predicate: 'competes_with', confidence: 0.92 },\n        { src: 'langchain', tgt: 'claude-sonnet-4.5', predicate: 'uses', confidence: 0.95 },\n        { src: 'crewai', tgt: 'langchain', predicate: 'depends_on', confidence: 0.84 },\n        { src: 'mt-bench', tgt: 'claude-sonnet-4.5', predicate: 'cites', confidence: 0.91 },\n        { src: 'mmlu', tgt: 'claude-sonnet-4.5', predicate: 'cites', confidence: 0.88 },\n        { src: 'h100', tgt: 'pytorch', predicate: 'supports', confidence: 0.78 },\n        { src: 'attention-is-all', tgt: 'pytorch', predicate: 'implements', confidence: 0.82 },\n        { src: 'hf-datasets', tgt: 'mmlu', predicate: 'hosts', confidence: 0.94 }\n      ],\n      hitl: [\n        { id: 'h-1', src: 'crewai', tgt: 'langchain', predicate: 'depends_on', confidence: 0.72, url: 'github.com/joaomdmoura/crewai/issues/1842', quote: 'CrewAI builds on LangChain primitives for tool chaining.' },\n        { id: 'h-2', src: 'mt-bench', tgt: 'gpt-5', predicate: 'cites', confidence: 0.68, url: 'arxiv.org/abs/2306.05685', quote: 'GPT-5 achieves 9.31 on MT-Bench Tier 2.' },\n        { id: 'h-3', src: 'h100', tgt: 'claude-sonnet-4.5', predicate: 'supports', confidence: 0.79, url: 'anthropic.com/news/sonnet-4.5', quote: 'Sonnet 4.5 inference best-in-class on H100 SXM.' }\n      ]\n    };\n\n    // Vault — tier distribution, 7 categories, 8 featured skills\n    this.VL = {\n      dist: { gold: 412, silver: 1840, bronze: 3160 },\n      cats: [\n        { label: 'Writing', count: 1240, gold: 142, silver: 480, bronze: 618 },\n        { label: 'Coding', count: 1180, gold: 98, silver: 412, bronze: 670 },\n        { label: 'Analysis', count: 920, gold: 84, silver: 318, bronze: 518 },\n        { label: 'Design', count: 540, gold: 28, silver: 188, bronze: 324 },\n        { label: 'Research', count: 680, gold: 32, silver: 240, bronze: 408 },\n        { label: 'Orchestration', count: 482, gold: 18, silver: 142, bronze: 322 },\n        { label: 'Finance', count: 370, gold: 10, silver: 60, bronze: 300 }\n      ],\n      featured: [\n        { name: 'ap-reconciliation/match', cat: 'Finance', tier: 'gold', score: '94 %', ctx: true, uses: '4.820 runs', up: '24.05' },\n        { name: 'meeting-prep/brief', cat: 'Analysis', tier: 'gold', score: '91 %', ctx: true, uses: '3.140 runs', up: '22.05' },\n        { name: 'podcast-brief/ranker', cat: 'Writing', tier: 'gold', score: '89 %', ctx: false, uses: '2.210 runs', up: '20.05' },\n        { name: 'pr-summary/changelog', cat: 'Coding', tier: 'silver', score: '78 %', ctx: false, uses: '1.620 runs', up: '18.05' },\n        { name: 'cv-builder/rewrite', cat: 'Writing', tier: 'silver', score: '74 %', ctx: true, uses: '1.180 runs', up: '14.05' },\n        { name: 'headshot-pack/prompt', cat: 'Design', tier: 'gold', score: '92 %', ctx: false, uses: '894 runs', up: '12.05' },\n        { name: 'crm-enrich/match', cat: 'Analysis', tier: 'silver', score: '71 %', ctx: true, uses: '712 runs', up: '10.05' },\n        { name: 'invoice-router/classify', cat: 'Finance', tier: 'bronze', score: '62 %', ctx: false, uses: '418 runs', up: '08.05' }\n      ]\n    };\n\n    // NotebookLM sync — pipeline health, 5 megadocs, 2 notebooks\n    this.MS = {\n      health: [\n        { label: 'LaunchAgent', v: '12 min ago · 17:38', sub: '0 errors / 24 h' },\n        { label: 'DWD token', v: '14.07.2026 (47 d)', sub: 'last success 12 min ago' }\n      ],\n      docs: [\n        { f: 'INDEX.md', size: '14 KB', sync: '17:38', drive: '1A_xK…', indexed: '17:42' },\n        { f: 'MEGADOC-part-1.md', size: '342 KB', sync: '17:38', drive: '1B_yL…', indexed: '17:44' },\n        { f: 'MEGADOC-part-2.md', size: '318 KB', sync: '17:38', drive: '1C_zM…', indexed: '17:45' },\n        { f: 'MEGADOC-part-3.md', size: '298 KB', sync: '17:38', drive: '1D_aN…', indexed: '17:46' },\n        { f: 'MEGADOC-part-4.md', size: '326 KB', sync: '17:38', drive: '1E_bO…', indexed: '17:48' }\n      ],\n      notebooks: [\n        { name: 'aaas · agentic architecture & best practices', uuid: '337118fc-44b9-4580…', sources: '5 sources', primary: true },\n        { name: 'aaas-internals', uuid: '037be2cf-…', sources: '5 sources', primary: false }\n      ]\n    };\n\n    // KB — personal / workspace / project\n    this.KBT = [\n      { label: 'Personal KB · jor@gmail.com', icon: 'person', count: '142', docs: [\n        { f: 'about-me.md', tag: 'identity' }, { f: 'communication-style.md', tag: 'preferences' },\n        { f: 'timezone-and-availability.md', tag: 'context' }, { f: 'tools-i-use.md', tag: 'context' }] },\n      { label: 'Workspace KB · Intrinsic GmbH', icon: 'finances', count: '88', docs: [\n        { f: 'company-mission.md', tag: 'identity' }, { f: 'customer-personas.md', tag: 'product' },\n        { f: 'tone-of-voice.md', tag: 'brand' }, { f: 'gdpr-compliance.md', tag: 'compliance' }] },\n      { label: 'Project KB · Q3-2026-launch', icon: 'play', count: '112', docs: [\n        { f: 'goals.md', tag: 'project' }, { f: 'constraints.md', tag: 'project' }, { f: 'stakeholders.md', tag: 'project' }] }\n    ];\n\n    // Query audit — who asked what, and what it cost\n    this.IAUD = [\n      { ts: '17:48', actor: 'jor@gmail.com', verb: 'asked', target: '\"Which LLM is best for GDPR-compliant invoice processing?\"', meta: '4 sources · €0,004 · 1,4 s', tone: 'ok' },\n      { ts: '17:46', actor: 'patrick@intrinsic', verb: 'asked', target: '\"Confidence threshold for auto-confirm?\"', meta: '2 sources · €0,002 · 0,8 s', tone: 'ok' },\n      { ts: '17:34', actor: 'ap-recon agent', verb: 'queried', target: 'fuzzy_match thresholds in vault', meta: '1 source · €0,001 · 0,3 s', tone: 'ok' },\n      { ts: '17:18', actor: 'jor@gmail.com', verb: 'asked', target: '\"Best practice for Decision-Branch confidence\"', meta: '3 sources · €0,003 · 1,1 s', tone: 'ok' },\n      { ts: '16:42', actor: 'reyes@korufm', verb: 'asked', target: '\"How does Replay-from-Step work?\"', meta: '2 sources · €0,002 · 0,9 s', tone: 'ok' },\n      { ts: '15:18', actor: 'jor@gmail.com', verb: 'reviewed', target: 'edge h-1 · CrewAI depends_on LangChain', meta: 'accepted · evidence verified', tone: 'warn' },\n      { ts: '14:02', actor: 'system', verb: 'pipeline', target: 'graph extractor v3.1 · 142 new entities', meta: '+142 entities · +1.2k edges', tone: 'ok' },\n      { ts: '12:00', actor: 'system', verb: 'sync', target: 'NotebookLM megadocs · 5 / 5 OK', meta: 'LaunchAgent · DWD', tone: 'ok' }\n    ];\n\n    // Prompt library — 8 prompts, versioned, two live A/B tests\n    this.PLL = [\n      { id: 'p-co-system', slug: 'agent-co-system-v3', kind: 'system', tags: ['brand', 'voice'], lang: 'de', ab: true,\n        agents: 1, runs: '4.832 runs/30d', tokens: '487t', active: 'v3.2',\n        body: 'You are Client Orchestrator (CO). Run a structured 4-tier interview with {{client_name}} from the {{industry}} industry.\\n\\nKeep the tone calm, structured, and competence-signaling. Never close-end on tier 1.\\n\\nTier 1 — discovery (3 questions max):\\n  · What outcome would make the next 90 days a win?\\n  · Where do agents fit on the priority list today?\\n  · Who else should be in the room?\\n\\nTier 2..4 follow the same shape but go deeper per branch detected.\\n\\nOutput: GOAL_BRIEF.md + CLIENT_ACCOUNTS.md.',\n        vars: '{{client_name}} {{industry}} {{goal_focus}}', models: 'gemini-2.5-flash, claude-opus-4-7',\n        test: { a: 'v3.2 · 60%', av: '0.78', b: 'v3.3-exp · 40%', bv: '0.81', foot: 'n = 287', delta: 'Δ +3.0%' },\n        versions: [\n          { v: 'v3.2', on: true, ago: '2d ago', by: 'Jorian', note: 'Shorter + industry dynamic' },\n          { v: 'v3.1', ago: '12d ago', by: 'Tobias', note: 'Spelling + tonality' },\n          { v: 'v3.0', ago: '1mo ago', by: 'Jorian', note: 'Structured 4-tier question shape' },\n          { v: 'v2.4', ago: '3mo ago', by: 'Jorian', note: 'Archive · pre-restructure' }\n        ], usedBy: ['Client Orchestrator'] },\n      { id: 'p-brand-voice', slug: 'brand-voice-formal-de', kind: 'template', tags: ['brand', 'voice'], lang: 'de',\n        agents: 8, runs: '12.480 runs/30d', tokens: '142t', active: 'v1.5',\n        body: 'Voice: technical · confident · terse.\\n\\nUse: agents · builders · platform · context · deploy.\\nDon\\u2019t use: chatbots · users · solution · knowledge base · launch.\\n\\nHeadlines short. Imperative when possible. Body sentence-case + direct.',\n        vars: '—', models: 'gemini-2.5-flash, claude-opus-4-7',\n        versions: [\n          { v: 'v1.5', on: true, ago: '5d ago', by: 'Tobias', note: 'Sharpened do/don\\u2019t pairs' },\n          { v: 'v1.4', ago: '3w ago', by: 'Tobias', note: 'Initial public draft' }\n        ], usedBy: ['Client Orchestrator', 'Cold-Email Personalizer', 'Prospect Intel', 'Brand Canvas', 'Blog Writer', 'Podcast Brief', 'Social Content', 'Action Pack Gen'] },\n      { id: 'p-cold-email-cta', slug: 'cold-email-cta-v4', kind: 'per_step', tags: ['experimental'], lang: 'en', ab: true,\n        agents: 1, runs: '1.820 runs/30d', tokens: '86t', active: 'v4',\n        body: 'CTA shape:\\n\\n1. One sentence that names the recipient\\u2019s outcome (not your product).\\n2. One sentence that proves you\\u2019ve done the homework (one specific fact).\\n3. One sentence that offers a 15-min walkthrough next Tuesday or Thursday.\\n\\nNo \"circling back\". No \"quick chat\". No \"synergies\".',\n        vars: '{{recipient_name}} {{recipient_signal}}', models: 'gemini-2.5-flash',\n        test: { a: 'v4 · 50%', av: '0.22', b: 'v5-exp · 50%', bv: '0.26', foot: 'n = 412', delta: 'Δ +4.0%' },\n        versions: [\n          { v: 'v4', on: true, ago: '1d ago', by: 'Jorian', note: 'Reply rate 14 → 22 % in pilot' },\n          { v: 'v3', ago: '8d ago', by: 'Jorian', note: 'Tightened the homework sentence' }\n        ], usedBy: ['Cold-Email Personalizer'] },\n      { id: 'p-pics-style', slug: 'pics-style-axes-prompt', kind: 'template', tags: ['experimental'], lang: 'en',\n        agents: 1, runs: '8.820 runs/30d', tokens: '218t', active: 'v1.2',\n        body: 'A {{composition}} on {{background}}, shot with a {{camera}}, lit by {{lighting}}.\\n\\nKeep the subject\\u2019s face the focal point. No surreal artifacts. No extra limbs. Skin natural, no over-smoothing.',\n        vars: '{{composition}} {{background}} {{camera}} {{lighting}}', models: 'flux-1.5-pro',\n        versions: [\n          { v: 'v1.2', on: true, ago: '4d ago', by: 'Jorian', note: 'Skin guardrail added' },\n          { v: 'v1.1', ago: '12d ago', by: 'Jorian', note: '4 axes' }\n        ], usedBy: ['.pics Studio'] },\n      { id: 'p-safety-base', slug: 'safety-guardrail-base', kind: 'guardrail', tags: ['safety'], lang: 'multi',\n        agents: 6, runs: '24.180 runs/30d', tokens: '312t', active: 'v2.1',\n        body: 'Refuse: sexual content involving minors · weapons synthesis · self-harm instructions.\\n\\nFlag for review: claims of medical / legal / financial advice without disclaimer.\\n\\nLog: every refusal with category + run_id.',\n        vars: '—', models: 'gemini-2.5-flash, claude-opus-4-7, flux-1.5-pro',\n        versions: [\n          { v: 'v2.1', on: true, ago: '14d ago', by: 'jorian (admin-only)', note: 'Added flag-not-refuse for medical disclaimers' },\n          { v: 'v2.0', ago: '2mo ago', by: 'jorian (admin-only)', note: 'Multi-language reference table' }\n        ], usedBy: ['Client Orchestrator', 'Cold-Email Personalizer', 'Prospect Intel', 'Action Pack Gen', '.pics Studio', 'Blog Writer'] },\n      { id: 'p-ri-system', slug: 'agent-ri-system-v2', kind: 'system', tags: [], lang: 'de',\n        agents: 1, runs: '1.840 runs/30d', tokens: '612t', active: 'v2.0',\n        body: 'You are Research Initiator (RI). Take a goal brief and produce 5 evidence-backed hypotheses worth testing this quarter…',\n        vars: '{{goal_brief}}', models: 'claude-opus-4-7',\n        versions: [{ v: 'v2.0', on: true, ago: '3w ago', by: 'Tobias', note: 'Initial' }], usedBy: ['Research Initiator'] },\n      { id: 'p-output-goalbrief', slug: 'output-schema-goal-brief', kind: 'output_schema', tags: [], lang: 'multi',\n        agents: 1, runs: '4.820 runs/30d', tokens: '142t', active: 'v1',\n        body: '{ \"client\": { \"name\": str, \"industry\": str }, \"outcome_focus\": str, \"tier_questions\": [ … ], \"next_steps\": [str] }',\n        vars: '—', models: 'gemini-2.5-flash, claude-opus-4-7',\n        versions: [{ v: 'v1', on: true, ago: '1mo ago', by: 'Jorian', note: 'Initial schema' }], usedBy: ['Client Orchestrator'] },\n      { id: 'p-fewshot-co', slug: 'fewshot-co-interview-examples', kind: 'fewshot_example', tags: [], lang: 'de',\n        agents: 1, runs: '4.820 runs/30d', tokens: '1.420t', active: 'v1.1',\n        body: '[Example 1]\\nclient: ACME GmbH\\nindustry: SaaS\\noutput: { … }\\n\\n[Example 2] …',\n        vars: '—', models: 'gemini-2.5-flash',\n        versions: [\n          { v: 'v1.1', on: true, ago: '8d ago', by: 'Jorian', note: 'Added Mittelstand example' },\n          { v: 'v1.0', ago: '1mo ago', by: 'Jorian', note: 'Initial 3 examples' }\n        ], usedBy: ['Client Orchestrator'] }\n    ];\n\n    // Closeout — 17 ratification themes, 20 surfaces still to brief, 3 build phases\n    this.COB = {\n      themes: [\n        { id: 1, title: 'Canvas IA structure', q: 'New sections as top-level modules or nested?',\n          rec: '8 top-level modules · DESIGN / PODCAST / COSTS / OPS / QUALITY / MARKETING + new CONTENT + new INTELLIGENCE. No further top-level additions. Everything else nests; Domains route via 3-bucket left-rail grouping (CONTENT/MARKET/COMMUNITY).',\n          touches: '#1735 · #1742 Q3 · #1744 · #1749 · #1750 · #1751 · #1753 · #1755' },\n        { id: 2, title: 'Subscription tier retention + budget', q: 'Same retention scheme across every surface?',\n          rec: 'Uniform 7d-free / 30d-paid / 90d-enterprise · across Decisions traces · query history · inference history · Pics images · Diy private transcripts · Tasks · Agent runs. Public sources cache forever; rejected inferences retain longer as training data.',\n          touches: '#1741 Q12 · #1749 Q7 · #1750 Q12 · #1751 Q8 · #1753 Q6 · #1755 Q13' },\n        { id: 3, title: 'RBAC scope', q: 'Admin-only or role-tiered?',\n          rec: 'Tiered. v1 admin-only for context-editing + Vault tier promotion + Prompt edits. v2 = 9-role model (owner/admin/brand_editor/product_owner/sales_lead/legal_editor/ops_editor/hr_editor/viewer). Prompts inherit RBAC from tags.',\n          touches: '#1750 Q5+Q6+Q10 · #1751 Q2 · #1755 Q6' },\n        { id: 4, title: 'Component library packaging', q: 'New package or extend?',\n          rec: 'Extend @supraforge/ui with canvas/ sub-export · canvas/content/ · canvas/finance/ · canvas/intelligence/. No new package.',\n          touches: '#1742 Q9+Q13 · #1744 Q11+Q13' },\n        { id: 5, title: 'Public TLD subdomains vs Canvas', q: 'Keep TLDs or sunset?',\n          rec: 'Keep all TLDs alive forever · user portals + SEO + email-signature value. Canvas = operator + power-user lens · two render contexts via mode: \"operator\" | \"user\" prop. aaas.love stays deferred.',\n          touches: '#1737 · #1744 Q5+Q10+Q14 · #1750 Q15 · #1751 · #1753 Q12' },\n        { id: 6, title: 'Memory corrections', q: 'Which prior claims need updating?',\n          rec: '3 corrections · Vault = Skill Library (not secrets) · .diy has paid tiers · aaas.name is a real platform page (not 301). Plus update reference_aaas_notebooklm_kb path migration.',\n          touches: '#1737 · #1739 · #1749 Q2+Q15 · #1750' },\n        { id: 7, title: 'Currency + locale', q: 'EUR primary or per-product?',\n          rec: 'EUR primary across operator surfaces. .diy displays USD-primary + EUR-secondary (product pricing in USD). USD upstream converts at Stripe-recorded rate, not today\\u2019s. Operator locale de-DE; user locale per-product.',\n          touches: '#1737 Q1+Q11 · #1739 · #1735 · #1751 · #1753' },\n        { id: 8, title: 'Auto-apply / approval gates', q: 'When does system auto-apply?',\n          rec: 'Severity-gated. Trivial (typo/casing/dedup) auto-apply with 30-day revert + audit. Low/medium/high require user review. One-shot operator actions immediate, reversible via audit log.',\n          touches: '#1750 Q3 · #1755 Q5' },\n        { id: 9, title: 'A/B testing infrastructure', q: 'Stateless or sticky per-uid?',\n          rec: 'Stateless per-call in v1 · sticky per-uid in P3. Outcome scoring auto-eval is primary signal (was it accepted/re-run/shared); human ratings as override.',\n          touches: '#1751 Q4 · #1755 Q4+Q5' },\n        { id: 10, title: 'NotebookLM federation', q: 'User-facing or operator-only?',\n          rec: 'Operator-only v1; user-side gets Gemini-grounded fallback on same megadoc corpus. NotebookLM Enterprise only after 100+ users actively ask ecosystem questions. Remote-trigger sync-now.sh via Cloud Function in P1.',\n          touches: '#1749 Q3+Q4+Q14' },\n        { id: 11, title: 'Cross-Canvas notifications', q: 'Single inbox or per-source?',\n          rec: 'Single NotificationCenter with per-source tabs inside the drawer. Sources: inferences · subscription events · cost anomalies · edge-review · agent failures · content reports · DSAR. Per-domain unread badges on rail.',\n          touches: '#1744 · #1750 Q8' },\n        { id: 12, title: 'Migration sequencing', q: 'What order?',\n          rec: '3-phase. P1 (10 weeks Q3): 8-module IA · Costs+Subs · Decisions inbox · Services Toggle · KB · Ask MVP · Context Inbox · top-5 content kit · .diy P1 · .pics P1 · Toolbox · Prompt Lib read · Agent Catalog read. P2 (10 weeks Q4): Email Editor · 5 ops dashboards · per-domain integrations one-per-sprint · Prompt write+A/B · R1 migration first batch · funnel analytics · RBAC enforcement. P3 (16 weeks Q1-Q2 2027): compliance suite · support console · brand book · AP/Receipt/RevRec · status page · webhook log · cross-pipeline tracer · previous execution backend teardown · SPA migration complete · NotebookLM user-side · embedding pipeline · builder workshop migration.',\n          touches: 'all' },\n        { id: 13, title: 'Two-Canvas R1 timing', q: 'When does apps/canvas SPA merge into platform?',\n          rec: 'R1 over 2-quarter window. Start P2, complete P3. R3 \"Studio\" is interim posture. Module-by-module: .diy → /select/diy (P1) · .builders (P2) · .today (P2) · rest P3. Each batch: build Next route → copy → ship behind flag → A/B → flip → retire SPA.',\n          touches: '#1744 Q1+Q12' },\n        { id: 14, title: 'Data source naming + reconciliation', q: 'Unify duplicate registries?',\n          rec: 'Single Firestore SoT with code-mirrors for static-build SEO. /agents/{id} ← select-agents.ts + registry.json. /prompts/{id} ← scattered prompt files. /services_catalog/{id} ← auth-core (with sku_tiers vs defaultEntitlement separation). Codegen back to TS for SEO preservation.',\n          touches: '#1745 Q12 · #1755 Q1+Q3+Q12' },\n        { id: 15, title: 'Pricing + tier consistency', q: 'How do tiers gate features?',\n          rec: 'Tier check via available_to_tier server-side, surfaced as ServiceUnlockModal (Mode C) when locked. Free 7d/free skills/10 packages-month/3 sources/no Pics. Pro all skills/unlimited/11 sources/Pics. Team +MCP+SSO+team library. Enterprise custom. All checks via canAccessFeature(uid, feature_id).',\n          touches: '#1737 · #1745 Q5 · all per-product briefs' },\n        { id: 16, title: 'Vendor health + cost reconciliation', q: 'Real-time + daily?',\n          rec: 'Two-layer. Real-time /canvas/ops/integrations for up/down per 13 vendors. Daily BigQuery cost-export flagged \"geschätzt\" until invoice closes. Outage triggers AnomalyCard auto. Over-budget triggers BudgetRing red + gates dependent agent runs (operator-override).',\n          touches: '#1735 · #1742 §3 #5 · #1751 Q3' },\n        { id: 17, title: 'Closeout meta', q: 'When do per-domain briefs ship?',\n          rec: 'Per-domain briefs are P2-onwards. This closeout closes structural design phase. Operator ratifies these 17 themes → the design surface ingests the 14-brief corpus + this closeout to design Canvas v2 in October → per-domain implementation briefs come AFTER design lands, one per sprint at engineering pace.',\n          touches: '#1742 Q11 · this closeout' }\n      ],\n      surfaces: [\n        { id: '1', status: 'needs-brief', title: 'Tasks system', route: '/canvas/intelligence/tasks', body: 'Operator browse/search/triage across all agents\\u2019 tasks. Virtualized table + kanban toggle. lib/tasks.ts CRUD + bulk reassign + retry. Cross-link Zooey/cost/agent.' },\n        { id: '3', status: 'extend', title: 'Daily AI Podcast pipeline overview', route: '/canvas/podcast/pipeline', body: 'End-to-end view: aggregator (4 sources) → Gemini ranker → NotebookLM Cloud Run Job → MP3 → RSS+WebSub → Postiz. Per-stage health + failure-replay.' },\n        { id: '4', status: 'needs-brief', title: 'Funnel architecture v1 ops', route: '/canvas/marketing/funnel', body: '5-surface funnel viz (landing → signup → onboarding → first-task → paid) + per-stream attribution + UTM breakdown + 13 cutpoints Q2-Q4 2026.' },\n        { id: '5', status: 'needs-brief', title: 'EIE / Hetzner SMTP port-25', route: '/canvas/ops/email-infra', body: '3 VPS health table + port-25 unblock-ticket status + deliverability score + bounce rate + Resend fallback toggle.' },\n        { id: '6', status: 'cleanup', title: 'previous execution backend retirement verification', route: '/canvas/ops/legacy-systems', body: '4-step teardown checklist. Remove page once verified. Cost-recovery KPI vs previous execution backend burn.' },\n        { id: '7', status: 'cleanup', title: 'apps/canvas SPA migration tracker', route: '/canvas/ops/spa-migration', body: 'Per-module migration status table + LoC + 308-redirect + burndown chart. Remove after R1 complete.' },\n        { id: '8', status: 'inventory', title: 'Email Template Editor · 67 templates', route: '/canvas/marketing/email-templates', body: 'Split MJML/React-Email source + live HTML preview + send-test + variable tester. Diff/rollback + A/B same pattern as Prompt Library.' },\n        { id: '9', status: 'inventory', title: 'Cloud Scheduler · 62 jobs', route: '/canvas/ops/scheduler', body: 'Table: cron + target + status + last-fired + last-error. Bulk enable/disable with reason. Per-job audit. Trigger-now.' },\n        { id: '10', status: 'inventory', title: 'Firestore Index Audit · 119 indexes', route: '/canvas/ops/firestore-indexes', body: 'Sync from firestore.indexes.json + drift detector vs GCP + unused-index flag + cost-impact estimate.' },\n        { id: '11', status: 'inventory', title: 'Secrets Dashboard', route: '/canvas/ops/secrets', body: 'All Secret Manager entries + rotation reminder (>90d) + per-secret usage map. Read-only; rotation via gcloud only.' },\n        { id: '12', status: 'inventory', title: 'Integration Health · 13 vendors', route: '/canvas/ops/integrations', body: '13-vendor grid + status + last-response + monthly spend + last-incident. Status history strip + credentials health cross-link.' },\n        { id: '13', status: 'inventory', title: 'Apps Health · 18 apps', route: '/canvas/ops/apps', body: '18-app grid + Cloud Run / Hosting + last-deploy + p50/p95 + error rate + cost. Per-app drill: deploy history + traffic split + rollback.' },\n        { id: '14', status: 'inventory', title: 'DLQ Inspector', route: '/canvas/ops/dlq', body: 'All Pub/Sub DLQ subscriptions + message count + oldest age + per-message inspector + replay + acknowledge-and-drop.' },\n        { id: '15', status: 'inventory', title: 'Cross-Pipeline Tracer · 11 flows', route: '/canvas/ops/tracer', body: '11-flow registry + per-flow live trace (composes DecisionTimeline) + Cloud Trace p50/p95 + failure-pattern dashboard.' },\n        { id: '16', status: 'compliance', title: 'Compliance suite · 6 surfaces', route: '/canvas/ops/compliance/*', body: 'Audit Log · DSAR Queue · Consent Records · ToS Versions · Cookie Consent · DPA Registry. 90-day audit retention; export to GCS for legal hold.' },\n        { id: '17', status: 'needs-brief', title: 'Support Queue + Chat Console', route: '/canvas/quality/support', body: 'Inbound triage (email/form/pics-report) + priority + tags + SLA timer. Chat console with template library + context auto-populated from agentContext.' },\n        { id: '18', status: 'needs-brief', title: 'AP Inbox + Receipt OCR + FX + RevRec', route: '/canvas/costs/*', body: 'Vendor bills + receipt OCR (Gemini Vision) + USD-EUR rate history (ECB) + deferred-revenue waterfall (ASC 606-light).' },\n        { id: '19', status: 'needs-brief', title: 'Status Page + Changelog + Announcements', route: '/canvas/marketing/*', body: 'Incident posts to status.aaas.today · markdown changelog with RSS · in-app banners with cohort targeting · email blasts consent-gated · web-push composer.' },\n        { id: '20', status: 'inventory', title: 'Brand Book + Logo Library + Voice + A11y', route: '/canvas/design/brandbook', body: 'Canonical single-page reference · logos with usage rules · voice/tone do/don\\u2019t · a11y contrast/aria scan across deployed surfaces.' },\n        { id: '21', status: 'needs-brief', title: 'KYC Queue + Demo Seeder + Onboarding Editor', route: '/canvas/ops/onboarding/*', body: 'High-tier KYC queue · one-click fake-tenant generator · 6-step v2 flow editor with conditional + A/B.' }\n      ],\n      phases: [\n        { id: 'P1', title: 'Q3 2026', window: '10 WEEKS', items: ['8-module IA shell · INTELLIGENCE + CONTENT modules', 'Costs + Subscriptions dashboards', 'Decisions/Zooey inbox', 'Services Toggle adaptive Canvas', 'KB editor + Knowledge Ask MVP', 'Context Inbox', 'Top-5 content surface kit', '.diy upgrade P1', '.pics operator overlay P1', 'Toolbox surfacer', 'Prompt Library read-only', 'Agent Catalog read-only'] },\n        { id: 'P2', title: 'Q4 2026', window: '10 WEEKS', items: ['Email Template Editor', '5 ops dashboards (Scheduler · Indexes · Secrets · Integrations · Apps)', 'Per-domain integrations, one per sprint', 'Prompt Library write + A/B testing', 'Two-Canvas R1 migration first batch', 'Funnel + Audience + Coverage analytics', 'RBAC enforcement'] },\n        { id: 'P3', title: 'Q1–Q2 2027', window: '16 WEEKS', items: ['Compliance suite (GDPR · DSAR · Consent · ToS · Cookie · DPA)', 'Support console + KB articles', 'Brand book + a11y', 'AP Inbox + Receipt OCR + RevRec', 'Status page + webhook log', 'Cross-pipeline tracer', 'previous execution backend teardown + SPA migration complete', 'NotebookLM user-side', 'Embedding pipeline · builder workshop migration'] }\n      ]\n    };\n\n    this.CHAT_REPLIES_OPS = [\n      { m: /vault|skill/i, o: 'Vault is the 5,400+ skill library — Intelligence → Vault. Skills carry quality tiers; the gold ones drive the shipped agents.' },\n      { m: /run|agent|worker/i, o: 'Runs live in Agent Runs. Every run keeps its worktree and a replay — open one and step through what the agent actually did.' },\n      { m: /cost|spend|budget/i, o: 'Costs → Overview. MTD spend, biggest mover and budget headroom sit in the KPI strip; per-run attribution is in the ledger.' },\n      { m: /outreach|mail|email/i, o: 'Outreach is the GTM email agent. Queue holds what is about to send; every draft there is the real file the sender ships.' },\n      { m: /client|mvp|customer/i, o: 'Flip the MVP toggle to see the canvas as the client does — same shell, four streams, their brand tokens.' }\n    ];\n\n    // Full rail (Owner). Role views select from this same set of streams.\n    // ── View archetypes (docs/CANVAS-IMPROVEMENT-PLAN.md #1 · source: 05_Canvas/C_ INtro+Ads+Work+\n    // Mobile/app/views.jsx `window.VIEW_OPTIONS`). The source lists 10; only the ones this shell can\n    // actually render are kept — a strip of icons that mostly do nothing is worse than a short one.\n    // A tab opts in by declaring `tabViews[label]`; the first entry is its default.\n    this.VIEW_OPTIONS = [\n      { id: 'chart',    label: 'Chart',    icon: 'chart' },\n      { id: 'table',    label: 'Table',    icon: 'table' },\n      { id: 'board',    label: 'Board',    icon: 'columns' },\n      { id: 'cards',    label: 'Cards',    icon: 'grid' },\n      { id: 'timeline', label: 'Timeline', icon: 'timeline' },\n      { id: 'tiles',    label: 'Tile grid', icon: 'apps' }\n    ];\n\n    // D165: Owner = the generic Inbox group, then every role's groups once, in role order. Every stream keeps a home.\n    this.RAIL_GROUPS = [\n      this.INBOX_TOP,\n      this.CONTENT_CAT,\n      ...this.ACCOUNT_SEGMENTS.map(g => ({ id: g.id, label: 'Account · ' + g.label, ids: g.ids })),\n      { id: 'newuser', label: 'New user', ids: ['zui', 'deliverables'] },\n      { id: 'mkt', label: 'Marketing', ids: ['podcast', 'content', 'voice', 'content2', 'podcast2', 'ads', 'ads2', 'outbound2', 'growth2', 'money2', 'estate2', 'foundry2', 'account2', 'design2', 'marketing', 'outreach', 'growth', 'product', 'cro'] },\n      { id: 'gtm', label: 'Go-to-market', ids: ['icp', 'leads', 'scout', 'composer', 'convert', 'kb', 'growthRun'] },\n      { id: 'proposals', label: 'Proposals', ids: ['packages', 'proposalRequest', 'offeringMatch', 'proposalBuilder', 'proposalReview', 'proposalPipeline', 'clientIndex'] },\n      { id: 'build', label: 'Developer', ids: ['agentRuns', 'workspace', 'automations', 'review', 'backends', 'observability', 'quality', 'library', 'templates'] },\n      { id: 'money', label: 'Finance', ids: ['costs', 'finances', 'billing'] },\n      { id: 'admin', label: 'Account · Platform', ids: ['platform', 'ops', 'projects', 'agents', 'services', 'domains', 'ops2', 'knowledge2'] },\n      { id: 'archive', label: 'Archive', ids: ['onboardingV2'] }\n    ];\n    // socialpane (2026-10-05): the Social · v2 process stream rides the Marketing rail group —\n    // additive push so the group literal above keeps its shipped bytes (recipe step 04's \"add the\n    // id to a RAIL_GROUPS group\"; the MOCK tabs it sits beside are untouched).\n    this.RAIL_GROUPS.find(g => g && g.id === 'mkt').ids.push('social2');\n\n    // ── D2 permission model (docs/DECISIONS.md, item 15) ──────────────────────\n    // Step 1, mechanical, zero behaviour change: PERMISSIONS is the union of each role's\n    // current ROLE_RAIL ids, derived by hand from the same source above (same technique as\n    // WP-06's ownsTabs extraction). Owner keeps null = unrestricted, matching ROLE_RAIL's own\n    // convention. This is a SEPARATE table from rail membership on purpose (D2): rail membership\n    // is a view choice (which groups render), permission is a rule enforced at every resolution\n    // point, not just the rail button.\n    this.PERMISSIONS = {\n      Inbox: ['inbox', 'account', 'onboarding'],\n      Developer: ['inbox', 'agentRuns', 'workspace', 'automations', 'review', 'backends', 'platform',\n        'observability', 'quality', 'library', 'templates', 'context', 'zui', 'foundry', 'toolbox'],\n      Marketing: ['inbox', 'pitches', 'voice', 'video', 'podcast', 'content2', 'podcast2', 'design', 'select', 'intelligence', 'context', 'zui', 'foundry',\n        'cro', 'icp', 'leads', 'scout', 'composer', 'convert', 'kb', 'ads', 'domains', 'content', 'protocol', 'services', 'outreach',\n        'marketing', 'product', 'growth', 'deliverables', 'team',\n        'packages', 'proposalRequest', 'offeringMatch', 'proposalBuilder', 'proposalReview', 'proposalPipeline', 'clientIndex'],\n      Sales: ['inbox', 'icp', 'leads', 'scout', 'composer', 'convert', 'kb', 'select', 'intelligence', 'content', 'domains', 'protocol',\n        'services', 'deliverables', 'team', 'growth', 'outreach', 'marketing',\n        'packages', 'proposalRequest', 'offeringMatch', 'proposalBuilder', 'proposalReview', 'proposalPipeline', 'clientIndex'],\n      GTM: ['inbox', 'podcast', 'intelligence', 'select', 'packages', 'protocol', 'clientIndex',\n        'context', 'icp', 'leads', 'scout', 'composer', 'convert', 'kb', 'growthRun', 'content', 'ads', 'outreach', 'deliverables', 'marketing',\n        'proposalPipeline', 'growth', 'quality', 'ops', 'toolbox'],\n      Finance: ['inbox', 'costs', 'finances', 'ads', 'observability', 'billing'],\n      Owner: null,\n      Admin: ['inbox', 'platform', 'ops', 'quality', 'ads', 'backends', 'costs', 'finances', 'team',\n        'billing', 'projects', 'account', 'onboarding', 'toolbox']\n    };\n    // The three streams that ARE the operator's own workspace. They live in the non-ops Personal-view\n    // rail, which is offered to every role — so scoping them by role denies a person their own home,\n    // their own onboarding and their own settings. Permitted for everyone, same shape as the MVP\n    // exemption below: the view itself is the gate, not the role.\n    // Found 2026-09-03 (verifier, O5): the Personal rail listed `home` for every role while\n    // PERMISSIONS granted it to Owner alone, so the rail offered a button its own table denied and\n    // the DENIED overlay fired over an already-rendered pane. Third instance of the rail/permission\n    // disagreement class (PaneProtocol defect 3, PaneGrowth defect 3) — fixed here as a rule for all\n    // three ids at once rather than a fourth per-stream patch. Derived from PERSONAL_RAIL below so\n    // the two cannot drift again.\n    // D165: one source — a role may open exactly what its rail shows (GTM keeps its hand list above).\n    Object.keys(this.ROLE_RAIL).forEach(r => { const rr = this.ROLE_RAIL[r]; if (!rr || r === 'GTM') return;\n      this.PERMISSIONS[r] = Array.from(new Set([].concat(...rr.map(g => g.ids), this.ACCOUNT_IDS))); });\n    this.PERMISSIONS.GTM = Array.from(new Set(this.PERMISSIONS.GTM.concat(this.INBOX_TOP.ids, this.ACCOUNT_IDS)));\n    // D181: v2 streams sit beside their v1 in every role that can see the v1.\n    const V2P = { Marketing: ['ads2', 'outbound2', 'growth2', 'knowledge2', 'estate2', 'design2'], Developer: ['ops2', 'knowledge2', 'foundry2'], Finance: ['money2'], Sales: ['outbound2', 'knowledge2'], GTM: ['outbound2', 'growth2', 'ops2', 'ads2', 'estate2'], Admin: ['ops2', 'knowledge2', 'money2', 'estate2', 'account2', 'foundry2', 'design2'] };\n    Object.keys(V2P).forEach(r => { if (this.PERMISSIONS[r]) this.PERMISSIONS[r] = Array.from(new Set(this.PERMISSIONS[r].concat(V2P[r]))); });\n    // socialpane (2026-10-05): D181's rule for the new stream — social2 sits beside its v1 surface\n    // (the Marketing MOCK tabs) in every role that can already see Marketing › Social. Additive\n    // concat, same shape as the V2P line above; PERMISSIONS' own shipped lines are untouched.\n    ['Marketing', 'Sales', 'GTM', 'Admin'].forEach(r => { if (this.PERMISSIONS[r]) this.PERMISSIONS[r] = Array.from(new Set(this.PERMISSIONS[r].concat(['social2']))); });\n    // D194 · PROMOTION. Operator ruled every built v2 replaces its v1. Nothing is deleted: v1 streams keep id, file\n    // and permissions, are labelled \"· v1\", and move out of their rail groups into one collapsed \"v1 · archive\"\n    // group at the end of every rail. v2 streams drop the \"· v2\" suffix and the v2 crumb.\n    this.V2_PROMOTED = { content2: ['content'], podcast2: ['podcast'], ads2: ['ads'], outbound2: ['icp', 'scout', 'leads', 'composer', 'convert', 'outreach', 'cro'],\n      growth2: ['growth', 'growthRun'], ops2: ['platform', 'ops', 'observability', 'agentRuns', 'backends'], knowledge2: ['intelligence', 'kb', 'select', 'protocol' /* D207: knowledge2 › SOP renders it */],\n      money2: ['costs', 'finances'], estate2: ['video', 'voice'], account2: ['team', 'onboarding', 'onboardingV2', 'account' /* D207: Identity/Plan/People render it */], foundry2: ['foundry', 'library', 'templates', 'toolbox'], design2: ['design'] };\n    const replaced = [];\n    Object.keys(this.V2_PROMOTED).forEach(v2 => {\n      const st2 = this.STREAMS[v2]; if (st2) { st2.label = st2.label.replace(/ · v2$/, ''); st2.crumb = String(st2.crumb || '').replace(/^v2 › /, '').replace(/ · v2$/, ''); st2.promoted = true; }\n      this.V2_PROMOTED[v2].forEach(v1 => { const s1 = this.STREAMS[v1]; if (s1 && !/ · v1$/.test(s1.label)) { s1.label = s1.label + ' · v1'; s1.v1 = true; } replaced.push(v1); });\n    });\n    // D210 (P4.5) · every stream declares a dataContract. Undeclared → planned (endpoint null), so the W-0 marker renders\n    // 'modeled' instead of UNDECLARED. `declaredBy: 'default'` marks these so a real endpoint can replace them stream by stream.\n    this.CONTRACT_DEFAULTED = Object.keys(this.STREAMS).filter(id => !this.STREAMS[id].dataContract);\n    this.CONTRACT_DEFAULTED.forEach(id => { this.STREAMS[id].dataContract = { endpoint: null, status: 'planned', shape: 'items[]+heroKpis[]', declaredBy: 'default' }; });\n    const ARCHIVE_GROUP = (ids) => ({ id: 'v1archive', label: 'v1 · archive', ids, collapsed: true });\n    const strip = (groups) => { if (!Array.isArray(groups)) return groups; const gone = []; const out = groups.map(g => { if (!g || !Array.isArray(g.ids)) return g; const keep = g.ids.filter(id => replaced.indexOf(id) < 0); g.ids.forEach(id => { if (replaced.indexOf(id) > -1 && gone.indexOf(id) < 0) gone.push(id); }); return Object.assign({}, g, { ids: keep }); }).filter(g => !g || !Array.isArray(g.ids) || g.ids.length > 0 || g.bare); if (gone.length) out.push(ARCHIVE_GROUP(gone)); return out; };\n    Object.keys(this.ROLE_RAIL).forEach(r => { if (Array.isArray(this.ROLE_RAIL[r])) this.ROLE_RAIL[r] = strip(this.ROLE_RAIL[r]); });\n    this.RAIL_GROUPS = strip(this.RAIL_GROUPS);\n    this.ACCOUNT_SEGMENTS = strip(this.ACCOUNT_SEGMENTS).filter(g => g.id !== 'v1archive');\n    // Library role (operator 2026-10-03): EVERY stream, ordered by business process — the order streams get perfected\n    // into User. One process per stream (v2 first, its v1s after). A stream listed nowhere lands in 'Unsorted' so\n    // nothing hides. PROCESS_OF is the stream → process lookup the system registry reads as an `inProcess` edge.\n    this.LIBRARY_PROCESS = [\n      { id: 'research', label: '01 · Research', ids: ['intelligence', 'select', 'podcast2', 'podcast'] },\n      { id: 'context', label: '02 · Context', ids: ['knowledge2', 'context', 'protocol', 'kb', 'clientIndex'] },\n      { id: 'content', label: '03 · Content', ids: ['websites', 'domains', 'content2', 'content', 'design2', 'design', 'estate2', 'video', 'voice', 'pitches'] },\n      { id: 'market', label: '04 · Market', ids: ['growth2', 'growth', 'growthRun', 'ads2', 'ads', 'marketing', 'cro', 'product'] },\n      { id: 'sell', label: '05 · Sell', ids: ['outbound2', 'icp', 'scout', 'leads', 'composer', 'convert', 'outreach', 'packages', 'offeringMatch', 'services', 'proposalRequest', 'proposalBuilder', 'proposalReview', 'proposalPipeline'] },\n      { id: 'produce', label: '06 · Produce', ids: ['projects', 'deliverables', 'automations', 'workspace', 'agentRuns', 'review', 'agents', 'zui', 'cbrief', 'cdelivery', 'csupport'] },\n      { id: 'operate', label: '07 · Operate', ids: ['ops2', 'ops', 'platform', 'observability', 'backends', 'quality', 'foundry2', 'foundry', 'library', 'templates', 'toolbox'] },\n      { id: 'administrate', label: '08 · Administrate', ids: ['home', 'account2', 'account', 'team', 'onboarding', 'onboardingV2', 'money2', 'costs', 'finances', 'billing', 'cbilling'] }\n    ];\n    // socialpane (2026-10-05): the Library rail (the shell's sidebar) is built from THIS list — a\n    // stream listed nowhere lands in Unsorted, so the Social · v2 process stream joins its process\n    // group here, before the build below runs. Same additive push as the mkt group above.\n    this.LIBRARY_PROCESS.find(g => g && g.id === 'market').ids.push('social2');\n    (() => { const all = Object.keys(this.STREAMS), seen = {}; this.PROCESS_OF = {};\n      this.INBOX_TOP.ids.forEach(id => { seen[id] = 1; this.PROCESS_OF[id] = 'inbox'; });\n      const cats = this.LIBRARY_PROCESS.map(p => { const ids = p.ids.filter(id => this.STREAMS[id] && !seen[id]);\n        ids.forEach(id => { seen[id] = 1; this.PROCESS_OF[id] = p.id; }); return { id: 'lib-' + p.id, label: p.label, ids, collapsed: true }; });\n      const rest = all.filter(id => !seen[id]); rest.forEach(id => { this.PROCESS_OF[id] = 'unsorted'; });\n      if (rest.length) cats.push({ id: 'lib-unsorted', label: 'Unsorted', ids: rest, collapsed: true });\n      this.ROLE_RAIL.Library = [this.INBOX_TOP].concat(cats.filter(c => c.ids.length));\n      this.PERMISSIONS.Library = all.slice(); })();\n\n    this.PERSONAL_IDS = ['home', 'onboarding', 'account'];\n    // Everything the User view's rail offers. selectStream keeps the User view for these instead of\n    // flipping to Ops on group — the old rule sent every \"Your work\" click into the Ops rail.\n    this.USER_VIEW_IDS = ['home', 'onboarding', 'account', 'projects', 'deliverables', 'agents', 'billing', 'team',\n      'context', 'intelligence', 'select', 'podcast', 'domains', 'cdelivery', 'cbrief', 'cbilling', 'csupport'];\n    // FQ-3 (D157): a flow = an ordered set of streams shown as ONE rail row with a step strip. Panes,\n    // STREAMS entries and ownsTabs are untouched; the row collapses only where >=2 of its ids share a group.\n    this.FLOWS = { proposals: { label: 'Proposals', icon: 'content', ids: ['packages', 'proposalRequest', 'offeringMatch', 'proposalBuilder', 'proposalReview', 'proposalPipeline', 'clientIndex'] } };\n    this.FLOW_OF = {};\n    Object.keys(this.FLOWS).forEach(f => this.FLOWS[f].ids.forEach(id => { this.FLOW_OF[id] = f; }));\n    // A stream reachable only behind the MVP toggle (docs/DECISIONS.md D1) is permitted there\n    // regardless of role — the toggle itself is the gate, per WP-26's finding.\n    // Toolset pack loader (F2). One read of the tenant manifest fills the stream KPIs, the lens\n    // plane and the app-switcher tile — one number, one source. Fails closed to the honest '—'.\n    this.loadToolset = () => {\n      // FQ-9 (D158): the toolset follows the BUSINESS (tenant), not the brand preview.\n      const want = this.state.business || 'aaas';\n      // O1.4 · per-tenant. A tenant with no toolset pack must show the honest empty state, never the\n      // previous tenant's templates.\n      if (window.__dcToolsetLoaded === want) return;\n      window.__dcToolsetLoaded = want;\n      window.__dcToolset = null; window.__dcSchemas = {}; window.__dcContext = null;\n      this.STREAMS.toolbox.kpis = [k('Templates', '—'), k('Phases', '—'), k('Context-mapped', '—'), k('Finalised', '0')];\n      this.STREAMS.toolbox.subs = [{ label: 'Overview' }, { label: 'Studio' }];\n      delete this.DEV_PANES.toolbox;\n      const tenant = want;\n      const url = (tenant === 'aaas' && window.__resources && window.__resources['toolset-default']) || (this.rootPrefix + 'tenants/' + tenant + '/toolset/toolbox-manifest.json');\n      fetch(url).then(r => r.ok ? r.json() : null).then(m => {\n        if (!m || !m.phases) return;\n        const phases = m.phases, items = [];\n        phases.forEach(p => (p.templates || []).forEach(tp => items.push({\n          title: tp.name, meta: p.id + ' · ' + p.name, phase: p.id, desc: tp.description || '',\n          href: 'https://aaas-toolbox.web.app/' + p.dir + '/' + tp.slug + '.html', kind: 'html' })));\n        const n = items.length, ph = phases.length;\n        window.__dcToolset = { tenant, phases, items, n, ph };\n        // Context pack + seeded schemas — what the document editor edits against.\n        fetch(this.rootPrefix + 'tenants/' + tenant + '/context/company-context.json').then(r => r.ok ? r.json() : null).then(c => { if (c) window.__dcContext = c; }).catch(() => {});\n        window.__dcSchemas = window.__dcSchemas || {};\n        ['One_Pager_Executive_Summary', 'Dream_and_Lean_Canvas', 'Privacy_Policy_Generator_GDPR'].forEach(nm => {\n          fetch(this.rootPrefix + 'tenants/' + tenant + '/toolset/schemas/' + nm + '.schema.json').then(r => r.ok ? r.json() : null).then(sc => { if (sc) window.__dcSchemas[nm] = sc; }).catch(() => {});\n        });\n        // renderVals builds DEV_PANES.toolbox from this (table lens helpers live there).\n        const s = this.STREAMS.toolbox;\n        s.kpis = [k('Templates', String(n)), k('Phases', String(ph)), k('Interactive', String(n), null, 'teal'), k('Context-ready', String(phases.filter(p => /Brand|Idea|Plan|Launch/.test(p.name)).reduce((a, p) => a + (p.templates || []).length, 0)))];\n        s.subs = subs(['Overview', 'Studio'].concat(phases.map(p => p.id + ' ' + p.name)), []);\n        // Context-mapped = fields that auto-fill from the tenant's company-context.json. Honest count\n        // needs the per-template schemas; only the seeded ones are on disk, so report what is measured.\n        fetch(this.rootPrefix + 'tenants/' + tenant + '/toolset/schemas/Privacy_Policy_Generator_GDPR.schema.json').then(r => r.ok ? r.json() : null).then(sc => {\n          if (!sc) return; const seeded = 3;\n          s.kpis[2] = k('Context-mapped', seeded + ' schemas seeded', null, 'gold'); this.forceUpdate();\n        }).catch(() => {});\n        // domain tile count reads the same manifest — the typed \"267 templates across 21 categories\" retires\n        const tile = (this.SVC_CATALOG || []).find(d => d.id === 'toolbox');\n        if (tile) tile.desc = n + ' templates across ' + ph + ' phases, OSS-auditable.';\n        // O1.3 · one number, one source. Every surface naming the real Toolbox reads this manifest; the\n        // repo documents eight drifted hand-typed counts, so none of these may become literals again.\n        const dm = (this.DOMAINS || {})['.toolbox'];\n        if (dm) { dm.tagline = n + ' templates · ' + ph + ' phases'; dm.desc = n + ' business templates in ' + ph + ' startup phases. User-portal view of the Toolbox stream.';\n          if (dm.kpis && dm.kpis[0]) dm.kpis[0] = ['Templates', String(n), null];\n          if (dm.kpis && dm.kpis[2]) dm.kpis[2] = ['Phases', String(ph)];\n          if (dm.browser && dm.browser.filters && dm.browser.filters[0]) dm.browser.filters[0] = ['All', String(n)]; }\n        const cst = this.STREAMS.content;\n        if (cst && cst.kpis && cst.kpis[0]) cst.kpis[0] = k('Toolbox templates', String(n));\n        this.forceUpdate();\n      }).catch(() => {});\n    };\n    if (!window.__dcToolsetScheduled) { window.__dcToolsetScheduled = true; setTimeout(() => this.loadToolset(), 0); }\n\n    this.canAccess = (role, streamId) => {\n      // D25 (operator, 2026-09-06) — **at this stage nothing is hidden or greyed by role.** The operator's\n      // ruling: \"we need to be able to visualise the full design, irregardless of fake/missing data or\n      // conflicting roles. That hierarchy perfection comes later.\" So `canAccess` short-circuits while\n      // `showAllPlanes` is on (default), which neutralises all 6 gate sites at once — the rail filter,\n      // `selectStream`, the 3 ⌘K filters, the mega tiles and `inboxRoute` — and with it the DENIED overlay,\n      // since nothing ever denies. PERMISSIONS below is left INTACT, not deleted: it is still the D2 model\n      // and the authority is the owner/admin role view (D25), so flipping the prop off restores gating\n      // whole. Never re-implement hiding at a call site; this is the one switch.\n      if (this.props.showAllPlanes !== false) return true;\n      if (this.MVP_IDS && this.MVP_IDS.indexOf(streamId) > -1) return true;\n      if (this.PERSONAL_IDS && this.PERSONAL_IDS.indexOf(streamId) > -1) return true;\n      // FQ-5 (D154): the Use view is the Member's own — never narrowed by the Run role's permissions.\n      if (this.state && !this.state.ops && this.USER_VIEW_IDS && this.USER_VIEW_IDS.indexOf(streamId) > -1) return true;\n      const p = this.PERMISSIONS[role];\n      return p === null || p === undefined ? true : p.indexOf(streamId) > -1;\n    };\n    // Real pulled marketing-site designs (assets/domains/<id>/), distinct from the fictional\n    // content simulated by this.DOMAINS above. Path is relative to this file's own location\n    // (templates/canvas-dashboard/) — up two levels to project root, then into assets/domains/.\n    /* ADR-036 §3's wordmark rule: the visible wordmark is the label's brand segment with every\n       \"aaas\" occurrence stripped, so the mark's own AaaS is never duplicated in the same lockup.\n       Order matters — a PATH-scoped host names its product in the path (\"aaas.love/tripleA\" →\n       TRIPLEA), so the path wins before any host stripping runs. Without that, a product page on\n       the apex would wear the apex's own wordmark (LOVE), which is the front door's identity.\n       ⚠ ADR-036 also defines a \"heart\" mode — the bouncing heart replaces the pulsing dot on\n       aaas.love ITSELF, never on a path under it. No surface in this project is the apex (its\n       homepage is the monorepo's apps/platform root route and has no file here), so heart mode is\n       deliberately NOT implemented rather than shipped as a branch nothing can reach: add it with\n       the apex entry, keyed on the host being exactly \"aaas.love\". */\n    this.domainWordmark = (label) => {\n      const raw = String(label || '');\n      if (raw.indexOf('/') > -1) {\n        const seg = raw.split('/').filter(Boolean).pop();\n        if (seg) return seg;\n      }\n      let s = raw\n        .replace(/\\.(web\\.app|love|eu|com|dev)$/i, '')  // brand-less host suffix\n        .replace(/(^|[.-])aaas([.-]|$)/gi, '$1$2')      // every \"aaas\" segment, wherever it sits\n        .replace(/^[.-]+|[.-]+$/g, '');\n      return s || raw;\n    };\n    /* D120 · path prefix from THIS document to the project root, DERIVED, never hard-coded.\n       The domain tiles opened through `'../../' + page.file`, which is only correct while the DC\n       runs in place at templates/canvas-dashboard/. Pipeline stages 2 and 3 are served FROM the\n       project root, where '../../' walks off the serve root — every app-switcher tile 404'd in\n       the standalone and in `AaaS Canvas Shell.html` (measured: `/v1/design/projects/assets/...`\n       instead of `/serve/assets/...`). Unlike the fetches above, an iframe src has no\n       window.__resources id to fall back on, so the prefix itself has to be right.\n       Computed from the document's own directory: two levels up only when we ARE in\n       templates/canvas-dashboard/, otherwise root-relative. */\n    this.rootPrefix = (() => {\n      try {\n        const dir = String(location.pathname || '').replace(/[^/]*$/, '');\n        return /templates\\/canvas-dashboard\\/$/.test(dir) ? '../../' : '';\n      } catch (e) { return '../../'; }\n    })();\n    /* D120 · the OTHER axis. rootPrefix walks up to the project root; sibBase points at this DC's\n       own directory, which is where the shell's helper modules live. It is an ABSOLUTE URL on\n       purpose: the logic class is an inline `script[data-dc-script]` that the runtime evaluates\n       against a BLOB base, and a relative specifier cannot resolve there — `import('./x.js')`\n       throws \"Failed to resolve module specifier\", which is why every plane rendered unvalidated\n       in the bundle even after the sibling files were reachable. fetch() is unaffected (it always\n       resolves against the document), so a working fetch probe proves nothing about import().\n       `new URL('./', location.href)` is this file's own directory when running in place. */\n    this.sibBase = (() => {\n      try {\n        return new URL(this.rootPrefix ? './' : 'templates/canvas-dashboard/', location.href).href;\n      } catch (e) { return './'; }\n    })();\n    const D = 'assets/domains/';\n    /* Head banner on every domain (operator call 2026-09-18). Eleven domains were named; five\n       already carry the set as DC pages (academy, blog, builders, diy, today) and six could not:\n       forum, name, company, business, select and pics are plain HTML/JSX and a dc-import resolves\n       siblings only, so the banner cannot be mounted INSIDE them. Their first tab now opens\n       `_banner/Domain Front.dc.html`, which mounts the real Head Banner and frames the existing\n       page underneath it, untouched — the banner reads ?door= itself (first in its resolveDoor\n       order), so it behaves exactly as it does on a DC domain. `business` has no door film; Smart\n       Cinema falls to the nearest door by tag overlap, which is its documented behaviour.\n       The alternative was converting six pages (one of them a 2.3 MB bundle) to DC pages — a\n       rewrite of working surfaces to gain a header. */\n    const BF = (door, site) => D + '_banner/Domain Front.dc.html?door=' + door + '&site=../' + site;\n    /* 2026-09-21 (operator: \".boutique doesn't have the ecosystem menu integrated yet, and\n       doesn't .store, .business, .company and maybe more of them?\"). Correct, and it was\n       \"maybe more\": EVERY plain-HTML domain was missing it, and the ones that were framed\n       had it only on their arrival tab. BF frames a page WITH the Work It takeover — right\n       for the arrival tab, wrong for the other seven tabs of a domain, where a toll video\n       plays on each one. BN is the same front with the bar and no takeover, so the\n       ecosystem menu is on every page of every domain and the film stays an arrival.\n       `seg` rather than `door` because two of these are a sub-page of a brand rather than\n       a brand: grants wears boutique's bar, tripleA wears love's. */\n    const BN = (seg, site) => D + '_banner/Domain Front.dc.html?seg=' + seg + '&banner=0&site=../' + site;\n    this.DOMAIN_SITES = [\n      { id: 'academy', label: 'Academy', tld: 'aaas.academy', icon: 'sparkles', pages: [\n        { label: 'Home', file: D + 'academy/AaaS Academy.dc.html' },\n        { label: 'Packs', file: D + 'academy/AaaS Academy Pages.dc.html?route=packs' },\n        { label: 'Flow', file: D + 'academy/AaaS Academy Pages.dc.html?route=flow' },\n        { label: 'Tracks', file: D + 'academy/AaaS Academy Pages.dc.html?route=tracks' },\n        { label: 'Prompts', file: D + 'academy/AaaS Academy Pages.dc.html?route=prompts' },\n        { label: 'Courses', file: D + 'academy/AaaS Academy Pages.dc.html?route=courses' },\n        { label: 'Challenges', file: D + 'academy/AaaS Academy Pages.dc.html?route=challenges' },\n        { label: 'Leaderboard', file: D + 'academy/AaaS Academy Pages.dc.html?route=leaderboard' }] },\n      { id: 'blog', label: 'Blog', tld: 'aaas.blog', icon: 'book', pages: [{ label: 'Home', file: D + 'blog/AaaS Knowledge Index.dc.html' }] },\n      /* 2026-09-21: boutique was the one plain-HTML domain left un-framed — it opened its bare\n         index.html, so it alone had no Work It takeover and no door film, while business, forum,\n         name, company, select and pics all arrived through Domain Front. It has a door film\n         (doors/boutique.v2.mp4, 91s) — there was nothing to build, only a wrapper to add. */\n      { id: 'boutique', label: 'Boutique', tld: 'aaas.boutique', icon: 'gift', pages: [\n        { label: 'Home', file: BF('boutique', 'boutique/index.html') }, { label: 'Eligibility', file: BN('boutique', 'boutique/eligibility.html') },\n        { label: 'Grants', file: BN('boutique', 'boutique/grants.html') }, { label: 'Retainers', file: BN('boutique', 'boutique/retainers.html') },\n        { label: 'Quote', file: BN('boutique', 'boutique/quote-template.html') }] },\n      /* Track 1 (operator call 2026-09-06) · the REAL storefront, openable in-canvas. Without this the\n         shell had only the `.store` DOMAINS listing — an operator surface ABOUT the store — and no way\n         to reach the 8 pages that actually exist in assets/domains/store/. All 8 as tabs, in the\n         README's order. Both flow pages self-seed, so they open with content rather than empty:\n         cart.jsx ships two buy-mode lines by default and handoff.jsx falls back to the CRM item when\n         no ?item= is given — checked in the files, not assumed, so neither needs a query string here. */\n      { id: 'store', label: 'Store', tld: 'store.aaas.love', icon: 'costs', pages: [\n        { label: 'Discover', file: BN('store', 'store/index.html') }, { label: 'Shop', file: BN('store', 'store/plp.html') },\n        { label: 'PDP Buy', file: BN('store', 'store/pdp-buy.html') }, { label: 'PDP Partner', file: BN('store', 'store/pdp-partner.html') },\n        { label: 'PDP Provider', file: BN('store', 'store/pdp-provider.html') }, { label: 'Compare', file: BN('store', 'store/compare.html') },\n        { label: 'Cart', file: BN('store', 'store/cart.html') }, { label: 'Handoff', file: BN('store', 'store/handoff.html') }] },\n      { id: 'builders', label: 'Builders', tld: 'aaas.builders', icon: 'foundry', pages: [{ label: 'Home', file: D + 'builders/aaas.builders.dc.html' }] },\n      { id: 'business', label: 'Business', tld: 'aaas.business', icon: 'columns', pages: [\n        { label: 'Home', file: BF('business', 'business/index.html') }, { label: 'Apply', file: BN('business', 'business/apply.html') },\n        { label: 'How', file: BN('business', 'business/how.html') }, { label: 'Grants', file: BN('business', 'business/grants.html') },\n        { label: 'Overview', file: BN('business', 'business/overview.html') }, { label: 'Verticals', file: BN('business', 'business/verticals.html') }] },\n      { id: 'company', label: 'Company', tld: 'aaas.company', icon: 'user', pages: [\n        { label: 'Home', file: BF('company', 'company/aaas-company.html') }, { label: 'Account Hub', file: BN('company', \"company/aaas.company - Account Hub (standalone).html\") }] },\n      { id: 'diy', label: 'DIY', tld: 'aaas.diy', icon: 'brush', pages: [{ label: 'Home', file: D + 'diy/Install the Knowledge.dc.html' }] },\n      /* Wireframes, design canvases and build plans were dropped from every entry below on\n         2026-09-20 (operator). The switcher is a map of the product, and a pre-final artifact\n         beside a shipping page reads as an alternative version of it. The files are untouched on\n         disk — this is a visibility decision, not a deletion. */\n      { id: 'forum', label: 'Forum', tld: 'aaas.forum', icon: 'contacts', pages: [\n        { label: 'Home', file: BF('forum', 'forum/aaas-forum-redesign.html') }, { label: 'Dashboard', file: BN('forum', 'forum/aaas-forum-prototype.html') }] },\n      /* `grants` was removed as a top-level tile 2026-09-22 (operator: \"Grants does not need a\n         separate field as it's part of Boutique drop-down\"). Correct — and it was worse than\n         redundant: this tile pointed at `grants/eligibility.html` + `grants/grants.html` while\n         Boutique's own dropdown points at `boutique/eligibility.html` + `boutique/grants.html`.\n         Two tiles, two different files, same two page names, no way to tell from the switcher\n         which was the real one. Boutique's entry is the survivor because Boutique is the product;\n         the `assets/domains/grants/` folder is untouched on disk. */\n      /* The apex PRODUCT pages — the routes behind the Platform mega-menu, all five in one DC.\n         The apex HOMEPAGE (HOSTS.root = https://aaas.love, served live by apps/platform's root\n         route) is now `platform/AaaS Love Home.dc.html`. It had been the one deliberate hole in\n         this registry — the earlier note here said a placeholder would be a forgery of the live\n         front door, and that was right while this project only MIRRORED the ecosystem. Operator\n         ruling 2026-09-22 changed the premise: this project is now the basis for the full AaaS\n         front-end, so the apex page is authored here from the repo's own IA (the two mega-menu\n         columns verbatim) rather than stood in for. */\n      { id: 'love', label: 'AaaS Love', tld: 'aaas.love', icon: 'globe', pages: [\n        { label: 'Home', file: D + 'platform/AaaS Love Home.dc.html' },\n        { label: 'Replay', file: D + 'platform/AaaS Love Replay.dc.html' },\n        /* The sitemap as a page (2026-09-22, operator: \"where can we see the final sitemap?\").\n           canvas/00-north-star/SITEMAP.md stays the narrative for anyone reading the repo; this\n           is its reader-facing half, and it is GENERATED from this very registry plus ROUTE_FILES\n           via platform/sitemap-data.json — so it reports what the build contains rather than what\n           a document remembers. Regenerate the JSON whenever DOMAIN_SITES or ROUTE_FILES change. */\n        { label: 'Sitemap', file: D + 'platform/AaaS Sitemap.dc.html', pageTld: 'aaas.love/sitemap' },\n        /* Restored 2026-09-22 (operator: \"the earlier work page seems to not have been included but\n           was already built and in the project\"). Correct on both counts — and it belongs HERE, not\n           where it used to sit. Its own tile claimed `canvas.aaas.love/work`; apps/platform's\n           sitemap.ts lists `${HOSTS.root}/work` (weekly, priority 0.8), so /work is an APEX route,\n           served by the same app as this tile's other pages and live on agents-as-a-service.com too.\n           Replacing the tile with Legal dropped a built page from view; folding it into the apex\n           restores it and fixes the host at the same time. */\n        /* Framed via BN, not opened bare (2026-09-22). `work/Your Agents' Work.dc.html`\n           mounts no Domain Nav — it has no copy of it in its folder — so opening it\n           directly gave a page with no ecosystem bar and therefore no way back to Canvas:\n           a dead end the operator caught. BN wraps it in Domain Front, which supplies the\n           bar and the route home without copying two guarded components into work/. */\n        { label: 'Work', file: BN('love', \"work/Your Agents' Work.dc.html\"), pageTld: 'aaas.love/work' },\n        { label: 'Vault', file: D + 'platform/AaaS Platform.dc.html?route=vault' },\n        { label: 'Foundry', file: D + 'platform/AaaS Platform.dc.html?route=foundry' },\n        { label: 'Projects', file: D + 'platform/AaaS Platform.dc.html?route=projects' },\n        { label: 'API Docs', file: D + 'platform/AaaS Platform.dc.html?route=api' },\n        { label: 'Partner briefing', file: D + 'platform/AaaS Platform.dc.html?route=partners' }] },\n      { id: 'name', label: 'Name', tld: 'aaas.name', icon: 'sparkles', pages: [\n        { label: 'One-Pager', file: BF('name', 'name/AaaS Name One-Pager.html') }, { label: 'Context', file: BN('name', 'name/context.html') }] },\n      /* Consolidated 2026-09-22 (operator: \"move pics, TripleA, Retainers and Qualify underneath\n         Select\" — \"to make things a bit more consumable\"). Twenty top-level tiles had stopped\n         being a map of the estate; three of these were single-page tiles carrying one surface each.\n         They keep their own hosts — `pageTld` below — because a page under Select is still served\n         from aaas.pics / aaas.love/tripleA / canvas.aaas.love, and the dropdown tooltip has to say\n         so or the grouping quietly misattributes four hosts to one.\n         Retainers is NOT here: it was never a top-level tile, it is `boutique/retainers.html`, and\n         it already sits in Boutique's dropdown beside Grants and Quote. Moving it under Select\n         would take it out of the product it belongs to — the same reasoning that removed the\n         duplicate Grants tile in this pass. Flagged for the operator rather than actioned. */\n      { id: 'select', label: 'Select', tld: 'aaas.select', icon: 'select', pages: [\n        { label: 'Hi-Fi', file: BF('select', 'select/AaaS Select - Hi-Fi.html') },\n        { label: 'Offline', file: BN('select', 'select/AaaS Select (offline).html') },\n        /* .pics is TWO live surfaces and this tile carried only the second one.\n           `Pics` is the front door — apps/pics/src/app/page.tsx, \"Professional Business\n           Photography. Powered by AI.\", three packs and the intake form — which existed\n           nowhere in this project until 2026-09-22; a DC now, so it mounts the ecosystem\n           bar, the pics door film and the footer like every other domain page (that is\n           why it takes D rather than BF: it needs no framing, it IS a DC page).\n           `Pics Showcase` is the seven-screen LoRA flow, unchanged and still framed —\n           upstream serves it at /pics/showcase/*, never at the root, which is what\n           ROUTE_FILES had wrong. The 31-artboard design canvas beside it\n           (pics/Aaas.Pics Canvas.html) is deliberately still off the switcher under the\n           2026-09-20 call that removed design canvases — worth revisiting, since upstream\n           serves that exact file at /pics-canvas/canvas.html behind select's admin gate\n           (components/select/modules/pics-canvas-module.tsx). */\n        { label: 'Pics', file: D + 'pics/AaaS Pics.dc.html', pageTld: 'aaas.pics' },\n        /* The studio is no longer a framed standalone: its seven screens are routes\n           on the page above, mounted from the prototype's own component files, so\n           this row opens the integrated studio rather than the prototype's fake\n           browser bar. `Aaas.Pics Prototype.html` stays on disk beside the canvas —\n           both are the claude.ai/design handoff bundle upstream serves at\n           /pics-canvas/ — but the switcher now points at the product. */\n        { label: 'Pics Studio', file: D + 'pics/AaaS Pics.dc.html?route=studio', pageTld: 'aaas.pics/studio' },\n        { label: 'Pics Canvas', file: D + 'pics/Aaas.Pics Canvas.html', pageTld: 'aaas.love/select · /pics-canvas' },\n        { label: 'TripleA', file: BN('love', 'triplea/project/aaas.love.html'), pageTld: 'aaas.love/tripleA' },\n        /* Same fix, same reason: the Qualifying Layer page mounts no Domain Nav either, and\n           it matters more here than anywhere — since 2026-09-22 it is also where every\n           footer CTA lands when no qualifier overlay is mounted, so an unframed version\n           would turn the project's most-linked fallback into its worst dead end. */\n        { label: 'Qualify', file: BN('love', 'qualify/Qualifying Layer Onboarding.dc.html'), pageTld: 'canvas.aaas.love/qualifier' }] },\n      { id: 'today', label: 'Today', tld: 'aaas.today', icon: 'content', pages: [{ label: 'Home', file: D + 'today/AaaS Today.dc.html' }] },\n      /* `aaas.love/toolbox`, NOT `toolbox.aaas.love` — checked against the SSoT 2026-09-20 after an\n         operator query. canonical-hosts.ts pins HOSTS.toolbox = \"https://aaas.love/toolbox\", and its\n         status banner lists toolbox among the apex-less ids \"still NOT flipped\". `toolbox.aaas.love`\n         is RESERVED for that flip (ecosystem-links-host-migration.test.ts →\n         RESERVED_PRODUCT_SUBDOMAINS = [\"canvas.aaas.love\", \"toolbox.aaas.love\"]) but does not serve\n         yet. The tld shown here is the live route, so it stays path-scoped until the flip lands. */\n      { id: 'toolbox', label: 'Toolbox', tld: 'aaas.love/toolbox', icon: 'apps', pages: [{ label: 'Home', file: D + 'toolbox/AaaS Business Toolbox.dc.html' }] },\n      /* Corrected 2026-09-20 (operator). This entry was labelled \"AAA\" at tld `aaas.love`, which read\n         as the ecosystem apex. It is not: the file is the TripleA product page, whose live route is\n         `aaas.love/tripleA` (packages/ui/src/ecosystem-links.ts — [\"Products\",\"Love\",\".../tripleA\"]).\n         2026-09-22: folded into Select as a page (see that entry); the apex homepage it was once\n         confused with now has its own file at platform/AaaS Love Home.dc.html. */\n      /* Corrected 2026-09-20. This said `vault.aaas.love` — the future host, not the live one, which\n         is the exact error the operator suspected on toolbox. canonical-hosts.ts pins HOSTS.vault =\n         \"https://github.com/AaaS-Love/aaas-vault\" and names vault a migration that \"hasn't started\";\n         `vault.aaas.love` appears only as the eventual target. The path also disagrees with the\n         Platform mega-menu, which links Vault at `/vault` on the apex. The apex route is what a\n         visitor can reach today, so that is the tld; the GitHub origin is where it is served from. */\n      { id: 'vault', label: 'Vault', tld: 'aaas.love/vault', icon: 'grid', pages: [{ label: 'Home', file: D + 'vault/Vault Redesign.dc.html' }] },\n      /* Replaced \"Agents' Work\" 2026-09-22 (operator: Legal \"show the agents-as-a-service.com site,\n         which also includes the work page and redirects to it\").\n         The host is the operator's, and the repo backs it: canonical-hosts.ts:43 pins\n         agents-as-a-service.com as \"a SEPARATE, PERSISTENT, self-canonical corporate apex\"\n         (directive #3830) that \"renders the same root route as aaas.love but must not redirect or\n         canonicalize to it\", listed in CORPORATE_APEX_HOSTS; redirect-rules.test.ts asserts it is\n         never a redirect SOURCE. One app (apps/platform) serves both hosts, so these four pages are\n         live on .com and on the apex alike — hence the .com tld here and the .com paths per page.\n         All four are separate documents on purpose: the German pair is legally authoritative\n         (§ 5 TMG, DSGVO), the English pair is labelled a translation, and\n         legal-page-canonicals.test.ts forbids either canonicalising onto the other.\n         Terms is the fifth page and is PRODUCT-SCOPED, which is why its pageTld is a boutique\n         path: only boutique and builders have a Terms document in the repo (each mirrored on the\n         apex) and there is no ecosystem-wide one — so it is labelled by scope rather than\n         presented as the platform's. The Impressum is the opposite case: thirteen apps ship an\n         /impressum route and every one is a ~460-byte wrapper around the same shared\n         ImpressumContent, so one page serves all fourteen URLs.\n         `work/Your Agents' Work.dc.html` is untouched on disk — a visibility decision, as with the\n         wireframes dropped on 2026-09-20. */\n      { id: 'legal', label: 'Legal', tld: 'agents-as-a-service.com', icon: 'verified', pages: [\n        /* Home added 2026-10-12 (operator): the corporate apex page itself — the one that introduces AaaS to\n           investors, founders and operators, with the team and the earlier-work summary. Source:\n           AaaS-Love/AaaS.Love_old apps/platform /corporate. First, so it is the stream's landing page. */\n        { label: 'Home', file: D + 'platform/AaaS Corporate Home.dc.html', pageTld: 'agents-as-a-service.com' },\n        { label: 'Legal Notice', file: D + 'platform/AaaS Legal.dc.html?route=legal', pageTld: 'agents-as-a-service.com/legal' },\n        { label: 'Impressum', file: D + 'platform/AaaS Legal.dc.html?route=impressum', pageTld: 'agents-as-a-service.com/impressum' },\n        { label: 'Privacy', file: D + 'platform/AaaS Legal.dc.html?route=privacy', pageTld: 'agents-as-a-service.com/privacy' },\n        { label: 'Datenschutz', file: D + 'platform/AaaS Legal.dc.html?route=datenschutz', pageTld: 'agents-as-a-service.com/datenschutz' },\n        { label: 'Terms', file: D + 'platform/AaaS Legal.dc.html?route=terms', pageTld: 'aaas.love/boutique/terms' }] },\n    ];\n\n    this.MEGA = [\n      { label: 'Home', ids: ['home'] },\n      { label: 'Products', ids: ['podcast', 'design', 'select', 'intelligence', 'content'] },\n      { label: 'Canvas 2.0', ids: ['zui', 'foundry', 'cro'] },\n      { label: 'Go-to-market', ids: ['icp', 'leads'] },\n      { label: 'Paid ads', ids: ['ads'] },\n      { label: 'Domains', ids: ['domains'] },\n      { label: 'Money', ids: ['costs', 'finances'] },\n      { label: 'Platform', ids: ['ops', 'quality', 'marketing', 'product', 'platform'] }\n    ];\n    this.SLOTS = [\n      { icon: 'table', name: 'Data table', desc: 'Runs, orders, invoices — sortable rows with status + cost.' },\n      { icon: 'timeline', name: 'Decision timeline', desc: 'Run steps with expandable alternates.' },\n      { icon: 'grid', name: 'Metric grid', desc: 'Value + delta + progress cells.' },\n      { icon: 'platform', name: 'Agent list', desc: 'Roster rows with live status dots.' },\n      { icon: 'chart', name: 'Chart / graph', desc: 'Cost trend · throughput · funnel.' },\n      { icon: 'drawer', name: 'Detail drawer', desc: 'Row → provenance · error · replay.' }\n    ];\n    this.ROSTER = [\n      { name: 'gen-agt', status: 'run', task: 'narrating ep 05-27 · 14m elapsed' },\n      { name: 'postiz-agt', status: 'warn', task: 'rate-limited on X · backoff 4m' },\n      { name: 'sales-agt', status: 'run', task: 'enriching Helsing inbound' },\n      { name: 'ranker-agt', status: 'ok', task: 'idle · last run 06:30' },\n      { name: 'inventory-agt', status: 'warn', task: 'SKU-441 low · awaiting decision' },\n      { name: 'ops-agt', status: 'ok', task: 'monitoring · 2 alerts' }\n    ];\n    this.TASKS = [\n      { id: 't1', label: 'Run podcast pipeline · 2026-05-27', meta: 'stage 3 of 5 · gen-agt', tone: 'run' },\n      { id: 't2', label: 'Enrich Helsing inbound', meta: 'sales-agt · 4 sources', tone: 'run' },\n      { id: 't3', label: 'Replay Postiz syndication · 05-27', meta: 'awaiting OpenRouter quota', tone: 'warn' }\n    ];\n\n    // ── 01 · ZUI process-tree seed (adversarial: 14 nodes, 7 depths, all states) ──\n    // col 0..5 → x ; depth 0..6 → row. Positions are grid cells; layout is top-down.\n    this.ZUI_NODES = [\n      { id: 'n1', col: 2, row: 0, title: 'Plan: reconcile May AP ledger', type: 'gate', state: 'ok', started: '06:30:02', durationMs: 1400, summary: 'Approved · 5 sub-goals', output: 'goals=[fetch, match, flag, post, report]\\nbudget=€0.50 confidence=0.91' },\n      { id: 'n2', col: 1, row: 1, title: 'fetch-invoices', type: 'tool', state: 'ok', started: '06:30:04', durationMs: 3120, summary: '412 invoices · Stripe + GCS', output: 'pulled 412 invoices\\nspan=2026-05-01..05-31' },\n      { id: 'n3', col: 3, row: 1, title: 'fetch-bank-statements', type: 'tool', state: 'ok', started: '06:30:04', durationMs: 2870, summary: '388 lines · 2 accounts', output: 'accounts=[EUR-main, EUR-fees]\\nlines=388' },\n      { id: 'n4', col: 1, row: 2, title: 'normalize-currency', type: 'agent', state: 'ok', started: '06:30:08', durationMs: 5400, summary: 'FX @ ECB daily', output: 'converted 71 non-EUR rows' },\n      { id: 'n5', col: 3, row: 2, title: 'match-agt', type: 'agent', state: 'ok', started: '06:30:08', durationMs: 12400, summary: '372 / 412 auto-matched', output: '90.3% matched\\nresidual=40 rows' },\n      { id: 'n6', col: 2, row: 3, title: 'reconciliation-ledger.parquet', type: 'artifact', state: 'ok', started: '06:30:21', durationMs: 900, summary: 'gs://aaas-fin/recon/2026-05', output: 'rows=412 size=2.1MB' },\n      { id: 'n7', col: 1, row: 4, title: 'flag-exceptions-agt (long-running deep-research pass over residual rows)', type: 'agent', state: 'running', started: '06:30:22', durationMs: 0, summary: 'scoring 40 residuals…', output: 'streaming…\\n17/40 scored\\nETA 40s' },\n      { id: 'n8', col: 3, row: 4, title: 'vendor-enrich-tool', type: 'tool', state: 'blocked', started: '—', durationMs: 0, summary: 'waiting on rate-limit', output: 'blocked: OpenCorporates 429\\nretry-after=120s' },\n      { id: 'n9', col: 0, row: 5, title: 'post-to-ledger-gate', type: 'gate', state: 'idle', started: '—', durationMs: 0, summary: 'awaits n7', output: 'gate: requires exceptions cleared' },\n      { id: 'n10', col: 2, row: 5, title: 'anomaly-detect-agt', type: 'agent', state: 'failed', started: '06:30:31', durationMs: 2100, summary: 'model timeout', output: 'FAILED: upstream 504\\nafter 2 retries' },\n      { id: 'n11', col: 4, row: 5, title: 'notify-slack-tool', type: 'tool', state: 'idle', started: '—', durationMs: 0, summary: 'awaits n8', output: 'idle' },\n      { id: 'n12', col: 0, row: 6, title: 'AP-report-May.pdf', type: 'artifact', state: 'idle', started: '—', durationMs: 0, summary: 'awaits n9', output: 'idle' },\n      { id: 'n13', col: 3, row: 6, title: 'retry-anomaly-agt', type: 'agent', state: 'idle', started: '—', durationMs: 0, summary: 'awaits n10 review', output: 'idle' },\n      { id: 'n14', col: 2, row: 4, title: 'human-review-gate', type: 'gate', state: 'blocked', started: '—', durationMs: 0, summary: 'needs operator', output: 'blocked: 1 approval pending' }\n    ];\n    this.ZUI_EDGES = [\n      { from: 'n1', to: 'n2', kind: 'dep' }, { from: 'n1', to: 'n3', kind: 'dep' },\n      { from: 'n2', to: 'n4', kind: 'data' }, { from: 'n3', to: 'n5', kind: 'data' },\n      { from: 'n4', to: 'n6', kind: 'data' }, { from: 'n5', to: 'n6', kind: 'data' },\n      { from: 'n6', to: 'n7', kind: 'data' }, { from: 'n6', to: 'n8', kind: 'dep' },\n      { from: 'n6', to: 'n14', kind: 'dep' },\n      { from: 'n7', to: 'n9', kind: 'dep' }, { from: 'n8', to: 'n11', kind: 'dep' },\n      { from: 'n8', to: 'n10', kind: 'data', state: 'failed' },\n      { from: 'n9', to: 'n12', kind: 'data' }, { from: 'n10', to: 'n13', kind: 'dep', state: 'failed' }\n    ];\n\n    // ── 02 · Foundry seed (F1–F6 roster + queue + escalations) ──\n    const spark = (arr) => arr;\n    this.FOUNDRY_WORKERS = [\n      { id: 'w1', name: 'intake-agt', maturity: 1, state: 'running', currentTask: 'triage inbound #4821', tp: spark([2,3,2,4,3,5,4,6]) },\n      { id: 'w2', name: 'classify-agt', maturity: 1, state: 'ok', currentTask: 'idle · last 06:12', tp: spark([1,2,1,3,2,2,3,2]) },\n      { id: 'w3', name: 'scrape-worker-eu-west (residential-proxy pool, long name)', maturity: 1, state: 'blocked', currentTask: 'proxy pool exhausted', tp: spark([3,2,1,0,0,1,0,0]) },\n      { id: 'w4', name: 'dedupe-agt', maturity: 2, state: 'running', currentTask: 'dedupe 12k rows', tp: spark([4,5,4,6,5,7,6,8]) },\n      { id: 'w5', name: 'enrich-agt', maturity: 2, state: 'ok', currentTask: 'idle', tp: spark([2,2,3,3,4,3,4,4]) },\n      { id: 'w6', name: 'summarize-agt', maturity: 3, state: 'running', currentTask: 'summarize ep 05-27', tp: spark([5,6,5,7,6,8,7,9]) },\n      { id: 'w7', name: 'ranker-agt', maturity: 3, state: 'failed', currentTask: 'model 504 · 2 retries', tp: spark([6,5,4,2,0,0,1,0]) },\n      { id: 'w8', name: 'match-agt', maturity: 3, state: 'ok', currentTask: 'idle · 90.3% last run', tp: spark([7,7,8,8,7,9,8,9]) },\n      { id: 'w9', name: 'sales-agt', maturity: 4, state: 'running', currentTask: 'enrich Helsing inbound', tp: spark([8,9,8,10,9,11,10,12]) },\n      { id: 'w10', name: 'gen-agt', maturity: 4, state: 'warn', currentTask: 'rate-limited on X', tp: spark([9,8,7,9,6,7,8,6]) },\n      { id: 'w11', name: 'recon-agt', maturity: 5, state: 'ok', currentTask: 'AP ledger posted', tp: spark([10,11,10,12,11,13,12,14]) },\n      { id: 'w12', name: 'ops-agt', maturity: 6, state: 'running', currentTask: 'fleet monitor · 2 alerts', tp: spark([12,13,12,14,13,15,14,16]) }\n    ];\n    this.FOUNDRY_QUEUE = [\n      { id: 'q1', task: 'triage inbound #4821', worker: 'intake-agt', stream: 'Sales', state: 'run', age: '2m 10s', attempts: 1 },\n      { id: 'q2', task: 'dedupe residual rows (batch 7 of 9, largest partition this cycle)', worker: 'dedupe-agt', stream: 'Data', state: 'run', age: '11m 02s', attempts: 1 },\n      { id: 'q3', task: 'scrape vendor registry EU-west', worker: 'scrape-worker-eu-west', stream: 'Enrich', state: 'blocked', attempts: 4, age: '38m 44s', blockReason: 'proxy pool exhausted — awaiting residential IP lease (429 from upstream)' },\n      { id: 'q4', task: 'rank May candidates', worker: 'ranker-agt', stream: 'Select', state: 'fail', age: '6m 30s', attempts: 3 },\n      { id: 'q5', task: 'summarize ep 05-27', worker: 'summarize-agt', stream: 'Podcast', state: 'run', age: '4m 12s', attempts: 1 },\n      { id: 'q6', task: 'post AP ledger', worker: 'recon-agt', stream: 'Finance', state: 'ok', age: '—', attempts: 1 },\n      { id: 'q7', task: 'syndicate to X', worker: 'gen-agt', stream: 'Marketing', state: 'warn', age: '9m 05s', attempts: 2 }\n    ];\n    this.FOUNDRY_ESC = [\n      { id: 'e1', severity: 'fail', ask: 'Promote recon-agt to F6 (full autonomy on ledger posting)', owner: 'you', waiting: '2h 14m' },\n      { id: 'e2', severity: 'fail', ask: 'ranker-agt failed 3× — approve rollback to prior model', owner: 'ops', waiting: '46m' },\n      { id: 'e3', severity: 'warn', ask: 'scrape-worker blocked — approve residential-proxy budget €40/mo', owner: 'you', waiting: '38m' },\n      { id: 'e4', severity: 'warn', ask: 'gen-agt rate-limited — approve X API tier upgrade', owner: 'growth', waiting: '22m' },\n      { id: 'e5', severity: 'ok', ask: 'Approve F3→F4 promotion for match-agt', owner: 'you', waiting: '12m' },\n      { id: 'e6', severity: 'ok', ask: 'Review dedupe-agt throughput dip', owner: 'data', waiting: '5m' }\n    ];\n\n    // ── 03 · CRO seed (overall + dimensions + funnel) ──\n    this.CRO_OVERALL = { score: 72, band: 'ok', deltaPct: 2.1 };\n    this.CRO_DIMS = [\n      { name: 'Checkout friction', score: 38, band: 'fail', deltaPct: -6.4, topIssue: '3-step checkout with forced account creation before payment step', spark: [58,54,49,46,44,41,39,38], sub: [ { c: 'Guest checkout available', s: 'fail', impact: 'High' }, { c: 'Payment methods ≥ 3', s: 'warn', impact: 'Med' }, { c: 'Address autocomplete', s: 'fail', impact: 'Med' } ] },\n      { name: 'Trust signals', score: 61, band: 'warn', deltaPct: -1.2, topIssue: 'No visible security badges near payment', spark: [64,63,63,62,62,61,61,61], sub: [ { c: 'SSL badge shown', s: 'ok', impact: 'Low' }, { c: 'Reviews visible', s: 'warn', impact: 'High' }, { c: 'Return policy linked', s: 'ok', impact: 'Low' } ] },\n      { name: 'Mobile UX', score: 66, band: 'warn', deltaPct: 3.8, topIssue: 'Tap targets < 44px on filter chips', spark: [58,60,61,62,63,64,65,66], sub: [ { c: 'Tap targets ≥ 44px', s: 'warn', impact: 'Med' }, { c: 'Viewport meta set', s: 'ok', impact: 'Low' } ] },\n      { name: 'Clarity', score: 78, band: 'ok', deltaPct: 0.5, topIssue: 'Primary CTA competes with 2 secondaries above fold', spark: [77,77,78,78,77,78,78,78], sub: [ { c: 'Single primary CTA', s: 'warn', impact: 'Med' }, { c: 'Value prop above fold', s: 'ok', impact: 'Low' } ] },\n      { name: 'Performance', score: 91, band: 'ok', deltaPct: 1.4, topIssue: 'LCP 2.1s on PDP (target < 2.5s — passing)', spark: [86,87,88,89,90,90,91,91], sub: [ { c: 'LCP < 2.5s', s: 'ok', impact: 'Low' }, { c: 'CLS < 0.1', s: 'ok', impact: 'Low' } ] },\n      { name: 'Page speed index', score: 94, band: 'ok', deltaPct: 0.2, topIssue: 'Third-party tags add 180ms', spark: [92,93,93,93,94,94,94,94], sub: [ { c: 'TTFB < 600ms', s: 'ok', impact: 'Low' } ] }\n    ];\n    this.CRO_FUNNEL = [\n      { step: 'Landing', count: 48200, pct: 100, health: 'ok' },\n      { step: 'Product view', count: 31300, pct: 65, health: 'ok' },\n      { step: 'Add to cart', count: 14100, pct: 29, health: 'warn' },\n      { step: 'Checkout', count: 5900, pct: 12, health: 'fail' },\n      { step: 'Purchase', count: 1640, pct: 3.4, health: 'warn' }\n    ];\n\n    // ── Onboarding · 6 canonical steps ──\n    this.ONB_STEPS = [\n      { id: 0, label: 'Value', title: 'Why a canvas', blurb: 'Your canvas is one shell over every AaaS surface — agents, runs, and dashboards in a single operator view.', kind: 'read' },\n      { id: 1, label: 'Context', title: 'Tell us your context', blurb: 'A few details tune which agents and templates surface first.', kind: 'context' },\n      { id: 2, label: 'Detect engine', title: 'Detecting your stack', blurb: 'We scan connected signals to detect the context engine that fits your workflow.', kind: 'detect' },\n      { id: 3, label: 'Connect', title: 'Connect a source', blurb: 'Wire one platform so agents have something real to work on.', kind: 'connect' },\n      { id: 4, label: 'Spawn demo agent', title: 'Spawn your first agent', blurb: 'Run a demo agent end-to-end against your context — no commitment.', kind: 'spawn' },\n      { id: 5, label: 'Land', title: \"You're set\", blurb: 'That\\u2019s the loop. Pick where your canvas opens and go.', kind: 'land' }\n    ];\n    this.ONB_CONNECT = [\n      { id: 'stripe', name: 'Stripe', tag: 'Payments', connected: true },\n      { id: 'gcs', name: 'Google Cloud', tag: 'Storage', connected: true },\n      { id: 'slack', name: 'Slack', tag: 'Comms', connected: false },\n      { id: 'gh', name: 'GitHub', tag: 'Code', connected: false },\n      { id: 'hubspot', name: 'HubSpot', tag: 'CRM', connected: false },\n      { id: 'x', name: 'X / Twitter', tag: 'Social', connected: false }\n    ];\n    this.ONB_CHECKLIST = [\n      { id: 'brand', label: 'Set your brand', done: true },\n      { id: 'source', label: 'Connect a source', done: false },\n      { id: 'agent', label: 'Run your first agent', done: false },\n      { id: 'invite', label: 'Invite a teammate', done: false }\n    ];\n\n    // ── Account & Settings ──\n    this.LANDING_OPTS = [\n      { id: 'onboarding', label: 'Onboarding', desc: 'Until setup is complete', dest: 'Onboarding' },\n      { id: 'home', label: 'Home', desc: 'Your member dashboard', dest: 'Home' },\n      { id: 'last', label: 'Last-touched', desc: 'Resume where I left off', dest: 'Process Tree' },\n      { id: 'specific', label: 'A specific dashboard', desc: 'Always open one stream', dest: 'CRO' }\n    ];\n    this.ACC_SERVICES = [\n      { id: 'stripe', name: 'Stripe', state: 'ok', meta: 'synced 06:12' },\n      { id: 'gcs', name: 'Google Cloud', state: 'ok', meta: 'synced 06:12' },\n      { id: 'openrouter', name: 'OpenRouter', state: 'warn', meta: 'quota 82%' },\n      { id: 'slack', name: 'Slack', state: 'idle', meta: 'not connected' },\n      { id: 'gh', name: 'GitHub', state: 'idle', meta: 'not connected' },\n      { id: 'x', name: 'X / Twitter', state: 'fail', meta: 'auth expired' },\n      // W2 piece 4 spot-check: the four K.accountSettings services the live list lacked (kinds-member.jsx:343), states verbatim.\n      { id: 'postiz', name: 'Postiz', state: 'ok', meta: 'connected' },\n      { id: 'notion', name: 'Notion', state: 'ok', meta: 'connected' },\n      { id: 'linear', name: 'Linear', state: 'idle', meta: 'optional' },\n      { id: 'airtable', name: 'Airtable', state: 'idle', meta: 'optional' }\n    ];\n    this.ACC_NOTIFS = [\n      { id: 'digest', label: 'Daily digest', desc: 'One summary email each morning', on: true },\n      { id: 'alerts', label: 'Run alerts', desc: 'Ping me when a run fails or blocks', on: true },\n      { id: 'nurture', label: 'Product nurture', desc: 'Tips as new capabilities ship', on: false }\n    ];\n    this.ACC_SECTIONS = ['Profile', 'Home landing', 'Email & notifications', 'Connected services', 'Brand', 'Referral', 'Danger zone'];\n\n    // ── ICP Builder seed (real field names — no invented metrics) ──\n    this.ICP_FIRMO = [\n      { key: 'industry', label: 'industry[]', values: ['SaaS', 'Fintech', 'Healthtech', 'Logistics'], selected: ['SaaS', 'Fintech'] },\n      { key: 'naics_sic', label: 'NAICS / SIC[]', values: ['5415', '5182', '5221', '3254'], selected: ['5415'] },\n      { key: 'business_model', label: 'business_model[]', values: ['B2B', 'B2B2C', 'Marketplace', 'PLG'], selected: ['B2B', 'PLG'] },\n      { key: 'geo', label: 'geo[]', values: ['NA', 'EU', 'UK', 'APAC', 'LATAM'], selected: ['NA', 'EU'] },\n      { key: 'tech_stack', label: 'tech_stack[]', values: ['AWS', 'Snowflake', 'Salesforce', 'HubSpot', 'Segment'], selected: ['Snowflake', 'Salesforce'] }\n    ];\n    this.ICP_RANGES = [\n      { key: 'employee_range', label: 'employee_range', value: '50 – 500' },\n      { key: 'revenue_range_usd', label: 'revenue_range_usd', value: '$10M – $100M' }\n    ];\n    this.ICP_SEGMENTS = [\n      { name: 'Mid-market SaaS', priority: 'P1', rationale: 'Fastest sales cycle, strong data maturity', tam: '$1.8B' },\n      { name: 'EU Fintech', priority: 'P2', rationale: 'High ACV, compliance-gated but sticky', tam: '$1.5B' },\n      { name: 'Healthtech scale-ups', priority: 'P3', rationale: 'Long cycle, expansion potential', tam: '$0.9B' }\n    ];\n    this.ICP_PERSONAS = [\n      { id: 'p1', name: 'VP Revenue Ops', role_titles: ['VP RevOps', 'Head of Revenue Operations'], buying_role: 'economic', tam: '$1.8B', spark: [4,5,5,6,7,7,8,9],\n        goals: ['Unify pipeline data', 'Forecast accuracy > 90%', 'Reduce CAC payback'], watering_holes: ['RevOps Co-op', 'Pavilion', 'LinkedIn #revops'], pains: ['Fragmented CRM data', 'Manual reconciliation', 'No single source of truth'] },\n      { id: 'p2', name: 'Head of Growth', role_titles: ['Head of Growth', 'Director of Demand Gen'], buying_role: 'champion', tam: '$1.2B', spark: [3,4,4,5,5,6,7,7],\n        goals: ['Scale qualified pipeline', 'Improve funnel conversion', 'Attribute spend'], watering_holes: ['Demand Curve', 'Growth Hackers', 'r/marketing'], pains: ['Leaky funnel', 'Weak lead scoring', 'Opaque attribution'] },\n      { id: 'p3', name: 'SDR Manager', role_titles: ['SDR Manager', 'Sales Development Lead'], buying_role: 'user', tam: '$0.7B', spark: [2,3,3,4,4,5,5,6],\n        goals: ['Higher connect rates', 'Cleaner lead lists', 'Faster ramp'], watering_holes: ['Sales Hacker', 'RepDev', 'Outreach community'], pains: ['Stale contact data', 'Low answer rates', 'Territory overlap'] },\n      { id: 'p4', name: 'CISO', role_titles: ['CISO', 'VP Security'], buying_role: 'blocker', tam: '$0.5B', spark: [5,5,4,4,3,3,2,2],\n        goals: ['Data residency compliance', 'Vendor risk control', 'Auditability'], watering_holes: ['CISO Circle', 'ISC2', 'r/netsec'], pains: ['Uncontrolled data export', 'Consent gaps', 'Shadow tooling'] }\n    ];\n    this.ICP_COUNTRIES = [\n      { code: 'US', bucket: 'allow' }, { code: 'CA', bucket: 'allow' }, { code: 'GB', bucket: 'allow' }, { code: 'AU', bucket: 'allow' }, { code: 'NL', bucket: 'allow' },\n      { code: 'DE', bucket: 'consent' }, { code: 'FR', bucket: 'consent' }, { code: 'ES', bucket: 'consent' }, { code: 'IT', bucket: 'consent' },\n      { code: 'RU', bucket: 'block' }, { code: 'IR', bucket: 'block' }\n    ];\n\n    // ── Leads seed (verify buckets: deliverable | risky | catch-all | undeliverable) ──\n    this.LEADS = [\n      { id: 'l1', company: 'Acme Dynamics', contact: 'M. Sundqvist', title: 'VP RevOps', email: 'm.sundqvist@acme.io', li: 'linkedin.com/in/msundqvist', verify: 'deliverable', icp: 0.88 },\n      { id: 'l2', company: 'Quorvela', contact: 'T. Bianchi', title: 'Head of Growth', email: 't.bianchi@quorvela.com', li: 'linkedin.com/in/tbianchi', verify: 'catch-all', icp: 0.71 },\n      { id: 'l3', company: 'Northwind Labs', contact: 'A. Okafor', title: 'SDR Manager', email: 'a.okafor@northwind.co', li: 'linkedin.com/in/aokafor', verify: 'deliverable', icp: 0.82 },\n      { id: 'l4', company: 'Ferrix Systems', contact: 'L. Marchetti', title: 'Director Demand Gen', email: 'l.marchetti@ferrix.eu', li: 'linkedin.com/in/lmarchetti', verify: 'risky', icp: 0.64 },\n      { id: 'l5', company: 'Ballast & Co', contact: 'S. Yamamoto', title: 'CISO', email: 's.yamamoto@ballast.jp', li: 'linkedin.com/in/syamamoto', verify: 'undeliverable', icp: 0.41 },\n      { id: 'l6', company: 'Helios Freight', contact: 'K. Novak', title: 'VP Revenue Operations', email: 'k.novak@heliosfreight.com', li: 'linkedin.com/in/knovak', verify: 'deliverable', icp: 0.79 },\n      { id: 'l7', company: 'Petal Health', contact: 'R. Desai', title: 'Head of RevOps', email: 'r.desai@petalhealth.io', li: 'linkedin.com/in/rdesai', verify: 'risky', icp: 0.68 },\n      { id: 'l8', company: 'Cobalt Str.', contact: 'E. Larsson', title: 'Sales Development Lead', email: 'e.larsson@cobalt.se', li: 'linkedin.com/in/elarsson', verify: 'catch-all', icp: 0.73 }\n    ];\n    this.DISCO_SOURCES = [\n      { id: 'apollo', label: 'Apollo', on: true }, { id: 'clay', label: 'Clay', on: true }, { id: 'li', label: 'LinkedIn Sales Nav', on: false }, { id: 'crunch', label: 'Crunchbase', on: false }\n    ];\n    this.DISCO_ICPS = ['Mid-market SaaS (P1)', 'EU Fintech (P2)', 'Healthtech scale-ups (P3)'];\n\n    // ═══ SQUR Ads Command Center (brief 08) — real, load-bearing numbers ═══\n    // §5 · Campaign specs (8, two tiers, 3 channels)\n    this.SQUR_SPECS = [\n      { file: '01-search-brand.json', name: 'SQUR-Search-Brand', channel: 'SEARCH', tier: 'day0', budget: 5, bid: 'Manual CPC ≤€1.00', goal: 'scan_email_submitted', adGroups: 1, keywords: 12, rsaH: 8, rsaD: 4 },\n      { file: '02-search-compliance-lt.json', name: 'SQUR-Search-Compliance-LT', channel: 'SEARCH', tier: 'day0', budget: 35, bid: 'Max Conversions', goal: 'fix_kit_purchased', fallback: 'scan', adGroups: 4, keywords: 68, rsaH: 12, rsaD: 4, leadForm: true },\n      { file: '03-search-fix-kit.json', name: 'SQUR-Search-Fix-Kit', channel: 'SEARCH', tier: 'day0', budget: 20, bid: 'Max Conversions', goal: 'fix_kit_purchased', fallback: 'scan', adGroups: 3, keywords: 41, rsaH: 9, rsaD: 4 },\n      { file: '04-display-remarketing.json', name: 'SQUR-Display-Remarketing', channel: 'DISPLAY', tier: 'day0', budget: 10, bid: 'Max Conversions', goal: 'scan_email_submitted', banners: ['300×250', '336×280', '728×90', '160×600', '320×100'], audience: 'RM-AllVisitors-30d', message: 'Free attack surface scan' },\n      { file: 'phase2/05-search-asm.json', name: 'SQUR-Search-ASM', channel: 'SEARCH', tier: 'phase2', budget: 25, bid: 'Max Conversions', goal: 'fix_kit_purchased', adGroups: 5, keywords: 54, rsaH: 10, rsaD: 4, note: 'ASM head terms deferred until Phase-1 CVR data' },\n      { file: 'phase2/06-search-pentest.json', name: 'SQUR-Search-Pentest', channel: 'SEARCH', tier: 'phase2', budget: 30, bid: 'Max Conversions', goal: 'pentest_purchased', adGroups: 4, keywords: 47, rsaH: 11, rsaD: 4, note: \"live-adjacent — squr.ai's actual funnel product\" },\n      { file: 'phase2/07-search-competitor.json', name: 'SQUR-Search-Competitor', channel: 'SEARCH', tier: 'phase2', budget: 10, bid: 'Manual CPC ≤€1.50', goal: 'scan_email_submitted', adGroups: 2, keywords: 18, rsaH: 6, rsaD: 3, note: 'never names competitor; mute on cease-and-desist' },\n      { file: 'phase2/08-demandgen-eu.json', name: 'SQUR-DemandGen-EU', channel: 'DEMAND_GEN', tier: 'phase2', budget: 15, bid: 'Max Conversions', goal: 'scan_email_submitted', variants: ['A', 'B', 'C'], note: 'Variants A/B/C creative, no RSA structure' }\n    ];\n    // Per-spec dynamic validation outcomes (rule-driven, not one frozen case)\n    this.SQUR_VALIDATION = {\n      '02-search-compliance-lt.json': [{ rule: 'banned token: \"comprehensive\"', where: 'headline[4]', text: 'Comprehensive LT compliance' }],\n      'phase2/06-search-pentest.json': [{ rule: 'headline ≤ 30 chars', where: 'headline[7]', text: 'Autonomous continuous pentest platform (41)' }, { rule: 'banned token: \"!\"', where: 'description[1]', text: 'Book your pentest now!' }]\n    };\n    this.SQUR_CHANNELS = { SEARCH: { color: 'var(--app-accent)', icon: 'radar' }, DISPLAY: { color: 'var(--app-warn)', icon: 'display' }, DEMAND_GEN: { color: 'rgb(var(--pastel-lavender,147 154 255))', icon: 'sparkles' } };\n\n    // §7 · Conversion actions (6, all REPLACE_ME)\n    this.SQUR_CONV = [\n      { action: 'scan_email_submitted', value: '€0', count: 'ONE', primary: 'Phase-1 primary', note: 'micro-conversion' },\n      { action: 'fix_kit_purchased', value: '€21', count: 'EVERY', primary: 'Phase 1-2 primary' },\n      { action: 'subscription_started', value: '€42', count: 'EVERY', primary: null },\n      { action: 'subscription_yearly_renewed', value: '€500', count: 'EVERY', primary: null, isNew: true },\n      { action: 'pentest_purchased', value: '€1,696', count: 'EVERY', primary: 'Phase 2-3 primary revenue' },\n      { action: 'autonomous_pentest_inquiry', value: '€5,000', count: 'EVERY', primary: null, isNew: true, estimate: true }\n    ];\n\n    // §6 · Assets & Brand Safety config groups\n    this.SQUR_ASSETS = [\n      { id: 'sitelinks', src: 'ad_extensions.yaml', label: 'Sitelinks', count: 8, note: 'text + 2 description lines · Google rotates 4–6', state: 'applied' },\n      { id: 'callouts', src: 'ad_extensions.yaml', label: 'Callouts', count: 10, note: 'Google rotates 6+', state: 'applied' },\n      { id: 'snippets', src: 'ad_extensions.yaml', label: 'Structured snippets', count: 3, note: 'Services / Featured / Types', state: 'review' },\n      { id: 'promo', src: 'ad_extensions.yaml', label: 'Promotion extension', count: 1, note: '\"Free attack surface scan\"', state: 'notapplied' },\n      { id: 'leadform', src: 'ad_extensions.yaml', label: 'Lead-form extension', count: 1, note: 'Compliance-LT only · 4 fields + webhook', state: 'notapplied' },\n      { id: 'call', src: 'ad_extensions.yaml', label: 'Call extension', count: 0, note: 'OFF by default — deliberate', state: 'disabled' }\n    ];\n    this.SQUR_SAFETY = [\n      { id: 'inventory', label: 'Inventory type', kind: 'value', value: 'LIMITED', note: '~30% less reach · accepted B2B tradeoff', state: 'applied' },\n      { id: 'topics_off', label: 'Off-target topic exclusions', kind: 'exclude', count: 19, sample: ['Games', 'Sports', 'Entertainment celebs', 'Dating'], state: 'applied' },\n      { id: 'topics_sensitive', label: 'Sensitive-topic exclusions', kind: 'exclude', count: 6, sample: ['Tragedy & conflict', 'Sensitive social issues', 'Profanity'], state: 'applied' },\n      { id: 'topics_target', label: 'Target topics (allow-list)', kind: 'allow', count: 9, sample: ['Enterprise tech', 'Cybersecurity', 'IT & networking', 'Business software'], state: 'review' },\n      { id: 'placements', label: 'Placement-domain exclusions', kind: 'exclude', count: 16, sample: ['ehow.com', 'buzzfeed.com', 'reddit.com', 'wikihow.com'], state: 'applied' },\n      { id: 'apps', label: 'App-category exclusions', kind: 'exclude', count: 7, sample: ['Games', 'Casual', 'Entertainment'], state: 'review' },\n      { id: 'wildcards', label: 'Domain-pattern wildcards', kind: 'exclude', count: 3, sample: ['*free download*', '*warez*', '*crack*'], state: 'applied' }\n    ];\n\n    // §8 · Budget pacing / greenlight / bid progression / kill-switch\n    this.SQUR_PHASES = [\n      { phase: 'Phase 1', window: 'Day 0-14', budget: 70, ceiling: 5000, spent: 2870, target: '~70 scans · ~14 buyers · CPL €14', state: 'running' },\n      { phase: 'Phase 2', window: 'Greenlight met', budget: 180, ceiling: null, spent: 0, target: 'scale winners', state: 'locked' },\n      { phase: 'Phase 3', window: 'Day 45+', budget: 240, ceiling: null, spent: 0, target: 'pentest sales attributed', state: 'locked' }\n    ];\n    this.SQUR_GREENLIGHT = [\n      { crit: '≥ 40 scan emails', target: '50–70', value: 46, met: true },\n      { crit: '≥ 4% click→scan CVR', target: '4%', value: 4.6, met: true, pct: true },\n      { crit: '≥ 15% scan→€25 CVR', target: '6 paid by Day 14', value: 13, met: false, pct: true, unit: '%' },\n      { crit: 'blended CPL ≤ €20', target: '≤ €20', value: 14, met: true, eur: true }\n    ];\n    this.SQUR_BIDPROG = [\n      { campaign: 'SQUR-Search-Brand', stage: 'pinned', label: 'Manual CPC €0.50–1.00 (never auto-progresses)' },\n      { campaign: 'SQUR-Search-Compliance-LT', stage: 'cold', label: 'Cold start · Max Conversions (Day 0-14)' },\n      { campaign: 'SQUR-Search-Fix-Kit', stage: 'lock', label: 'Lock · 30 conv/30d → tCPA ×0.8' },\n      { campaign: 'SQUR-Display-Remarketing', stage: 'cold', label: 'Cold start · Max Conversions' },\n      { campaign: 'SQUR-Search-Pentest', stage: 'revenue', label: 'Revenue · pentest data → tROAS 3.0' }\n    ];\n    this.SQUR_KILL = [\n      { scope: 'Ad-group', rule: '€100 spend / 0 conversions → pause + audit search-terms', level: 'amber', hint: '€87/€100 spend · 0 conv' },\n      { scope: 'Ad-group', rule: 'QS < 5 across ad group → rebuild RSAs or LP', level: 'green', hint: 'min QS 6 · safe' },\n      { scope: 'Campaign', rule: '14-day CPL > 2× target (€50+) → pause, root-cause', level: 'green', hint: 'CPL €14 · target €25' },\n      { scope: 'Campaign', rule: '< 5 conversions/14d at > €200 → pause, reallocate', level: 'green', hint: '13 conv · €2,870' },\n      { scope: 'Account', rule: '30-day blended CAC > €1,200, no improving trend → pause ALL', level: 'green', hint: 'CAC €412 · improving' },\n      { scope: 'Account', rule: '60-day spend > €10k with 0 pentest sales → pause ALL', level: 'green', hint: '€2.9k · Day 41' },\n      { scope: 'Account', rule: 'Monthly burn > 110% budget for 2 months → pause ALL', level: 'amber', hint: 'month 1 · 104%' },\n      { scope: 'Account', rule: 'Brand-safety incident → pause Display + Demand Gen within 24h', level: 'red', hint: 'placement flag on buzzfeed.com · 6h ago' }\n    ];\n  }\n\n  ic(name, size) {\n    size = size || 18;\n    return React.createElement('span', {\n      style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: size, height: size, flexShrink: 0 },\n      dangerouslySetInnerHTML: { __html: '<svg style=\"flex-shrink:0\" viewBox=\"0 0 24 24\" width=\"' + size + '\" height=\"' + size + '\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\">' + (this.ICONS[name] || '') + '<\u002Fsvg>' }\n    });\n  }\n\n  componentDidMount() {\n    // Org-control P1: agent-profiles.js installs late (helmet scripts are unordered); re-render once it has.\n    try { if (!(window.AgentRegistry && window.AgentRegistry.profile)) window.addEventListener('agent-profiles-ready', () => this.forceUpdate(), { once: true }); } catch (e) {}\n    // D210 (P4) · probe hook: tools/probe/states.js drives the shell through this handle. Read-only for product code.\n    try { window.__canvasShell = this; } catch (e) {}\n    // D180: the shelf reads assets/index.json (generated) — never typed counts.\n    try { fetch((this.rootPrefix || '') + 'assets/index.json').then(r => r.ok ? r.json() : null).then(j => { if (!j || !j.classes) return;\n      this.ASSET_INDEX = { gallery: j.classes.gallery.items || [], decks: j.classes.decks.items || [], videos: j.classes.videos.pieces || [] };\n      // D219 (P4.4) · KPI counts read the same generated index, never a typed copy of it.\n      this.ASSET_COUNTS = Object.fromEntries(Object.keys(j.classes).map(c => [c, Number(j.classes[c].count) || 0]));\n      this.forceUpdate(); }).catch(() => {}); } catch (e) {}\n    // D155 · first contact is entered by an offer link (?offer=<tenant>), the email-agent use case.\n    this.tenantIds().then(ids => { if (ids && ids.length) this.setState({ businessIds: ids }); });\n    setTimeout(() => this.anchorToCta(), 400);\n    try { const off = new URLSearchParams(location.search).get('offer'); if (off) this.setState({ mvp: true, ops: false, offer: off, active: 'cdelivery', sub: 0 }); } catch (e) {}\n    /* CM-G: register the CM-5 fan-out trigger here, independently of `applyContextPacks`. It was first\n       called from inside that loader, and it never ran — the loader has an early return\n       (`__dcContextLoaded`) and a retry path, so trigger registration was hostage to pack-loading\n       control flow it has nothing to do with. Probed: the store dispatched the event and a\n       hand-registered trigger fired, while ours had never been added. Poll briefly because\n       context-store.js is still loading when this mounts; `_ctxTrig` makes it idempotent. */\n    const armCtx = (n) => {\n      this.ctxRegisterTriggers();\n      if (!this._ctxTrig && n < 40) setTimeout(() => armCtx(n + 1), 60);\n    };\n    armCtx(0);\n    // Track 2: the .store pane's catalog comes from the domain asset, never from this file.\n    this._loadStoreCatalog();\n    // D201 · Money reads the Finances fixture; the Finance Agent raises unpaid invoices once IT/AR are up.\n    try { fetch((this.rootPrefix || '') + 'assets/data/finances-source.json').then(r => r.ok ? r.json() : null).then(j => {\n      if (!j || !j.data || !j.data.INVOICES) return;\n      this.FIN_SRC = j.data; this.FIN_TOTALS = j.eurTotals || null;\n      const go = (n) => { if (window.InboxThreads && window.AgentRegistry) { this.financeRaiseUnpaid(); this.forceUpdate(); } else if (n < 40) setTimeout(() => go(n + 1), 80); };\n      go(0); }).catch(() => {}); } catch (e) {}\n    Promise.all([\n      import(this.sibBase + 'plane-validator.js'),\n      fetch(this.rootPrefix + 'assets/blocks/descriptors.json').then(r => r.json()),\n      import(this.sibBase + 'approvals.js'),\n      import(this.sibBase + 'catalog.js')\n    ]).then(([mod, idx, ap, cat]) => {\n      this._planeValidator = mod;\n      this._descriptorsIndex = idx;\n      this._approvals = ap;\n      this._catalog = cat;\n      this._unsubApprovals = ap.subscribe(() => this.forceUpdate());\n      this.forceUpdate();\n    }).catch(e => { console.warn('[plane-validator] failed to load, planes render unvalidated:', e); });\n    try {\n      const force = /[?&]intro=1/.test(window.location.search);\n      const seen = localStorage.getItem('canvas.introSeen') === '1';\n      if (force || !seen) this.setState({ tour: 0 });\n    } catch (e) {}\n    // item 13: restore the Content Viewer's Readable/Rich-media escalation choice, per workspace.\n    try {\n      const vm = localStorage.getItem('canvas.viewerMode');\n      if (vm === 'readable' || vm === 'rich') this.setState({ viewerMode: vm });\n    } catch (e) {}\n    this._key = (e) => {\n      if ((e.metaKey || e.ctrlKey) && e.key && e.key.toLowerCase() === 'k') { e.preventDefault(); this.setState(s => { const p = !s.palette; if (p) setTimeout(() => this._pin && this._pin.focus(), 30); return { palette: p, mega: false, q: '' }; }); }\n      if (e.key === 'Escape') this.setState({ palette: false, mega: false, detailRecord: null, accessDenied: null });\n    };\n    window.addEventListener('keydown', this._key);\n    this._rz = () => { const m = window.innerWidth <= 720, n = window.innerWidth <= 1000; if (m !== this.state.mob) this.setState({ mob: m, railOpen: false, settings: false, rosterOpen: !m }); if (n !== this.state.narrow) this.setState({ narrow: n }); };\n    if (window.innerWidth <= 720) this.setState({ rosterOpen: false });\n    this._rz();\n    window.addEventListener('resize', this._rz);\n    // Gallery/template iframes mount and reload as the user navigates tabs — there is no reliable\n    // single \"frame ready\" hook wired into the sc-for template (an onLoad attribute there hit a\n    // template-scoping bug), so a light standing interval re-broadcasts the active brand to every\n    // currently-mounted iframe instead. Cheap: postMessage no-ops harmlessly when nothing's listening.\n    this._brandBroadcastInterval = setInterval(() => this.broadcastBrandToFrames(), 900);\n    /* The framed domain's CANVAS button is the only way out of the domain view\n       now that the shell's own bar is gone. It posts, we ack (so its fallback\n       does not also fire a signup form on the way out) and drop the view. */\n    this._canvasReturn = e => {\n      const d = e && e.data;\n      if (!d || d.type !== 'aaas:canvas-return') return;\n      try { if (e.source) e.source.postMessage({ type: 'aaas:canvas-ack' }, '*'); } catch (err) {}\n      if (this.state.domainSite) this.setState({ domainSite: null });\n    };\n    window.addEventListener('message', this._canvasReturn);\n    this._domainEsc = e => {\n      if (e.key === 'Escape' && this.state.domainSite) this.setState({ domainSite: null });\n    };\n    window.addEventListener('keydown', this._domainEsc);\n    /* Stale-link watchdog: a lazy pane whose file can't be fetched stays an .sc-placeholder forever.\n       If one lingers >8s, probe the page URL; a failed fetch means the preview link expired. */\n    this._phSeen = new WeakMap();\n    this._staleT = setInterval(async () => {\n      if (this.state.linkStale || this._probing) return;\n      const now = Date.now(); let stuck = false;\n      document.querySelectorAll('.sc-placeholder').forEach(el => {\n        if (!this._phSeen.has(el)) this._phSeen.set(el, now);\n        else if (now - this._phSeen.get(el) > 8000 && el.offsetParent) stuck = true;\n      });\n      if (!stuck) return;\n      this._probing = true;\n      try { const r = await fetch(location.href, { method: 'HEAD', cache: 'no-store' }); if (!r.ok) throw 0; }\n      catch (e) { this.setState({ linkStale: true }); }\n      this._probing = false;\n    }, 4000);\n  }\n  componentWillUnmount() { clearInterval(this._staleT); window.removeEventListener('message', this._canvasReturn); window.removeEventListener('keydown', this._domainEsc); window.removeEventListener('keydown', this._key); window.removeEventListener('resize', this._rz); clearTimeout(this._holdT); if (this._brandBroadcastInterval) clearInterval(this._brandBroadcastInterval); }\n\n  holdStart() {\n    this.setState({ holdActive: true });\n    this._holdT = setTimeout(() => {\n      const cb = this.state.confirm && this.state.confirm.onConfirm;\n      this.setState({ holdActive: false, confirm: null });\n      if (cb) cb();\n    }, 1200);\n  }\n  holdCancel() { clearTimeout(this._holdT); this.setState({ holdActive: false }); }\n  // D213 · reversible demo flag (Tweaks-only, off by default): every HITL gate resolves instantly instead of\n  // waiting on the press-and-hold, but stays visible (the hold bar still fills to 100% — same dialog, same\n  // verb, no silent skip) and is logged to localStorage['canvas.autoApprovals'], never invisibly. Scoped to\n  // this file's own state.confirm channel — the one choke point every Approve/Reject/Decline/gate call already\n  // routes through project-wide. Does NOT touch PI.checkGates' rung logic, ctxRaise's D191 authority check, or\n  // approvals.js's persisted queue — those still run and still record normally; this only removes the shell's\n  // own wait-for-a-human-to-hold step in front of them. Reversible: flip the Tweaks toggle off, nothing here\n  // fires, D191/D192/D193 stand exactly as ruled.\n  _autoApprove() {\n    if (this.state.confirm && !this.state.confirm._auto) {\n      const c = Object.assign({}, this.state.confirm, { _auto: true });\n      this._logAutoApprove(c.title, c.verb);\n      this.setState({ confirm: c, holdActive: true });\n      clearTimeout(this._autoApproveT);\n      this._autoApproveT = setTimeout(() => {\n        if (!this.state.confirm || !this.state.confirm._auto) return; // user cancelled meanwhile\n        const cb = this.state.confirm.onConfirm;\n        this.setState({ confirm: null, holdActive: false });\n        if (cb) cb();\n      }, 550);\n    }\n  }\n  _logAutoApprove(title, verb) {\n    try {\n      const KEY = 'canvas.autoApprovals';\n      const log = JSON.parse(localStorage.getItem(KEY) || '[]');\n      log.unshift({ title: title, verb: verb, at: new Date().toISOString() });\n      localStorage.setItem(KEY, JSON.stringify(log.slice(0, 200)));\n    } catch (_e) {}\n    console.info('[auto-approve demo] ' + verb + ' — ' + title);\n  }\n  holdStartPause() {\n    this.setState({ holdActive: true });\n    this._holdT = setTimeout(() => this.setState({ holdActive: false, pauseAll: null }), 1200);\n  }\n\n  dotCol(s) {\n    return s === 'ok' ? 'rgb(var(--accent-teal))' : s === 'run' ? 'rgb(var(--circuit-glow))' : s === 'warn' ? 'rgb(var(--pastel-gold))' : s === 'fail' ? 'rgb(var(--accent-red))' : 'rgb(var(--text) / 0.4)';\n  }\n\n  // state → --app-* color for nodes / workers (running|ok|blocked|failed|idle)\n  appState(s) {\n    return s === 'running' ? 'var(--app-running)' : s === 'ok' ? 'var(--app-success)' : s === 'blocked' ? 'var(--app-warn)' : s === 'failed' ? 'var(--app-fail)' : 'transparent';\n  }\n  svg(inner, w, h, extra) {\n    return React.createElement('svg', Object.assign({ viewBox: '0 0 ' + w + ' ' + h, width: w, height: h, fill: 'none', dangerouslySetInnerHTML: { __html: inner } }, extra || {}));\n  }\n\n  // ── ZUI whiteboard: layered top-down tree, scroll-to-pan, zoom via factor z ──\n  zuiCanvas(z) {\n    const nodes = this.ZUI_NODES, edges = this.ZUI_EDGES;\n    const cellW = 250, cellH = 152, padX = 44, padY = 34, nodeW = 200, nodeH = 86;\n    const W = (padX * 2 + 5 * cellW + nodeW) * z, H = (padY * 2 + 6 * cellH + nodeH) * z;\n    const pos = {}; nodes.forEach(n => { pos[n.id] = { x: (padX + n.col * cellW) * z, y: (padY + n.row * cellH) * z, w: nodeW * z, h: nodeH * z }; });\n    // edges\n    let paths = '';\n    edges.forEach(e => {\n      const a = pos[e.from], b = pos[e.to]; if (!a || !b) return;\n      const x1 = a.x + a.w / 2, y1 = a.y + a.h, x2 = b.x + b.w / 2, y2 = b.y;\n      const my = (y1 + y2) / 2;\n      const stroke = e.state === 'failed' ? 'var(--app-fail)' : e.kind === 'data' ? 'var(--app-accent)' : 'var(--app-rule)';\n      const dash = e.state === 'failed' ? ' stroke-dasharray=\"5 4\"' : '';\n      paths += '<path d=\"M' + x1 + ' ' + y1 + ' C' + x1 + ' ' + my + ' ' + x2 + ' ' + my + ' ' + x2 + ' ' + y2 + '\" stroke=\"' + stroke + '\" stroke-width=\"' + (1.5 * z) + '\"' + dash + ' fill=\"none\"/>';\n      // arrowhead\n      const ah = 5 * z;\n      paths += '<path d=\"M' + (x2 - ah) + ' ' + (y2 - ah) + ' L' + x2 + ' ' + y2 + ' L' + (x2 + ah) + ' ' + (y2 - ah) + '\" stroke=\"' + stroke + '\" stroke-width=\"' + (1.5 * z) + '\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>';\n    });\n    const edgeSvg = React.createElement('svg', { width: W, height: H, viewBox: '0 0 ' + W + ' ' + H, style: { position: 'absolute', inset: 0, pointerEvents: 'none' }, dangerouslySetInnerHTML: { __html: paths } });\n    // node cards\n    const nodeEls = nodes.map(n => {\n      const p = pos[n.id], sel = this.state.zuiSel === n.id, col = this.appState(n.state);\n      const pulse = n.state === 'running';\n      return React.createElement('button', {\n        key: n.id, title: n.title, onClick: () => this.setState({ zuiSel: n.id }),\n        style: {\n          position: 'absolute', left: p.x, top: p.y, width: p.w, height: p.h,\n          textAlign: 'left', cursor: 'pointer', padding: 10 * z, borderRadius: 10 * z, overflow: 'hidden',\n          background: 'var(--app-surface)', backdropFilter: 'blur(8px)',\n          borderTop: '1px solid var(--app-rule)', borderRight: '1px solid var(--app-rule)', borderBottom: '1px solid var(--app-rule)',\n          borderLeft: (3 * z) + 'px solid ' + (n.state === 'idle' ? 'var(--app-rule)' : col),\n          boxShadow: sel ? '0 0 0 2px var(--app-accent), 0 0 18px var(--app-accent-glow)' : '0 2px 10px rgba(0,0,0,0.25)',\n          display: 'flex', flexDirection: 'column', gap: 3 * z, fontFamily: 'var(--font-inter)'\n        }\n      }, [\n        React.createElement('div', { key: 'h', style: { display: 'flex', alignItems: 'center', gap: 6 * z } }, [\n          React.createElement('span', { key: 'd', style: { width: 7 * z, height: 7 * z, borderRadius: '50%', flexShrink: 0, background: n.state === 'idle' ? 'var(--app-ink-faint)' : col, boxShadow: n.state === 'idle' ? 'none' : '0 0 8px ' + col, animation: pulse ? 'aaasPulse 1.6s var(--liquid-ease) infinite' : 'none' } }),\n          React.createElement('span', { key: 't', style: { fontFamily: 'var(--font-jetbrains)', fontSize: 9.5 * z, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--app-ink-faint)' } }, n.type)\n        ]),\n        React.createElement('div', { key: 'ti', style: { fontSize: 12.5 * z, fontWeight: 600, color: 'var(--app-ink)', lineHeight: 1.25, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, n.title),\n        React.createElement('div', { key: 's', style: { fontSize: 11 * z, color: 'var(--app-ink-soft)', lineHeight: 1.3, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, n.summary)\n      ]);\n    });\n    return React.createElement('div', { style: { position: 'relative', width: W, height: H } }, [edgeSvg].concat(nodeEls));\n  }\n\n  zuiMinimap() {\n    const nodes = this.ZUI_NODES; const w = 160, h = 110, cw = w / 6, ch = h / 7;\n    let r = '';\n    nodes.forEach(n => { const col = this.appState(n.state); r += '<rect x=\"' + (n.col * cw + 6) + '\" y=\"' + (n.row * ch + 4) + '\" width=\"' + (cw - 10) + '\" height=\"' + (ch - 8) + '\" rx=\"2\" fill=\"' + (n.state === 'idle' ? 'var(--app-rule)' : col) + '\" opacity=\"' + (this.state.zuiSel === n.id ? 1 : 0.55) + '\"/>'; });\n    return this.svg(r, w, h, { style: { display: 'block' } });\n  }\n\n  gauge(score, band) {\n    const col = 'var(--app-' + (band === 'fail' ? 'fail' : band === 'warn' ? 'warn' : 'success') + ')';\n    const r = 66, c = 2 * Math.PI * r, off = c * (1 - score / 100), cx = 80, cy = 80;\n    const inner = '<circle cx=\"' + cx + '\" cy=\"' + cy + '\" r=\"' + r + '\" stroke=\"var(--app-rule)\" stroke-width=\"12\"/>' +\n      '<circle cx=\"' + cx + '\" cy=\"' + cy + '\" r=\"' + r + '\" stroke=\"' + col + '\" stroke-width=\"12\" stroke-linecap=\"round\" stroke-dasharray=\"' + c + '\" stroke-dashoffset=\"' + off + '\" transform=\"rotate(-90 ' + cx + ' ' + cy + ')\" style=\"transition:stroke-dashoffset .6s var(--liquid-ease)\"/>';\n    return React.createElement('div', { style: { position: 'relative', width: 160, height: 160 } }, [\n      this.svg(inner, 160, 160, { key: 'g' }),\n      React.createElement('div', { key: 'l', style: { position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' } }, [\n        React.createElement('span', { key: 'v', style: { fontFamily: 'var(--font-jetbrains)', fontSize: 40, fontWeight: 700, color: 'var(--app-ink)', lineHeight: 1 } }, score),\n        React.createElement('span', { key: 'o', style: { fontFamily: 'var(--font-jetbrains)', fontSize: 12, color: 'var(--app-ink-faint)', marginTop: 4 } }, '/ 100')\n      ])\n    ]);\n  }\n\n  sparkline(arr, w, h, color, dot) {\n    const max = Math.max.apply(null, arr), min = Math.min.apply(null, arr), rng = (max - min) || 1;\n    const pts = arr.map((v, i) => [i / (arr.length - 1) * (w - 4) + 2, h - 2 - (v - min) / rng * (h - 4)]);\n    const d = pts.map((p, i) => (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1)).join(' ');\n    let inner = '<path d=\"' + d + '\" stroke=\"' + color + '\" stroke-width=\"1.5\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>';\n    if (dot) { const last = pts[pts.length - 1]; inner += '<circle cx=\"' + last[0].toFixed(1) + '\" cy=\"' + last[1].toFixed(1) + '\" r=\"2.4\" fill=\"' + color + '\"/>'; }\n    return this.svg(inner, w, h, { style: { display: 'block', overflow: 'visible' } });\n  }\n\n  funnelEl() {\n    const steps = this.CRO_FUNNEL;\n    const rows = steps.map((s, i) => {\n      const col = 'var(--app-' + s.health + ')';\n      const worst = s.health === 'fail';\n      return React.createElement('div', { key: s.step, style: { display: 'flex', alignItems: 'center', gap: 14 } }, [\n        React.createElement('div', { key: 'lb', style: { width: 108, flexShrink: 0, fontSize: 13, color: 'var(--app-ink)', display: 'flex', alignItems: 'center', gap: 7 } }, [\n          worst ? React.createElement('span', { key: 'w', style: { width: 6, height: 6, borderRadius: '50%', background: col, boxShadow: '0 0 8px ' + col } }) : null,\n          s.step\n        ]),\n        React.createElement('div', { key: 'br', style: { flex: 1, height: 30, borderRadius: 6, background: 'var(--app-rule-soft)', overflow: 'hidden', position: 'relative' } },\n          React.createElement('div', { style: { width: s.pct + '%', height: '100%', background: col, opacity: 0.85, borderRadius: 6, transition: 'width .5s var(--liquid-ease)' } })\n        ),\n        React.createElement('div', { key: 'ct', style: { width: 118, flexShrink: 0, textAlign: 'right', fontFamily: 'var(--font-jetbrains)', fontSize: 12.5, color: 'var(--app-ink-soft)' } }, s.count.toLocaleString() + ' · ' + s.pct + '%')\n      ]);\n    });\n    return React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 10 } }, rows);\n  }\n\n\n\n  /* D187 · first-open loading marker. Child panes are lazy DCs: on first mount the runtime fetches and parses\n     the file, and for a second or two the slot is an empty box. We don't get a callback, so we watch the DOM:\n     a <dc-import> under the pane scroller with no element children = booting. Cleared the moment it fills. */\n  watchPaneBoot() {\n    if (typeof document === 'undefined') return;\n    const root = document.querySelector('[data-testid=\"pane-scroll\"]'); if (!root) return;\n    const check = () => {\n      // The runtime replaces <dc-import> with a .sc-host mount; the child's own root is [data-testid=\"pane-root\"].\n      const hosts = Array.from(root.querySelectorAll('.sc-host[data-sc-name], [data-sc-name]'));\n      const empty = hosts.find(el => { const pr = el.querySelector('[data-testid=\"pane-root\"]'); return !pr || pr.getBoundingClientRect().height < 8; });\n      const booting = !!empty;\n      if (booting !== !!this.state.paneBooting) this.setState({ paneBooting: booting });\n    };\n    if (!this._bootObs) {\n      this._bootObs = new MutationObserver(() => { clearTimeout(this._bootT); this._bootT = setTimeout(check, 30); });\n      this._bootObs.observe(root, { childList: true, subtree: true });\n    }\n    clearTimeout(this._bootT); this._bootT = setTimeout(check, 30);\n  }\n\n\n  /* D199b · the one content calendar — Growth › Content calendar and Content · v2 › Publish both read this. */\n  growthCalItemsList() {\n    return [\n      { id: 'cc-1', title: 'Cold outreach — batch 14', subtitle: 'Awaiting reviewer', date: '2026-07-13', owner: 'cold-email-personalizer', status: 'warn', statusLabel: 'in review' },\n      { id: 'cc-2', title: 'Follow-up sequence — SaaS ICP', subtitle: 'Sends at 09:00', date: '2026-07-13', owner: 'sequence-writer', status: 'ok', statusLabel: 'approved' },\n      { id: 'cc-3', title: 'LinkedIn post — case study', subtitle: 'Not yet submitted', date: '2026-07-14', owner: 'content-writer', status: 'idle', statusLabel: 'drafted' },\n      { id: 'cc-4', title: 'Cold outreach — batch 13', subtitle: 'Delivered 07:02', date: '2026-07-12', owner: 'cold-email-personalizer', status: 'ok', statusLabel: 'sent' },\n      { id: 'cc-5', title: 'Newsletter — weekly roundup', subtitle: 'Flagged: tone check', date: '2026-07-15', owner: 'newsletter-agent', status: 'warn', statusLabel: 'in review' }\n    ];\n  }\n\n  /* D201 (NIGHT-PLAN P2c) · Money on one clock. Invoices + expenses come from assets/data/finances-source.json\n     (the Finances fixture); \"now\" is AgentRegistry.NOW — the instant Inbox and the roster read. Unpaid status is\n     derived from the due date against that clock, never taken from the fixture's authored sent/overdue label. */\n  moneyClock() {\n    const AR = window.AgentRegistry, now = (AR && AR.NOW) || Date.parse('2026-09-25T09:42:00Z'), D = this.FIN_SRC;\n    const dates = D ? D.INVOICES.map(i => i.issued).concat(D.EXPENSES.map(e => e.date)).sort() : [];\n    const period = dates.length ? dates[dates.length - 1].slice(0, 7) : null;\n    const pEnd = period ? Date.UTC(+period.slice(0, 4), +period.slice(5, 7), 1) : 0;\n    const iso = new Date(now).toISOString();\n    return { now, day: iso.slice(0, 10), hhmm: iso.slice(11, 16), period, closed: !!period && now >= pEnd,\n      periodLabel: period ? new Date(Date.UTC(+period.slice(0, 4), +period.slice(5, 7) - 1, 1)).toLocaleString('en-GB', { month: 'long', year: 'numeric', timeZone: 'UTC' }) : '' };\n  }\n  moneyNum(s) { return parseFloat(String(s).replace(/[\\u2212\\u2013\\u2014]/g, '-').replace(/[€\\s,]/g, '')) || 0; }\n  moneyEur(v) { return v.toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €'; }\n  moneyInvoices() {\n    const D = this.FIN_SRC; if (!D) return null;\n    const c = this.moneyClock();\n    return D.INVOICES.map(i => {\n      const unpaid = i.status === 'sent' || i.status === 'overdue';\n      const dueEnd = Date.parse(i.due + 'T23:59:59Z');\n      const status = unpaid ? (dueEnd < c.now ? 'overdue' : 'sent') : i.status;\n      return Object.assign({}, i, { status, authored: i.status, unpaid, value: this.moneyNum(i.amount),\n        daysOver: status === 'overdue' ? Math.ceil((c.now - dueEnd) / 86400000) : 0 });\n    });\n  }\n  financeRaiseUnpaid() {\n    const inv = this.moneyInvoices(); if (!inv || this._finRaised) return;\n    this._finRaised = true;\n    const c = this.moneyClock(), have = new Set((this.state.ctxThreads || []).map(t => t.id));\n    inv.filter(i => i.unpaid && !have.has('fin-unpaid-' + i.id)).forEach(i => {\n      const over = i.status === 'overdue', eur = this.moneyEur(i.value);\n      this.ctxRaise({ id: 'fin-unpaid-' + i.id, category: 'unpaid_invoices', sourceStream: 'finances', sourceStatus: 'issued',\n        state: over ? 'needs' : 'working', who: 'Finance Agent', initials: 'FI', addr: '<finance@yourco.aaas.love>', at: c.hhmm, unread: over,\n        subject: i.id + ' · ' + i.client + ' — ' + (over ? 'overdue ' + i.daysOver + 'd' : 'due ' + i.due), tag: 'Money · Invoices',\n        snip: eur + ' issued ' + i.issued + ', due ' + i.due + (over ? ' — ' + i.daysOver + ' days past due. Chase, extend or write off.' : ' — not yet due.'),\n        msgs: [{ from: 'Finance Agent', when: '· ' + c.hhmm + (over ? ' · blocked' : ' · watching'),\n          body: i.id + ' to ' + i.client + ' (' + eur + ', VAT ' + i.vat + ') was issued ' + i.issued + ' with net-14 terms, due ' + i.due + '. ' + (over ? 'It is ' + i.daysOver + ' days past due on today\\'s clock. Decide: send a reminder, extend terms, or write it off.' : 'Not yet due — I\\'ll raise it again if it passes the due date.') }],\n        route: ['money2', 'Open Invoices', 1] });\n    });\n  }\n\n  /* D202 (NIGHT-PLAN P2d) · one AW_RUNS row → an instance of the `agent-run` template. The run's `stage`\n     names the current node; earlier nodes are done, later ones todo; the current one is active / done /\n     blocked from `statusLabel`, with the run's own note as the stated reason (never a silent block). */\n  runInstance(run, tpl) {\n    if (!run || !tpl || !Array.isArray(tpl.nodes)) return null;\n    const cur = tpl.nodes.findIndex(n => n.id === String(run.stage || '').toLowerCase());\n    const nodes = {};\n    tpl.nodes.forEach((n, i) => {\n      const state = i < cur ? 'done' : i > cur ? 'todo' : (run.statusLabel === 'done' ? 'done' : run.statusLabel === 'running' ? 'active' : 'blocked');\n      nodes[n.id] = { state, owner: i <= cur ? run.harness + ' · ' + run.tier : null, startedAt: null, doneAt: null,\n        artifacts: i === cur && run.note ? [{ type: 'run-note', label: run.note, ref: run.worktree }] : [],\n        blockedOn: state === 'blocked' ? (run.statusLabel + ' — ' + run.note) : null,\n        dataContract: { endpoint: '/ops/runs', status: 'stub' } };\n    });\n    return { id: 'inst-' + run.id, templateId: tpl.id, client: run.id + ' · ' + run.worktree, owner: run.harness, startedAt: null, nodes };\n  }\n  /* D202 · run state → the D189 status vocabulary. `blocked` splits by cause: a human decision → needs,\n     a technical cause (missing secret, failing test) → stuck. */\n  runState(r) {\n    if (r.statusLabel === 'running') return 'running';\n    if (r.statusLabel === 'done') return 'done';\n    if (r.statusLabel === 'failed') return 'stuck';\n    return (r.statusLabel === 'awaiting approval' || /approval/i.test(r.note || '')) ? 'needs' : 'stuck';\n  }\n\n  /* D192 · v2 node → archetype config. Returns null when the tab is a plain lens/pane node. */\n  v2ArchFor(streamId, tab) {\n    const PI = window.ProcessInstantiate || window.PI;\n    const PS = window.ProcessSpec || window.PS;\n    const verdictOf = (p) => { try { if (PI && typeof PI.checkGates === 'function') return PI.checkGates(p, this._planeValidator || {}); } catch (e) {} return null; };\n    const decided = this.state.v2Gate || {};\n    // tplId: the v2 template the proposal is a node of — passing it makes rung 1 (validity) a real check.\n    const gate = (id, title, agent, stream, summary, live, tplId) => {\n      const d = decided[id];\n      const template = (PS && Array.isArray(PS.TEMPLATES) && PS.TEMPLATES.find(t => t.id === tplId)) || null;\n      const proposal = { id, title, agent, streamId: stream, summary, preview: summary, template, dataContract: { status: live ? 'live' : 'stub' } };\n      const v = verdictOf(proposal);\n      const blocked = v && v.route === 'blocked';\n      return { type: 'gate', source: live ? 'live' : 'stub', proposal, gates: v ? v.gates : null, blockedReason: blocked ? v.reason : null,\n        note: d ? (d.ok ? 'Approved · ' + d.at : 'Rejected · ' + d.reason + ' · ' + d.at) : (blocked ? 'Blocked · ' + v.reason : (v ? (v.route === 'auto' ? 'Gates pass · stub target, auto-apply permitted' : 'Gates pass · live target, a human approves') : 'Hold to approve · reject needs a reason')), noteTone: d ? (d.ok ? 'ok' : 'warn') : (blocked ? 'fail' : 'soft'),\n        onApprove: () => this.setState(s => ({ v2Gate: Object.assign({}, s.v2Gate, { [id]: { ok: true, at: new Date().toISOString().slice(11, 16) } }) })),\n        onReject: (p, reason) => this.setState(s => ({ v2Gate: Object.assign({}, s.v2Gate, { [id]: { ok: false, reason, at: new Date().toISOString().slice(11, 16) } }) })) };\n    };\n    const diff = (title, before, after, note) => ({ type: 'diff', source: 'fixture', diff: { title, before, after }, note, noteTone: 'soft' });\n    const ledger = (title, rows, cap, actual, actualPct, projectionPct, note) => ({ type: 'ledger', source: 'fixture', ledger: { title, rows, cap, actual, actualPct, projectionPct }, note, noteTone: 'soft' });\n    const A = {\n      podcast2: {\n        // D200 (NIGHT-PLAN P2b): Generate = the daily-brief template over its ep-204 instance, validated before mount.\n        Generate: (() => {\n          const tpl = PS && Array.isArray(PS.TEMPLATES) ? PS.TEMPLATES.find(t => t.id === 'daily-brief') : null;\n          const inst = PS && PS.instanceById ? PS.instanceById('inst-ep-204') : null;\n          const V = this._planeValidator;\n          const r = V && tpl && inst ? V.validateInstance(inst, tpl) : null;\n          return { type: 'process', source: 'fixture', template: tpl, instance: inst,\n            note: r ? (r.ok ? 'Instance inst-ep-204 validated against daily-brief · ' + tpl.nodes.length + ' nodes · 0 errors' : 'Instance invalid · ' + r.errors[0].message) : 'Validator loading — not checked yet',\n            noteTone: r ? (r.ok ? 'soft' : 'fail') : 'soft' };\n        })(),\n        Review: diff('Script · ep-204 vs ep-203', 'Intro · 40s\\nToday: three model releases and one EU ruling\\nSegment 1 · Claude Sonnet 4.5 on Vertex europe-west\\nSegment 2 · Gemini 2.5 Flash pricing\\nOutro · tomorrow: agent inboxes', 'Intro · 35s\\nToday: two model releases, one EU ruling, one benchmark\\nSegment 1 · Claude Sonnet 4.5 on Vertex europe-west\\nSegment 2 · Reranking beats embedding — the paper\\nSegment 3 · Gemini 2.5 Flash pricing\\nOutro · tomorrow: agent inboxes', 'Draft vs previous episode · audio QA pending'),\n        Publish: gate('pod-204', 'Publish ep-204 · daily brief', 'gen', 'podcast', 'Publishes ep-204 (11:38) to RSS, X and LinkedIn via postiz-agt. Protocol self-audit: identity · audience · CTA policy · guardrails.', false, 'daily-brief')\n      },\n      social2: {\n        // socialpane (2026-10-05): Process = the whole social-publishing pipeline over its real\n        // instance, validated before mount (podcast2.Generate's shape) plus the gate ladder run for\n        // real — PI.checkGates over agent postiz → stream social2 (AGENT_WRITE row added in\n        // process-instantiate.js), liveness from the stream's stub target. The note renders the\n        // verdict, never a canned pass.\n        Process: (() => {\n          const tpl = PS && Array.isArray(PS.TEMPLATES) ? PS.TEMPLATES.find(t => t.id === 'social-publishing') : null;\n          const inst = PS && PS.instanceById ? PS.instanceById('inst-weekly-social') : null;\n          const V = this._planeValidator;\n          const t = V && tpl ? V.validateTemplate(tpl) : null;\n          const i = V && tpl && inst ? V.validateInstance(inst, tpl) : null;\n          const ok = !!(t && t.ok && i && i.ok);\n          const v = ok && PI && typeof PI.checkGates === 'function'\n            ? PI.checkGates({ template: tpl, instance: inst, agent: 'postiz', streamId: 'social2', dataContract: { status: 'stub' } }, V || {}) : null;\n          const ladder = v ? ' · gates: ' + v.gates.map(g => g.id + ' ' + g.state).join(', ') + ' — route ' + v.route : '';\n          return { type: 'process', source: 'fixture', template: tpl, instance: inst,\n            note: ok ? ('Instance inst-weekly-social validated against social-publishing · ' + tpl.nodes.length + ' nodes · 0 errors' + ladder)\n              : (t && !t.ok ? 'Template invalid · ' + t.errors[0].message : (i && !i.ok ? 'Instance invalid · ' + i.errors[0].message : 'Validator loading — not checked yet')),\n            noteTone: ok ? 'soft' : 'fail' };\n        })()\n      },\n      content2: {\n        // D199b: Publish = the one content calendar (same list Growth › Content calendar mounts).\n        Publish: { type: 'calendar', source: 'fixture', items: this.growthCalItemsList(), note: 'Same calendar as Growth › Content calendar · one list, two mounts', noteTone: 'soft' },\n        // D199 (NIGHT-PLAN P2a): pre-filled from the toolbox brief generator + ctx.company.*, per the pane card.\n        Brief: { type: 'brief', source: 'fixture', brief: { title: 'Brief · Newsletter — weekly roundup', reference: 'AI_Content_Brief_Generator (toolbox, phase 05) + Messaging_Framework',\n          meta: [{ k: 'content_type', v: 'newsletter' }, { k: 'target_word_count', v: '600' }, { k: 'company_name', v: ((this.BRANDS[this.state.brandKey] || {}).name || 'AAAS') }],\n          sections: [\n            { label: 'topic', body: 'Weekly agent-ecosystem roundup', required: true },\n            { label: 'primary_keyword', body: 'AI agents' },\n            { label: 'secondary_keywords', body: 'agent deployment, context packages' },\n            { label: 'target_audience', body: 'Builders and ops teams evaluating agent platforms' },\n            { label: 'unique_angle', body: 'Ship notes, not press releases — what actually shipped this week' },\n            { label: 'heading_structure', body: 'Intro → 3 stories → CTA' },\n            { label: 'cta', body: 'Free context package in 2 min', required: true }\n          ] },\n          note: 'Pre-filled from AI_Content_Brief_Generator + Messaging_Framework (toolbox) and ctx.company.* · edit before Compose.', noteTone: 'soft' },\n        Review: diff('Newsletter — weekly roundup · draft vs Protocol', 'Subject: This week in agents\\nCTA: Book a demo\\nTone: casual', 'Subject: This week in agents\\nCTA: Free context package in 2 min\\nTone: technical, terse (Protocol §voice)', 'Protocol checklist applied · 2 changes'),\n        Approve: gate('cnt-followup', 'Approve · Follow-up sequence — SaaS ICP', 'outreach', 'content', 'Approves the 3-step follow-up (T1) for the SaaS ICP list. Sends via Send node on approval.', false, 'content-production')\n      },\n      ads2: {\n        // D201 (NIGHT-PLAN P2c): Launch proposes the spec selected in Build (state.specSel) — one campaign per gate id.\n        Launch: (() => {\n          const spec = this.SQUR_SPECS.find(s => s.file === this.state.specSel) || this.SQUR_SPECS[0];\n          const viol = this.SQUR_VALIDATION[spec.file] || [];\n          const g = gate('ads-launch-' + spec.file.replace(/^.*\\//, '').replace(/\\.json$/, ''), 'Launch · ' + spec.name + ' (created PAUSED)', 'marketing', 'ads',\n            'Un-pauses ' + spec.name + ' · €' + spec.budget + '/day · ' + spec.channel.replace('_', ' ') + ' · goal ' + spec.goal + ' · ' + spec.bid + '. Selected in Build (' + spec.file + '). Created PAUSED per API guidance; this gate is the go-live.', true, 'paid-campaign');\n          if (viol.length && !decided[g.proposal.id]) { g.blockedReason = 'Build · ' + viol.length + ' validation issue' + (viol.length > 1 ? 's' : '') + ' on ' + spec.file + ' (' + viol[0].rule + ') — fix in Build, or pick another spec'; g.note = 'Blocked · ' + g.blockedReason; g.noteTone = 'fail'; }\n          else if (!decided[g.proposal.id]) g.note = 'Spec from Build · ' + spec.file + ' · ' + g.note;\n          return g;\n        })(),\n        'Kill-switch': gate('ads-kill', 'Kill-switch · pause all on threshold', 'marketing', 'ads', 'Pauses every live campaign when daily spend exceeds €4.2k or CPA > 3× target. Reads live delivery spend, not the async report.', true, 'paid-campaign'),\n        // D201: rows are the Day-0 specs from Build; no spend until /ads/campaigns is live (was typed €38/€22 against €5/day caps).\n        Measure: (() => {\n          const day0 = this.SQUR_SPECS.filter(s => s.tier === 'day0');\n          const capSum = day0.reduce((a, s) => a + s.budget, 0);\n          const rows = day0.map(s => { const on = decided['ads-launch-' + s.file.replace(/^.*\\//, '').replace(/\\.json$/, '')]; return { label: s.name, amount: '—', state: 'empty',\n            note: 'cap €' + s.budget + '/day · ' + (on && on.ok ? 'un-paused ' + on.at + ' · awaiting first report (async, ≤1h)' : 'PAUSED · no delivery until Launch approves') }; });\n          return ledger('Spend vs cap · today · Day-0 specs', rows, '€' + capSum + '/day', '€0', 0, 0, '/ads/campaigns is planned — no delivery data yet; caps are the Build specs');\n        })()\n      },\n      money2: {\n        Budgets: gate('bud-meta', 'Budget raise · Meta +€4.2k / month', 'marketing', 'costs', 'Raises the paid-social budget from €6k to €10.2k for October. Circuit breaker at €12k stays.', false, 'money'),\n        // D201 (NIGHT-PLAN P2c): Costs, Revenue and Invoices read one fixture on one clock (moneyClock).\n        ...(() => {\n          const D = this.FIN_SRC, c = this.moneyClock(), inv = this.moneyInvoices(), E = (v) => this.moneyEur(v), N = (s) => this.moneyNum(s);\n          if (!D || !inv) { const wait = { type: 'ledger', source: 'fixture', ledger: null, note: 'Finances fixture loading — not read yet', noteTone: 'soft' };\n            return { Costs: wait, Revenue: wait, Invoices: { type: 'board', source: 'fixture', items: [], note: 'Finances fixture loading — not read yet', noteTone: 'soft' } }; }\n          const clockNote = c.periodLabel + ' · ' + (c.closed ? 'closed' : 'open') + ' · clock ' + c.day + ' (AgentRegistry.NOW, same as Inbox)';\n          const COST_CAP = 7500;   // the monthly cap Costs v1 states (\"of €7,500 cap\"); no fixture file carries it\n          const cats = {}; D.EXPENSES.forEach(e => { const k = e.category; cats[k] = cats[k] || { v: 0, n: 0, noRcpt: 0 }; cats[k].v += N(e.amount); cats[k].n++; if (!e.receipt) cats[k].noRcpt++; });\n          const expTotal = Object.keys(cats).reduce((a, k) => a + cats[k].v, 0), expPct = Math.round(expTotal / COST_CAP * 100);\n          const costRows = Object.keys(cats).sort((a, b) => cats[b].v - cats[a].v).map(k => ({ label: k, amount: E(cats[k].v), state: cats[k].noRcpt ? 'modeled' : 'live',\n            spentPct: Math.round(cats[k].v / expTotal * 100), note: cats[k].n + ' row' + (cats[k].n > 1 ? 's' : '') + (cats[k].noRcpt ? ' · ' + cats[k].noRcpt + ' without receipt (unbooked)' : ' · receipts on file') }));\n          const paid = inv.filter(i => i.status === 'paid'), unpaid = inv.filter(i => i.unpaid), drafts = inv.filter(i => i.status === 'draft');\n          const sum = (a) => a.reduce((x, i) => x + i.value, 0), invoiced = sum(paid) + sum(unpaid), collected = sum(paid), colPct = Math.round(collected / invoiced * 100);\n          const overdueN = unpaid.filter(i => i.status === 'overdue').length;\n          const subs = D.SUBS.filter(s => s.status === 'active' || s.status === 'trial'), mrr = subs.reduce((a, s) => a + N(s.mrr), 0);\n          const openCredits = D.CREDITS.filter(x => x.status === 'open');\n          const revRows = [\n            { label: 'Collected · ' + paid.length + ' invoices', amount: E(collected), state: 'live', spentPct: colPct, note: 'paid in full' },\n            { label: 'Outstanding · ' + unpaid.length + ' invoices', amount: E(sum(unpaid)), state: 'live', tone: overdueN ? 'over' : 'near', spentPct: Math.round(sum(unpaid) / invoiced * 100), note: overdueN + ' of ' + unpaid.length + ' overdue on the clock · each is an Inbox thread (Finance Agent)' },\n            { label: 'Drafts · ' + drafts.length + ' not sent', amount: E(sum(drafts)), state: 'empty', note: 'not revenue until issued' },\n            { label: 'Open credits · ' + openCredits.length, amount: E(openCredits.reduce((a, x) => a + N(x.amount), 0)), state: 'live', tone: 'near', note: openCredits.map(x => x.id + ' ' + x.client).join(' · ') || 'none' },\n            { label: 'Recurring MRR · ' + subs.length + ' plans', amount: E(mrr), state: 'live', note: 'active + trial subscriptions' }\n          ];\n          const cols = [{ id: 'draft', label: 'Draft', tint: 'idle' }, { id: 'sent', label: 'Issued · not due', tint: 'accent' }, { id: 'overdue', label: 'Overdue', tint: 'fail' }, { id: 'paid', label: 'Paid', tint: 'ok' }];\n          const tone = { draft: 'idle', sent: 'accent', overdue: 'fail', paid: 'ok' };\n          const relabelled = inv.filter(i => i.unpaid && i.status !== i.authored).length;\n          return {\n            Costs: ledger('Costs · ' + c.periodLabel, costRows, E(COST_CAP), E(expTotal), expPct, expPct, clockNote + ' · ' + D.EXPENSES.length + ' expense rows · ' + (c.closed ? 'period closed, projection = actual' : 'period open')),\n            Revenue: ledger('Revenue · ' + c.periodLabel, revRows, E(invoiced), E(collected), colPct, colPct, clockNote + ' · invoiced = paid + unpaid (drafts excluded)'),\n            Invoices: { type: 'board', source: 'fixture', groups: cols,\n              items: inv.map(i => ({ group: i.status, title: i.id, subtitle: i.client, value: E(i.value), date: 'due ' + i.due, owner: 'finance-agt', status: tone[i.status] || 'soft',\n                statusLabel: i.status === 'overdue' ? i.daysOver + 'd overdue' : i.status, meta: i.unpaid ? 'Inbox thread fin-unpaid-' + i.id : '' })),\n              note: unpaid.length + ' unpaid · ' + E(sum(unpaid)) + ' → ' + unpaid.length + ' unpaid_invoices threads (Finance Agent) · status from due date on clock ' + c.day + (relabelled ? ' · ' + relabelled + ' authored \"sent\" now past due' : ''), noteTone: overdueN ? 'fail' : 'soft' }\n          };\n        })()\n      },\n      design2: (() => {\n        const t4Sections = [{ label: 'hookTag', body: 'STAT OF THE WEEK', required: false }, { label: 'headline', body: 'Every agent ships with the full skill vault', required: true }, { label: 'stat', body: '5,400+' }, { label: 'statLabel', body: 'structured AI skills' }, { label: 'body', body: '', required: false }, { label: 'footerTag', body: 'VAULT' }, { label: 'image', body: '', required: false }];\n        const saveGate = gate('design-save-t4', 'Save · T4 instance → Content · v2 › Shelf', 'design', 'content', 'Writes the edited T4 LinkedIn Square Post as a produced instance into the Content shelf (social). Nothing is published; Content › Publish does that.', false, 'design-library');\n        // D199 (NIGHT-PLAN P2a): approving the gate writes {templateId, slots, brand, at} into contentInstances —\n        // the exact shape Content · v2 › Shelf reads. Chained onto the gate's own v2Gate bookkeeping, not replacing it.\n        const origApprove = saveGate.onApprove;\n        saveGate.onApprove = () => { origApprove(); this.setState(s => ({ contentInstances: (s.contentInstances || []).concat([{ templateId: 'T4', slots: Object.fromEntries(t4Sections.map(sec => [sec.label, sec.body])), brand: s.brandKey || 'aaas', at: new Date().toISOString() }]) })); };\n        return {\n          Edit: { type: 'brief', source: 'fixture', brief: { title: 'Edit · T4 LinkedIn Square Post', reference: 'assets/templates/T4 LinkedIn Square Post.manifest.json', meta: ['hookType · stat', 'kind · stat', 'brand · ' + ((this.BRANDS[this.state.brandKey] || {}).name || 'AAAS')],\n            sections: t4Sections },\n            note: 'Slots from the template manifest · values are the template\\'s own examples until you type · brand from the logo switch', noteTone: 'soft' },\n          'Save to Content': saveGate\n        };\n      })(),\n      estate2: {\n        Publish: gate('psi-tokens', 'PSI gate · token edit → live surfaces', 'ops', 'design', 'Publishes the --status-* token set (D189) to every mirrored surface. Four gates between a token edit and a live page.', true, 'estate'),\n        // D203 (NIGHT-PLAN P2e): Tokens = the tokens IN EFFECT, read off the shell root at render time — never a typed\n        // copy of the stylesheet. --status-* come from _ds (D189); the brand override string is BRANDS[key][theme].\n        Tokens: (() => {\n          const root = typeof document !== 'undefined' ? document.querySelector('div[data-theme]') : null;\n          const cs = root ? getComputedStyle(root) : null, rd = (k) => cs ? cs.getPropertyValue(k).trim() : '';\n          const bk = this.state.brandKey || 'aaas', br = this.brandOrPlaceholder(bk), theme = this.state.theme === 'light' ? 'light' : 'dark';\n          const STATUS = ['running', 'needs', 'stuck', 'done', 'idle'];\n          const statusBody = STATUS.map(s => '--status-' + s + ': ' + (rd('--status-' + s) || 'not resolved')).join('\\n');\n          const pairs = String(br[theme] || '').split(';').filter(Boolean).map(p => { const i = p.indexOf(':'); return [p.slice(0, i).trim(), p.slice(i + 1).trim()]; });\n          const brandBody = pairs.length ? pairs.map(p => p[0] + ': ' + p[1]).join('\\n') : br.name + ' is tenant-0 — its tokens ARE the bound design system (colors_and_type.css); no override string is appended';\n          const APP = ['--app-bg', '--app-surface', '--app-ink', '--app-ink-soft', '--app-accent', '--app-signal', '--app-success', '--app-rule'];\n          const appBody = APP.map(k => k + ': ' + (rd(k) || 'not set on the root')).join('\\n');\n          const resolved = STATUS.filter(s => rd('--status-' + s)).length;\n          return { type: 'brief', source: 'live', brief: { title: 'Tokens in effect · ' + br.name + ' · ' + theme,\n            reference: '_ds/…/colors_and_type.css (--status-*, D189) · BRANDS.' + bk + '.' + theme + (bk === 'aaas' ? ' (tenant-0)' : ' (tenants/' + bk + ')'),\n            meta: [{ k: 'brand', v: br.name }, { k: 'theme', v: theme }, { k: 'read', v: cs ? 'getComputedStyle · shell root' : 'root not mounted yet' }],\n            sections: [\n              { label: '--status-* · D189 · ' + resolved + ' of ' + STATUS.length + ' resolved', body: statusBody, required: true },\n              { label: 'brand override · ' + theme + ' · ' + pairs.length + ' tokens', body: brandBody },\n              { label: '--app-* resolved on the root', body: appBody }\n            ] },\n            note: 'Values read from the mounted shell root, not typed · switch brand or theme and this tab re-reads · Publish (PSI gate) is what moves a token edit to the mirrored surfaces', noteTone: 'soft' };\n        })()\n      },\n      account2: {\n        // D203 (NIGHT-PLAN P2e): Agents = the one cast (agent-registry.js) with states derived from the thread projection\n        // (same agentList() the roster and heartbeat read) and each agent's write scope from PI.AGENT_WRITE.\n        Agents: (() => {\n          const AR = window.AgentRegistry, list = this.agentList(), c = this.agentCounts();\n          const W = (PI && PI.AGENT_WRITE) || {};\n          const LABEL = { running: 'running', needs: 'needs a decision', stuck: 'stuck', done: 'done', idle: 'idle' };\n          const scope = (id) => !(id in W) ? 'write scope not declared' : W[id] === null ? 'writes: unrestricted (by design)' : W[id].length ? 'writes: ' + W[id].join(' · ') : 'read-only agent';\n          const items = (AR ? AR.sorted(list) : list).map(a => { const pj = AR && AR.project(a.project); return { id: a.id, title: a.name, subtitle: a.doing, owner: a.handle, value: pj ? pj.label : '', date: AR ? AR.sinceLabel(a.since) : '',\n            status: a.state, statusLabel: LABEL[a.state] || a.state, meta: scope(a.id) + (a.progress ? ' · ' + a.progress.label : '') }; });\n          return { type: 'cards', source: 'fixture', items, onSelect: (it) => this.goTo('account2', 'Profile', { acAgent: it.id }),\n            note: (AR ? items.length + ' agents · agent-registry.js (one cast) · ' : 'agent-registry.js not loaded · ') + 'states from the Inbox projection: needs ' + c.needs + ' · stuck ' + c.stuck + ' · running ' + c.running + ' · done ' + c.done + ' · idle ' + c.idle + ' · same numbers as roster + heartbeat · write scope from process-instantiate.js · click a card for its profile (its threads: roster)', noteTone: 'soft' };\n        })(),\n        // Org-control P1: org chart + profile are two views of the Agents node (swimlanes + brief over the one cast; modeled config in agent-profiles.js).\n        'Org chart': (() => { const AR = window.AgentRegistry; if (!AR || !AR.orgChart) return { type: 'swimlanes', source: 'fixture', items: [], note: 'agent-profiles.js not loaded', noteTone: 'warn' };\n          return AR.orgChart(AR.sorted(this.agentList()), (it) => this.goTo('account2', 'Profile', { acAgent: it.id })); })(),\n        Profile: (() => { const AR = window.AgentRegistry; if (!AR || !AR.profileBrief) return { type: 'brief', source: 'fixture', brief: { title: 'Agent profile', reference: 'agent-profiles.js not loaded', sections: [] }, note: 'agent-profiles.js not loaded', noteTone: 'warn' };\n          const list = AR.sorted(this.agentList()), sel = this.state.acAgent && AR.byId(this.state.acAgent) ? this.state.acAgent : (list[0] && list[0].id);\n          return AR.profileBrief(sel, { write: (PI && PI.AGENT_WRITE) || {}, live: list.find(a => a.id === sel) }); })(),\n        // Org-control P2 · Control: governance (brief), connectors (cards over ACC_SERVICES, the one services list), secrets (references).\n        Governance: (() => { const AR = window.AgentRegistry; return AR && AR.governanceBrief ? AR.governanceBrief({ write: (PI && PI.AGENT_WRITE) || {} }) : { type: 'brief', source: 'fixture', brief: { title: 'Governance', reference: 'agent-profiles.js not loaded', sections: [] }, note: 'agent-profiles.js not loaded', noteTone: 'warn' }; })(),\n        Connectors: (() => { const AR = window.AgentRegistry, S = this.ACC_SERVICES || [], ST = { ok: 'done', warn: 'stuck', fail: 'needs', idle: 'idle' }, LB = { ok: 'connected', warn: 'attention', fail: 'auth expired', idle: 'not connected' };\n          const items = S.map(s => { const u = AR && AR.toolUsers ? AR.toolUsers(s.name) : []; return { id: s.id, title: s.name, subtitle: s.meta, owner: 'service', status: ST[s.state] || 'idle', statusLabel: LB[s.state] || s.state, meta: u.length ? 'used by ' + u.map(a => a.name).join(', ') : 'no agent uses it yet' }; });\n          const n = (k) => S.filter(s => s.state === k).length;\n          return { type: 'cards', source: 'fixture', items, note: S.length + ' services \\u00b7 same list as Settings \\u00b7 connected ' + n('ok') + ' \\u00b7 attention ' + (n('warn') + n('fail')) + ' \\u00b7 not connected ' + n('idle') + ' \\u00b7 \"used by\" comes from the modeled tool lists \\u00b7 agent actions that need an OK first wait in Inbox \\u00b7 Awaiting approval', noteTone: 'soft' }; })(),\n        Secrets: (() => { const AR = window.AgentRegistry; return AR && AR.secretCards ? AR.secretCards() : { type: 'cards', source: 'fixture', items: [], note: 'agent-profiles.js not loaded', noteTone: 'warn' }; })(),\n        // Org-control P3 · Audit: three views over the one Inbox projection (agent-profiles.js auditViews).\n        ...(() => { const AR = window.AgentRegistry, IT = window.InboxThreads, none = (n) => ({ type: 'thread', source: 'fixture', items: [], note: n, noteTone: 'warn' });\n          if (!AR || !AR.auditViews || !IT || typeof IT.rawThreads !== 'function') return { Activity: none('audit needs agent-profiles.js and inbox-threads.js'), Runs: none('audit needs agent-profiles.js and inbox-threads.js'), Timeline: none('audit needs agent-profiles.js and inbox-threads.js') };\n          return AR.auditViews(IT.projectThreads(IT.rawThreads(this.state.ctxThreads)), (it) => this.goTo('inbox', 'Inbox', { inboxAgent: it.agent })); })(),\n        // Org-control P4 · Work: Tasks (board over the Inbox projection), Routines (the one automations list), Artifacts (assets/index.json by class).\n        Tasks: (() => { const AR = window.AgentRegistry, IT = window.InboxThreads; if (!AR || !AR.taskBoard || !IT || typeof IT.rawThreads !== 'function') return { type: 'board', source: 'fixture', items: [], note: 'tasks need agent-profiles.js and inbox-threads.js', noteTone: 'warn' };\n          return AR.taskBoard(AR.tasks(IT.projectThreads(IT.rawThreads(this.state.ctxThreads)), this.state.brandKey.slice(0, 3).toUpperCase()), (it) => this.goTo('inbox', 'Inbox', { inboxAgent: it.agent })); })(),\n        Routines: (() => { const A = this.AW_AUTOMATIONS || [], ST = { ok: 'done', fail: 'needs' };\n          const items = A.map(a => ({ id: a.id, title: a.title, subtitle: a.trigger, owner: a.backend, value: a.fires + ' fires', date: a.last, status: ST[a.state] || 'idle', statusLabel: a.stateLabel || a.state, meta: 'last fired ' + a.last + ' \\u00b7 backend ' + a.backend }));\n          return { type: 'cards', source: 'fixture', items, onSelect: () => this.goTo('automations', 'All'), note: items.length + ' routines \\u00b7 same list as Developer \\u203a Automations (one source) \\u00b7 each fire is a run; one task per fire is a modeled rule, not built \\u00b7 click opens Automations', noteTone: 'soft' }; })(),\n        Artifacts: (() => { const AI = this.ASSET_INDEX; if (!AI) return { type: 'cards', source: 'fixture', items: [], note: 'assets/index.json not loaded yet', noteTone: 'warn' };\n          const by = (c) => (AI.gallery || []).filter(g => g.class === c).length;\n          const rows = [['Emails', by('emails'), 'gallery \\u00b7 send as-is'], ['Social', by('social'), 'gallery \\u00b7 post formats'], ['Objects', by('objects'), 'gallery \\u00b7 hero objects'], ['Decks', (AI.decks || []).length, 'pitch and investor decks'], ['Videos', (AI.videos || []).length, 'explainers']];\n          const items = rows.map((r, i) => ({ id: 'art' + i, title: r[0], subtitle: r[2], owner: 'assets/index.json', value: String(r[1]), status: r[1] ? 'done' : 'idle', statusLabel: r[1] ? r[1] + ' items' : 'none', meta: 'type filter \\u00b7 click opens the Design library' }));\n          return { type: 'cards', source: 'fixture', items, onSelect: () => this.goTo('design2', 'Overview'), note: rows.reduce((n, r) => n + r[1], 0) + ' artifacts by type from assets/index.json \\u00b7 not grouped by task: assets carry no task link yet \\u00b7 the Design shelf shows the first 20 emails and 20 social items (shell limit)', noteTone: 'soft' }; })(),\n        // Org-control P5 · Portability: Export (agent roster JSON) and Adapters (derived). Import deliberately not built.\n        Export: (() => { const AR = window.AgentRegistry, bk = this.state.brandKey; if (!AR || !AR.exportCards) return { type: 'cards', source: 'fixture', items: [], note: 'agent-profiles.js not loaded', noteTone: 'warn' };\n          return AR.exportCards(bk, this.ACC_SERVICES, (it) => { if (it.id === 'pkg') AR.downloadPackage(bk, this.ACC_SERVICES); }); })(),\n        Adapters: (() => { const AR = window.AgentRegistry; return AR && AR.adapterCards ? AR.adapterCards() : { type: 'cards', source: 'fixture', items: [], note: 'agent-profiles.js not loaded', noteTone: 'warn' }; })(),\n        // D203: Identity = brief over ctx.company.* / the active brand, with provenance shown. The scaffold example pack\n        // (\"Example Co GmbH\", no _provenance) renders as \"example pack — not data\" (CONTEXT-MANAGEMENT-PLAN §1.5), never as fact.\n        Identity: (() => {\n          const ctx = window.__dcContext || {}, co = ctx.company || {}, bk = this.state.brandKey || 'aaas', br = this.brandOrPlaceholder(bk);\n          const prov = ctx._provenance || co._provenance || null;\n          const realPack = !!prov && !/^Example Co/i.test(String(co.name || ''));\n          const loaded = !!(ctx && Object.keys(ctx).length);\n          const v = (x) => (x === undefined || x === null || x === '') ? '' : String(x);\n          const addr = co.address ? [co.address.street, [co.address.zip, co.address.city].filter(Boolean).join(' '), co.address.country].filter(Boolean).join(', ') : '';\n          const provText = prov ? (typeof prov === 'string' ? prov : Object.keys(prov).map(k => k + ': ' + prov[k]).join(' · ')) : 'no _provenance on the pack';\n          const sections = [\n            { label: 'account · brand', body: br.name + ' · ' + br.tag + ' · brandKey ' + bk + ' · role ' + (this.state.role || 'Owner'), required: true },\n            { label: 'account · tenant pack', body: bk === 'aaas' ? 'tenant-0 · tenants/aaas' : 'tenants/' + bk },\n            { label: 'company · name', body: v(co.name) + (co.legal_form ? ' (' + co.legal_form + ')' : ''), required: true },\n            { label: 'company · address', body: addr },\n            { label: 'company · web · mail', body: [v(co.website), v(co.email)].filter(Boolean).join(' · ') },\n            { label: 'industry', body: ctx.industry ? [ctx.industry.primary, ctx.industry.secondary].filter(Boolean).join(' · ') : '' },\n            { label: 'product', body: ctx.product ? [ctx.product.name, ctx.product.tagline].filter(Boolean).join(' — ') : '' },\n            { label: 'team', body: ctx.team && ctx.team.headcount != null ? ctx.team.headcount + ' headcount' : '' },\n            { label: '_provenance', body: provText, required: true }\n          ];\n          return { type: 'brief', source: realPack ? 'live' : 'fixture', brief: { title: 'Identity · ' + (realPack ? v(co.name) : br.name + ' · example pack — not data'),\n            reference: 'ctx.account.* (brand switch + role) · ctx.company.* (tenants/' + bk + '/context/company-context.json)' + (loaded ? '' : ' · pack not loaded yet'),\n            meta: [{ k: 'pack', v: realPack ? 'real · provenance on file' : (loaded ? 'example pack — shape fixture, not data' : 'not loaded') }, { k: 'brand', v: br.name }, { k: 'endpoint', v: '/account · planned' }],\n            sections },\n            note: realPack ? 'Company block from the tenant pack with _provenance · account block from the shell (brand + role) · /account is planned, nothing here is written back'\n              : 'Company fields come from the scaffold example pack (Example Co GmbH, no _provenance) — shown for shape, never as fact · account block (brand + role) is live from the shell · /account is planned', noteTone: realPack ? 'soft' : 'warn' };\n        })()\n      },\n      foundry2: {\n        // D203 (NIGHT-PLAN P2e): Compose = swimlanes over ZUI_NODES (the Process Tree seed), one lane per node type, states in\n        // the D189 vocabulary: ok → done · running → running · blocked gate → needs (a human) · blocked tool/agent → stuck · failed → stuck.\n        Compose: (() => {\n          const N = this.ZUI_NODES || [], E = this.ZUI_EDGES || [];\n          const st8 = (n) => n.state === 'ok' ? 'done' : n.state === 'running' ? 'running' : n.state === 'failed' ? 'stuck' : n.state === 'blocked' ? (n.type === 'gate' ? 'needs' : 'stuck') : 'idle';\n          const LABEL = { done: 'done', running: 'running', needs: 'needs a decision', stuck: 'stuck', idle: 'idle' };\n          const ORDER = { gate: 0, agent: 1, tool: 2, artifact: 3 };\n          const dur = (ms) => ms ? (ms >= 1000 ? (ms / 1000).toFixed(1) + 's' : ms + 'ms') : '—';\n          const items = N.slice().sort((a, b) => (ORDER[a.type] - ORDER[b.type]) || (a.row - b.row)).map(n => { const s = st8(n); const deps = E.filter(e => e.to === n.id).map(e => e.from); return { id: n.id, owner: n.type + 's', title: n.title, subtitle: n.summary, value: dur(n.durationMs), date: n.started,\n            status: s, statusLabel: LABEL[s], meta: n.id + ' · row ' + n.row + (deps.length ? ' · after ' + deps.join(', ') : ' · root') + ' · ' + String(n.output || '').split('\\n')[0] }; });\n          const tally = {}; items.forEach(i => { tally[i.status] = (tally[i.status] || 0) + 1; });\n          return { type: 'swimlanes', source: 'fixture', items,\n            note: N.length + ' nodes · ' + E.length + ' edges · ZUI_NODES (Process Tree seed: reconcile May AP ledger) · lanes = node type · states → --status-*: ' + Object.keys(tally).map(k => k + ' ' + tally[k]).join(' · ') + ' · /foundry/queue is planned · the Process Tree (zui) is the same graph as a canvas', noteTone: 'soft' };\n        })(),\n        // D203: Score = diff of the 8 featured Vault skills against the tier floors. No rubric file exists on disk, so the floors\n        // are INFERRED from the fixture's own tier boundaries (lowest gold / lowest silver score) and the note says so.\n        Score: (() => {\n          const F = (this.VL && this.VL.featured) || [];\n          const pct = (s) => parseInt(String(s), 10) || 0;\n          const floor = (t) => { const xs = F.filter(f => f.tier === t).map(f => pct(f.score)); return xs.length ? Math.min.apply(null, xs) : null; };\n          const GOLD = floor('gold'), SILVER = floor('silver');\n          const line = (f) => f.name + ' · ' + f.tier + ' · ' + f.score + ' · ' + (f.ctx ? 'context-enhanced' : 'no context') + ' · ' + f.uses;\n          const next = (f) => { const p = pct(f.score); if (f.tier === 'gold') return line(f);\n            if (f.tier === 'silver') return f.name + ' · gold needs ≥ ' + GOLD + ' % (' + (GOLD - p > 0 ? '+' + (GOLD - p) + ' pts' : 'score meets floor') + ')' + (f.ctx ? '' : ' · add context') + ' · ' + f.uses;\n            return f.name + ' · silver needs ≥ ' + SILVER + ' % (' + (SILVER - p > 0 ? '+' + (SILVER - p) + ' pts' : 'score meets floor') + ')' + (f.ctx ? '' : ' · add context') + ' · ' + f.uses; };\n          const promotable = F.filter(f => f.tier !== 'gold' && (f.tier === 'silver' ? GOLD - pct(f.score) : SILVER - pct(f.score)) <= 10);\n          const goldNoCtx = F.filter(f => f.tier === 'gold' && !f.ctx);\n          const checks = [\n            { label: 'Rubric declared on disk', state: 'pending', detail: 'none — floors inferred from the 8 featured skills: gold ≥ ' + GOLD + ' % · silver ≥ ' + SILVER + ' %' },\n            { label: 'Skills scored', state: F.length && F.every(f => pct(f.score) > 0) ? 'pass' : 'fail', detail: F.length + ' of ' + F.length + ' carry a score' },\n            { label: 'Within 10 pts of the next tier', state: promotable.length ? 'pass' : 'pending', detail: promotable.length ? promotable.map(f => f.name).join(' · ') : 'none' },\n            { label: 'Gold without context', state: goldNoCtx.length ? 'fail' : 'pass', detail: goldNoCtx.length ? goldNoCtx.map(f => f.name).join(' · ') + ' — gold, not context-enhanced' : 'every gold skill is context-enhanced' }\n          ];\n          return { type: 'diff', source: 'fixture', diff: { title: 'Score · tier today vs the next floor · ' + F.length + ' featured Vault skills', before: F.map(line).join('\\n'), after: F.map(next).join('\\n'), checks },\n            note: F.length + ' skills from VL.featured (Vault fixture) · no rubric file on disk — tier floors are inferred from the fixture, not ruled · /foundry/queue is planned · the 5,412-skill totals from v1 are not repeated here', noteTone: 'warn' };\n        })(),\n        Publish: gate('foundry-publish', 'Publish · ap-reconciliation/match → marketplace', 'ops', 'projects', 'Publishes the gold skill ap-reconciliation/match (94 %, context-enhanced) to the community marketplace. Reads the Score tab; a skill below the gold floor cannot pass.', false, 'foundry')\n      },\n      ops2: {\n        Deploys: gate('dep-shell', 'Deploy · canvas shell 0.3 → production', 'ops', 'platform', 'Promotes the current shell build to production. Rollback keeps the previous revision.', true, 'platform-ops'),\n        // D202 (NIGHT-PLAN P2d): Runs = board over the AW_RUNS fixture, states in the D189 vocabulary; a card click selects the run Replay mounts.\n        Runs: (() => {\n          const runs = this.AW_RUNS, sel = this.state.runSel || (runs[0] && runs[0].id);\n          const groupOf = (r) => r.statusLabel === 'awaiting approval' ? 'blocked' : r.statusLabel;\n          const groups = [{ id: 'running', label: 'Running', tint: 'running' }, { id: 'blocked', label: 'Blocked', tint: 'needs' }, { id: 'done', label: 'Done', tint: 'done' }, { id: 'failed', label: 'Failed', tint: 'stuck' }];\n          const LABEL = { running: 'running', needs: 'needs a decision', stuck: 'stuck', done: 'done' };\n          const items = runs.map(r => { const s = this.runState(r); return { id: r.id, group: groupOf(r), title: r.title, subtitle: r.note, owner: r.harness + ' · ' + r.tier, value: r.cost, date: r.elapsed,\n            status: s, statusLabel: LABEL[s], meta: r.id + ' · ' + r.stage + ' · ' + r.elapsed + (r.id === sel ? ' · selected → Replay' : '') }; });\n          const tally = {}; items.forEach(i => { tally[i.status] = (tally[i.status] || 0) + 1; });\n          return { type: 'board', source: 'fixture', groups, items, onSelect: (it) => this.setState({ runSel: it.id }),\n            note: runs.length + ' runs · AW_RUNS fixture (Agent Workspace patch) · /ops/runs is a stub · states → --status-*: ' + Object.keys(tally).map(k => k + ' ' + tally[k]).join(' · ') + ' · click a card to replay it (selected: ' + sel + ')', noteTone: 'soft' };\n        })(),\n        // D202: Replay = the selected run as an instance of the agent-run template, validated before mount.\n        Replay: (() => {\n          const tpl = PS && Array.isArray(PS.TEMPLATES) ? PS.TEMPLATES.find(t => t.id === 'agent-run') : null;\n          const run = this.AW_RUNS.find(r => r.id === this.state.runSel) || this.AW_RUNS[0];\n          const inst = this.runInstance(run, tpl);\n          const V = this._planeValidator;\n          const r = V && tpl && inst ? V.validateInstance(inst, tpl) : null;\n          return { type: 'process', source: 'fixture', template: tpl, instance: inst,\n            note: run ? ('Replay · ' + run.id + ' · ' + run.title + ' · built from AW_RUNS on agent-run (' + (tpl ? tpl.nodes.length : 0) + ' nodes) · ' + (r ? (r.ok ? 'validated · 0 errors' : 'instance invalid · ' + r.errors[0].message) : 'validator loading — not checked yet') + ' · pick another run in Runs') : 'No run selected',\n            noteTone: r && !r.ok ? 'fail' : 'soft' };\n        })()\n      },\n      knowledge2: {\n        // D202 (NIGHT-PLAN P2d): Index = cards whose values are counted from what the shell actually holds at runtime.\n        // Upstream endpoints with no fixture on disk render '—' and say so — v1's typed 7,960 / 5,412 are not repeated.\n        Index: (() => {\n          const IX = window.__dcIndex, CS = window.ContextStore, SC = window.__dcSchemas || {}, AI = this.ASSET_INDEX, IT = window.InboxThreads;\n          // The store's read is `getAll`, not `all` (verifier-caught, D202) — the wrong name rendered a mounted store as \"not mounted\".\n          let blocks = null; try { blocks = CS && CS.store && typeof CS.store.getAll === 'function' ? CS.store.getAll(CS.INTERNAL) : null; } catch (e) { blocks = null; }\n          const uniq = (a) => Array.from(new Set(a));\n          const card = (title, subtitle, value, owner, meta) => ({ title, subtitle, owner, value: value == null ? '—' : String(value), status: value == null ? 'idle' : 'done', statusLabel: value == null ? 'not counted' : 'counted live', meta });\n          const threads = IT && typeof IT.rawThreads === 'function' ? IT.projectThreads(IT.rawThreads(this.state.ctxThreads)) : null;\n          const items = [\n            card('Context blocks', 'company · personal · account · derived domain blocks', blocks ? blocks.length : null, 'context-store.js', blocks ? uniq(blocks.map(b => b.type || b.scope || 'block')).join(' · ') : 'store not mounted'),\n            card('Datapoints', 'the context dictionary — every ctx.* field a consumer may read', IX && IX.raw ? (IX.raw.datapoints || []).length : null, 'canvas.index.json', IX ? 'index ' + IX.version : 'index loads after mount'),\n            card('Planes indexed', 'surfaces with declared reads', IX && IX.planes ? Object.keys(IX.planes).length : null, 'canvas.index.json', IX ? Object.keys(IX.planes).filter(id => (IX.planes[id].reads || []).some(x => String(x).indexOf('ctx.') === 0)).length + ' declare ctx.* reads' : ''),\n            card('Toolbox schemas loaded', 'document templates with a context_map', Object.keys(SC).length || null, 'tenants/<tenant>/toolset/schemas', Object.keys(SC).length ? Object.keys(SC).join(' · ') : 'none loaded for this tenant'),\n            card('Assets indexed', 'gallery · decks · videos', AI ? AI.gallery.length + AI.decks.length + AI.videos.length : null, 'assets/index.json', AI ? AI.gallery.length + ' gallery · ' + AI.decks.length + ' decks · ' + AI.videos.length + ' videos' : 'index loads after mount'),\n            card('Inbox threads', 'the one projection every consumer reads', threads ? threads.length : null, 'inbox-threads.js', threads ? uniq(threads.map(t => t.state)).join(' · ') : 'not loaded'),\n            card('Entity graph', 'aaas.blog graph · entities and edges', null, '/intelligence/graph', 'endpoint planned · no fixture on disk — nothing to count'),\n            card('Vault skills', 'skill library · gold / silver / bronze', null, '/intelligence/vault', 'endpoint planned · no fixture on disk — nothing to count'),\n            card('KB articles', 'your own knowledge base', null, '/kb', 'endpoint planned · no fixture on disk — nothing to count')\n          ];\n          const counted = items.filter(i => i.status === 'done').length;\n          return { type: 'cards', source: 'fixture', items, note: counted + ' of ' + items.length + ' counted from files the shell loads · 3 upstream endpoints have no fixture and read \"—\" · no typed number on this tab', noteTone: 'soft' };\n        })(),\n        // D202: Impact = diff over the declared consumers, before / after the last context change (ctxFanOut → state.ctxStale).\n        Impact: (() => {\n          const cons = this.ctxConsumers(), stale = this.state.ctxStale || {}, staleIds = Object.keys(stale);\n          const line = (c, s) => c.label + ' · ' + c.kind + ' · ' + c.reads.length + ' read' + (c.reads.length === 1 ? '' : 's') + ' · ' + (s ? s.fields.length + ' stale: ' + s.fields.join(', ') : 'fresh');\n          const before = cons.map(c => line(c, null)).join('\\n');\n          const after = cons.map(c => line(c, stale[c.id])).join('\\n');\n          const armed = window.__cm5Armed;\n          const checks = [\n            { label: 'CM-5 trigger armed', state: armed ? 'pass' : 'pending', detail: armed ? 'context.updated → ctxFanOut · since ' + String(armed.at).slice(11, 19) : 'registers in componentDidMount' },\n            { label: 'Consumers declared', state: cons.length ? 'pass' : 'pending', detail: cons.filter(c => c.kind === 'plane').length + ' planes · ' + cons.filter(c => c.kind === 'doc').length + ' toolbox documents' },\n            { label: 'Stale after last change', state: staleIds.length ? 'fail' : 'pass', detail: staleIds.length ? staleIds.length + ' consumer' + (staleIds.length === 1 ? '' : 's') + ' · one Inbox thread each (Review in Sources)' : 'no context change this session' }\n          ];\n          const history = staleIds.map(id => ({ actor: 'context-agt', action: 'marked ' + stale[id].fields.length + ' field' + (stale[id].fields.length === 1 ? '' : 's') + ' stale on ' + stale[id].label + (stale[id].block ? ' (block ' + stale[id].block + ')' : ''), timestamp: String(stale[id].at).slice(11, 16), tone: 'warn' }));\n          return { type: 'diff', source: 'fixture', diff: { title: 'Impact · declared consumers before / after the last context change', before, after, checks, history },\n            note: !cons.length ? 'No consumers declared yet — canvas.index.json and the toolbox schemas load after mount' : (staleIds.length ? staleIds.length + ' of ' + cons.length + ' consumers stale · fed by ctxFanOut (CM-5), same event that raised the Inbox threads' : 'No context change yet this session · ' + cons.length + ' consumers declared · edit a block in Context › Company and this tab shows the fan-out'),\n            noteTone: staleIds.length ? 'warn' : 'soft' };\n        })()\n      }\n    };\n    return (A[streamId] && A[streamId][tab]) || null;\n  }\n\n  /* D177 · roster by attention — reads agent-registry.js (one cast, one counter).\n     NIGHT-PLAN P1 §3 (BUILD-PLAN §1.3): state/since/progress now derive from the shared thread\n     projection (IT.agentStates) instead of the fixture's own `state` field — an agent with no\n     matching thread falls back to idle, per §1.3. Known gap: `inventory` has no RAW thread yet\n     (BUILD-PLAN §1.4 named only 3 blockers), so it now shows idle instead of its old fixture\n     'needs' — flagged in the P1 receipt, not silently fixed. */\n  agentList() {\n    const AR = window.AgentRegistry, IT = window.InboxThreads; if (!AR) return [];\n    const seen = this.state.agentSeen || {};\n    const paused = this.state.agentPaused || {};\n    const derived = IT ? IT.agentStates(IT.projectThreads(IT.rawThreads(this.state.ctxThreads)), AR.AGENTS) : {};\n    return AR.AGENTS.map(a => {\n      const d = derived[a.id];\n      const base = d ? Object.assign({}, a, { state: d.state, since: d.since, progress: d.progress || a.progress }) : a;\n      return (base.state === 'done' && seen[a.id]) ? Object.assign({}, base, { state: 'idle' })\n        : paused[a.id] ? Object.assign({}, base, { state: 'idle', doing: 'Paused by you · ' + base.doing }) : base;\n    });\n  }\n  agentCounts() {\n    const AR = window.AgentRegistry;\n    return AR ? AR.counts(this.agentList()) : { total: 0, needs: 0, stuck: 0, done: 0, running: 0, idle: 0, active: 0 };\n  }\n  topNeedsName() {\n    const AR = window.AgentRegistry; if (!AR) return '';\n    const n = AR.sorted(this.agentList()).filter(a => a.state === 'needs')[0];\n    return n ? n.name : '';\n  }\n  goTo(streamId, tabLabel, extra) {\n    const st = this.STREAMS[streamId]; if (!st) return;\n    if (!this.canAccess(this.state.role, streamId)) { this.setState({ accessDenied: { label: st.label, role: this.state.role } }); return; }\n    const subs = st.subs || [];\n    let i = subs.findIndex(x => (typeof x === 'string' ? x : (x && (x.label || x.name))) === tabLabel); if (i < 0) i = 0;\n    this.setState(Object.assign({ active: streamId, sub: i, ops: true, mvp: false, pane: 'default', mega: false, palette: false }, extra || {}));\n  }\n  rosterVals() {\n    const AR = window.AgentRegistry;\n    if (!AR) return { rvViews: [], rvChips: [], rvGroups: [], rvProjects: [], rvAttention: true, rvProjectView: false, rvEmpty: false };\n    const list = this.agentList(), c = AR.counts(list);\n    const view = this.state.rosterView || 'attention', filt = this.state.rosterFilter || null;\n    const rs = this.state.rosterSections || {};\n    const isOpen = (k, def) => (rs[k] !== undefined ? rs[k] : def);\n    const tog = (k, def) => () => this.setState(s => { const r = Object.assign({}, s.rosterSections); r[k] = !(r[k] !== undefined ? r[k] : def); return { rosterSections: r }; });\n    const flags = (st) => ({ isNeeds: st === 'needs', isStuck: st === 'stuck', isDone: st === 'done', isRunning: st === 'running', isIdle: st === 'idle' });\n    const row = (a) => {\n      const pj = AR.project(a.project), p = a.progress;\n      const openAgent = () => {\n        if (a.state === 'done') { this.setState(s => ({ agentSeen: Object.assign({}, s.agentSeen, { [a.id]: true }) })); if (a.open) { this.goTo(a.open[0], a.open[1], { inboxAgent: a.name }); return; } }\n        this.goTo('inbox', 'Inbox', { inboxAgent: a.name });\n      };\n      return Object.assign(flags(a.state), {\n        key: a.id, name: a.name, title: a.handle + ' · click to see its inbox threads', since: AR.sinceLabel(a.since),\n        weight: a.state === 'done' ? '700' : '500', doing: a.doing, projectLabel: pj ? pj.label : '',\n        hasProgress: !!p, pct: p ? Math.round(p.at / p.of * 100) + '%' : '0%', progressLabel: p ? p.label : '',\n        hasTask: !!a.task, task: a.task || '',\n        hasCta: a.state === 'needs' && !!a.needs, cta: a.needs ? a.needs.cta : '',\n        act: () => { if (a.needs) this.goTo(a.needs.route[0], a.needs.route[1], { inboxAgent: a.needs.route[0] === 'inbox' ? a.name : null }); },\n        canPause: a.state === 'running' || a.state === 'stuck',\n        nudge: () => this.setState({ rosterOpen: true, askOn: true, askDraft: 'Nudge ' + a.name + ': ' }),\n        pause: () => this.setState({ confirm: { title: 'Pause ' + a.name + ' — ' + a.doing, verb: 'PAUSE', onConfirm: () => this.setState(s => ({ agentPaused: Object.assign({}, s.agentPaused, { [a.id]: true }) })) } }),\n        trace: () => this.goTo('ops2', 'Runs'),\n        open: openAgent });\n    };\n    const shown = filt ? list.filter(a => a.state === filt) : list;\n    const hints = { needs: 'oldest first', stuck: 'technical', done: 'not opened yet', running: '', idle: '' };\n    const groups = AR.ORDER.map(k => {\n      const rows = AR.sorted(shown.filter(a => a.state === k)).map(row);\n      const def = k !== 'idle';\n      return Object.assign(flags(k), { key: k, label: AR.STATES[k].label, n: rows.length, hint: hints[k], rows,\n        open: isOpen('g-' + k, def), caret: isOpen('g-' + k, def) ? 'rotate(0deg)' : 'rotate(-90deg)', toggle: tog('g-' + k, def) });\n    }).filter(g => g.n > 0);\n    const projects = AR.PROJECTS.map(pj => {\n      const ru = AR.rollup(pj.id, shown); if (!ru.agents.length) return null;\n      return Object.assign(flags(ru.worst), { key: pj.id, label: pj.label, hint: ru.summary, rows: AR.sorted(ru.agents).map(row),\n        open: isOpen('p-' + pj.id, true), caret: isOpen('p-' + pj.id, true) ? 'rotate(0deg)' : 'rotate(-90deg)', toggle: tog('p-' + pj.id, true) });\n    }).filter(Boolean);\n    const shortL = { needs: 'need you', stuck: 'stuck', done: 'done', running: 'running', idle: 'idle' };\n    return {\n      rvViews: [['attention', 'ATTENTION'], ['project', 'PROJECT']].map(([id, label]) => ({ key: id, label, on: view === id,\n        bg: view === id ? 'rgb(var(--lead) / 0.14)' : 'transparent', ink: view === id ? 'rgb(var(--text))' : 'var(--app-ink-soft, rgb(var(--text) / 0.7))',\n        pick: () => this.setState({ rosterView: id }) })),\n      rvChips: AR.ORDER.filter(k => c[k] > 0).map(k => Object.assign(flags(k), { key: k, n: c[k], short: shortL[k], label: AR.STATES[k].label + ' · ' + c[k] + (filt === k ? ' · click to clear' : ' · click to filter'), on: filt === k,\n        bd: filt === k ? 'rgb(var(--text) / 0.6)' : 'var(--border)', bg: filt === k ? 'rgb(var(--text) / 0.08)' : 'transparent',\n        pick: () => this.setState(s => ({ rosterFilter: s.rosterFilter === k ? null : k })) })),\n      rvAttention: view === 'attention', rvProjectView: view === 'project',\n      rvGroups: groups, rvProjects: projects, rvEmpty: groups.length === 0\n    };\n  }\n\n  /* D176 · Ask the agent lives in the roster plane: one conversation, always present, scoped to whatever\n     stream + tab is open. It answers through window.claude.complete when reachable (same race-and-fail\n     rule as Optimise), otherwise says plainly that the reply is modeled. \"File to Inbox\" goes through\n     ctxRaise → InboxThreads.raiseThread, so an ask becomes a thread in the one queue (INBOX-CONTRACT §1);\n     \"Open in Intelligence\" hands off to the full Ask surface (intelligence › Ask anything). */\n  askScopeOf() {\n    const st = this.STREAMS[this.state.active] || {};\n    const x = (st.subs || [])[this.state.sub || 0];\n    const tab = typeof x === 'string' ? x : (x && (x.label || x.name)) || '';\n    return { st, tab, label: st.label || '', text: (st.label || '') + (tab ? ' › ' + tab : '') };\n  }\n  askSend(q) {\n    q = String(q || '').trim(); if (!q || this.state.askBusy) return;\n    const sc = this.askScopeOf(), id = 'ask-' + Date.now();\n    this.setState(s => ({ askDraft: '', askBusy: true, askOn: true, askLog: (s.askLog || []).concat([{ id: id + '-q', role: 'user', text: q }]) }));\n    const reply = (text, meta) => this.setState(s => ({ askBusy: false,\n      askLog: (s.askLog || []).concat([{ id: id + '-a', role: 'agent', q, text, meta, stream: sc.st.id, tab: sc.tab }]) }));\n    const kp = (sc.st.kpis || []).map(k => (k.label || k.l || '') + ': ' + (k.value || k.v || '')).join(' · ');\n    const prompt = 'You are the operator agent inside the AaaS Canvas shell. The operator is on stream \"' + sc.label + '\"'\n      + (sc.tab ? ', tab \"' + sc.tab + '\"' : '') + '. What this stream is: ' + (sc.st.desc || sc.st.tag || '') + (kp ? '\\nKPIs on screen: ' + kp : '')\n      + '\\nCompany context (JSON): ' + JSON.stringify(window.__dcContext || {}).slice(0, 3000)\n      + '\\n\\nQuestion: ' + q + '\\n\\nAnswer in at most 90 words, terse and technical, plain text only (no markdown, no emoji). If an action follows, end with one line starting \"Next:\".';\n    // Fallback is grounded the way the old floating chat was: the plane's own canvas.index.json row, else the stream desc.\n    const modeled = () => {\n      const b = typeof this.planeBrief === 'function' ? this.planeBrief(sc.st.id, sc.tab) : null;\n      const txt = b ? (b.purpose + ' Stage ' + b.stage + ' of 7, verdict ' + b.verdict + '.'\n          + (b.reads && b.reads.length ? ' Reads ' + b.reads.slice(0, 4).join(', ') + '.' : '')\n          + (b.writes && b.writes.length ? ' Writes ' + b.writes.slice(0, 3).join(', ') + '.' : ''))\n        : String(sc.st.desc || sc.st.tag || 'No description declared for this stream.').slice(0, 240);\n      reply('On ' + sc.text + ': ' + txt, b ? 'MODELED · FROM CANVAS.INDEX.JSON' : 'MODELED · NO MODEL REACHABLE FROM THIS FRAME');\n    };\n    const plain = (t) => String(t || '').replace(/\\*\\*|__|`/g, '').replace(/^#{1,6}\\s*/gm, '').replace(/^\\s*[-*]\\s+/gm, '– ').trim();\n    if (!(window.claude && typeof window.claude.complete === 'function')) { modeled(); return; }\n    let done = false;\n    const tmo = setTimeout(() => { if (!done) { done = true; modeled(); } }, 25000);\n    window.claude.complete(prompt).then(t => { if (done) return; done = true; clearTimeout(tmo); reply(plain(t) || '(empty reply)', 'ANSWERED · SCOPE ' + sc.text.toUpperCase()); })\n      .catch(() => { if (done) return; done = true; clearTimeout(tmo); modeled(); });\n  }\n  askVals() {\n    const on = this.state.askOn !== false, sc = this.askScopeOf(), log = this.state.askLog || [], filed = this.state.askFiled || {};\n    const short = sc.tab || sc.label || 'this view';\n    return {\n      askOn: on,\n      askTrack: on ? 'rgb(var(--lead) / 0.22)' : 'rgb(var(--basalt-deep))',\n      askTrackBorder: on ? 'rgb(var(--lead))' : 'rgb(var(--lead) / 0.55)',\n      askKnob: on ? 'rgb(var(--lead))' : 'rgb(255 255 255 / 0.45)',\n      askKnobLeft: on ? '15px' : '1px',\n      askToggle: () => this.setState(s => ({ askOn: s.askOn === false })),\n      askCaret: on ? 'rotate(180deg)' : 'rotate(0deg)',\n      askFocus: () => { if (this.state.askOn === false) this.setState({ askOn: true }); },\n      askScope: sc.text.toUpperCase() || 'CANVAS', askScopeShort: short, askPlaceholder: 'Ask about ' + short + '…',\n      askEmpty: log.length === 0 && !this.state.askBusy, askHasLog: log.length > 0, askBusy: !!this.state.askBusy,\n      askSuggest: ['Summarise ' + short, 'Draft the next step for ' + short].concat(this.topNeedsName() ? ['Why is ' + this.topNeedsName() + ' waiting?'] : [])\n        .map(q => ({ q, ask: () => this.askSend(q) })),\n      askMsgs: log.slice(-8).map(m => ({\n        key: m.id, isUser: m.role === 'user', isAgent: m.role === 'agent', text: m.text, meta: m.meta || '',\n        fileLabel: filed[m.id] ? 'Filed to Inbox' : 'File to Inbox',\n        onFile: () => { if (filed[m.id]) return;\n          const at = new Date().toISOString();\n          const t = this.ctxRaise({ id: m.id, category: 'questions', state: 'working', sourceStatus: 'ask', sourceStream: m.stream || 'intelligence',\n            subject: String(m.q || 'Ask').replace(/[.?!]+$/, ''), who: 'Ask Agent', addr: 'ask@', unread: true, at,\n            msgs: [{ from: 'you', when: '· ' + at.slice(11, 16), body: m.q }, { from: 'ask-agt', when: '· ' + at.slice(11, 16), body: m.text }],\n            route: [m.stream || 'intelligence', m.tab || 'Overview'] });\n          if (t) this.setState(s => ({ askFiled: Object.assign({}, s.askFiled, { [m.id]: true }) })); },\n        onOpen: () => { if (!this.canAccess(this.state.role, 'intelligence')) { this.setState({ accessDenied: { label: 'Intelligence', role: this.state.role } }); return; }\n          this.setState({ active: 'intelligence', sub: 1, mvp: false, ops: true, pane: 'default' }); }\n      })),\n      askDraft: this.state.askDraft || '',\n      askInput: (e) => this.setState({ askDraft: e && e.target ? e.target.value : '' }),\n      askKey: (e) => { if (e && e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.askSend(this.state.askDraft); } },\n      askSendDraft: () => this.askSend(this.state.askDraft),\n      askClear: () => this.setState({ askLog: [], askFiled: {} })\n    };\n  }\n\n  selectStream(id) {\n    const st = this.STREAMS[id];\n    if (!this.canAccess(this.state.role, id)) { this.setState({ accessDenied: { label: st.label, role: this.state.role } }); return; }\n    const fl = this.FLOW_OF[id];\n    this.setState(s0 => ({ active: id, sub: 0, mega: false, palette: false, railOpen: false, settings: false, inboxAgent: null,\n      // D164: the Use view is retired — outside the new-user MVP canvas every stream opens in the role view.\n      ops: !(s0.mvp && this.MVP_IDS.indexOf(id) > -1), pane: 'default',\n      mvp: s0.mvp && this.MVP_IDS.indexOf(id) > -1,\n      flowLast: fl ? Object.assign({}, s0.flowLast, { [fl]: id }) : s0.flowLast }));\n  }\n\n  // WP-20: record-detail plane. openDetail() takes a generic record shape built by the table/board\n  // lens wiring below — { kind, id, title, chip, chipColor, meta, sections: [{label, value}] } —\n  // so any lens-rendered row/card can open it without a per-tab detail component.\n  /* Build the document's editable context-field list. A seeded schema (tenants/<id>/toolset/schemas/)\n     gives the real list — context_keys + fields[].context_map/ai_prompt. Without one we do NOT invent\n     fields: we show the four context keys every template resolves against and say the schema is not\n     pulled yet, so the drawer never implies more than exists. */\n  /* ══ aaas.store · the operator surface reads the REAL catalog ═══════════════════════════════════\n     Track 2 of the operator's 2026-09-06 call. The `.store` DOMAINS entry first carried the 14\n     catalog rows TYPED into the shell — the same \"written down instead of computed\" defect that cost\n     four verifier rounds on the domain counts. They are gone: the shell fetches the domain asset's own\n     `data.jsx` and scans it, so the pane cannot disagree with the storefront it describes.\n     Why a text scan and not an import: `data.jsx` is a Babel-in-browser module whose CATALOG contains\n     `review(...)` helper calls, so it cannot be JSON-parsed and evaluating it would mean running the\n     domain asset's code inside the shell. The three audits this pane owes (completeness, disclosure\n     compliance, mode integrity) need FIELD PRESENCE per item, which a scan gives honestly.\n     Failure is loud, never partial: a scan that finds no items leaves the rows empty and says why. */\n  parseStoreCatalog(text) {\n    const marks = [];\n    const re = /\\n  \\{\\n\\s*id: \"([^\"]+)\",\\n\\s*name: \"([^\"]+)\",\\n\\s*category: \"([^\"]+)\",\\n\\s*mode: \"([^\"]+)\",/g;\n    let m;\n    while ((m = re.exec(text))) marks.push({ id: m[1], name: m[2], category: m[3], mode: m[4], at: m.index });\n    return marks.map((mk, i) => {\n      const seg = text.slice(mk.at, i + 1 < marks.length ? marks[i + 1].at : text.length);\n      const str = (key) => { const mm = seg.match(new RegExp(key + ': \"((?:[^\"\\\\\\\\]|\\\\\\\\.)*)\"')); return mm ? mm[1] : null; };\n      const prov = seg.match(/provider: \\{ name: \"([^\"]+)\"/);\n      // reviewCount is captured because listing.reviews sums it — it was hand-summed once (5,123) and\n      // would drift the moment any item's count changed or a 15th item landed.\n      const rc = seg.match(/reputationScore: [\\d.]+, reviewCount: (\\d+)/);\n      return { id: mk.id, name: mk.name, category: mk.category, mode: mk.mode,\n        totalCost: str('totalCostLine'), provider: prov ? prov[1] : null,\n        reviewCount: rc ? parseInt(rc[1], 10) : 0,\n        hasReturns: /\\n\\s*returns: \"/.test(seg), hasDelivery: /\\n\\s*delivery: \"/.test(seg),\n        hasSpecs: /\\n\\s*specs: \\{/.test(seg), hasReviews: /\\n\\s*reviews: \\[/.test(seg),\n        hasDisclosure: /\\n\\s*disclosure: \"/.test(seg), hasJsonLd: /\\n\\s*jsonLd: \\{/.test(seg) };\n    });\n  }\n\n  /* The only writer of anything the .store pane renders. Called once the fetch resolves — and once on\n     failure, so the pane states the failure instead of silently keeping its last good numbers. */\n  applyStoreCatalog() {\n    const d = this.DOMAINS && this.DOMAINS['.store'];\n    if (!d) return;\n    const c = this._storeCatalog || { items: [] };\n    const items = c.items || [];\n    /* Contract rows — the surfaces this pane owes but has no feed for. The first three are the ones\n        the operator named in the original assignment; they were in the entry literal and this method\n        rebuilds `ops` wholesale, so a previous pass silently dropped them off the screen. Restored here,\n        where every row the pane renders is decided.\n        **Deliberately countless, and this comment is the claim to hold me to.** The first cut of these\n        three said \"the 3 partner items\", \"the 2 provider items\", \"the 9 buy items\" — typed counts under a\n        comment asserting none carried a number, which was false; the same buy-item count then rendered\n        twice on one screen, once derived in the Disclosure row and once typed here; and because CONTRACT\n        is built before the scan result is known, those numbers also printed in the FAILURE branch beside\n        \"could not read the catalog\". The per-mode numbers live in exactly one place — the KPI strip's\n        derived `Buy / Partner / Provider` — and these rows name the missing feed, which is their job. */\n    const CONTRACT = [\n      ['Partner clicks tracked', 'not wired — outbound affiliate contract; the partner items have no click log, and no destination URL exists yet'],\n      ['Provider handoffs', 'not wired — provider items redirect off-platform; handoff.html is the interstitial, nothing records the exit'],\n      ['Cart abandonment', 'not wired — own checkout covers buy-mode items only, and there is no checkout to abandon yet'],\n      ['Mode mix & revenue', 'not wired — needs per-item margin and referral-fee figures; none exist and typing them is what D19/D20 forbid'],\n      ['Partner / provider vetting', 'not wired — needs an approval record (approved-by · last-reviewed · reputation source); no such file yet']\n    ];\n    if (!items.length) {\n      d.listing.recent = [];\n      d.kpis = [['Catalog items', '—'], ['Source', 'unreadable']];\n      d.ops = [['Catalog scan', 'FAILED — ' + (c.error || 'not read yet') + ' · ' + (c.from || 'no url')]].concat(CONTRACT);\n      return;\n    }\n    const by = (mode) => items.filter(i => i.mode === mode);\n    const outbound = items.filter(i => i.mode === 'partner' || i.mode === 'provider');\n    // Audit 1 · completeness — every item owes total cost, returns, delivery, specs and reviews.\n    const REQ = [['totalCost', 'total cost'], ['hasReturns', 'returns'], ['hasDelivery', 'delivery'],\n      ['hasSpecs', 'specs'], ['hasReviews', 'reviews']];\n    const incomplete = items.map(i => ({ name: i.name, missing: REQ.filter(r => !i[r[0]]).map(r => r[1]) }))\n      .filter(x => x.missing.length);\n    // Audit 2 · disclosure compliance — partner and provider items must carry one; buy items must not need one.\n    const missingDisc = outbound.filter(i => !i.hasDisclosure);\n    // Audit 3 · mode integrity — every item declares exactly one mode from the known vocabulary.\n    const MODES = ['buy', 'partner', 'provider'];\n    const badMode = items.filter(i => MODES.indexOf(i.mode) < 0);\n    d.kpis = [\n      ['Catalog items', String(items.length)],\n      ['Buy / Partner / Provider', by('buy').length + ' / ' + by('partner').length + ' / ' + by('provider').length],\n      ['Total-cost coverage', Math.round((items.filter(i => i.totalCost).length / items.length) * 100) + '%',\n        items.filter(i => i.totalCost).length + ' / ' + items.length],\n      ['Disclosure coverage', outbound.length ? Math.round(((outbound.length - missingDisc.length) / outbound.length) * 100) + '%' : 'n/a',\n        (outbound.length - missingDisc.length) + ' / ' + outbound.length]\n    ];\n    d.ops = [\n      ['Completeness', incomplete.length\n        ? incomplete.length + ' of ' + items.length + ' item(s) missing a required field · ' +\n          incomplete.slice(0, 3).map(x => x.name + ' (' + x.missing.join(', ') + ')').join(' · ')\n        : 'all ' + items.length + ' items carry total cost, returns, delivery, specs and reviews'],\n      ['Disclosure compliance', missingDisc.length\n        ? missingDisc.length + ' outbound item(s) with NO disclosure · ' + missingDisc.map(i => i.name).join(', ')\n        : 'all ' + outbound.length + ' partner/provider items carry a disclosure · ' + by('buy').length + ' buy items need none'],\n      ['Mode integrity', badMode.length\n        ? badMode.length + ' item(s) declare an unknown mode · ' + badMode.map(i => i.mode).join(', ')\n        : 'every item declares exactly one of buy · partner · provider'],\n      ['Catalog source', c.from + ' · scanned ' + (c.at ? c.at.slice(11, 16) : '—') + ' · ' + items.length + ' items']\n    ].concat(CONTRACT);\n    // The listing rows ARE the catalog now — 5 columns fixed by the sibling shape\n    // (title · publisher · rating · installs · price); mode rides in the third.\n    const MODE_LABEL = { buy: 'Buy', partner: 'Partner', provider: 'Provider' };\n    d.listing.recent = items.map(i => [i.name, i.provider || '—', MODE_LABEL[i.mode] || i.mode,\n      i.category, i.totalCost || 'no total cost declared']);\n    d.listing.installs = items.length + ' items';\n    d.listing.priceChip = items.length + ' items';\n    // Summed, not typed. `rating` stays '—' on purpose: averaging the per-provider reputationScore\n    // would publish a store-level rating the source never claims.\n    const reviewTotal = items.reduce((a, i) => a + (i.reviewCount || 0), 0);\n    d.listing.reviews = reviewTotal ? reviewTotal.toLocaleString('en-US') + ' provider reviews' : 'provider reviews';\n  }\n\n  /* CM/domains · every rendered domain count comes from here, and from nowhere else. Called twice:\n     once in the constructor (mounted/live/inCanvas are computable from `this.DOMAINS`), and again from\n     `_loadGalleryIndex()` once `assets/index.json` resolves `pulled`. Three surfaces read these strings\n     — the KPI strip, the hero desc and the hub note — plus the mega footer and the landing chip.\n     `k()` is a constructor-local helper and is not reachable here, so `kk` rebuilds its exact shape. */\n  applyDomainCounts() {\n    const c = this._domCounts;\n    if (!c || !this.STREAMS || !this.STREAMS.domains) return;\n    // Observable, on purpose. `pulled` renders the same \"18\" whether it came from assets/index.json or\n    // from the fallback, so on-screen text cannot tell a working fetch from a failed one — the exact\n    // silent-failure shape that cost three rounds here and one in CM-G. `pulledFrom` says which.\n    if (typeof window !== 'undefined') window.__domCounts = c;\n    const kk = (label, value, delta, accent) => ({ label: label, value: value, delta: delta || null,\n      accent: accent || 'circuit', goTab: null });\n    const st = this.STREAMS.domains;\n    st.tag = c.mounted + ' domains · 1 shell';\n    this._domFooter = c.mounted + ' AaaS domains · 1 shell';\n    // The unmounted remainder is derived too — it was typed as \"the 2 extra\" and would have gone\n    // stale on the next import exactly like the counts either side of it.\n    const extra = Math.max(0, c.pulled - c.mounted);\n    st.desc = c.mounted + ' AaaS sub-domains mounted in one Canvas shell (' + c.pulled +\n      ' domain assets are pulled into assets/domains/' +\n      (extra ? ' — the ' + extra + ' extra ' + (extra === 1 ? 'is' : 'are') + ' not mounted here yet' : '') + '). ' +\n      'Server-rendered embeds, NOT iframes — Cmd+K, auth, and theming stay shared. Operator and user, dual-mode, same components.';\n    st.kpis = [kk('Domains · mounted', String(c.mounted)), kk('Live now', String(c.live), null, 'teal'),\n      kk('In Canvas', c.inCanvas + ' / ' + c.mounted, null, 'teal'), kk('Pulled assets', String(c.pulled)),\n      kk('Roadmap P1', 'top 5'), kk('Search index', 'Typesense'), kk('Component kit', '11')];\n    const lf = this.LENS && this.LENS.pages && this.LENS.pages.sample && this.LENS.pages.sample.features;\n    if (!lf) {\n      console.warn('[domains] LENS.pages.sample.features missing — landing chip not counted');\n    } else {\n      const i = lf.findIndex(x => /sub-domains$/.test(String(x)));\n      if (i > -1) lf[i] = c.mounted + ' sub-domains';\n      else console.warn('[domains] no sub-domains chip found to count in LENS.pages.sample.features');\n    }\n  }\n\n  /* CM-G · the Studio side of the fan-out. The field-state vocabulary is\n     `context · ai · manual · stale · empty` (CONTEXT-MANAGEMENT-PLAN §2), so a stale field takes\n     `origin: 'stale'` rather than gaining a parallel flag nothing else reads. Value is kept, not\n     blanked: the operator needs to see what is now suspect, not lose it. */\n  ctxMarkStale(consumerId, fields) {\n    const rec = (this.state.ctxStale || {})[consumerId];\n    if (!rec || !rec.fields || !rec.fields.length) return fields;\n    return fields.map(f => {\n      if (!f.source || rec.fields.indexOf(f.source) < 0) return f;\n      return Object.assign({}, f, { origin: 'stale',\n        hint: (f.hint ? f.hint + ' ' : '') + 'Stale: ' + f.source + ' changed in context' + (rec.at ? ' at ' + rec.at.slice(11, 16) : '') + '. Re-optimise or accept the current value.' });\n    });\n  }\n\n  toolboxDoc(it) {    const TS = window.__dcToolset || {}, SC = window.__dcSchemas || {};\n    const ctx = window.__dcContext || {};\n    const at = (p) => String(p || '').split('.').reduce((a, k) => (a && a[k] !== undefined ? a[k] : undefined), ctx);\n    const slug = (it.href || '').split('/').pop().replace(/\\.html$/, '');\n    const s = SC[slug];\n    if (s && s.fields) {\n      const fields = Object.keys(s.fields).map(id => {\n        const f = s.fields[id], mapped = f.context_map ? at(f.context_map) : undefined;\n        return { id, label: f.label || id, long: f.type === 'textarea', aiPrompt: f.ai_prompt || '',\n          value: mapped !== undefined ? String(mapped) : '',\n          origin: mapped !== undefined ? 'context' : 'ai',\n          source: f.context_map ? f.context_map : (f.ai_prompt ? 'ai · optimise' : 'manual'),\n          placeholder: f.placeholder || (f.ai_prompt ? f.ai_prompt : ''),\n          hint: f.description || '' };\n      });\n      return { id: slug, name: s.name || it.title, fields: this.ctxMarkStale(slug, fields) };\n    }\n    return { id: slug, name: it.title, fields: [\n      { id: 'company.name', label: 'Company name', origin: 'context', source: 'company.name', value: at('company.name') || '', hint: 'From the tenant context pack.' },\n      { id: 'company.legal_form', label: 'Legal form', origin: 'context', source: 'company.legal_form', value: at('company.legal_form') || '', hint: 'Legal details drive every generated contract and policy.' },\n      { id: 'product.description', label: 'What the product is', origin: 'context', source: 'product.description', long: true, value: at('product.description') || '', hint: 'Used wherever a template needs the offer in words.' },\n      { id: 'market.target_audience', label: 'Target audience', origin: 'context', source: 'market.target_audience', long: true, value: at('market.target_audience') || '', hint: 'Ambition and market framing.' },\n      { id: '_note', label: 'Schema not pulled for this template yet', origin: 'ai', source: 'partial', long: true, value: '',\n        hint: 'Only 3 of ' + (TS.n || 275) + ' template schemas are on disk. Pull this one to get its full declared field list instead of these four shared context keys.' }\n    ] };\n  }\n\n  /* O1b · document persistence. The prototype writes to localStorage under one key; production writes\n     tenants/<id>/toolset/data/<Name>.data.json through the proposal gates (PHASE4). Same shape either\n     way: { <fieldId>: value } plus _meta. */\n  docStoreKey() { return 'canvas.toolboxDocs'; }\n  docLoad() { try { return JSON.parse(localStorage.getItem(this.docStoreKey()) || '{}'); } catch (e) { return {}; } }\n  docSave(id, fields, version) {\n    const all = this.docLoad();\n    all[id] = { _meta: { schema_id: id, generated: new Date().toISOString().slice(0, 10), generator: 'canvas-studio-v1', version: version || null }, fields };\n    try { localStorage.setItem(this.docStoreKey(), JSON.stringify(all)); } catch (e) {}\n  }\n\n  /* P4 · the Gaps output. A pane brief for whatever the index says this stream is still missing,\n     shaped like prompts/canvas-dashboard-brief.md (non-negotiables → where the content goes → the\n     STREAMS entry to extend), so it can be handed to a builder as-is. Every line is index-derived;\n     \"nothing open\" is printed as a fact, with the benchmark line as its evidence. */\n  openGapsBrief(streamId) {\n    const g = this.streamGaps(streamId);\n    const st = this.STREAMS[streamId];\n    const IX = window.__dcIndex;\n    if (!g || !st) return;\n    const bm = IX.benchmark;\n    const sections = [];\n    sections.push({ label: 'Stream', value: st.label + ' · ' + streamId + ' — ' + (st.desc || '') });\n    if (!g.total) {\n      sections.push({ label: 'Open gaps', value: 'None. Every need on this stream resolves to a plane with verdict \"integrated\".' });\n      sections.push({ label: 'Evidence', value: 'canvas.index.json ' + IX.version + ' — benchmark ' +\n        (bm ? bm.shipped + ' shipped / ' + bm.designed + ' designed / ' + bm.absent + ' absent' : 'not published') });\n    } else {\n      g.needs.forEach(n => sections.push({ label: 'Need · ' + n.status.toUpperCase() + ' · stage ' + n.stage,\n        value: n.need + (n.planes && n.planes.length ? ' — served partly by ' + n.planes.join(', ') : ' — no plane serves this') }));\n      g.planes.forEach(p => sections.push({ label: 'Plane · ' + p.verdict.toUpperCase() + (p.cause ? ' · cause ' + p.cause : ''),\n        value: p.tab + ' — ' + p.purpose + (p.reason ? ' (' + p.reason + ')' : '') +\n          (p.buildItem ? ' · build item ' + p.buildItem : '') }));\n    }\n    // Honest qualifier while P3 is a first cut: the index only carries a plane row per build item plus\n    // one representative per integrated stream, so an uncovered stage is usually MISSING INDEX, not a\n    // missing product surface. Claiming otherwise would make this brief the confident-but-wrong output\n    // the loss ledger exists to catch. 4.5 completes P3; this line firms up when it does.\n    if (g.missingStages.length) sections.push({ label: 'Stages with no plane ROW',\n      value: g.missingStages.join(', ') + ' of 7 (PROCESS-DASHBOARD-STRATEGY §2). canvas.index.json is at ' +\n        IX.version + ' — a first cut whose plane rows follow build items, not full stage coverage, so treat this as an index gap to close in P3 before reading it as a product gap.' });\n    sections.push({ label: 'Build rules (fixed)', value: 'You are not building chrome — the shell ships it. One STREAMS entry, data-driven. Compose with the design system; never restyle raw HTML to imitate it. Six slot patterns only: data table, decision timeline, metric grid, agent list, chart, detail drawer.' });\n    sections.push({ label: 'Where the content goes', value: 'Hero label/desc/actions2 · 3–6 kpis (numerals, mono) · subs[] tabs (bucket dividers, scaffold flags) · the main region. Declare ownsTabs for any tab this stream renders natively, or the generic lens wins.' });\n    sections.push({ label: 'Extend, do not fork', value: \"STREAMS.\" + streamId + \" in CanvasDashboard.dc.html — the one hand-edited file. Then re-derive CanvasDashboard.standalone.dc.html.\" });\n    this.openDetail({ kind: 'GAPS · ' + st.label, id: streamId, title: 'Pane brief — ' + st.label,\n      chip: g.total ? g.total + ' open' : 'no gaps', chipColor: g.total ? 'var(--app-signal)' : 'rgb(var(--accent-teal))',\n      sections });\n    this.setState({ palette: false });\n  }\n\n  /* Optimise. Runs the schema's own ai_prompt per empty field against the tenant context pack and\n     proposes a diff — never writes silently. window.claude.complete is the prototype path; when it is\n     absent we say so rather than fabricating content. */\n  docOptimise(doc) {\n    if (this.state.docBusy) return;\n    const ctx = window.__dcContext || {};\n    const ed = this.state.docEdits || {}, ai = this.state.docAi || {};\n    const todo = doc.rawFields.filter(f => {\n      const key = doc.id + '·' + f.id;\n      return !(ed[key] || ai[key] || f.value);\n    });\n    if (!todo.length) { this.setState({ docDiff: { id: doc.id, name: doc.name, rows: [], note: 'Every field already has a value — nothing to optimise.' } }); return; }\n    const ask = 'You are filling in a business document for this company.\\n\\nCOMPANY CONTEXT (JSON):\\n'\n      + JSON.stringify(ctx).slice(0, 6000)\n      + '\\n\\nDOCUMENT: ' + doc.name + '\\n\\nWrite each field below from the context. Be specific and use the company\\'s own facts; never invent a legal or financial figure that is not in the context — for those answer exactly UNKNOWN.\\n\\n'\n      + todo.map(f => '- ' + f.id + ' :: ' + (f.label || f.id) + ' :: ' + (f.aiPrompt || f.hint || '')).join('\\n')\n      + '\\n\\nReturn ONLY a JSON object mapping each field id to its string value.';\n    this.setState({ docBusy: true });\n    const done = (obj) => {\n      const rows = todo.map(f => ({ field: f.id, label: f.label || f.id, before: '', after: String((obj && obj[f.id]) || '') })).filter(r => r.after && r.after !== 'UNKNOWN');\n      this.setState({ docBusy: false, docDiff: { id: doc.id, name: doc.name, rows,\n        note: rows.length ? null : 'The model returned nothing usable for these fields — the context pack may not carry them yet.' } });\n    };\n    if (window.claude && typeof window.claude.complete === 'function') {\n      // The call must never leave the button spinning: window.claude is present but unresolvable when the\n      // shell runs inside another frame (e.g. the tour), so race it against a timeout and fail honestly.\n      let settled = false;\n      const fail = (why) => { if (settled) return; settled = true; this.setState({ docBusy: false, docDiff: { id: doc.id, name: doc.name, rows: [], note: why } }); };\n      const tmo = setTimeout(() => fail('Optimise timed out after 30s — no model response reached this frame. Nothing was written. (The model is not reachable when the canvas runs embedded; open the canvas directly and retry.)'), 30000);\n      window.claude.complete(ask).then(txt => {\n        if (settled) return; settled = true; clearTimeout(tmo);\n        let obj = null; try { obj = JSON.parse(String(txt).replace(/^[^{]*/, '').replace(/[^}]*$/, '')); } catch (e) { obj = null; }\n        done(obj);\n      }).catch(() => { clearTimeout(tmo); fail('Optimise failed — no model response. Nothing was written.'); });\n    } else {\n      this.setState({ docBusy: false, docDiff: { id: doc.id, name: doc.name, rows: [],\n        note: 'No model is wired in this build (window.claude.complete absent), so nothing was generated. The prompt is built from this template\\'s own ai_prompt fields plus the tenant context pack.' } });\n    }\n  }\n  docAcceptDiff() {\n    const d = this.state.docDiff; if (!d) return;\n    this.setState(s => { const ai = Object.assign({}, s.docAi); d.rows.forEach(r => { ai[d.id + '·' + r.field] = r.after; }); return { docAi: ai, docDiff: null }; },\n      () => { const all = Object.assign({}, this.state.docEdits, this.state.docAi); const f = {}; Object.keys(all).forEach(kk => { if (kk.indexOf(d.id + '·') === 0) f[kk.split('·')[1]] = all[kk]; }); this.docSave(d.id, f); });\n  }\n  docFinalise(doc) {\n    const all = Object.assign({}, this.state.docEdits, this.state.docAi);\n    const fields = {}; doc.rawFields.forEach(f => { const key = doc.id + '·' + f.id; fields[f.id] = all[key] !== undefined ? all[key] : (f.value || ''); });\n    const empty = doc.rawFields.filter(f => !fields[f.id]).length;\n    const prev = (this.state.docFinal || {})[doc.id];\n    const v = (prev ? prev.v : 0) + 1;\n    this.docSave(doc.id, fields, 'v' + v);\n    this.setState(s => { const fin = Object.assign({}, s.docFinal); fin[doc.id] = { v, at: new Date().toISOString().slice(0, 16).replace('T', ' '), empty }; return { docFinal: fin }; });\n    // The document joins the mail metaphor: finalising files a thread. Reply revises, forward hands off.\n    // Fixed 2026-09-06 (CM-G): this called `InboxThreads.push`, which does not exist on that module —\n    // guarded, so finalising a document has filed NOTHING since this shipped, under a comment saying it\n    // did. Now goes through `ctxRaise` → the real `raiseThread`, with the §3 fields it requires.\n    this.ctxRaise({\n      id: 'doc-final-' + String(doc.id).replace(/[^a-z0-9]+/gi, '-').toLowerCase() + '-v' + v,\n      category: 'offers', sourceStream: 'toolbox', sourceStatus: 'delivered', state: 'delivered',\n      who: 'toolbox-agt', initials: 'TB', addr: '<toolbox@yourco.aaas.love>',\n      at: new Date().toISOString().slice(11, 16),\n      subject: doc.name + ' · finalised v' + v, tag: 'Toolbox document',\n      snip: empty ? 'Finalised with ' + empty + ' field(s) still empty.' : 'All declared context fields filled.',\n      msgs: [{ from: 'toolbox-agt', when: '· finalised v' + v, body: 'Document finalised as version ' + v + '.' }],\n      route: ['toolbox', 'Open in Toolbox']\n    });\n  }\n\n  openDetail(record) { this.setState({ detailRecord: record, detailSelId: record.selId || record.id, detailSplit: false }); }\n  closeDetail() { this.setState({ detailRecord: null, detailSplit: false }); }\n  openFullPage() { this.setState({ detailSplit: true }); }\n  setViewerMode(m) { try { localStorage.setItem('canvas.viewerMode', m); } catch (e) {} this.setState({ viewerMode: m }); }\n  selectRole(role) {\n    if (role === 'New user') { this.enterMvp(); return; }\n    const rail = this.ROLE_RAIL[role] || this.RAIL_GROUPS;\n    // First PERMITTED stream across the whole rail — with gates on, rail[0][0] can be denied (DENIED on landing).\n    const all = [].concat(...rail.map(g => g.ids)).filter(id => this.STREAMS[id]);\n    const first = all.find(id => this.canAccess(role, id)) || 'inbox';\n    this.setState({ role, roleMenu: false, settings: false, railOpen: false, active: first, sub: 0, pane: 'default', ops: true, mvp: false });\n  }\n  enterMvp() { this.setState({ mvp: true, ops: false, active: 'cdelivery', sub: 0, pane: 'default', roleMenu: false, settings: false, railOpen: false, helpMenu: false }); }\n  toggleOps() { this.setState(s => { const ops = !s.ops; return { ops, active: ops ? (this.canAccess(s.role, 'domains') ? 'domains' : 'inbox') : 'home', sub: 0 }; }); }\n  toggleTheme() { this.setState(s => ({ theme: s.theme === 'dark' ? 'light' : 'dark' }), () => setTimeout(() => this.broadcastBrandToFrames(), 30)); }\n  openPalette() { this.setState({ palette: true, q: '' }); setTimeout(() => this._pin && this._pin.focus(), 30); }\n\n  renderVals() {\n    const P = this.props || {};\n    const lead = P.accentLead === 'red' ? 'red' : 'circuit';\n    const leadVar = lead === 'red' ? 'var(--accent-red)' : 'var(--circuit-glow)';\n    const mob = this.state.mob;\n    const dz = mob ? { mp: 14, sg: 20, gg: 12, cp: 14 } : (P.density === 'compact' ? { mp: 22, sg: 20, gg: 12, cp: 16 } : { mp: 36, sg: 30, gg: 16, cp: 22 });\n    // --app-* aliases — the Canvas 2.0 pane token contract mapped onto AaaS DS tokens.\n    // Panes below reference only var(--app-*), never a literal hex (per the design prompts).\n    const appTokens = [\n      '--app-bg:rgb(var(--basalt-deep))',\n      '--app-surface:rgb(var(--basalt-surface) / 0.35)',\n      '--app-elevated:rgb(var(--basalt-surface) / 0.62)',\n      '--app-ink:rgb(var(--text))',\n      // 4.12 · fixed at the definition, never per site (the 4.4g rule). --text-muted is 0.5 alpha, which\n      // cannot reach 4.5:1 on this ground at any size — measured 4.28:1 on the KPI strip's 9px labels,\n      // chrome on every stream, and 4.31–4.4:1 across PaneContext's four WP-11 tabs. Both steps are now\n      // derived from the brand's own --text triplet (so a tenant pack still owns the colour) at alphas\n      // that pass: 0.7 for soft, 0.62 for faint. The hierarchy survives — ink 1.0 > soft 0.7 > faint 0.62.\n      '--app-ink-soft:rgb(var(--text) / 0.7)',\n      '--app-ink-faint:rgb(var(--text) / 0.62)',\n      '--app-accent:rgb(var(--lead))',\n      '--app-accent-glow:rgb(var(--lead) / 0.3)',\n      '--app-accent-dim:rgb(var(--lead) / 0.12)',\n      '--app-success:rgb(var(--accent-teal))',\n      '--app-warn:rgb(var(--pastel-gold))',\n      '--app-fail:rgb(var(--accent-red))',\n      '--app-running:rgb(var(--circuit-glow))',\n      '--app-rule:var(--border)',\n      '--app-rule-soft:rgb(var(--text) / 0.08)'\n    ].join(';');\n    const mvp = !!this.state.mvp;\n    const dark = this.state.theme !== 'light';\n    // Data-viz palette — the upstream canvas-blocks contract (identical in all 8 C-blocks).\n    // These hold TRIPLES, consumed as rgb(var(--app-series-1)) / rgb(var(--app-series-1)/.25).\n    // Never wrap in rgb() here, or lifted chart markup double-wraps and drops the declaration.\n    // 1-5 ride DS tokens so brand + theme switching cascades; 6-8 have no DS counterpart.\n    const vizTokens = ';' + [\n      '--app-series-1:var(--lead)',\n      '--app-series-2:var(--accent-red)',\n      '--app-series-3:var(--pastel-gold)',\n      '--app-series-4:var(--pastel-lavender)',\n      '--app-series-5:var(--accent-teal)',\n      '--app-series-6:' + (dark ? '255 159 67' : '180 90 20'),\n      '--app-series-7:' + (dark ? '178 219 255' : '70 130 145'),\n      '--app-series-8:' + (dark ? '200 200 205' : '100 116 139'),\n      '--app-seq-1:' + (dark ? '200 250 255' : '200 245 250'),\n      '--app-seq-2:' + (dark ? '120 230 250' : '150 235 245'),\n      '--app-seq-3:' + (dark ? '60 210 245' : '70 200 220'),\n      '--app-seq-4:' + (dark ? '0 180 210' : '0 137 150'),\n      '--app-seq-5:' + (dark ? '0 120 145' : '0 90 100'),\n      '--app-div-neg:var(--accent-red)',\n      '--app-div-mid:' + (dark ? '120 118 114' : '150 158 175'),\n      '--app-div-pos:var(--accent-teal)',\n      '--app-grid:' + (dark ? '255 255 255' : '13 13 14'),\n      '--app-axis:' + (dark ? '168 166 160' : '90 100 120'),\n      '--app-accent-soft:' + (dark ? '0 60 65' : '200 245 250'),\n      '--app-surface-1:var(--basalt-surface)',\n      '--app-surface-2:var(--basalt-bright)',\n      '--app-surface-3:' + (dark ? '46 46 50' : '236 236 232'),\n      // Context-suite alias set (upstream context-suite-design/canvas-base.css). COLOURS, not\n      // triples — that package consumes them directly. No alpha-slash forms: this whole string\n      // reaches the template through a hole, and a `/` alpha can be dropped silently there.\n      '--app-signal:rgb(var(--accent-red))',\n      '--app-signal-dim:rgba(244,63,108,0.18)',\n      '--app-pending:' + (dark ? 'rgb(176,107,255)' : 'rgb(124,58,237)'),\n      '--app-pending-dim:' + (dark ? 'rgba(176,107,255,0.16)' : 'rgba(124,58,237,0.12)'),\n      '--app-accent-ink:rgb(var(--basalt-deep))',\n      '--app-radius:12px',\n      '--app-radius-sm:6px',\n      '--app-radius-pill:9999px',\n      '--app-font-display:var(--font-inter)',\n      '--app-font-body:var(--font-inter)',\n      '--app-font-mono:var(--font-jetbrains)',\n      '--app-shadow:' + (dark ? '0 10px 30px -12px rgba(0,0,0,0.6)' : '0 10px 30px -12px rgba(0,0,0,0.22)'),\n      '--app-shadow-glow:0 0 24px rgba(0,243,255,0.18)',\n      '--app-glass-bg:' + (dark ? 'rgba(26,26,28,0.6)' : 'rgba(247,247,243,0.85)'),\n      '--app-glass-blur:blur(24px)',\n      '--app-density:1'\n    ].join(';');\n    const brandKey = mvp && this.state.brandKey === 'aaas' ? 'squr' : this.state.brandKey;\n    const brand = this.brandOrPlaceholder(brandKey);   // unknown keys render their own placeholder, never tenant-0\n    const brandTokens = (brand.dark && brand.light) ? (';' + (dark ? brand.dark : brand.light)) : '';\n    const clientTokens = brandTokens;\n    const rootStyle = '--lead:' + leadVar + ';--main-pad:' + dz.mp + 'px;--section-gap:' + dz.sg + 'px;--grid-gap:' + dz.gg + 'px;--card-pad:' + dz.cp + 'px;' + appTokens + vizTokens + clientTokens + ';position:relative;height:100vh;overflow:hidden;background:var(--app-bg);color:var(--app-ink);font-family:var(--font-inter)';\n    // PR-1 (4.5c → operator call: per-stream flag now, fold into the role model later). The proposal\n    // source declares `mode: 'user'` on its two client-facing streams and suppresses the operator roster\n    // there (`rightPanel = … && STREAMS[active].mode !== 'user'`). Restored here as the source had it —\n    // with one deliberate difference: suppressed is NOT hidden — the roster starts closed and the FAB\n    // re-opens it, on desktop as well as mobile, via `rosterClient`. That keeps D25 rule 5 true (nothing\n    // hidden from the operator at this stage) while stopping the internal roster from sitting beside a\n    // proposal a client is reading. Verified live: FAB present and the panel re-opens on both streams.\n    // (First cut of this shipped broken — `rosterFab` AND-ed `!rosterOpen`, which is false on desktop, and\n    // `rightPanel` false-gated on `!clientChrome` unconditionally, so the control was invisible AND dead.)\n    const clientChrome = (this.STREAMS[this.state.active] || {}).mode === 'user';\n    // One effective open-state, so the panel, the FAB and the switch visuals cannot disagree. On a\n    // `mode:'user'` stream the roster starts closed and `rosterClient` is what the FAB toggles; everywhere\n    // else the existing desktop/mobile behaviour is untouched.\n    // D178: desktop roster = pinned (rosterOpen, docked) or peek (hover at the right edge, overlay, folds on leave).\n    const rosterPeek = !clientChrome && !mob && !this.state.rosterOpen && !!this.state.rosterPeek;\n    const rosterEff = clientChrome ? this.state.rosterClient : (this.state.rosterOpen || rosterPeek);\n    const rosterShown = clientChrome ? this.state.rosterClient : (mob ? this.state.rosterOpen : rosterEff);\n    const rightPanel = P.rightPanel !== false && !mvp && rosterShown;\n    // One condition for \"the header speaks the selected tenant's pack\": the MVP client canvas, or any non-default tenant.\n    const packHeader = mvp || brandKey !== 'aaas';\n    const wordmark = packHeader ? brand.name : (P.brandWordmark || 'AAAS');\n    const tagline = packHeader ? brand.tag : (P.brandTagline || 'CURATED AI AGENTS');\n    // `brand` for the template's own {{ brand.* }} placeholders (the chat footer). Built from the SAME\n    // header values as `wordmark`/`tagline` above, never a second source: a tenant (or the MVP client\n    // canvas) speaks its pack's name; the default tenant speaks its own mixed-case name, which is the\n    // footer's literal before it was templated -- the editor prop's all-caps default is the header's\n    // display form and would be a visible change here, so a wordmark that is just the tenant key (or\n    // empty) takes the mixed-case name, and any other custom editor value wins. A missing value falls\n    // back to that name, never to an empty string (the placeholder resolves to nothing when the key\n    // is absent, which printed ' FRAMEWORK').\n    const defaultMark = 'AaaS';\n    const footerMark = packHeader ? wordmark : (!P.brandWordmark || String(P.brandWordmark).toLowerCase() === brandKey ? defaultMark : P.brandWordmark);\n    const footerBrand = { wordmark: footerMark || defaultMark, tagline };\n\n    const sec = (k) => this.state.rosterSections[k] !== false;\n    const secCaret = (k) => (this.state.rosterSections[k] !== false ? 'rotate(180deg)' : 'rotate(0deg)');\n    const secToggle = (k) => () => this.setState(st3 => ({ rosterSections: { ...st3.rosterSections, [k]: st3.rosterSections[k] === false } }));\n\n    const isDark = this.state.theme === 'dark';\n    const ops = this.state.ops;\n    const collapsed = this.state.collapsed;\n    const railPeek = !mob && !!this.state.railPeek && (collapsed || this.state.railHidden);\n    const showLabels = mob ? this.state.railOpen : (!collapsed || railPeek);  // collapsed strip on mobile until the drawer opens\n\n    const st = (mvp\n      ? (this.MVP_IDS.indexOf(this.state.active) >= 0 ? this.STREAMS[this.state.active] : this.STREAMS.cdelivery)\n      : (this.STREAMS[this.state.active] || this.STREAMS.domains));\n    const up = st.label.toUpperCase();\n\n    const beats = (mvp ? [\n      { label: 'Script', value: 'in review' },\n      { label: 'Offer', value: 'expires 6 d', warn: true },\n      { label: 'Revisions', value: '2 left' },\n      { label: 'Support', value: '1 open' }\n    ] : [\n      { label: 'System', value: '12/14 ok' },\n      // D177: Needs you / Alerts / Agents read the same counter as the roster chips (agent-registry.js).\n      // Roster glyphs on the attention beats: ◆ needs · ▲ stuck · ✓ done-unseen. \"Alerts\" was the stuck count under another name → now \"Stuck\".\n      { label: 'Needs you', value: String(this.agentCounts().needs), warn: this.agentCounts().needs > 0, shape: 'needs' },\n      { label: 'Stuck', value: String(this.agentCounts().stuck), shape: 'stuck', tone: this.agentCounts().stuck > 0 ? 'rgb(var(--status-stuck, 248 217 116))' : null },\n      { label: 'Unseen', value: String(this.agentCounts().done), shape: 'done', tone: this.agentCounts().done > 0 ? 'rgb(var(--status-done, 0 212 184))' : null },\n      { label: 'DLQ', value: '0' },\n      { label: 'Cost · day', value: '€114' },\n      { label: 'Agents', value: this.agentCounts().total + ' · ' + this.agentCounts().active + ' active' }\n    ]).map(b => ({ label: b.label, value: b.value, color: b.warn ? 'rgb(var(--accent-red))' : (b.tone || 'rgb(var(--text) / 0.85)'),\n      isNeeds: b.shape === 'needs', isStuck: b.shape === 'stuck', isDone: b.shape === 'done' }));\n\n    const roleRail = this.ROLE_RAIL[this.state.role] || this.RAIL_GROUPS;\n    // The non-ops Personal view's rail. Declared once (it used to be an inline literal in two places —\n    // WP-26 already had to patch the other copy for onboardingV2) and, unlike the role rails, it is\n    // shown to EVERY role, so its ids are filtered through canAccess: the `personal` group always\n    // survives via PERSONAL_IDS, while the `member` work surfaces stay role-scoped and simply do not\n    // render a button the permission table would refuse. Empty groups drop out rather than render bare.\n    // FQ-5 (D154/D155): the Use rail. Member-first; never role-filtered (canAccess exempts USER_VIEW_IDS in Use).\n    // In first contact only the offer + the hand-over (onboarding) show.\n    const USE_OFFER = { id: 'useOffer', label: 'Your offer', ids: ['cdelivery', 'cbrief', 'cbilling', 'csupport'] };\n    const USE_START = { id: 'start', label: 'Get started', ids: ['onboarding'] };\n    const PERSONAL_RAIL = mvp ? [USE_OFFER, USE_START] : [\n      { id: 'personal', label: 'Personal', ids: ['home', 'account'] },\n      { id: 'member', label: 'Your work', ids: ['projects', 'deliverables', 'agents', 'billing', 'team'] },\n      { id: 'useTools', label: 'Context & tools', ids: ['context', 'intelligence', 'select', 'podcast'] },\n      { id: 'useDoors', label: 'Doors', ids: ['domains'] },\n      USE_START\n    ];\n    const groupsSrc0 = mvp ? this.MVP_RAIL : (!ops ? PERSONAL_RAIL : roleRail);\n    // Founder: FOUNDER_INBOX streams move up under Inbox and out of every other group (empty groups drop).\n    const groupsSrc = !(this.state.founder && ops) ? groupsSrc0 : groupsSrc0.map(g => g.id === 'inboxTop'\n      ? Object.assign({}, g, { ids: g.ids.concat(this.FOUNDER_INBOX.filter(id => g.ids.indexOf(id) < 0)) })\n      : Object.assign({}, g, { ids: g.ids.filter(id => this.FOUNDER_INBOX.indexOf(id) < 0) })).filter(g => g.bare || g.ids.length);\n    const railGroups = groupsSrc.map(g => ({\n      id: g.id, label: g.label, count: g.ids.length, headed: showLabels && !g.bare,\n      open: g.bare ? true : (g.collapsed ? this.state.groupOpen[g.id] === true : this.state.groupOpen[g.id] !== false),\n      caret: (g.collapsed ? this.state.groupOpen[g.id] === true : this.state.groupOpen[g.id] !== false) ? 'rotate(180deg)' : 'rotate(0deg)',\n      onToggle: () => this.setState(st2 => { const cur = g.collapsed ? st2.groupOpen[g.id] === true : st2.groupOpen[g.id] !== false; return { groupOpen: { ...st2.groupOpen, [g.id]: !cur } }; }),\n      items: (() => {\n        // Gate site 1 (D2/D25): the rail filters through canAccess — the one switch. With showAllPlanes on\n        // nothing drops; off, Run narrows to PERMISSIONS[role] while Use stays whole (FQ-5 exemption).\n        const raw = g.ids.filter(id => this.STREAMS[id] && this.canAccess(this.state.role, id));\n        const mk = (id) => {\n          const s = this.STREAMS[id]; const active = this.state.active === id;\n        return { id, label: s.label, iconEl: this.ic(s.icon, 18), active, dot: !!s.dot && showLabels,\n          badgePill: showLabels ? (s.badge || null) : null,\n          badgeMini: !showLabels ? (s.badge || null) : null,\n          iconRing: '1px solid transparent',\n          iconBox: 'auto',\n          pad: showLabels ? '7px 9px' : '7px 6px',\n          color: active ? 'rgb(var(--lead))' : 'rgb(var(--text) / 0.7)',\n          onClick: () => this.selectStream(id) };\n        };\n        const out = [], seen = {};\n        raw.forEach(id => {\n          const f = this.FLOW_OF[id];\n          const members = f ? raw.filter(x => this.FLOW_OF[x] === f) : [];\n          if (members.length < 2) { out.push(mk(id)); return; }\n          if (seen[f]) return; seen[f] = true;\n          const F = this.FLOWS[f]; const active = members.indexOf(this.state.active) > -1;\n          const badge = members.reduce((n, x) => n + (parseInt(this.STREAMS[x].badge, 10) || 0), 0) || null;\n          const last = (this.state.flowLast || {})[f];\n          out.push({ id: 'flow:' + f, label: F.label + ' · ' + members.length, iconEl: this.ic(F.icon, 18), active,\n            dot: members.some(x => this.STREAMS[x].dot) && showLabels,\n            badgePill: showLabels ? badge : null, badgeMini: !showLabels ? badge : null,\n            iconRing: '1px solid transparent', iconBox: 'auto', pad: showLabels ? '7px 9px' : '7px 6px',\n            color: active ? 'rgb(var(--lead))' : 'rgb(var(--text) / 0.7)',\n            onClick: () => this.selectStream(members.indexOf(last) > -1 ? last : members[0]) });\n        });\n        return out;\n      })()\n    })).filter(g => g.items.length).map(g => ({ ...g, count: g.items.length }));\n\n    const flowId = this.FLOW_OF[st.id];\n    const flowSteps = flowId ? this.FLOWS[flowId].ids.filter(id => this.STREAMS[id]).map((id, i) => {\n      const on = id === st.id;\n      return { n: String(i + 1).padStart(2, '0'), label: this.STREAMS[id].label, active: on,\n        bg: on ? 'rgb(var(--lead) / 0.1)' : 'transparent', color: on ? 'rgb(var(--lead))' : 'var(--app-ink-soft, rgb(var(--text) / 0.7))',\n        onClick: () => this.selectStream(id) };\n    }) : [];\n    const subItems = (st.subs || []).map((s, i) => ({\n      label: s.label, bucket: !!s.bucket, isTab: !s.bucket, scaffold: !!s.scaffold,\n      active: (i === this.state.sub && !s.bucket),\n      color: (i === this.state.sub && !s.bucket) ? 'rgb(var(--text))' : 'var(--app-ink-soft, rgb(var(--text) / 0.7))',\n      onClick: () => this.setState({ sub: i })\n    }));\n\n    const shSub = (st.subs && st.subs[this.state.sub]) ? st.subs[this.state.sub].label : null;\n    const sh = ((this.SUBHERO || {})[st.id] || {})[shSub] || {};\n    const actions2 = (sh.actions2 || st.actions2 || [{ label: 'New run', icon: 'play' }, { label: 'Runbook', icon: 'book' }]).map(a => ({\n      label: a.label, iconEl: this.ic(a.icon, 16),\n      onClick: (a.label === 'Correct something' || a.label === 'Ask for a change') ? () => {\n          this.setState({ chat: 'open', chatDraft: '', chatTurns: 3, chatMsgs: [{ from: 'agent',\n            text: a.label === 'Correct something' ? 'What would you like to correct, John?' : 'What would you like to change, John?' }] });\n          setTimeout(() => this._chatIn && this._chatIn.focus(), 60);\n        }\n        : (a.label === 'Take the tour') ? () => this.setState({ tour: 0 })\n        : (a.label === 'Clear queue') ? () => { const n = this.WK_QUEUE.length + this.WK_QUEUE_SRC.length; if (!n) return;\n            this.setState({ confirm: { title: 'Clear queue — resolve all ' + n + ' open loops', verb: 'CLEAR', onConfirm: () => {\n              this.WK_QUEUE = []; this.WK_QUEUE_SRC = []; this.setState(x => ({ wkCleared: (x.wkCleared || 0) + n })); } } }); }\n        : (a.label === 'Submit a brief') ? () => this.setState({ briefWiz: { step: 0, text: '', agent: null } })\n        : (a.label === 'Run discovery') ? () => this.setState({ disco: { step: 0, icp: this.DISCO_ICPS[0], maxLeads: 250, sources: ['apollo', 'clay'], status: null } })\n        : (a.label === 'Pause ALL campaigns') ? () => this.setState({ pauseAll: { typed: '' } })\n        : () => {}\n    }));\n    const subLabels = (st.subs || []).map(x => x.label);\n    const kpis = (sh.kpis || st.kpis || []).map(kk => {\n      const tabIdx = kk.goTab ? subLabels.indexOf(kk.goTab) : -1;\n      return {\n      // D210 (P4.4) · a KPI with value null is MODELED: '—' + a gold 'modeled' chip — never a confident number by omission.\n      label: kk.label, value: kk.value == null ? '—' : kk.value, delta: kk.value == null ? (kk.delta || 'modeled') : kk.delta,\n      cellStyle: 'padding:18px 20px;border-left:1px solid var(--border);text-align:left;width:100%;background:transparent;font:inherit;color:inherit;'\n        + (tabIdx >= 0 ? 'cursor:pointer;border-top:none;border-right:none;border-bottom:none' : 'cursor:default;border-top:none;border-right:none;border-bottom:none'),\n      isLink: tabIdx >= 0,\n      onClick: tabIdx >= 0 ? () => this.setState({ sub: tabIdx, pane: 'default' }) : () => {},\n      chipColor: kk.accent === 'teal' ? 'rgb(var(--accent-teal))' : kk.accent === 'red' ? 'rgb(var(--accent-red))' : kk.accent === 'lavender' ? 'rgb(var(--pastel-lavender))' : (kk.accent === 'gold' || kk.value == null) ? 'rgb(var(--pastel-gold, 248 217 116))' : 'rgb(var(--lead))',\n      chipBg: kk.accent === 'teal' ? 'rgb(var(--accent-teal) / 0.12)' : kk.accent === 'red' ? 'rgb(var(--accent-red) / 0.12)' : kk.accent === 'lavender' ? 'rgb(var(--pastel-lavender) / 0.12)' : (kk.accent === 'gold' || kk.value == null) ? 'rgb(var(--pastel-gold, 248 217 116) / 0.12)' : 'rgb(var(--lead) / 0.12)'\n    }; });\n    const hero = { overline: sh.over || (up + ' · ' + up + ' OVERVIEW'), title: sh.title || st.label, desc: sh.desc || st.desc, actions2, kpis };\n\n    // ══ Developer panes — one lens per subnav tab, all from the seed data above ══\n    const dcol = (st2) => this.dotCol(st2);\n    const tCell = (v, opts) => Object.assign({ v, flex: 1, align: 'left', font: 'var(--font-inter)', color: 'var(--app-ink)' }, opts || {});\n    const mono = (v, opts) => tCell(v, Object.assign({ font: 'var(--font-jetbrains)', color: 'var(--app-ink-soft)' }, opts || {}));\n    const runRow = (r) => ({ cells: [\n      tCell(r.title, { flex: 3 }),\n      tCell(r.statusLabel, { flex: 1.2, font: 'var(--font-jetbrains)', color: dcol(r.status) }),\n      mono(r.worktree, { flex: 2 }),\n      mono(r.stage, { flex: 1 }),\n      mono(r.elapsed, { flex: 1 }),\n      mono(r.cost, { flex: 0.8, align: 'right' })\n    ] });\n    const RUN_COLS = [\n      { label: 'Agent · task', flex: 3, align: 'left' }, { label: 'Status', flex: 1.2, align: 'left' },\n      { label: 'Worktree', flex: 2, align: 'left' }, { label: 'Node', flex: 1, align: 'left' },\n      { label: 'Elapsed', flex: 1, align: 'left' }, { label: 'Cost', flex: 0.8, align: 'right' }\n    ];\n    const byStatus = (label) => this.AW_RUNS.filter(r => r.statusLabel === label);\n    const table = (title, note, cols, rows) => ({ view: 'table', title, note, cols, rows });\n    // View-mode switcher (docs/CANVAS-IMPROVEMENT-PLAN.md #1): a table spec that also wants a Board\n    // view attaches boardAlt — the SAME items, grouped into named columns. The switcher shown above\n    // the table lets the tab toggle between them; state lives in this.state.viewMode[tabLabel].\n    // Hand-authored per tab (not auto-derived from table rows) — matches the \"hybrid\" recommendation\n    // in the source doc: designed pieces, not full schema-generation.\n    const board = (title, note, cols) => ({ view: 'board', title, note, columns: cols.map(c => ({\n      label: c.label, count: String(c.items.length), color: c.color || 'var(--app-ink-soft)',\n      items: c.items.map(it => ({ title: it.title, meta: it.meta, onClick: it.onClick })) })) });\n    const list = (title, note, items) => ({ view: 'list', title, note, items });\n    const cards = (title, note, cds) => ({ view: 'cards', title, note, cards: cds });\n    const tail = (title, note, tailTitle, lines) => ({ view: 'tail', title, note, tailTitle, lines });\n    const catCard = (c) => ({ title: c.title, tag: c.kind, meta: c.meta, tagColor: 'rgb(var(--lead) / 0.5)' });\n    const inboxItems = (filter) => this.AW_INBOX.filter(i => !filter || i.chip === filter)\n      .map(i => ({ title: i.title, meta: i.meta, chip: i.chip, color: dcol(i.sev === 'fail' ? 'fail' : 'warn') }));\n\n    this.DEV_PANES = {\n      // ── D180 · Content · v2 (content-production template) ──\n      content2: (() => {\n        const NODES = [\n          ['brief', 'Brief', 'define', 'brief', 'audience · message · CTA · surface · template pick'],\n          ['compose', 'Compose', 'build', 'cards · frames', 'library template T1–T20 + brief → draft'],\n          ['review', 'Review', 'review', 'diff', 'draft vs Protocol checklist'],\n          ['approve', 'Approve', 'approve', 'gate · human', 'validity → authority → liveness'],\n          ['publish', 'Publish', 'deliver', 'calendar', 'the content calendar (one surface with Growth)'],\n          ['shelf', 'Shelf', 'operate', 'cards', 'asset index by surface']\n        ];\n        const INFLIGHT = [\n          ['Cold outreach — batch 14', 'email', 'review', 'cold-email-personalizer', 'T1'],\n          ['Follow-up sequence — SaaS ICP', 'email', 'approve', 'sequence-writer', 'T1'],\n          ['LinkedIn post — case study', 'social', 'compose', 'content-writer', 'T4'],\n          ['Newsletter — weekly roundup', 'email', 'review', 'newsletter-agent', 'T3'],\n          ['Launch set — Decisions', 'social', 'brief', 'content-writer', 'T17']\n        ];\n        const lead = 'rgb(var(--lead) / 0.5)';\n        const nodeCards = NODES.map(n => ({ title: n[1], tag: n[2], meta: n[4] + ' · archetype: ' + n[3] + ' · in flight: ' + INFLIGHT.filter(f => f[2] === n[0]).length, tagColor: lead }));\n        const stageCards = (stage) => INFLIGHT.filter(f => f[2] === stage).map(f => ({ title: f[0], tag: f[1], meta: f[3] + ' · template ' + f[4], tagColor: lead }));\n        const TPL_LIST = [[\"T1\",\"Lifecycle Email Shell\",\"email\",600,1400],[\"T2\",\"Announcement Email\",\"email\",600,1200],[\"T3\",\"Digest Newsletter Email\",\"email\",600,1600],[\"T4\",\"LinkedIn Square Post\",\"social\",1080,1080],[\"T5\",\"LinkedIn Carousel\",\"social\",1080,1080],[\"T6\",\"LinkedIn Banner\",\"social\",1584,396],[\"T7\",\"X Twitter Card\",\"social\",1200,675],[\"T8\",\"X Profile Header\",\"social\",1500,500],[\"T9\",\"Instagram Post Set\",\"social\",1080,1350],[\"T10\",\"TikTok Hook Cover\",\"social\",1080,1920],[\"T11\",\"YouTube Thumbnail\",\"social\",1280,720],[\"T12\",\"Pinterest Pin\",\"social\",1000,1500],[\"T13\",\"Open Graph Card\",\"social\",1200,630],[\"T14\",\"Snapchat Tile\",\"social\",1080,1920],[\"T15\",\"WhatsApp Asset Set\",\"social\",1080,1080],[\"T16\",\"Reddit Telegram Set\",\"social\",1920,384],[\"T17\",\"Launch Set\",\"social\",1200,630],[\"T18\",\"Pitch Deck Kit\",\"deck\",1920,1080],[\"T19\",\"Brand Book One-Pager\",\"brand\",1200,1600],[\"T20\",\"Favicon Logo Lockup\",\"brand\",1200,800]];\n        const tmplFrames = this.tmplFrames = (copy) => TPL_LIST.map(t => ({ id: t[0] + '-' + copy, grp: copy, label: t[0] + ' · ' + t[1], meta: t[2] + ' · ' + (copy === 'generic' ? 'template placeholders' : 'brand + company context re-cut · switch brand (logo) or Founder to see it'), w: t[3], h: t[4], src: '../templates/' + encodeURIComponent(t[0] + ' ' + t[1] + '.dc.html') + '?copy=' + copy }));\n        // D199 (NIGHT-PLAN P2a): instances Design · v2 › Save to Content wrote land on the shelf first, real slots injected.\n        const producedFrames = () => (this.state.contentInstances || []).slice().reverse().map((inst, i) => {\n          const t = TPL_LIST.find(x => x[0] === inst.templateId) || [inst.templateId, inst.templateId, 'social', 1080, 1080];\n          const grp = t[2] === 'email' ? 'email' : (t[2] === 'deck' || t[2] === 'brand' ? 'deck' : 'social');\n          return { id: 'P' + i, grp, label: t[0] + ' · ' + t[1] + ' (saved)', meta: 'produced instance · ' + (inst.brand || 'aaas') + ' · ' + (inst.at || '').slice(11, 16), w: t[3], h: t[4], src: '../templates/' + encodeURIComponent(t[0] + ' ' + t[1] + '.dc.html') + '?copy=tenant', slots: inst.slots };\n        });\n        const shelfFrames = () => { const AI = this.ASSET_INDEX || {}; return producedFrames()\n          .concat((AI.gallery || []).filter(g => g.class === 'emails').slice(0, 20).concat((AI.gallery || []).filter(g => g.class === 'social').slice(0, 20)).map((g, i) => ({ id: 'G' + i, grp: g.class === 'emails' ? 'email' : 'social', label: g.id, meta: g.class + ' · gallery', w: g.class === 'emails' ? 600 : 1080, h: g.class === 'emails' ? 1400 : 1080, src: g.src.replace(/^gallery\\//, '') })))\n          .concat((AI.decks || []).map(d => ({ id: d.id, grp: 'deck', label: d.id, meta: (d.slides ? d.slides + ' slides · ' : '') + 'deck', w: 1920, h: 1080, src: '../' + d.src + '/' + encodeURIComponent(d.entry) })))\n          .concat((AI.videos || []).map(v => ({ id: v.id, grp: 'video', label: v.id, meta: 'explainer', w: 1920, h: 1080, src: '../' + v.src.split('/').map(encodeURIComponent).join('/') }))); };\n        const spec = {\n          Overview: cards('content-production · one template, six nodes', 'Every asset type runs the same flow. Counts are in-flight instances per node (5 seeded from the content calendar).', nodeCards),\n          Brief: cards('Brief · define', 'Pre-filled from AI_Content_Brief_Generator + Messaging_Framework (toolbox) and ctx.company.*', stageCards('brief').concat([{ title: 'New brief', tag: 'template', meta: 'audience · message · CTA · surface · pick T1–T20', tagColor: lead }])),\n          Compose: { view: 'frames', segs: [{ id: 'generic', label: 'Generic copy' }, { id: 'tenant', label: 'Tenant copy' }], title: 'Compose · pick from the Design · v2 library', note: 'The 20 library templates rendered in place. Generic = the template\\'s own copy. Tenant = re-cut in the same frames, source in this order: the active brand when it is not AaaS (logo click) → a real tenant context pack (with provenance; the scaffold example never renders) → nothing. Founder map applies when on. Brand AaaS with no real pack = generic, by design.', frames: tmplFrames('generic').concat(tmplFrames('tenant')) },\n          Review: cards('Review · diff against the Protocol', 'Identity · audience · CTA policy · guardrails · self-audit — each draft shows what changed and what fails.', stageCards('review')),\n          Approve: cards('Approve · human gate', 'validity → authority → liveness. Approving writes the asset to the shelf and raises nothing new; rejecting needs a reason.', stageCards('approve')),\n          Publish: cards('Publish · content calendar', 'Same calendar Growth shows — one surface, mounted here as the deliver node.', INFLIGHT.map(f => ({ title: f[0], tag: f[2], meta: f[1] + ' · ' + f[3], tagColor: lead }))),\n          Shelf: { view: 'frames', segs: [{ id: 'email', label: 'Email' }, { id: 'social', label: 'Social' }, { id: 'deck', label: 'Decks' }, { id: 'video', label: 'Video' }], title: 'Shelf · what exists', note: 'Read from assets/index.json — gallery, decks and videos today; produced instances land here as the flow ships them. Brand book, designs and docs live with their owning streams.', frames: [] }\n        };\n        Object.defineProperty(spec.Shelf, 'frames', { get: shelfFrames, enumerable: true });\n        return spec;\n      })(),\n      ads2: {\n        Overview: cards('paid-campaign · one template', 'Every tab below is one node with one archetype. \"From v1\" names the tabs it folds; v1 stays on the rail until reassessment (D180).', [\n          { title: 'Brief', tag: 'define', meta: 'archetype: brief · ICP + offer → campaign brief', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Build', tag: 'build', meta: 'archetype: cards · brief → ad set + creatives (T4/T6/T7/T13)', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Brand safety', tag: 'review', meta: 'archetype: gate · auto · creatives → checked creatives', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Launch', tag: 'approve', meta: 'archetype: gate · human · checked set + budget → live campaign · campaigns are created PAUSED (Google Ads API guidance); this gate is what un-pauses', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Measure', tag: 'operate', meta: 'archetype: ledger · spend · conversions · CPA per day · Meta Insights reports run async (up to ~1h) — the ledger shows report age', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Kill-switch', tag: 'operate', meta: 'archetype: gate · human · threshold breach → pause · reads live delivery spend, never the async report', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        'Brief': cards('Brief · define · brief', 'ICP + offer → campaign brief', [\n          { title: 'From v1', tag: 'folds', meta: 'Launch Readiness (inputs)', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /ads/campaigns is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Build': cards('Build · build · cards', 'brief → ad set + creatives (T4/T6/T7/T13)', [\n          { title: 'From v1', tag: 'folds', meta: 'Campaign Builder', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /ads/campaigns is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Brand safety': cards('Brand safety · review · gate · auto', 'creatives → checked creatives', [\n          { title: 'From v1', tag: 'folds', meta: 'Assets & Brand Safety', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /ads/campaigns is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Launch': cards('Launch · approve · gate · human', 'checked set + budget → live campaign', [\n          { title: 'From v1', tag: 'folds', meta: 'Launch Readiness · Budget', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /ads/campaigns is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Measure': cards('Measure · operate · ledger', 'spend · conversions · CPA per day', [\n          { title: 'From v1', tag: 'folds', meta: 'Conversions · Account Overview', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /ads/campaigns is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Kill-switch': cards('Kill-switch · operate · gate · human', 'threshold breach → pause', [\n          { title: 'From v1', tag: 'folds', meta: 'Budget & Kill-Switch', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /ads/campaigns is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ])\n      },\n      outbound2: {\n        Overview: cards('outbound · one template', 'Every tab below is one node with one archetype. \"From v1\" names the tabs it folds; v1 stays on the rail until reassessment (D180).', [\n          { title: 'ICP', tag: 'define', meta: 'archetype: brief · market → ICP definition + gating', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Scout', tag: 'frame', meta: 'archetype: cards · ICP → discovered accounts', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Leads', tag: 'decide', meta: 'archetype: board · accounts → qualified leads by stage', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Compose', tag: 'build', meta: 'archetype: cards · lead + template (T1) → personalised draft', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Send', tag: 'deliver', meta: 'archetype: calendar · drafts → sends by day · follow-up ladder', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Convert', tag: 'review', meta: 'archetype: diff · replies → meetings · what changed per variant', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Score', tag: 'learn', meta: 'archetype: ledger · per-variant CRO scores → next ICP edit', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        'ICP': cards('ICP · define · brief', 'market → ICP definition + gating', [\n          { title: 'From v1', tag: 'folds', meta: 'ICP Builder', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /fleet/outbound is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Scout': cards('Scout · frame · cards', 'ICP → discovered accounts', [\n          { title: 'From v1', tag: 'folds', meta: 'Scout', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /fleet/outbound is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Leads': cards('Leads · decide · board', 'accounts → qualified leads by stage', [\n          { title: 'From v1', tag: 'folds', meta: 'Leads', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /fleet/outbound is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Compose': cards('Compose · build · cards', 'lead + template (T1) → personalised draft', [\n          { title: 'From v1', tag: 'folds', meta: 'Composer · Outreach › Templates · Variants', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /fleet/outbound is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Send': cards('Send · deliver · calendar', 'drafts → sends by day · follow-up ladder', [\n          { title: 'From v1', tag: 'folds', meta: 'Outreach › Queue · Cold A/B · Sent · Follow-up ladder · Deliverability', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /fleet/outbound is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Convert': cards('Convert · review · diff', 'replies → meetings · what changed per variant', [\n          { title: 'From v1', tag: 'folds', meta: 'Convert', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /fleet/outbound is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Score': cards('Score · learn · ledger', 'per-variant CRO scores → next ICP edit', [\n          { title: 'From v1', tag: 'folds', meta: 'CRO › Scores', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /fleet/outbound is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ])\n      },\n      growth2: {\n        Overview: cards('growth-loop · one template', 'Every tab below is one node with one archetype. \"From v1\" names the tabs it folds; v1 stays on the rail until reassessment (D180).', [\n          { title: 'Inventory', tag: 'frame', meta: 'archetype: cards · every sender, template, list — live/stale', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Send', tag: 'deliver', meta: 'archetype: calendar · scheduled sends across channels', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Measure', tag: 'operate', meta: 'archetype: ledger · opens · replies · conversions per send', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Ratchet', tag: 'approve', meta: 'archetype: gate · human · winner picked by the operator, never auto-significance', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Run', tag: 'build', meta: 'archetype: process · a growth run, stage by stage', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Ops', tag: 'learn', meta: 'archetype: thread · fleet issues → Inbox threads', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        'Inventory': cards('Inventory · frame · cards', 'every sender, template, list — live/stale', [\n          { title: 'From v1', tag: 'folds', meta: 'System inventory', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until an endpoint is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Send': cards('Send · deliver · calendar', 'scheduled sends across channels', [\n          { title: 'From v1', tag: 'folds', meta: 'Send console', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until an endpoint is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Measure': cards('Measure · operate · ledger', 'opens · replies · conversions per send', [\n          { title: 'From v1', tag: 'folds', meta: 'Analytics', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until an endpoint is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Ratchet': cards('Ratchet · approve · gate · human', 'winner picked by the operator, never auto-significance', [\n          { title: 'From v1', tag: 'folds', meta: 'Experiment ratchet', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until an endpoint is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Run': cards('Run · build · process', 'a growth run, stage by stage', [\n          { title: 'From v1', tag: 'folds', meta: 'GrowthRun', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until an endpoint is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Ops': cards('Ops · learn · thread', 'fleet issues → Inbox threads', [\n          { title: 'From v1', tag: 'folds', meta: 'Ops console', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until an endpoint is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ])\n      },\n      ops2: {\n        Overview: cards('platform-ops · one template', 'Every tab below is one node with one archetype. \"From v1\" names the tabs it folds; v1 stays on the rail until reassessment (D180).', [\n          { title: 'Runs', tag: 'build', meta: 'archetype: board · agent runs by state', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Alerts', tag: 'learn', meta: 'archetype: thread · incidents → Inbox failed_runs', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Deploys', tag: 'approve', meta: 'archetype: gate · human · build → environment, gates ladder', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Infra', tag: 'frame', meta: 'archetype: cards · environments · integrations · backends · capacity', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Cost', tag: 'operate', meta: 'archetype: ledger · LLM · GCP · per run', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Replay', tag: 'review', meta: 'archetype: process · any run, step by step', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        'Runs': cards('Runs · build · board', 'agent runs by state', [\n          { title: 'From v1', tag: 'folds', meta: 'Agent Runs › Board · Running · Blocked · Done · Failed · Workspace › Swimlanes · Automations › History', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /ops/runs is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Alerts': cards('Alerts · learn · thread', 'incidents → Inbox failed_runs', [\n          { title: 'From v1', tag: 'folds', meta: 'Ops › Alerts · Observability › Event tail', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /ops/runs is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Deploys': cards('Deploys · approve · gate · human', 'build → environment, gates ladder', [\n          { title: 'From v1', tag: 'folds', meta: 'Platform › Deploys · Ops › Deploys · Review › Checks · Gates', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /ops/runs is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Infra': cards('Infra · frame · cards', 'environments · integrations · backends · capacity', [\n          { title: 'From v1', tag: 'folds', meta: 'Platform › Infra · Environments · Integrations · Backends › Capacity · Migration', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /ops/runs is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Cost': cards('Cost · operate · ledger', 'LLM · GCP · per run', [\n          { title: 'From v1', tag: 'folds', meta: 'Observability › Cost · Costs › LLM · GCP', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /ops/runs is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Replay': cards('Replay · review · process', 'any run, step by step', [\n          { title: 'From v1', tag: 'folds', meta: 'the 6 Replays tabs · Automations › Audit', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /ops/runs is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ])\n      },\n      knowledge2: {\n        Overview: cards('knowledge · one template', 'Every tab below is one node with one archetype. \"From v1\" names the tabs it folds; v1 stays on the rail until reassessment (D180).', [\n          { title: 'Ask', tag: 'frame', meta: 'archetype: thread · question → cited answer (four sources)', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Index', tag: 'frame', meta: 'archetype: cards · entities · vault · KB articles', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Context', tag: 'define', meta: 'archetype: brief · company · personal · account blocks, per-field status/trust', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Impact', tag: 'review', meta: 'archetype: diff · what a context change touches (CM-5 fan-out)', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Decide', tag: 'approve', meta: 'archetype: gate · human · agent decisions needing review', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'SOP', tag: 'define', meta: 'archetype: brief · the Production Protocol as the standing procedure', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        'Ask': cards('Ask · frame · thread', 'question → cited answer (four sources)', [\n          { title: 'From v1', tag: 'folds', meta: 'Intelligence › Ask anything · roster Ask bar', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /intelligence/decisions is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Index': cards('Index · frame · cards', 'entities · vault · KB articles', [\n          { title: 'From v1', tag: 'folds', meta: 'Entity graph · Vault · KB · Knowledge Base stream', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /intelligence/decisions is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Context': cards('Context · define · brief', 'company · personal · account blocks, per-field status/trust', [\n          { title: 'From v1', tag: 'folds', meta: 'Context › Company · Personal · Foundation · Browse', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /intelligence/decisions is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Impact': cards('Impact · review · diff', 'what a context change touches (CM-5 fan-out)', [\n          { title: 'From v1', tag: 'folds', meta: 'Context › Impact · History', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /intelligence/decisions is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Decide': cards('Decide · approve · gate · human', 'agent decisions needing review', [\n          { title: 'From v1', tag: 'folds', meta: 'Select › Needs review · Run detail', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /intelligence/decisions is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'SOP': cards('SOP · define · brief', 'the Production Protocol as the standing procedure', [\n          { title: 'From v1', tag: 'folds', meta: 'Protocol', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /intelligence/decisions is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ])\n      },\n      money2: {\n        Overview: cards('money · one template', 'Every tab below is one node with one archetype. \"From v1\" names the tabs it folds; v1 stays on the rail until reassessment (D180).', [\n          { title: 'Invoices', tag: 'deliver', meta: 'archetype: board · issued → paid → overdue (unpaid → Inbox)', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Costs', tag: 'operate', meta: 'archetype: ledger · LLM · GCP · Stripe · FX per day', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Budgets', tag: 'approve', meta: 'archetype: gate · human · budget raise / circuit breaker → approval', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Revenue', tag: 'operate', meta: 'archetype: ledger · subscriptions · rev-rec · free users', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Report', tag: 'learn', meta: 'archetype: brief · EÜR · forecast, per period', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Billing', tag: 'frame', meta: 'archetype: cards · plan · seats · invoices for this account', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        'Invoices': cards('Invoices · deliver · board', 'issued → paid → overdue (unpaid → Inbox)', [\n          { title: 'From v1', tag: 'folds', meta: 'Finances › Invoices · Invoice detail · Gutschriften · Clients', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /finance/ledger is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Costs': cards('Costs · operate · ledger', 'LLM · GCP · Stripe · FX per day', [\n          { title: 'From v1', tag: 'folds', meta: 'Costs › Ledger · LLM · GCP · Stripe · FX · AP inbox · Sources', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /finance/ledger is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Budgets': cards('Budgets · approve · gate · human', 'budget raise / circuit breaker → approval', [\n          { title: 'From v1', tag: 'folds', meta: 'Costs › Budgets · Ads › Kill-switch', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /finance/ledger is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Revenue': cards('Revenue · operate · ledger', 'subscriptions · rev-rec · free users', [\n          { title: 'From v1', tag: 'folds', meta: 'Costs › Rev-rec · Subscriptions · Free users · Finances › Subscriptions', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /finance/ledger is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Report': cards('Report · learn · brief', 'EÜR · forecast, per period', [\n          { title: 'From v1', tag: 'folds', meta: 'Finances › EÜR report · Costs › Forecast', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /finance/ledger is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Billing': cards('Billing · frame · cards', 'plan · seats · invoices for this account', [\n          { title: 'From v1', tag: 'folds', meta: 'Billing · Settings › Billing', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until /finance/ledger is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ])\n      },\n      estate2: {\n        Overview: cards('estate · one template', 'Every tab below is one node with one archetype. \"From v1\" names the tabs it folds; v1 stays on the rail until reassessment (D180).', [\n          { title: 'Source', tag: 'frame', meta: 'archetype: cards · pages · decks · videos · VO as records', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Tokens', tag: 'define', meta: 'archetype: brief · the --status-* and brand tokens in effect, read off the shell root', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Build', tag: 'build', meta: 'archetype: cards · template × tokens → surface (Websites verticals, T18 decks, T19 brand)', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Review', tag: 'review', meta: 'archetype: diff · surface vs token source · mirrors drift', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Publish', tag: 'approve', meta: 'archetype: gate · human · PSI gate → live', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Live', tag: 'operate', meta: 'archetype: frames · every domain front in DOMAIN_SITES, live', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        'Source': cards('Source · frame · cards', 'pages · decks · videos · VO as records', [\n          { title: 'From v1', tag: 'folds', meta: 'Domains › Overview · Pitches › Deck detail · Video › Reels · VO Studio › Reels', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until an endpoint is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Tokens': cards('Tokens · define · brief', 'brand tokens + copy pack per tenant/vertical', [\n          { title: 'From v1', tag: 'folds', meta: 'Design › Tokens · Brand book · Voice + Tone · Palette · Logos', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until an endpoint is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Build': cards('Build · build · cards', 'template × tokens → surface (Websites verticals, T18 decks, T19 brand)', [\n          { title: 'From v1', tag: 'folds', meta: 'Websites · Design › Templates · Building Blocks · Pitches › Compose', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until an endpoint is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Review': cards('Review · review · diff', 'surface vs token source · mirrors drift', [\n          { title: 'From v1', tag: 'folds', meta: 'Design › Mirrors · Studio · VO Studio › Drift · Video › Alignment', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until an endpoint is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Publish': cards('Publish · approve · gate · human', 'PSI gate → live', [\n          { title: 'From v1', tag: 'folds', meta: 'Design › PSI Gate · Pitches › Send & track', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'stub', meta: 'no real record bound yet — renders modeled until an endpoint is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        // D203 (NIGHT-PLAN P2e): Live = frames of every domain in DOMAIN_SITES (the registry the app switcher and the\n        // Websites › AaaS estate tab already read) — count and list derive from the registry, never typed.\n        'Live': (() => { const spec = { view: 'frames', title: 'Live · the estate · every domain front', note: '', frames: [] };\n          Object.defineProperty(spec, 'frames', { enumerable: true, get: () => (this.DOMAIN_SITES || []).map((d, i) => ({ id: 'L' + (i + 1), label: d.tld, meta: d.label + ' · ' + d.pages.length + (d.pages.length === 1 ? ' page' : ' pages') + ' · front', w: 1440, h: 2200, src: '../' + encodeURI(String(d.pages[0].file).replace(/^assets\\//, '')) })) });\n          Object.defineProperty(spec, 'note', { enumerable: true, get: () => { const D = this.DOMAIN_SITES || [], n = D.reduce((a, d) => a + d.pages.length, 0); return D.length + ' domains · ' + n + ' pages in DOMAIN_SITES · the front of each, live from assets/domains (project mode; framed pages do not load offline) · same registry as the app switcher and Websites › AaaS estate'; } });\n          return spec; })()\n      },\n      account2: {\n        Overview: cards('account · one template', 'Every tab below is one node with one archetype. \"From v1\" names the tabs it folds; v1 stays on the rail until reassessment (D180).', [\n          { title: 'Identity', tag: 'frame', meta: 'archetype: brief · account + company blocks (ctx.account.*, ctx.company.*)', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Plan', tag: 'operate', meta: 'archetype: ledger · tier · seats · limits · invoices', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'People', tag: 'define', meta: 'archetype: cards · members · roles · activity', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Agents', tag: 'define', meta: 'archetype: cards · the registry: persona · address · owner (one cast) · Org chart (swimlanes) and Profile (brief) are views of this node', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Governance \\u00b7 Connectors \\u00b7 Secrets', tag: 'define', meta: 'brief + cards \\u00b7 write rights (live) \\u00b7 services and who uses them \\u00b7 secret references; policy is modeled', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Activity \\u00b7 Runs \\u00b7 Timeline', tag: 'operate', meta: 'thread + cards + swimlanes \\u00b7 the one Inbox projection as an audit trail; costs live in Money', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Tasks \\u00b7 Routines \\u00b7 Artifacts', tag: 'operate', meta: 'board + cards \\u00b7 tasks are Inbox threads with ids and a status \\u00b7 routines are the Automations list \\u00b7 artifacts by type', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Export \\u00b7 Adapters', tag: 'operate', meta: 'cards \\u00b7 agent roster as a JSON download (names only for secrets) \\u00b7 adapters in use \\u00b7 Import not built', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Projects', tag: 'build', meta: 'archetype: board · work · runs · credits per project', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Start', tag: 'frame', meta: 'archetype: process · connect → contextualise → automate; first run', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        'Identity': cards('Identity · frame · brief', 'account + company blocks (ctx.account.*, ctx.company.*)', [\n          { title: 'From v1', tag: 'folds', meta: 'Settings › Settings · Home › Overview', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /account is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Plan': cards('Plan · operate · ledger', 'tier · seats · limits · invoices', [\n          { title: 'From v1', tag: 'folds', meta: 'Billing › Plan · Invoices · Settings › Billing', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /account is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'People': cards('People · define · cards', 'members · roles · activity', [\n          { title: 'From v1', tag: 'folds', meta: 'Team › Team · Roles · Activity', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /account is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Agents': cards('Agents · define · cards', 'the registry: persona · address · owner (one cast)', [\n          { title: 'From v1', tag: 'folds', meta: 'Agents › All · Favourites · Connected · roster', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /account is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Projects': cards('Projects · build · board', 'work · runs · credits per project', [\n          { title: 'From v1', tag: 'folds', meta: 'Projects › Work · Runs · Library · Credits', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /account is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Start': cards('Start · frame · process', 'connect → contextualise → automate; first run', [\n          { title: 'From v1', tag: 'folds', meta: 'Onboarding › Walkthrough · Introduction › Landing · Connect · Contextualize · Automate · First run', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /account is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ])\n      },\n      foundry2: {\n        Overview: cards('foundry · one template', 'Every tab below is one node with one archetype. \"From v1\" names the tabs it folds; v1 stays on the rail until reassessment (D180).', [\n          { title: 'Compose', tag: 'build', meta: 'archetype: swimlanes · skills + tools → agent (the Process Tree as the build view)', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Score', tag: 'review', meta: 'archetype: diff · gold / silver / bronze before an agent may load it', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Publish', tag: 'approve', meta: 'archetype: gate · human · publish to marketplace · community', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Install', tag: 'deliver', meta: 'archetype: cards · skills · plugins · MCP · workflows installed per account', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Docs', tag: 'operate', meta: 'archetype: cards · the toolbox: 266 templates · Studio', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        'Compose': cards('Compose · build · swimlanes', 'skills + tools → agent (the Process Tree as the build view)', [\n          { title: 'From v1', tag: 'folds', meta: 'Foundry › Roster · Process Tree › Whiteboard', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /foundry/queue is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Score': cards('Score · review · diff', 'gold / silver / bronze before an agent may load it', [\n          { title: 'From v1', tag: 'folds', meta: 'Library › Skills · Foundry › Queue', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /foundry/queue is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Publish': cards('Publish · approve · gate · human', 'publish to marketplace · community', [\n          { title: 'From v1', tag: 'folds', meta: 'Templates › Gallery · Blueprints', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /foundry/queue is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Install': cards('Install · deliver · cards', 'skills · plugins · MCP · workflows installed per account', [\n          { title: 'From v1', tag: 'folds', meta: 'Library › All · Plugins · MCP · Templates › Automations · Workflows', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /foundry/queue is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ]),\n        'Docs': cards('Docs · operate · cards', 'the toolbox: 266 templates · Studio', [\n          { title: 'From v1', tag: 'folds', meta: 'Toolbox › Overview · Studio', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Instance', tag: 'planned', meta: 'no real record bound yet — renders modeled until /foundry/queue is declared live', tagColor: 'rgb(var(--pastel-gold) / 0.6)' }\n        ])\n      },\n      // ── D193 · Design · v2 — the template library (operator ruling: Design = reusable templates; edits save into Content) ──\n      design2: (() => {\n        const lead = 'rgb(var(--lead) / 0.5)';\n        const AI = () => this.ASSET_INDEX || {};\n        const spec = {\n          Overview: cards('design-library · one template', 'Library (T1–T20, live) → Edit (slots + brand) → Save to Content (instance lands on the Content · v2 shelf). Gallery shows what was produced before.', [\n            { title: 'Library', tag: 'frame', meta: 'archetype: cards · frames · 20 generator templates by surface', tagColor: lead },\n            { title: 'Gallery', tag: 'frame', meta: 'archetype: cards · frames · 257 produced instances (assets/index.json)', tagColor: lead },\n            { title: 'Edit', tag: 'build', meta: 'archetype: brief · slot values + brand tokens for the picked template', tagColor: lead },\n            { title: 'Save to Content', tag: 'deliver', meta: 'archetype: gate · human · writes the instance to content2 › Shelf', tagColor: lead }\n          ]),\n          Library: { view: 'frames', segs: [{ id: 'email', label: 'Email' }, { id: 'social', label: 'Social' }, { id: 'deck', label: 'Deck' }, { id: 'brand', label: 'Brand' }], title: 'Library · T1–T20, live', note: 'Every template rendered in place with the active brand tokens. Pick one → Edit.', frames: [] },\n          Gallery: { view: 'frames', segs: [{ id: 'email', label: 'Email' }, { id: 'social', label: 'Social' }], title: 'Gallery · produced instances', note: 'What the templates already produced. Read from assets/index.json.', frames: [] }\n        };\n        Object.defineProperty(spec.Library, 'frames', { enumerable: true, get: () => (this.tmplFrames ? this.tmplFrames('generic') : []).map(f => Object.assign({}, f, { grp: /T1 |T2 |T3 /.test(f.label) ? 'email' : /T18/.test(f.label) ? 'deck' : /T19|T20/.test(f.label) ? 'brand' : 'social' })) });\n        Object.defineProperty(spec.Gallery, 'frames', { enumerable: true, get: () => (AI().gallery || []).filter(g => g.class === 'emails').slice(0, 24).concat((AI().gallery || []).filter(g => g.class === 'social').slice(0, 24)).map((g, i) => ({ id: 'D' + i, grp: g.class === 'emails' ? 'email' : 'social', label: g.id, meta: g.class + ' · gallery', w: g.class === 'emails' ? 600 : 1080, h: g.class === 'emails' ? 1400 : 1080, src: g.src.replace(/^gallery\\//, '') })) });\n        return spec;\n      })(),\n      // ── D180 · Podcast · v2 (daily-brief template) ──\n      podcast2: (() => {\n        const lead = 'rgb(var(--lead) / 0.5)';\n        const N = [\n          ['Sources', 'frame', 'cards', '3 feeds → 42 items', 'Done · 0:04'],\n          ['Prompts', 'define', 'brief', 'v12 → 4 prompts', 'Done · 0:01'],\n          ['Generate', 'build', 'process', '42 items → draft', 'Running · 3:37'],\n          ['Review', 'review', 'diff', 'draft vs v(n−1) + audio', 'Pending'],\n          ['Publish', 'approve', 'gate · human', 'Protocol self-audit → published', 'Pending'],\n          ['Trigger', 'operate', 'calendar', '06:00 daily + manual', 'Armed'],\n          ['Alerts', 'learn', 'thread', 'failures → Inbox failed_runs', '1 open'],\n          ['Episodes', 'operate', 'cards', 'published shelf', '204 published']\n        ];\n        const c = (t, tag, meta) => ({ title: t, tag, meta, tagColor: lead });\n        return {\n          Overview: cards('daily-brief · instance ep-204', 'Eight nodes, one archetype each. Runs are instances of this template — pick one here instead of a Runs tab.', N.map(n => c(n[0], n[1], n[3] + ' · ' + n[4] + ' · archetype: ' + n[2]))),\n          Sources: cards('Sources · frame', 'Feeds and their liveness', [c('ai-news RSS', 'live', '18 items · synced 06:00'), c('arXiv cs.AI', 'live', '12 items'), c('HN front', 'live', '9 items'), c('Newsletter digest', 'live', '3 items'), c('Vendor blogs', 'stale', '0 items · last 3d ago')]),\n          Prompts: cards('Prompts · define (brief)', 'Prompt set v12 · four sections · reads ctx.company.voice.*', [c('Select', 'v12', 'rank 42 → 8 items'), c('Script', 'v12', 'tone from Protocol'), c('Show notes', 'v12', 'links + timestamps'), c('Title + cover', 'v12', 'T11 thumbnail template')]),\n          Generate: cards('Generate · build (process)', 'Run log for ep-204', [c('rank items', 'done', '0:12'), c('draft script', 'done', '2:01'), c('TTS · 2 voices', 'running', '1:24'), c('mix + master', 'todo', '—')]),\n          Review: cards('Review · diff', 'Script v204 vs v203, audio check', [c('Script diff', 'pending', 'awaiting draft'), c('Audio QA', 'pending', 'awaiting mix')]),\n          Publish: cards('Publish · human gate', 'validity → authority → liveness. Never auto — the Protocol self-audit is the checklist.', [c('ep-204', 'pending', 'gate opens when Review is done'), c('Syndication', 'postiz-agt', 'X · LinkedIn · RSS — rate-limited, retry 4m')]),\n          Trigger: cards('Trigger · calendar', '06:00 daily · manual run', [c('Daily 06:00', 'armed', 'next in 20h 18m'), c('Manual', 'idle', 'Run now → new instance')]),\n          Alerts: cards('Alerts · thread', 'Failures raise Inbox threads (failed_runs) — no separate list here.', [c('Postiz rate-limited on X', 'stuck', 'thread in Inbox › In progress · retry 4m')]),\n          Episodes: cards('Episodes · shelf', 'Published daily briefs', [c('ep-203', 'published', 'yesterday 06:06 · 11:40'), c('ep-202', 'published', '11:12'), c('ep-201', 'published', '12:03')])\n        };\n      })(),\n      agentRuns: {\n        Board: { view: 'board', title: 'Run board', note: 'Every agent run by pipeline state · click a card to steer it',\n          columns: [\n            { label: 'Running', state: 'run', items: byStatus('running') },\n            { label: 'Blocked', state: 'warn', items: byStatus('blocked') },\n            { label: 'Awaiting approval', state: 'warn', items: byStatus('awaiting approval') },\n            { label: 'Done', state: 'ok', items: byStatus('done') },\n            { label: 'Failed', state: 'fail', items: byStatus('failed') }\n          ] },\n        Running: table('Running', 'Live runs and their current pipeline node', RUN_COLS, byStatus('running').map(runRow)),\n        Blocked: table('Blocked', 'Waiting on a human decision or a missing grant', RUN_COLS, byStatus('blocked').map(runRow)),\n        'Awaiting approval': table('Awaiting approval', 'Work is finished — approve to let it ship', RUN_COLS, byStatus('awaiting approval').map(runRow)),\n        Done: table('Done', 'Merged or delivered in the last 24 hours', RUN_COLS, byStatus('done').map(runRow)),\n        Failed: table('Failed', 'Failed runs keep their worktree and replay', RUN_COLS, byStatus('failed').map(runRow)),\n        Replays: list('Replays', 'Stored run recordings — step through what the agent actually did',\n          this.AW_RUNS.slice(0, 4).map(r => ({ title: r.title, meta: r.id + ' · ' + r.stage + ' · ' + r.elapsed + ' · ' + r.cost, chip: 'replay', color: dcol(r.status) })))\n      },\n      workspace: {\n        Panels: cards('Open panels', '3 live sessions · 1 streaming · 2 awaiting your input', [\n          { title: 'claude-code', tag: 'streaming', meta: 'context-loop refactor · 14 msgs · $0.62', tagColor: 'rgb(var(--circuit-glow) / 0.5)' },\n          { title: 'codex', tag: 'awaiting input', meta: 'outbox schema · needs migration approval · $1.14', tagColor: 'rgb(var(--accent-red) / 0.5)' },\n          { title: 'gemini', tag: 'awaiting input', meta: 'docstring pass · confirm PR title · $0.28', tagColor: 'rgb(var(--accent-red) / 0.5)' }\n        ]),\n        Tools: table('Tools in this session', 'What each panel is allowed to call',\n          [{ label: 'Tool', flex: 2, align: 'left' }, { label: 'Panel', flex: 1.4, align: 'left' }, { label: 'Calls', flex: 0.8, align: 'left' }, { label: 'Scope', flex: 1.6, align: 'left' }],\n          [\n            { cells: [tCell('bash', { flex: 2 }), mono('claude-code', { flex: 1.4 }), mono('41', { flex: 0.8 }), mono('worktree only', { flex: 1.6 })] },\n            { cells: [tCell('edit', { flex: 2 }), mono('claude-code', { flex: 1.4 }), mono('18', { flex: 0.8 }), mono('4 files', { flex: 1.6 })] },\n            { cells: [tCell('postgres-mcp', { flex: 2 }), mono('codex', { flex: 1.4 }), mono('6', { flex: 0.8 }), mono('read-only', { flex: 1.6 })] },\n            { cells: [tCell('gh-review', { flex: 2 }), mono('gemini', { flex: 1.4 }), mono('2', { flex: 0.8 }), mono('open PR', { flex: 1.6 })] }\n          ]),\n        Files: table('Files touched', 'Dirty files across all open panels',\n          [{ label: 'File', flex: 3, align: 'left' }, { label: 'Panel', flex: 1.2, align: 'left' }, { label: '+', flex: 0.5, align: 'right' }, { label: '−', flex: 0.5, align: 'right' }],\n          this.AW_DIFF.map(f => ({ cells: [mono(f.file, { flex: 3, color: 'var(--app-ink)' }), mono('claude-code', { flex: 1.2 }), mono(f.add, { flex: 0.5, align: 'right', color: 'var(--app-success)' }), mono(f.del, { flex: 0.5, align: 'right', color: 'var(--app-fail)' })] }))),\n        Transcript: tail('Transcript', 'Merged stream across panels', 'aaas://workspace/transcript', [\n          { time: '06:28', prefix: '[cc]', msg: 'Read apps/engine/src/context/loop.ts (412 lines)', color: 'var(--app-accent)' },\n          { time: '06:29', prefix: '[cc]', msg: 'Extracted loop driver into runLoop() — 3 call sites updated', color: 'var(--app-accent)' },\n          { time: '06:31', prefix: '[cdx]', msg: 'Migration plan ready · awaiting approval to execute', color: 'var(--app-fail)' },\n          { time: '06:32', prefix: '[gem]', msg: 'Docstrings written for 22 exported symbols', color: 'var(--app-warn)' },\n          { time: '06:33', prefix: '[sys]', msg: 'Session cost $1.84 · opus 28% of tokens', color: 'var(--app-ink-faint)' }\n        ])\n      },\n      automations: {\n        All: table('All automations', '8 active · 2 paused · 31 fires in the last 24h',\n          [{ label: 'Automation', flex: 2.4, align: 'left' }, { label: 'Trigger', flex: 1.8, align: 'left' }, { label: 'State', flex: 1, align: 'left' }, { label: 'Backend', flex: 1.2, align: 'left' }, { label: 'Last fire', flex: 1, align: 'right' }],\n          this.AW_AUTOMATIONS.map(a => ({ cells: [tCell(a.title, { flex: 2.4 }), mono(a.trigger, { flex: 1.8 }), mono(a.stateLabel, { flex: 1, color: dcol(a.state) }), mono(a.backend, { flex: 1.2 }), mono(a.last, { flex: 1, align: 'right' })] }))),\n        Triggers: cards('Trigger types', 'What can start an agent run', [\n          { title: 'cron', tag: '3 rules', meta: 'Wall-clock schedules with a jitter window and a run budget.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'github', tag: '2 rules', meta: 'pull_request, push, issue_comment — signature verified.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'slack', tag: '1 rule', meta: 'Slash command with per-channel allow-list.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'webhook', tag: '2 rules', meta: 'Signed inbound events, replayable from the DLQ.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'poll', tag: '2 rules', meta: 'Interval polling with an ETag cache.', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        History: tail('Fire history', 'Last 24 hours', 'aaas://automations/history', [\n          { time: '03:00', prefix: '[cron]', msg: 'Nightly context reindex → r-372 (ok, 18m)', color: 'var(--app-accent)' },\n          { time: '05:41', prefix: '[gh]', msg: 'PR #401 opened → review run r-349 (ok)', color: 'var(--app-accent)' },\n          { time: '06:02', prefix: '[poll]', msg: 'Upstream release feed → 502 from source, retry 2/3', color: 'var(--app-fail)' },\n          { time: '06:12', prefix: '[slack]', msg: '/run outbox-retry by @jorian → r-368 (running)', color: 'var(--app-accent)' },\n          { time: '06:19', prefix: '[hook]', msg: 'Stripe invoice.paid → skipped, automation paused', color: 'var(--app-warn)' }\n        ]),\n        Audit: list('Audit trail', 'Who changed what, and what it cost', [\n          { title: 'Paused “Stripe webhook → ledger post”', meta: 'jorian · 3d ago · reason: ledger schema migration', chip: 'change', color: 'var(--app-warn)' },\n          { title: 'Raised nightly reindex budget to $2.00/run', meta: 'jorian · 5d ago · prev $1.20', chip: 'budget', color: 'var(--app-accent)' },\n          { title: 'Added GitHub trigger on pull_request', meta: 'tobias · 8d ago · repo aaas/canvas', chip: 'change', color: 'var(--app-accent)' }\n        ])\n      },\n      review: {\n        Diff: table('Diff · r-372', '23 files changed · +311 −88 · worktree feat/372-context-loop',\n          [{ label: 'File', flex: 3, align: 'left' }, { label: 'Note', flex: 2, align: 'left' }, { label: '+', flex: 0.5, align: 'right' }, { label: '−', flex: 0.5, align: 'right' }],\n          this.AW_DIFF.map(f => ({ cells: [mono(f.file, { flex: 3, color: 'var(--app-ink)' }), tCell(f.note, { flex: 2, color: 'var(--app-ink-soft)' }), mono(f.add, { flex: 0.5, align: 'right', color: 'var(--app-success)' }), mono(f.del, { flex: 0.5, align: 'right', color: 'var(--app-fail)' })] }))),\n        Checks: list('Checks', '6 of 7 green · 1 still running', [\n          { title: 'unit · engine', meta: '412 tests · 38s', chip: 'pass', color: 'var(--app-success)' },\n          { title: 'unit · shared', meta: '96 tests · 11s', chip: 'pass', color: 'var(--app-success)' },\n          { title: 'lint · oxlint', meta: '0 errors · 3 warnings', chip: 'pass', color: 'var(--app-success)' },\n          { title: 'types · tsc', meta: 'no errors', chip: 'pass', color: 'var(--app-success)' },\n          { title: 'e2e · playwright', meta: '14 of 18 specs · running 2m 40s', chip: 'running', color: 'var(--app-running)' },\n          { title: 'cost · per-run budget', meta: '$0.62 of $1.50', chip: 'pass', color: 'var(--app-success)' }\n        ]),\n        Gates: list('Quality gates', '2 approve · 1 pending — every gate has a verdict and a reason', [\n          { title: 'Coverage gate', meta: 'ratchet 82% → 84% · verdict: approve', chip: 'approve', color: 'var(--app-success)' },\n          { title: 'Public API gate', meta: 'RunStage union widened · verdict: approve with note', chip: 'approve', color: 'var(--app-success)' },\n          { title: 'Migration gate', meta: 'no schema change in this diff · verdict: pending human', chip: 'pending', color: 'var(--app-warn)' }\n        ]),\n        PR: list('Pull request', 'PR #402 · draft until the last check goes green', [\n          { title: 'feat(engine): extract context-loop driver', meta: 'canvas#372 · 5 commits · 2 reviewers requested', chip: 'draft', color: 'var(--app-warn)' },\n          { title: 'Reviewer: tobias', meta: 'requested 12m ago · no comments yet', chip: 'waiting', color: 'var(--app-ink-faint)' },\n          { title: 'Merge strategy', meta: 'squash · deletes worktree on merge', chip: 'squash', color: 'var(--app-accent)' }\n        ])\n      },\n      backends: {\n        Backends: table('Execution backends', 'Where a run executes — 3 registered, all reachable',\n          [{ label: 'Backend', flex: 2, align: 'left' }, { label: 'Type', flex: 1, align: 'left' }, { label: 'Reachability', flex: 1.2, align: 'left' }, { label: 'Capacity', flex: 1.2, align: 'left' }, { label: 'Default for', flex: 1.6, align: 'left' }],\n          this.AW_BACKENDS.map(b => ({ cells: [tCell(b.title, { flex: 2 }), mono(b.type, { flex: 1 }), mono(b.stateLabel, { flex: 1.2, color: dcol(b.state) }), mono(b.capacity, { flex: 1.2 }), tCell(b.use, { flex: 1.6, color: 'var(--app-ink-soft)' })] }))),\n        Capacity: cards('Capacity', 'Concurrency and cost per backend', this.AW_BACKENDS.map(b => ({ title: b.title, tag: b.capacity, meta: b.note + ' · default for ' + b.use, tagColor: 'rgb(var(--lead) / 0.5)' }))),\n        Migration: list('Mid-run migration', 'Switching backend mid-run is a migration — this is what survives', [\n          { title: 'Preserved', meta: 'worktree contents · run transcript · attached skills · cost ledger', chip: 'kept', color: 'var(--app-success)' },\n          { title: 'Restarted', meta: 'the current pipeline node · any running tool call', chip: 'restart', color: 'var(--app-warn)' },\n          { title: 'Lost', meta: 'local-only secrets and anything outside the worktree', chip: 'lost', color: 'var(--app-fail)' }\n        ])\n      },\n      observability: {\n        'Event tail': tail('Event tail', '214 events/min across 7 active runs', 'aaas://observability/events', [\n          { time: '06:33', prefix: '[run]', msg: 'r-372 node CODE → tool edit (loop.ts)', color: 'var(--app-accent)' },\n          { time: '06:33', prefix: '[llm]', msg: 'sonnet 4.6 · 3,180 in / 940 out · $0.04', color: 'var(--app-accent)' },\n          { time: '06:32', prefix: '[gate]', msg: 'r-360 blocked at TEST — approval required', color: 'var(--app-fail)' },\n          { time: '06:32', prefix: '[run]', msg: 'r-368 spawned on build-vm-01 · worktree ready', color: 'var(--app-accent)' },\n          { time: '06:31', prefix: '[mcp]', msg: 'postgres-mcp query 240ms · 1 row', color: 'var(--app-warn)' },\n          { time: '06:30', prefix: '[cost]', msg: 'day total $14.20 · 28% opus', color: 'var(--app-ink-faint)' },\n          { time: '06:29', prefix: '[run]', msg: 'r-349 DELIVER · PR #401 merged', color: 'var(--app-success)' }\n        ]),\n        Cost: table('Cost', 'Today · $14.20 across runs, tiers and backends',\n          [{ label: 'Bucket', flex: 2, align: 'left' }, { label: 'Runs', flex: 0.8, align: 'left' }, { label: 'Tokens', flex: 1.2, align: 'left' }, { label: 'Spend', flex: 1, align: 'right' }],\n          [\n            { cells: [tCell('opus', { flex: 2 }), mono('3', { flex: 0.8 }), mono('412k', { flex: 1.2 }), mono('$6.10', { flex: 1, align: 'right', color: 'var(--app-fail)' })] },\n            { cells: [tCell('sonnet', { flex: 2 }), mono('11', { flex: 0.8 }), mono('1.9M', { flex: 1.2 }), mono('$7.24', { flex: 1, align: 'right' })] },\n            { cells: [tCell('embeddings', { flex: 2 }), mono('—', { flex: 0.8 }), mono('3.1M', { flex: 1.2 }), mono('$0.62', { flex: 1, align: 'right' })] },\n            { cells: [tCell('Cloud Run compute', { flex: 2 }), mono('6', { flex: 0.8 }), mono('—', { flex: 1.2 }), mono('$0.24', { flex: 1, align: 'right', color: 'var(--app-success)' })] }\n          ]),\n        Replay: list('Replay', '312 stored replays · step through any run frame by frame', [\n          { title: 'r-341 · MCP handshake fix', meta: 'failed at TEST · 2 failures · 14 frames', chip: 'replay', color: 'var(--app-fail)' },\n          { title: 'r-349 · rate-limit backoff', meta: 'done · PR #401 · 41 frames', chip: 'replay', color: 'var(--app-success)' },\n          { title: 'r-360 · outbox schema', meta: 'blocked at TEST · 28 frames', chip: 'replay', color: 'var(--app-warn)' }\n        ])\n      },\n      // ── ARCHIVED 2026-09-23 (D151) · unreachable since WP-06 ownsTabs (keys match no Inbox folder). Its subject —\n      //    open loops — lives in Workspace › Operator queue (K.operatorInbox). Archive, don't delete. ──\n      inbox: {\n        All: list('Open loops', '11 open · 4 blocking a run · oldest 2d', inboxItems(null)),\n        Blocking: list('Blocking a run', 'Clear these first — an agent is idle until you do', inboxItems('blocking')),\n        Permissions: list('Permission gates', 'Secret grants and tool allow-list changes', inboxItems('permission')),\n        Billing: list('Billing', 'Spend approvals that need a human', inboxItems('billing'))\n      },\n      library: {\n        All: cards('Everything', '5,272 skills · 315 plugins · 36 MCP servers · 12 enabled this session', this.AW_LIBRARY.map(catCard)),\n        Skills: cards('Skills', 'Structured capability packs an agent can attach at spawn', this.AW_LIBRARY.filter(c => c.kind === 'Skills').map(catCard)),\n        Plugins: cards('Plugins', 'Shell extensions that add commands and views', this.AW_LIBRARY.filter(c => c.kind === 'Plugins').map(catCard)),\n        MCP: cards('MCP servers', 'Tool servers speaking the Model Context Protocol', this.AW_LIBRARY.filter(c => c.kind === 'MCP').map(catCard))\n      },\n      templates: {\n        Gallery: cards('Template gallery', 'Start from a working shape — every template is a real run', this.AW_TEMPLATES.map(catCard)),\n        Automations: cards('Automation templates', 'Pre-wired triggers with budgets and backends set', this.AW_TEMPLATES.filter(t => t.kind === 'Automations').map(catCard)),\n        Blueprints: cards('Agent blueprints', 'Named agents with skills, tools and guardrails attached', this.AW_TEMPLATES.filter(t => t.kind === 'Blueprints').map(catCard)),\n        Workflows: cards('Workflows', 'Multi-node pipelines with approval gates', this.AW_TEMPLATES.filter(t => t.kind === 'Workflows').map(catCard))\n      }\n    };\n\n\n    // ══ Panes for the remaining role streams (Marketing · Sales · Finance · Admin) ══\n    const C = (label, flex, align) => ({ label, flex: flex || 1, align: align || 'left' });\n    const row = (cells) => ({ cells });\n    const ROLE_PANES = {\n      podcast: {\n        Overview: list('Pipeline', 'The daily brief builds itself — you approve and publish', [\n          { title: 'Ingest · 42 sources', meta: 'last sweep 06:05 · 128 items scored', chip: 'ok', color: 'var(--app-success)' },\n          { title: 'Script · gen-agt', meta: 'episode 129 drafted · 3.2s build', chip: 'ready', color: 'var(--app-accent)' },\n          { title: 'Voice · tts', meta: '5m 12s rendered · €0.14', chip: 'ok', color: 'var(--app-success)' },\n          { title: 'Publish · aaas.today', meta: 'awaiting your approval', chip: 'you', color: 'var(--app-warn)' }\n        ]),\n        Runs: table('Runs', 'Last 5 pipeline runs', [C('Run', 1), C('Stage', 1.4), C('Build', 0.8), C('Cost', 0.8, 'right'), C('State', 1)], [\n          row([mono('#1291'), mono('publish'), mono('3.2s'), mono('€0.14', { align: 'right' }), mono('ready', { color: 'var(--app-warn)' })]),\n          row([mono('#1290'), mono('published'), mono('3.0s'), mono('€0.12', { align: 'right' }), mono('ok', { color: 'var(--app-success)' })]),\n          row([mono('#1289'), mono('published'), mono('3.4s'), mono('€0.15', { align: 'right' }), mono('ok', { color: 'var(--app-success)' })]),\n          row([mono('#1288'), mono('voice'), mono('—'), mono('€0.02', { align: 'right' }), mono('failed', { color: 'var(--app-fail)' })]),\n          row([mono('#1287'), mono('published'), mono('3.1s'), mono('€0.13', { align: 'right' }), mono('ok', { color: 'var(--app-success)' })])\n        ]),\n        Episodes: table('Episodes', '128 published · 3 this week', [C('Episode', 3), C('Length', 0.8), C('Listens', 0.8), C('Published', 1.2, 'right')], [\n          row([tCell('129 · Context packages ship everywhere', { flex: 3 }), mono('5:12', { flex: 0.8 }), mono('—', { flex: 0.8 }), mono('draft', { flex: 1.2, align: 'right', color: 'var(--app-warn)' })]),\n          row([tCell('128 · Relay handoff goes GA', { flex: 3 }), mono('4:58', { flex: 0.8 }), mono('412', { flex: 0.8 }), mono('05-27', { flex: 1.2, align: 'right' })]),\n          row([tCell('127 · The cost of an idle agent', { flex: 3 }), mono('5:31', { flex: 0.8 }), mono('389', { flex: 0.8 }), mono('05-26', { flex: 1.2, align: 'right' })])\n        ]),\n        Sources: cards('Sources', '42 feeds scored daily — top signal wins the slot', [\n          { title: 'arxiv · cs.AI', tag: '14 items', meta: 'Daily crawl, abstract-only scoring.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'hn · front page', tag: '9 items', meta: 'Score threshold 150 points.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'vendor changelogs', tag: '11 items', meta: 'Anthropic · OpenAI · Google · Mistral.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'aaas.blog', tag: '8 items', meta: 'Own posts, deduped against episodes.', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        Replays: list('Replays', 'Step through any build', [\n          { title: '#1288 · voice render failed', meta: 'tts quota exceeded · 9 frames', chip: 'replay', color: 'var(--app-fail)' },\n          { title: '#1290 · clean publish', meta: '22 frames · €0.12', chip: 'replay', color: 'var(--app-success)' }\n        ])\n      },\n      design: {\n        Overview: cards('Token groups', '214 tokens · 11 components · 98.7% coverage', [\n          { title: 'colors', tag: '46', meta: 'Basalt surfaces, two brand leads, three supportive.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'type', tag: '38', meta: 'Inter display + JetBrains Mono system labels.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'space & radius', tag: '52', meta: '4px base scale · one radius for every surface.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'motion', tag: '18', meta: 'One liquid ease · 200/300/500ms durations.', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        Tokens: table('Tokens', 'Values are the contract — never hardcode', [C('Token', 2), C('Value', 2), C('Group', 1), C('Used', 0.8, 'right')], [\n          row([mono('--circuit-glow', { flex: 2, color: 'var(--app-accent)' }), mono('0 243 255', { flex: 2 }), mono('colors', { flex: 1 }), mono('214×', { flex: 0.8, align: 'right' })]),\n          row([mono('--accent-red', { flex: 2, color: 'var(--app-fail)' }), mono('244 63 108', { flex: 2 }), mono('colors', { flex: 1 }), mono('96×', { flex: 0.8, align: 'right' })]),\n          row([mono('--radius', { flex: 2 }), mono('0.75rem', { flex: 2 }), mono('radius', { flex: 1 }), mono('488×', { flex: 0.8, align: 'right' })]),\n          row([mono('--liquid-ease', { flex: 2 }), mono('cubic-bezier(.23,1,.32,1)', { flex: 2 }), mono('motion', { flex: 1 }), mono('301×', { flex: 0.8, align: 'right' })])\n        ]),\n        Components: table('Components', '11 shipped · 1 added this week · 6 in review', [C('Component', 2), C('Variants', 1), C('State', 1), C('Owner', 1, 'right')], [\n          row([tCell('Button', { flex: 2 }), mono('4 · 3 sizes', { flex: 1 }), mono('shipped', { flex: 1, color: 'var(--app-success)' }), mono('design', { flex: 1, align: 'right' })]),\n          row([tCell('Card', { flex: 2 }), mono('3 · spotlight', { flex: 1 }), mono('shipped', { flex: 1, color: 'var(--app-success)' }), mono('design', { flex: 1, align: 'right' })]),\n          row([tCell('MetricCell', { flex: 2 }), mono('3 accents', { flex: 1 }), mono('shipped', { flex: 1, color: 'var(--app-success)' }), mono('design', { flex: 1, align: 'right' })]),\n          row([tCell('Terminal', { flex: 2 }), mono('live · cursor', { flex: 1 }), mono('in review', { flex: 1, color: 'var(--app-warn)' }), mono('platform', { flex: 1, align: 'right' })])\n        ]),\n        Mirrors: list('Mirrors', 'Where the system is consumed', [\n          { title: 'Canvas shell', meta: 'desktop + mobile · 2 templates', chip: 'in sync', color: 'var(--app-success)' },\n          { title: 'Marketing site', meta: 'ui_kits/platform · 6 screens', chip: 'in sync', color: 'var(--app-success)' },\n          { title: 'Email slots', meta: '5 templates · tokens.json bound', chip: '1 drift', color: 'var(--app-warn)' }\n        ]),\n        Replays: list('Replays', 'Token and component changes', [\n          { title: 'Added --radius-btn', meta: 'buttons 0.6rem · 4 files touched', chip: 'replay', color: 'var(--app-accent)' },\n          { title: 'Terminal component added', meta: 'prefix colour map locked', chip: 'replay', color: 'var(--app-accent)' }\n        ])\n      },\n      select: {\n        Overview: list('Decision core', 'Run #4827 · every step keeps its alternates', [\n          { title: 'Intake · normalise invoice batch', meta: '1.2s · 3 alternates considered', chip: 'ok', color: 'var(--app-success)' },\n          { title: 'Match · vendor ledger', meta: '4.8s · picked fuzzy+VAT over exact', chip: 'ok', color: 'var(--app-success)' },\n          { title: 'Post · AP reconciliation', meta: '6.4s · €0.042 · 4,180/2,640 tokens', chip: 'ok', color: 'var(--app-success)' },\n          { title: 'Audit · policy check', meta: '98.7% compliance · no exceptions', chip: 'clear', color: 'var(--app-success)' }\n        ]),\n        Runs: table('Runs', 'Last 5 decisions', [C('Run', 1), C('Agent', 2), C('Duration', 1), C('Cost', 1, 'right'), C('State', 1)], [\n          row([mono('#4827', { flex: 1 }), tCell('AP-Reconciliation', { flex: 2 }), mono('12.4s', { flex: 1 }), mono('€0.042', { flex: 1, align: 'right' }), mono('ok', { flex: 1, color: 'var(--app-success)' })]),\n          row([mono('#4826', { flex: 1 }), tCell('Lead-Scoring', { flex: 2 }), mono('8.1s', { flex: 1 }), mono('€0.028', { flex: 1, align: 'right' }), mono('ok', { flex: 1, color: 'var(--app-success)' })]),\n          row([mono('#4825', { flex: 1 }), tCell('Vendor-Match', { flex: 2 }), mono('21.7s', { flex: 1 }), mono('€0.061', { flex: 1, align: 'right' }), mono('warn', { flex: 1, color: 'var(--app-warn)' })]),\n          row([mono('#4824', { flex: 1 }), tCell('AP-Reconciliation', { flex: 2 }), mono('11.9s', { flex: 1 }), mono('€0.040', { flex: 1, align: 'right' }), mono('ok', { flex: 1, color: 'var(--app-success)' })])\n        ]),\n        // 'Run detail' has no lens — it renders natively (kind: 'runtrace'), the dt- decision timeline.\n        Agents: table('Agents', '9 online · skills attached at spawn', [C('Agent', 2), C('Skills', 1), C('Runs 24h', 1), C('Success', 1, 'right')], [\n          row([tCell('ap-agt', { flex: 2 }), mono('12', { flex: 1 }), mono('41', { flex: 1 }), mono('98%', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('match-agt', { flex: 2 }), mono('8', { flex: 1 }), mono('63', { flex: 1 }), mono('96%', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('recon-agt', { flex: 2 }), mono('14', { flex: 1 }), mono('22', { flex: 1 }), mono('91%', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        Replays: list('Replays', 'Stored decision traces', [\n          { title: '#4825 · vendor match warn', meta: 'two candidates within 0.02 · 31 frames', chip: 'replay', color: 'var(--app-warn)' },\n          { title: '#4827 · clean run', meta: '12.4s · 44 frames', chip: 'replay', color: 'var(--app-success)' }\n        ])\n      },\n      // I-3/I-4 (2026-09-05): Overview stays lens (no native `ua` branch handles it) — kept, not\n      // deleted; Services/Runs/Agents/Replays removed, they never matched a real tab label at all.\n      intelligence: {\n        Overview: cards('Services', '5 services · 3 live · 5,400+ skills in the vault', [\n          { title: 'context-engine', tag: 'live', meta: 'Deep research and packaging · p95 3.2s.', tagColor: 'rgb(var(--accent-teal) / 0.5)' },\n          { title: 'skill-vault', tag: 'live', meta: '5,272 skills indexed · 36 MCP servers.', tagColor: 'rgb(var(--accent-teal) / 0.5)' },\n          { title: 'decision-log', tag: 'live', meta: 'Every alternate kept, queryable.', tagColor: 'rgb(var(--accent-teal) / 0.5)' },\n          { title: 'memory', tag: 'beta', meta: 'Per-tenant recall with TTL windows.', tagColor: 'rgb(var(--pastel-gold) / 0.5)' }\n        ])\n      },\n      content: {\n        Overview: cards('Surfaces', '6 surfaces · 342 posts · 12.4k subscribers', [\n          { title: '.blog', tag: '188 posts', meta: 'Long-form essays, weekly cadence.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: '.podcast', tag: '128 eps', meta: 'Daily AI brief, 5-minute episodes.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: '.pics', tag: '24 packs', meta: 'AI image packs · €19 / €99.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: '.academy', tag: '31 lessons', meta: 'Daily capability upgrade.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: '.today', tag: '3 / day', meta: 'Daily picks with source links.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: '.toolbox', tag: '267 docs', meta: '21 categories, search-first.', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        '.blog': table('.blog', '188 posts · 5 this week', [C('Post', 3), C('Words', 0.8), C('Reads', 0.8), C('State', 1, 'right')], [\n          row([tCell('The cost of an idle agent', { flex: 3 }), mono('1,840', { flex: 0.8 }), mono('2.1k', { flex: 0.8 }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Context packages, explained', { flex: 3 }), mono('2,210', { flex: 0.8 }), mono('3.4k', { flex: 0.8 }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Why we killed the chat window', { flex: 3 }), mono('1,120', { flex: 0.8 }), mono('—', { flex: 0.8 }), mono('draft', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        '.podcast': table('.podcast', 'Published episodes', [C('Episode', 3), C('Length', 1), C('Listens', 1, 'right')], [\n          row([tCell('128 · Relay handoff goes GA', { flex: 3 }), mono('4:58', { flex: 1 }), mono('412', { flex: 1, align: 'right' })]),\n          row([tCell('127 · The cost of an idle agent', { flex: 3 }), mono('5:31', { flex: 1 }), mono('389', { flex: 1, align: 'right' })])\n        ]),\n        '.pics': cards('.pics', 'Image packs · €19 single, €99 bundle', [\n          { title: 'basalt-textures', tag: '€19', meta: '40 generative surfaces, dark and cool.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'circuit-abstracts', tag: '€19', meta: '32 cyan line studies for hero art.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'operator-bundle', tag: '€99', meta: 'Every pack + monthly additions.', tagColor: 'rgb(var(--accent-teal) / 0.5)' }\n        ]),\n        '.academy': list('.academy', '31 lessons · daily capability upgrade', [\n          { title: 'Attach skills at spawn', meta: 'lesson 31 · 6 min · published today', chip: 'live', color: 'var(--app-success)' },\n          { title: 'Read a decision timeline', meta: 'lesson 30 · 8 min', chip: 'live', color: 'var(--app-success)' },\n          { title: 'Budget a run properly', meta: 'lesson 32 · in review', chip: 'draft', color: 'var(--app-warn)' }\n        ]),\n        '.today': list('.today', 'Three picks a day, sourced and linked', [\n          { title: 'Context packages ship to every surface', meta: 'pick 1 · platform · 06:05', chip: 'live', color: 'var(--app-success)' },\n          { title: 'Relay handoff chain goes GA', meta: 'pick 2 · agents · 06:05', chip: 'live', color: 'var(--app-success)' },\n          { title: 'Cost per decision drops 18%', meta: 'pick 3 · money · 06:05', chip: 'live', color: 'var(--app-success)' }\n        ]),\n        '.toolbox': table('.toolbox', '267 docs · 21 categories', [C('Doc', 3), C('Category', 1.4), C('Views', 1, 'right')], [\n          row([tCell('Deploy an agent in 2 minutes', { flex: 3 }), mono('getting-started', { flex: 1.4 }), mono('1.9k', { flex: 1, align: 'right' })]),\n          row([tCell('Skill manifest reference', { flex: 3 }), mono('reference', { flex: 1.4 }), mono('1.2k', { flex: 1, align: 'right' })]),\n          row([tCell('Cost attribution model', { flex: 3 }), mono('money', { flex: 1.4 }), mono('640', { flex: 1, align: 'right' })])\n        ])\n      },\n      costs: {\n        Overview: table('Spend today', '€114 · 61% LLM · 78% of daily budget', [C('Bucket', 2), C('Today', 1, 'right'), C('Month', 1, 'right'), C('Share', 1, 'right')], [\n          row([tCell('LLM tokens', { flex: 2 }), mono('€69', { flex: 1, align: 'right' }), mono('€1,464', { flex: 1, align: 'right' }), mono('61%', { flex: 1, align: 'right' })]),\n          row([tCell('GCP compute', { flex: 2 }), mono('€28', { flex: 1, align: 'right' }), mono('€612', { flex: 1, align: 'right' }), mono('24%', { flex: 1, align: 'right' })]),\n          row([tCell('Stripe fees', { flex: 2 }), mono('€11', { flex: 1, align: 'right' }), mono('€238', { flex: 1, align: 'right' }), mono('10%', { flex: 1, align: 'right' })]),\n          row([tCell('Third-party APIs', { flex: 2 }), mono('€6', { flex: 1, align: 'right' }), mono('€128', { flex: 1, align: 'right' }), mono('5%', { flex: 1, align: 'right' })])\n        ]),\n        Ledger: table('Ledger', 'Per-run cost attribution', [C('Entry', 2.4), C('Stream', 1.2), C('Run', 1), C('Amount', 1, 'right')], [\n          row([tCell('sonnet tokens · 412k', { flex: 2.4 }), mono('select', { flex: 1.2 }), mono('#4827', { flex: 1 }), mono('€0.042', { flex: 1, align: 'right' })]),\n          row([tCell('opus tokens · 96k', { flex: 2.4 }), mono('agentRuns', { flex: 1.2 }), mono('r-360', { flex: 1 }), mono('€1.14', { flex: 1, align: 'right', color: 'var(--app-fail)' })]),\n          row([tCell('Cloud Run · 22 min', { flex: 2.4 }), mono('backends', { flex: 1.2 }), mono('r-368', { flex: 1 }), mono('€0.24', { flex: 1, align: 'right' })]),\n          row([tCell('tts render · 5m12s', { flex: 2.4 }), mono('podcast', { flex: 1.2 }), mono('#1291', { flex: 1 }), mono('€0.14', { flex: 1, align: 'right' })])\n        ]),\n        Budgets: list('Budgets', 'Hard caps — a run dies rather than overspend', [\n          { title: 'Daily platform budget', meta: '€114 of €145 · 78%', chip: 'watch', color: 'var(--app-warn)' },\n          { title: 'Per-run cap · opus', meta: '€1.50 · 1 run hit the cap today', chip: 'active', color: 'var(--app-accent)' },\n          { title: 'Marketing campaign cap', meta: '€40 of €400 this month', chip: 'ok', color: 'var(--app-success)' },\n          { title: 'Cloud Run concurrency', meta: '2 of 8 · raise needs approval', chip: 'gated', color: 'var(--app-warn)' }\n        ]),\n        Forecast: table('Forecast', 'Straight-line from the last 7 days', [C('Window', 2), C('Projected', 1, 'right'), C('vs budget', 1, 'right')], [\n          row([tCell('This month', { flex: 2 }), mono('€2,442', { flex: 1, align: 'right' }), mono('+4%', { flex: 1, align: 'right', color: 'var(--app-warn)' })]),\n          row([tCell('Next month', { flex: 2 }), mono('€2,610', { flex: 1, align: 'right' }), mono('+11%', { flex: 1, align: 'right', color: 'var(--app-fail)' })]),\n          row([tCell('Quarter', { flex: 2 }), mono('€7,890', { flex: 1, align: 'right' }), mono('+6%', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        Replays: list('Replays', 'Cost events worth re-reading', [\n          { title: 'opus run hit the €1.50 cap', meta: 'r-360 · killed at TEST', chip: 'replay', color: 'var(--app-fail)' },\n          { title: 'Embedding reindex spike', meta: '3.1M tokens in 40 min', chip: 'replay', color: 'var(--app-warn)' }\n        ])\n      },\n      finances: {\n        Overview: table('Position', 'MRR €18.2k · runway 14 months', [C('Line', 2), C('Amount', 1, 'right'), C('Change', 1, 'right')], [\n          row([tCell('MRR', { flex: 2 }), mono('€18,200', { flex: 1, align: 'right' }), mono('+4%', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Cash', { flex: 2 }), mono('€254,800', { flex: 1, align: 'right' }), mono('−€6.2k', { flex: 1, align: 'right', color: 'var(--app-warn)' })]),\n          row([tCell('Burn · month', { flex: 2 }), mono('€18,400', { flex: 1, align: 'right' }), mono('flat', { flex: 1, align: 'right' })]),\n          row([tCell('Overdue', { flex: 2 }), mono('€7,140', { flex: 1, align: 'right', color: 'var(--app-fail)' }), mono('2 invoices', { flex: 1, align: 'right' })])\n        ]),\n        Invoices: table('Invoices', '12 open · 2 overdue', [C('Invoice', 1.2), C('Client', 2), C('Amount', 1, 'right'), C('Due', 1, 'right'), C('State', 1, 'right')], [\n          row([mono('2026-118', { flex: 1.2 }), tCell('Helsing GmbH', { flex: 2 }), mono('€4,200', { flex: 1, align: 'right' }), mono('06-14', { flex: 1, align: 'right' }), mono('sent', { flex: 1, align: 'right' })]),\n          row([mono('2026-114', { flex: 1.2 }), tCell('Kanzlei Steuer', { flex: 2 }), mono('€2,940', { flex: 1, align: 'right' }), mono('05-30', { flex: 1, align: 'right' }), mono('overdue', { flex: 1, align: 'right', color: 'var(--app-fail)' })]),\n          row([mono('2026-109', { flex: 1.2 }), tCell('Studio Nord', { flex: 2 }), mono('€4,200', { flex: 1, align: 'right' }), mono('05-22', { flex: 1, align: 'right' }), mono('overdue', { flex: 1, align: 'right', color: 'var(--app-fail)' })]),\n          row([mono('2026-121', { flex: 1.2 }), tCell('Postiz BV', { flex: 2 }), mono('€1,800', { flex: 1, align: 'right' }), mono('06-21', { flex: 1, align: 'right' }), mono('draft', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        Clients: table('Clients', 'Retainers and usage plans', [C('Client', 2), C('Plan', 1.4), C('MRR', 1, 'right'), C('Since', 1, 'right')], [\n          row([tCell('Helsing GmbH', { flex: 2 }), mono('operator · retainer', { flex: 1.4 }), mono('€4,200', { flex: 1, align: 'right' }), mono('2025-11', { flex: 1, align: 'right' })]),\n          row([tCell('Kanzlei Steuer', { flex: 2 }), mono('pro · usage', { flex: 1.4 }), mono('€2,940', { flex: 1, align: 'right' }), mono('2026-02', { flex: 1, align: 'right' })]),\n          row([tCell('Studio Nord', { flex: 2 }), mono('operator · retainer', { flex: 1.4 }), mono('€4,200', { flex: 1, align: 'right' }), mono('2025-08', { flex: 1, align: 'right' })])\n        ]),\n        Expenses: table('Expenses', 'Recurring platform cost', [C('Vendor', 2), C('Category', 1.4), C('Month', 1, 'right')], [\n          row([tCell('Anthropic', { flex: 2 }), mono('LLM', { flex: 1.4 }), mono('€1,464', { flex: 1, align: 'right' })]),\n          row([tCell('Google Cloud', { flex: 2 }), mono('compute', { flex: 1.4 }), mono('€612', { flex: 1, align: 'right' })]),\n          row([tCell('Stripe', { flex: 2 }), mono('payments', { flex: 1.4 }), mono('€238', { flex: 1, align: 'right' })]),\n          row([tCell('Typesense', { flex: 2 }), mono('search', { flex: 1.4 }), mono('€89', { flex: 1, align: 'right' })])\n        ]),\n        Replays: list('Replays', 'Ledger events', [\n          { title: 'Invoice 2026-114 dunning run', meta: 'reminder 2 of 3 sent · no reply', chip: 'replay', color: 'var(--app-warn)' },\n          { title: 'Stripe payout reconciliation', meta: '28 lines · 0 exceptions', chip: 'replay', color: 'var(--app-success)' }\n        ])\n      },\n      ops: {\n        Overview: list('System', '1 alert · DLQ empty · 99.9% uptime', [\n          { title: 'Alert · tts quota at 92%', meta: 'podcast pipeline · raised 40m ago', chip: 'open', color: 'var(--app-fail)' },\n          { title: 'Deploy queue', meta: '2 waiting · engine + shell', chip: 'queued', color: 'var(--app-warn)' },\n          { title: 'Agents online', meta: '9 of 9 · no restarts in 24h', chip: 'ok', color: 'var(--app-success)' },\n          { title: 'Dead-letter queue', meta: 'empty · last drain 03:10', chip: 'clear', color: 'var(--app-success)' }\n        ]),\n        Alerts: list('Alerts', 'Only what needs a human', [\n          { title: 'tts quota at 92%', meta: 'podcast · will block episode 130', chip: 'critical', color: 'var(--app-fail)' },\n          { title: 'p95 on context-engine 3.2s → 4.1s', meta: 'watch · no SLO breach yet', chip: 'watch', color: 'var(--app-warn)' }\n        ]),\n        Queue: table('Deploy queue', 'What ships next', [C('Change', 2.4), C('Env', 1), C('Gate', 1.4), C('State', 1, 'right')], [\n          row([tCell('engine · context-loop driver', { flex: 2.4 }), mono('staging', { flex: 1 }), mono('checks green', { flex: 1.4 }), mono('ready', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('shell · role views', { flex: 2.4 }), mono('staging', { flex: 1 }), mono('1 check running', { flex: 1.4 }), mono('waiting', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        Deploys: table('Deploys', '42 this month · 0 incidents', [C('Deploy', 1.4), C('Target', 1.4), C('Duration', 1), C('Result', 1, 'right')], [\n          row([mono('d-1042', { flex: 1.4 }), mono('engine · prod', { flex: 1.4 }), mono('2m 10s', { flex: 1 }), mono('ok', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([mono('d-1041', { flex: 1.4 }), mono('shell · prod', { flex: 1.4 }), mono('1m 44s', { flex: 1 }), mono('ok', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([mono('d-1040', { flex: 1.4 }), mono('vault · prod', { flex: 1.4 }), mono('3m 02s', { flex: 1 }), mono('rolled back', { flex: 1, align: 'right', color: 'var(--app-fail)' })])\n        ]),\n        Replays: list('Replays', 'Incident and deploy traces', [\n          { title: 'd-1040 rollback', meta: 'index build OOM · 18 frames', chip: 'replay', color: 'var(--app-fail)' },\n          { title: 'DLQ drain 03:10', meta: '14 messages replayed clean', chip: 'replay', color: 'var(--app-success)' }\n        ])\n      },\n      quality: {\n        Overview: list('The bar', '214 evals · 96% pass · 3 regressions open', [\n          { title: 'Eval suite · nightly', meta: '214 cases · 96% pass · 38 min', chip: 'green', color: 'var(--app-success)' },\n          { title: 'Guardrails', meta: '18 active · 2 tripped this week', chip: 'active', color: 'var(--app-accent)' },\n          { title: 'Regressions', meta: '3 open · oldest 4 days', chip: 'open', color: 'var(--app-fail)' },\n          { title: 'Coverage ratchet', meta: '82% → 84% · rising', chip: 'ok', color: 'var(--app-success)' }\n        ]),\n        Evals: table('Evals', 'Nightly suite', [C('Suite', 2), C('Cases', 1), C('Pass', 1), C('Duration', 1, 'right')], [\n          row([tCell('decision-quality', { flex: 2 }), mono('96', { flex: 1 }), mono('98%', { flex: 1, color: 'var(--app-success)' }), mono('14m', { flex: 1, align: 'right' })]),\n          row([tCell('extraction-accuracy', { flex: 2 }), mono('64', { flex: 1 }), mono('94%', { flex: 1, color: 'var(--app-warn)' }), mono('11m', { flex: 1, align: 'right' })]),\n          row([tCell('tone-adherence', { flex: 2 }), mono('54', { flex: 1 }), mono('97%', { flex: 1, color: 'var(--app-success)' }), mono('13m', { flex: 1, align: 'right' })])\n        ]),\n        Guardrails: list('Guardrails', 'What an agent is never allowed to do', [\n          { title: 'No destructive migration without approval', meta: 'tripped today · r-360 blocked', chip: 'tripped', color: 'var(--app-fail)' },\n          { title: 'No spend above the per-run cap', meta: 'tripped today · r-360 killed at €1.50', chip: 'tripped', color: 'var(--app-warn)' },\n          { title: 'No PII in logs', meta: 'clean for 31 days', chip: 'clear', color: 'var(--app-success)' },\n          { title: 'No outbound email without a human', meta: 'clean for 31 days', chip: 'clear', color: 'var(--app-success)' }\n        ]),\n        Regressions: table('Regressions', '3 open', [C('Case', 2.6), C('Since', 1), C('Owner', 1), C('Severity', 1, 'right')], [\n          row([tCell('vendor match picks exact over fuzzy', { flex: 2.6 }), mono('4d', { flex: 1 }), mono('match-agt', { flex: 1 }), mono('high', { flex: 1, align: 'right', color: 'var(--app-fail)' })]),\n          row([tCell('tone drift on .blog drafts', { flex: 2.6 }), mono('2d', { flex: 1 }), mono('gen-agt', { flex: 1 }), mono('medium', { flex: 1, align: 'right', color: 'var(--app-warn)' })]),\n          row([tCell('duplicate .today picks', { flex: 2.6 }), mono('1d', { flex: 1 }), mono('dedupe-agt', { flex: 1 }), mono('low', { flex: 1, align: 'right' })])\n        ]),\n        Replays: list('Replays', 'Failed cases, frame by frame', [\n          { title: 'vendor match regression', meta: 'eval case 41 · 22 frames', chip: 'replay', color: 'var(--app-fail)' },\n          { title: 'guardrail trip · migration', meta: 'r-360 · 8 frames', chip: 'replay', color: 'var(--app-warn)' }\n        ])\n      },\n      marketing: {\n        Overview: cards('Campaigns', '6 live · 1.2k sent · 3.4% CTR', [\n          { title: 'relay-launch', tag: 'live', meta: 'Feature launch across .com and .blog · 412 sent.', tagColor: 'rgb(var(--accent-teal) / 0.5)' },\n          { title: 'cold-email · pr-1039', tag: 'live', meta: '40 prospects · 3 sources · 2 replies.', tagColor: 'rgb(var(--accent-teal) / 0.5)' },\n          { title: 'academy-digest', tag: 'live', meta: 'Weekly lifecycle digest · 12.4k list.', tagColor: 'rgb(var(--accent-teal) / 0.5)' },\n          { title: 'boutique-sprints', tag: 'draft', meta: 'Sprint launch, dual CTA · not scheduled.', tagColor: 'rgb(var(--pastel-gold) / 0.5)' }\n        ]),\n        Campaigns: table('Campaigns', 'Performance by campaign', [C('Campaign', 2), C('Channel', 1.2), C('Sent', 0.8), C('CTR', 0.8), C('State', 1, 'right')], [\n          row([tCell('relay-launch', { flex: 2 }), mono('email + blog', { flex: 1.2 }), mono('412', { flex: 0.8 }), mono('4.1%', { flex: 0.8, color: 'var(--app-success)' }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('cold-email · pr-1039', { flex: 2 }), mono('email', { flex: 1.2 }), mono('40', { flex: 0.8 }), mono('7.5%', { flex: 0.8, color: 'var(--app-success)' }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('academy-digest', { flex: 2 }), mono('email', { flex: 1.2 }), mono('748', { flex: 0.8 }), mono('2.8%', { flex: 0.8 }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('nis2-compliance-digest', { flex: 2 }), mono('email', { flex: 1.2 }), mono('—', { flex: 0.8 }), mono('—', { flex: 0.8 }), mono('scheduled', { flex: 1, align: 'right', color: 'var(--app-accent)' })]),\n          row([tCell('boutique-sprints', { flex: 2 }), mono('email', { flex: 1.2 }), mono('—', { flex: 0.8 }), mono('—', { flex: 0.8 }), mono('draft', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        Channels: table('Channels', 'Where syndication lands', [C('Channel', 1.6), C('Reach', 1), C('Posts 30d', 1), C('State', 1, 'right')], [\n          row([tCell('email', { flex: 1.6 }), mono('12.4k', { flex: 1 }), mono('18', { flex: 1 }), mono('ok', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('x', { flex: 1.6 }), mono('4.1k', { flex: 1 }), mono('62', { flex: 1 }), mono('rate-limited', { flex: 1, align: 'right', color: 'var(--app-warn)' })]),\n          row([tCell('linkedin', { flex: 1.6 }), mono('2.8k', { flex: 1 }), mono('31', { flex: 1 }), mono('ok', { flex: 1, align: 'right', color: 'var(--app-success)' })])\n        ]),\n        Queue: list('Queue', '3 items · pending / held / failed', [\n          { title: 'nis2-compliance-digest send window', meta: 'awaiting a schedule pick', chip: 'pending', color: 'var(--app-ink-faint)' },\n          { title: 'Replay Postiz syndication · 05-27', meta: 'awaiting OpenRouter quota · 2h', chip: 'held', color: 'var(--app-warn)' },\n          { title: 'x syndication · relay-launch clip', meta: 'rate-limited by platform · 3 retries exhausted', chip: 'failed', color: 'var(--app-fail)' }\n        ]),\n        Replays: list('Replays', 'Campaign runs', [\n          { title: 'relay-launch send', meta: '412 recipients · 0 bounces', chip: 'replay', color: 'var(--app-success)' },\n          { title: 'x syndication rate-limit', meta: 'backoff 3× then parked', chip: 'replay', color: 'var(--app-warn)' }\n        ])\n      },\n      product: {\n        Overview: cards('Now · next · later', '24 items on the roadmap · 5 shipped this month', [\n          { title: 'Now', tag: '6 items', meta: 'Role views, Developer panes, cost caps.', tagColor: 'rgb(var(--accent-teal) / 0.5)' },\n          { title: 'Next', tag: '9 items', meta: 'Engine C8 API, ACP interop, replay export.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Later', tag: '9 items', meta: 'Multi-tenant canvas, marketplace payouts.', tagColor: 'rgb(var(--text) / 0.62)' }\n        ]),\n        Roadmap: table('Roadmap', '24 items · 3 P1', [C('Item', 2.6), C('Bucket', 1), C('Priority', 1), C('Cycle', 1, 'right')], [\n          row([tCell('Engine C8 run API', { flex: 2.6 }), mono('next', { flex: 1 }), mono('P1', { flex: 1, color: 'var(--app-fail)' }), mono('9d', { flex: 1, align: 'right' })]),\n          row([tCell('Replay export (JSON + video)', { flex: 2.6 }), mono('next', { flex: 1 }), mono('P1', { flex: 1, color: 'var(--app-fail)' }), mono('6d', { flex: 1, align: 'right' })]),\n          row([tCell('Per-run cost caps', { flex: 2.6 }), mono('now', { flex: 1 }), mono('P1', { flex: 1, color: 'var(--app-fail)' }), mono('3d', { flex: 1, align: 'right' })]),\n          row([tCell('Marketplace payouts', { flex: 2.6 }), mono('later', { flex: 1 }), mono('P3', { flex: 1 }), mono('—', { flex: 1, align: 'right' })])\n        ]),\n        Specs: list('Specs', 'Every shipped thing starts here', [\n          { title: 'Role views on the Canvas shell', meta: 'spec 0142 · approved · in build', chip: 'building', color: 'var(--app-running)' },\n          { title: 'Agent Workspace dashboards (AW-01..09)', meta: 'spec 0140 · approved · in build', chip: 'building', color: 'var(--app-running)' },\n          { title: 'Engine C8 run API', meta: 'spec 0145 · in review', chip: 'review', color: 'var(--app-warn)' }\n        ]),\n        Shipped: table('Shipped', '5 this month · 9-day median cycle', [C('Item', 2.6), C('Shipped', 1.2), C('Cycle', 1, 'right')], [\n          row([tCell('Human blockers in the roster', { flex: 2.6 }), mono('06-01', { flex: 1.2 }), mono('4d', { flex: 1, align: 'right' })]),\n          row([tCell('Collapsible stream categories', { flex: 2.6 }), mono('05-30', { flex: 1.2 }), mono('2d', { flex: 1, align: 'right' })]),\n          row([tCell('Mobile Canvas shell', { flex: 2.6 }), mono('05-28', { flex: 1.2 }), mono('11d', { flex: 1, align: 'right' })])\n        ]),\n        Replays: list('Replays', 'How a spec became a ship', [\n          { title: 'Mobile Canvas shell', meta: 'spec 0138 → 11 days → prod', chip: 'replay', color: 'var(--app-success)' },\n          { title: 'Collapsible categories', meta: 'spec 0141 → 2 days → prod', chip: 'replay', color: 'var(--app-success)' }\n        ])\n      },\n      platform: {\n        Overview: list('Ground truth', '4 environments · 42 deploys · p95 210ms', [\n          { title: 'prod · europe-west4', meta: 'p95 210ms · 0 incidents · 9 agents', chip: 'healthy', color: 'var(--app-success)' },\n          { title: 'staging · europe-west4', meta: '2 deploys queued', chip: 'busy', color: 'var(--app-warn)' },\n          { title: 'build-vm-01', meta: '2 concurrent runs · attached repo copy', chip: 'ok', color: 'var(--app-success)' },\n          { title: 'Cloud Run · us-central1', meta: 'auto-scales · cost-metered', chip: 'ok', color: 'var(--app-success)' }\n        ]),\n        Infra: table('Infra', 'What is running where', [C('Service', 2), C('Region', 1.4), C('Instances', 1), C('p95', 1, 'right')], [\n          row([tCell('engine', { flex: 2 }), mono('europe-west4', { flex: 1.4 }), mono('4', { flex: 1 }), mono('210ms', { flex: 1, align: 'right' })]),\n          row([tCell('shell', { flex: 2 }), mono('europe-west4', { flex: 1.4 }), mono('2', { flex: 1 }), mono('88ms', { flex: 1, align: 'right' })]),\n          row([tCell('vault (Typesense)', { flex: 2 }), mono('europe-west4', { flex: 1.4 }), mono('1', { flex: 1 }), mono('180ms', { flex: 1, align: 'right' })]),\n          row([tCell('outbox worker', { flex: 2 }), mono('europe-west4', { flex: 1.4 }), mono('2', { flex: 1 }), mono('—', { flex: 1, align: 'right' })])\n        ]),\n        Environments: table('Environments', 'Promotion path', [C('Env', 1.4), C('Branch', 1.6), C('Last deploy', 1.2), C('State', 1, 'right')], [\n          row([mono('prod', { flex: 1.4, color: 'var(--app-ink)' }), mono('main', { flex: 1.6 }), mono('2h ago', { flex: 1.2 }), mono('healthy', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([mono('staging', { flex: 1.4, color: 'var(--app-ink)' }), mono('main', { flex: 1.6 }), mono('12m ago', { flex: 1.2 }), mono('deploying', { flex: 1, align: 'right', color: 'var(--app-running)' })]),\n          row([mono('preview', { flex: 1.4, color: 'var(--app-ink)' }), mono('feat/*', { flex: 1.6 }), mono('4m ago', { flex: 1.2 }), mono('ephemeral', { flex: 1, align: 'right' })]),\n          row([mono('local', { flex: 1.4, color: 'var(--app-ink)' }), mono('worktree', { flex: 1.6 }), mono('—', { flex: 1.2 }), mono('yours', { flex: 1, align: 'right' })])\n        ]),\n        Deploys: table('Deploys', '42 this month', [C('Deploy', 1.4), C('Target', 1.6), C('Duration', 1), C('Result', 1, 'right')], [\n          row([mono('d-1042', { flex: 1.4 }), mono('engine · prod', { flex: 1.6 }), mono('2m 10s', { flex: 1 }), mono('ok', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([mono('d-1041', { flex: 1.4 }), mono('shell · prod', { flex: 1.6 }), mono('1m 44s', { flex: 1 }), mono('ok', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([mono('d-1040', { flex: 1.4 }), mono('vault · prod', { flex: 1.6 }), mono('3m 02s', { flex: 1 }), mono('rolled back', { flex: 1, align: 'right', color: 'var(--app-fail)' })])\n        ]),\n        Replays: list('Replays', 'Deploy traces', [\n          { title: 'd-1040 rollback', meta: 'index build OOM at 78%', chip: 'replay', color: 'var(--app-fail)' },\n          { title: 'd-1042 clean deploy', meta: 'canary 10% → 100% in 90s', chip: 'replay', color: 'var(--app-success)' }\n        ])\n      },\n      home: {\n        // ARCHIVED 2026-09-03 (O5) — unreachable: STREAMS.home.ownsTabs now routes Overview to the\n        // native PaneHome. Kept in place per the archive-don't-delete rule, NOT a spare part to\n        // re-point a tab at; counts as archived-known in docs/CENSUS.md's orphan set.\n        Overview: list('Needs you', '7 unread · 2 waiting on you', [\n          { title: 'Approve destructive migration', meta: 'agentRuns · r-360 · 46m', chip: 'you', color: 'var(--app-fail)' },\n          { title: 'Approve episode 129 for publish', meta: 'podcast · #1291 · 1h', chip: 'you', color: 'var(--app-fail)' },\n          { title: 'relay-launch sent to 412', meta: 'marketing · 0 bounces', chip: 'fyi', color: 'var(--app-success)' },\n          { title: 'Invoice 2026-114 now overdue', meta: 'finances · €2,940 · 2d', chip: 'fyi', color: 'var(--app-warn)' }\n        ]),\n        Inbox: list('Inbox', 'Everything across every stream', [\n          { title: 'r-372 finished CODE node', meta: 'agentRuns · 12m ago', chip: 'run', color: 'var(--app-accent)' },\n          { title: 'Nightly eval suite green', meta: 'quality · 96% pass · 03:38', chip: 'ok', color: 'var(--app-success)' },\n          { title: 'tts quota at 92%', meta: 'ops · alert · 40m', chip: 'alert', color: 'var(--app-fail)' },\n          { title: '3 picks published to .today', meta: 'content · 06:05', chip: 'ok', color: 'var(--app-success)' }\n        ]),\n        Saved: list('Saved', '19 items you kept', [\n          { title: 'Decision trace #4827', meta: 'select · clean AP reconciliation', chip: 'saved', color: 'var(--app-accent)' },\n          { title: 'Context package · Helsing', meta: 'intelligence · 12 skills', chip: 'saved', color: 'var(--app-accent)' },\n          { title: 'Cost attribution model', meta: 'content · .toolbox doc', chip: 'saved', color: 'var(--app-accent)' }\n        ])\n      }\n    };\n    Object.assign(this.DEV_PANES, ROLE_PANES);\n\n\n    // ══ Panes folded in from the imported Canvas handoffs ══\n    // D206/D207: note, table and board all come from PRJ_ROWS — the hero getter counts the same array.\n    const PL = this.prjLanes();\n    const prjNote = PL.active + ' active · ' + PL.review + ' in review · 6.240 credits spent';\n    const prjLaneCol = { Draft: 'var(--app-ink-soft)', Running: 'var(--app-running)', Review: 'var(--app-warn)', Done: 'var(--app-success)' };\n    const prjStatusCol = { running: 'var(--app-running)', review: 'var(--app-warn)', done: 'var(--app-success)', failed: 'var(--app-fail)' };\n    const IMPORT_PANES = {\n      projects: {\n        // W2 · rows + columns verbatim from K.projects.schema / mockItems (kinds-member.jsx:73-100). Board lanes =\n        // boardColumns, lane = stageIdx. Was 4 abbreviated rows with an invented 'Stage' column; pr-1004 (failed) was missing.\n        Work: Object.assign(table('Work', prjNote, [C('Project', 2.6), C('Status', 0.9), C('Agent', 1.5), C('Created', 1), C('Last run', 1.3), C('Cost', 0.7, 'right')], [\n          ...(this.state.briefsSubmitted || []).map(b => row([tCell(b.title, { flex: 2.6 }), mono('running', { flex: 0.9, color: 'var(--app-running)' }), mono(b.agent, { flex: 1.5 }), mono('just now', { flex: 1 }), mono('live', { flex: 1.3 }), mono('€ 0,00', { flex: 0.7, align: 'right' })])),\n          ...this.PRJ_ROWS.map(r => row([tCell(r.title, { flex: 2.6 }), mono(r.status, Object.assign({ flex: 0.9 }, prjStatusCol[r.status] ? { color: prjStatusCol[r.status] } : {})), mono(r.agent, { flex: 1.5 }), mono(r.created, { flex: 1 }), mono(r.last, { flex: 1.3 }), mono(r.cost, { flex: 0.7, align: 'right' })]))\n        ]), { boardAlt: board('Work', prjNote, ['Draft', 'Running', 'Review', 'Done'].map(l => ({ label: l, color: prjLaneCol[l], items: this.PRJ_ROWS.filter(r => r.lane === l).map(r => ({ title: r.title, meta: r.meta })) }))) }),\n        Runs: table('Runs', 'Every run started by one of your projects', [C('Run', 1), C('Project', 2.2), C('Duration', 1), C('Credits', 1, 'right'), C('State', 1, 'right')], [\n          row([mono('#pr-1042', { flex: 1 }), tCell('Brand refresh', { flex: 2.2 }), mono('18h', { flex: 1 }), mono('420', { flex: 1, align: 'right' }), mono('review', { flex: 1, align: 'right', color: 'var(--app-warn)' })]),\n          row([mono('#pr-1039', { flex: 1 }), tCell('Cold-email pack', { flex: 2.2 }), mono('running', { flex: 1 }), mono('880', { flex: 1, align: 'right' }), mono('live', { flex: 1, align: 'right', color: 'var(--app-running)' })]),\n          row([mono('#pr-1031', { flex: 1 }), tCell('Podcast brief', { flex: 2.2 }), mono('2h 10m', { flex: 1 }), mono('310', { flex: 1, align: 'right' }), mono('done', { flex: 1, align: 'right', color: 'var(--app-success)' })])\n        ]),\n        Library: cards('Library', 'Reusable inputs your projects draw on', [\n          { title: 'brand-canvas', tag: 'brief', meta: 'Voice, audience and visual cues — feeds every content run.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'icp · indie operators', tag: 'audience', meta: 'Firmographics, personas and country gating.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'tone samples', tag: 'reference', meta: '12 approved passages the agents imitate.', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        Credits: table('Credits', '6,240 spent across projects this month', [C('Project', 2.4), C('Runs', 1), C('Credits', 1, 'right'), C('EUR', 1, 'right')], [\n          row([tCell('Cold-email pack', { flex: 2.4 }), mono('4', { flex: 1 }), mono('2,480', { flex: 1, align: 'right' }), mono('€24.80', { flex: 1, align: 'right' })]),\n          row([tCell('Brand refresh', { flex: 2.4 }), mono('3', { flex: 1 }), mono('2,120', { flex: 1, align: 'right' }), mono('€21.20', { flex: 1, align: 'right' })]),\n          row([tCell('Podcast brief', { flex: 2.4 }), mono('6', { flex: 1 }), mono('1,640', { flex: 1, align: 'right' }), mono('€16.40', { flex: 1, align: 'right' })])\n        ]),\n        Settings: list('Project settings', 'Defaults applied to every new project', [\n          { title: 'Default agent', meta: 'brand-canvas · overridable per project', chip: 'set', color: 'var(--app-accent)' },\n          { title: 'Per-project credit cap', meta: '3,000 credits · run pauses at the cap', chip: 'set', color: 'var(--app-accent)' },\n          { title: 'Auto-approve deliverables', meta: 'off — everything waits for a human', chip: 'off', color: 'var(--app-warn)' }\n        ])\n      },\n      deliverables: {\n        'In review': list('In review', '2 waiting on you · avg turnaround 18h', [\n          { title: 'Brand worksheet — voice, audience & visual cues', meta: 'brand-canvas · pr-1042 · rev 1 · €0.42 · 18h', chip: 'review', color: 'var(--app-warn)' },\n          { title: 'Cold-email sequence — 40 prospects', meta: 'cold-email-personalizer · pr-1039 · rev 2 · €0.88', chip: 'review', color: 'var(--app-warn)' }\n        ]),\n        Revisions: table('Revisions', 'What changed between versions', [C('Deliverable', 2.6), C('Rev', 0.6), C('Asked for', 2.2), C('When', 1, 'right')], [\n          row([tCell('Cold-email sequence', { flex: 2.6 }), mono('2', { flex: 0.6 }), tCell('shorter subject lines, drop the emoji', { flex: 2.2, color: 'var(--app-ink-soft)' }), mono('yesterday', { flex: 1, align: 'right' })]),\n          row([tCell('Brand worksheet', { flex: 2.6 }), mono('1', { flex: 0.6 }), tCell('first pass — no changes requested yet', { flex: 2.2, color: 'var(--app-ink-soft)' }), mono('2h ago', { flex: 1, align: 'right' })])\n        ]),\n        Approved: table('Approved', '18 approved · 3 this week', [C('Deliverable', 3), C('Project', 1.6), C('Revs', 0.6), C('Approved', 1, 'right')], [\n          row([tCell('Podcast brief — week 22', { flex: 3 }), mono('Podcast brief', { flex: 1.6 }), mono('1', { flex: 0.6 }), mono('05-27', { flex: 1, align: 'right' })]),\n          row([tCell('Landing hero — three variants', { flex: 3 }), mono('Brand refresh', { flex: 1.6 }), mono('2', { flex: 0.6 }), mono('05-25', { flex: 1, align: 'right' })]),\n          row([tCell('ICP one-pager', { flex: 3 }), mono('Cold-email pack', { flex: 1.6 }), mono('1', { flex: 0.6 }), mono('05-24', { flex: 1, align: 'right' })])\n        ]),\n        Archive: list('Archive', 'Older output, still downloadable', [\n          { title: 'Q1 content calendar', meta: 'archived 04-02 · 6 files', chip: 'archived', color: 'var(--app-ink-faint)' },\n          { title: 'Pricing page copy v1', meta: 'archived 03-18 · superseded', chip: 'archived', color: 'var(--app-ink-faint)' }\n        ])\n      },\n      // ── D151 · Credits + Usage are ARCHIVED (unreachable — no longer in subs; superseded by PaneAccount › Billing,\n      //    which renders K.billing verbatim). Plan + Invoices remain live lens tabs. Archive, don't delete. ──\n      billing: {\n        Credits: table('Credits', '24,840 balance · 1 credit = €0.01 · Pro plan', [C('Entry', 2.6), C('Type', 1.2), C('Credits', 1, 'right'), C('EUR', 1, 'right')], [\n          row([tCell('Pro monthly · 20,000 cr', { flex: 2.6 }), mono('purchase', { flex: 1.2 }), mono('+20,000', { flex: 1, align: 'right', color: 'var(--app-success)' }), mono('€199.00', { flex: 1, align: 'right' })]),\n          row([tCell('Cold-email pack · pr-1039', { flex: 2.6 }), mono('spend', { flex: 1.2 }), mono('−88', { flex: 1, align: 'right', color: 'var(--app-fail)' }), mono('€0.88', { flex: 1, align: 'right' })]),\n          row([tCell('Top-up · 5,000 cr', { flex: 2.6 }), mono('purchase', { flex: 1.2 }), mono('+5,000', { flex: 1, align: 'right', color: 'var(--app-success)' }), mono('€50.00', { flex: 1, align: 'right' })])\n        ]),\n        Plan: list('Plan', 'Pro · 20,000 credits included each month', [\n          { title: 'Pro — €199 / month', meta: 'current · 20,000 credits · 5 seats · renews 06-30', chip: 'current', color: 'var(--app-success)' },\n          { title: 'Operator — €499 / month', meta: '60,000 credits · 15 seats · priority queue', chip: 'upgrade', color: 'var(--app-accent)' },\n          { title: 'Starter — €49 / month', meta: '4,000 credits · 2 seats', chip: 'downgrade', color: 'var(--app-ink-faint)' }\n        ]),\n        Invoices: table('Invoices', 'Stripe-hosted · card details never touch the app', [C('Invoice', 1.4), C('Period', 1.6), C('Amount', 1, 'right'), C('State', 1, 'right')], [\n          row([mono('in-0442', { flex: 1.4 }), mono('May 2026', { flex: 1.6 }), mono('€199.00', { flex: 1, align: 'right' }), mono('paid', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([mono('in-0431', { flex: 1.4 }), mono('Apr 2026', { flex: 1.6 }), mono('€249.00', { flex: 1, align: 'right' }), mono('paid', { flex: 1, align: 'right', color: 'var(--app-success)' })])\n        ]),\n        Usage: table('Usage', '€62.40 this month against 20,000 included credits', [C('Bucket', 2.4), C('Credits', 1, 'right'), C('EUR', 1, 'right'), C('Share', 1, 'right')], [\n          row([tCell('Agent runs', { flex: 2.4 }), mono('4,180', { flex: 1, align: 'right' }), mono('€41.80', { flex: 1, align: 'right' }), mono('67%', { flex: 1, align: 'right' })]),\n          row([tCell('Context assembly', { flex: 2.4 }), mono('1,420', { flex: 1, align: 'right' }), mono('€14.20', { flex: 1, align: 'right' }), mono('23%', { flex: 1, align: 'right' })]),\n          row([tCell('Image generation', { flex: 2.4 }), mono('640', { flex: 1, align: 'right' }), mono('€6.40', { flex: 1, align: 'right' }), mono('10%', { flex: 1, align: 'right' })])\n        ])\n      },\n      // ── D151 · Members + Invites are ARCHIVED (unreachable; superseded by PaneAccount › Team, K.orgTeam verbatim).\n      //    Roles + Activity remain live lens tabs. ──\n      team: {\n        Members: table('Members', '4 members · 4 of 5 seats used', [C('Member', 2.4), C('Email', 2.4), C('Role', 1, 'right')], [\n          row([tCell('Jorian Lundqvist', { flex: 2.4 }), mono('jorian@aaas.love', { flex: 2.4 }), mono('owner', { flex: 1, align: 'right', color: 'var(--app-accent)' })]),\n          row([tCell('Tobias Reell', { flex: 2.4 }), mono('tobias@aaas.love', { flex: 2.4 }), mono('admin', { flex: 1, align: 'right' })]),\n          row([tCell('Mira Sundqvist', { flex: 2.4 }), mono('mira@aaas.love', { flex: 2.4 }), mono('member', { flex: 1, align: 'right' })]),\n          row([tCell('Bookkeeper', { flex: 2.4 }), mono('kanzlei@steuer.de', { flex: 2.4 }), mono('finance', { flex: 1, align: 'right' })])\n        ]),\n        Invites: list('Invites', '1 pending · invites expire after 7 days', [\n          { title: 'designer@studio.co', meta: 'member · sent 2 days ago · expires in 5', chip: 'pending', color: 'var(--app-warn)' }\n        ]),\n        Roles: list('Roles', 'What each role may do — the same roles the canvas switches between', [\n          { title: 'Owner', meta: 'everything, including plan and seats', chip: 'full', color: 'var(--app-accent)' },\n          { title: 'Admin', meta: 'platform, governance and money — no plan changes', chip: 'high', color: 'var(--app-accent)' },\n          { title: 'Finance', meta: 'costs, finances, billing — read-only elsewhere', chip: 'scoped', color: 'var(--app-warn)' },\n          { title: 'Member', meta: 'projects, deliverables and their own runs', chip: 'scoped', color: 'var(--app-warn)' }\n        ]),\n        Activity: tail('Activity', 'Who did what in this workspace', 'aaas://team/activity', [\n          { time: '06:31', prefix: '[jl]', msg: 'approved Podcast brief — week 22', color: 'var(--app-success)' },\n          { time: '05:14', prefix: '[tr]', msg: 'invited designer@studio.co as member', color: 'var(--app-accent)' },\n          { time: 'Mon', prefix: '[mira]', msg: 'created project “Blog series — autonomous finance”', color: 'var(--app-accent)' },\n          { time: 'Mon', prefix: '[jl]', msg: 'bought 5,000 credits', color: 'var(--app-ink-faint)' }\n        ])\n      },\n      protocol: {\n        // ── ARCHIVED 2026-08-05 · unreachable, preserved deliberately ──────────────────\n        // Identity / Audience / This piece / CTA / Guardrails / Output below are the old\n        // hand-written re-descriptions of the source screen (defect 2 in the PaneProtocol row of\n        // docs/PANE-AUDIT.md). They left `subs` when the stream became a native branch, so nothing\n        // renders them. Kept rather than deleted per the standing \"archive, don't delete\" rule —\n        // their content is now rendered live and correctly by PaneProtocol.dc.html, so do NOT\n        // re-point tabs at them; they are superseded, not spare.\n        Identity: list('Identity', 'Who is speaking — fixed for every run', [\n          { title: 'AaaS — Agents as a Service', meta: 'canonical facts ref · docs/brand/facts.md', chip: 'locked', color: 'var(--app-accent)' },\n          { title: 'Curated AI agents, automated end-to-end.', meta: 'one-line positioning', chip: 'locked', color: 'var(--app-accent)' },\n          { title: 'Voice pillars', meta: 'technical · confident · terse · concrete · never hyped', chip: '5', color: 'var(--app-accent)' },\n          { title: 'Register & reading level', meta: 'professional peer · grade 9 · one idea per sentence', chip: 'set', color: 'var(--app-success)' }\n        ]),\n        Audience: list('Audience', 'Who it is for, and what they already believe', [\n          { title: 'Indie builders and small ops teams', meta: 'already wiring AI into daily workflows', chip: 'primary', color: 'var(--app-accent)' },\n          { title: 'Pain', meta: 'tools that add steps instead of removing them', chip: 'pain', color: 'var(--app-fail)' },\n          { title: 'Awareness stage', meta: 'solution-aware · knows agents exist, distrusts demos', chip: 'stage 3', color: 'var(--app-warn)' }\n        ]),\n        'This piece': table('This piece', 'Per-run parameters', [C('Field', 1.6), C('Value', 2.6)], [\n          row([mono('channel', { flex: 1.6 }), tCell('.blog long-form', { flex: 2.6 })]),\n          row([mono('content type', { flex: 1.6 }), tCell('explainer with a worked example', { flex: 2.6 })]),\n          row([mono('language', { flex: 1.6 }), tCell('English (en-GB)', { flex: 2.6 })]),\n          row([mono('length · hard', { flex: 1.6 }), tCell('1,400–1,800 words', { flex: 2.6 })])\n        ]),\n        CTA: list('Call to action', 'One CTA per piece, always concrete', [\n          { title: 'Policy', meta: 'exactly one CTA · never mid-article · no urgency language', chip: 'policy', color: 'var(--app-accent)' },\n          { title: 'Mechanic', meta: 'inline link in the closing paragraph', chip: 'set', color: 'var(--app-success)' },\n          { title: 'Destination', meta: 'agents-as-a-service.com/activate', chip: 'set', color: 'var(--app-success)' },\n          { title: 'UTM template', meta: 'utm_source=blog&utm_campaign={slug}', chip: 'set', color: 'var(--app-success)' }\n        ]),\n        Guardrails: list('Guardrails', 'A run fails its own gate rather than break these', [\n          { title: 'Banned words · 31', meta: 'unlock · harness · revolutionize · magic · powered by AI', chip: 'hard', color: 'var(--app-fail)' },\n          { title: 'Banned devices', meta: 'rhetorical question openers · listicles without a thesis', chip: 'hard', color: 'var(--app-fail)' },\n          { title: 'Required · always', meta: 'one worked example with real numbers', chip: 'hard', color: 'var(--app-success)' },\n          { title: 'Freshness', meta: 'no source older than 90 days · claims must cite', chip: '90d', color: 'var(--app-warn)' }\n        ]),\n        'Assembled prompt': tail('Assembled prompt', 'Every field above compiled into the exact prompt the run receives', 'aaas://protocol/assembled', [\n          { time: '01', prefix: '[id]', msg: 'AaaS · canonical facts docs/brand/facts.md · byline Adam Lundqvist', color: 'var(--app-accent)' },\n          { time: '02', prefix: '[voice]', msg: '5 pillars · professional peer · grade 9 · one idea per sentence', color: 'var(--app-accent)' },\n          { time: '03', prefix: '[aud]', msg: 'Indie builders · solution-aware · pain: tools that add steps', color: 'var(--app-accent)' },\n          { time: '04', prefix: '[piece]', msg: '.blog explainer · en-GB · 1,400–1,800 words hard', color: 'var(--app-accent)' },\n          { time: '05', prefix: '[cta]', msg: 'One inline CTA, closing paragraph → /activate + UTM', color: 'var(--app-accent)' },\n          { time: '06', prefix: '[guard]', msg: '31 banned words · no rhetorical openers · cite < 90d · one worked example', color: 'var(--app-fail)' },\n          { time: '07', prefix: '[out]', msg: '3 variants · markdown + front-matter · self-audit gate on', color: 'var(--app-success)' },\n          { time: '08', prefix: '[gate]', msg: '2 of 3 variants passed · B failed the citation check', color: 'var(--app-warn)' }\n        ]),\n        Registry: list('Registry', 'The protocol is versioned and synced — every run records which version produced it', [\n          { title: 'Protocol version', meta: 'v4 · synced 4m ago · workspace aaas', chip: 'v4', color: 'var(--app-success)' },\n          { title: 'Bound to token contract', meta: 'design v4 — voice and visual rules move together', chip: 'linked', color: 'var(--app-accent)' },\n          { title: 'Consumers', meta: 'podcast · content · marketing · deliverables — 4 streams read it', chip: '4', color: 'var(--app-accent)' },\n          { title: 'Change control', meta: 'edits are proposals; hold-to-publish writes a new version', chip: 'gated', color: 'var(--app-warn)' }\n        ]),\n        Output: list('Output', 'What comes back, and how it is checked', [\n          { title: 'Variants · 3', meta: 'same thesis, three openings', chip: '3', color: 'var(--app-accent)' },\n          { title: 'Format', meta: 'markdown + front-matter, images referenced not embedded', chip: 'md', color: 'var(--app-accent)' },\n          { title: 'Self-audit gate', meta: 'banned-word scan, citation check, length check', chip: '92% pass', color: 'var(--app-success)' },\n          { title: 'Gate-passing variants', meta: '2 of 3 · variant B failed the citation check', chip: '2 / 3', color: 'var(--app-warn)' }\n        ])\n      },\n      context: {\n        Foundation: list('Foundation', 'Facts every package inherits', [\n          { title: 'Company facts', meta: '48 canonical statements · reviewed 04-18', chip: 'locked', color: 'var(--app-accent)' },\n          { title: 'Product truth', meta: '15 surfaces · pricing · limits', chip: 'locked', color: 'var(--app-accent)' },\n          { title: 'Do-not-say list', meta: 'shared with the production protocol', chip: 'linked', color: 'var(--app-warn)' }\n        ]),\n        Sources: table('Sources', '2,310 documents indexed · 88% coverage', [C('Source', 2.4), C('Docs', 1), C('Freshness', 1.2), C('State', 1, 'right')], [\n          row([tCell('Notion workspace', { flex: 2.4 }), mono('1,204', { flex: 1 }), mono('4m ago', { flex: 1.2 }), mono('synced', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('GitHub · docs/', { flex: 2.4 }), mono('612', { flex: 1 }), mono('1h ago', { flex: 1.2 }), mono('synced', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Drive · client folders', { flex: 2.4 }), mono('388', { flex: 1 }), mono('2d ago', { flex: 1.2 }), mono('stale', { flex: 1, align: 'right', color: 'var(--app-warn)' })]),\n          row([tCell('Support inbox', { flex: 2.4 }), mono('106', { flex: 1 }), mono('12m ago', { flex: 1.2 }), mono('synced', { flex: 1, align: 'right', color: 'var(--app-success)' })])\n        ]),\n        Browse: cards('Browse', 'Search-first — entities, documents and chunks', [\n          { title: 'entities', tag: '18.4k', meta: 'People, companies, products and their relations.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'documents', tag: '2,310', meta: 'Source documents with provenance and freshness.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'chunks', tag: '41.2k', meta: 'Embedded passages, 512 tokens, overlap 64.', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        Assembly: tail('Assembly', 'How the last package was built', 'aaas://context/assembly', [\n          { time: '00.0s', prefix: '[sel]', msg: 'Query → 312 candidate chunks', color: 'var(--app-accent)' },\n          { time: '00.4s', prefix: '[rnk]', msg: 'Reranked → top 48 kept (conf 0.91)', color: 'var(--app-accent)' },\n          { time: '01.1s', prefix: '[dedupe]', msg: '7 near-duplicates dropped', color: 'var(--app-warn)' },\n          { time: '02.0s', prefix: '[pack]', msg: 'Package 18k tokens · 12 skills attached', color: 'var(--app-success)' },\n          { time: '02.0s', prefix: '[aud]', msg: 'Every claim carries a source id', color: 'var(--pastel-lavender)' }\n        ])\n      },\n      // ── tabs added to existing streams ──\n      _design_extra: {\n        Catalog: table('Catalog', '11 components · 1 added this week · 6 in review', [C('Component', 2), C('Variants', 1.2), C('State', 1), C('PSI gate', 1, 'right')], [\n          row([tCell('Button', { flex: 2 }), mono('4 · 3 sizes', { flex: 1.2 }), mono('shipped', { flex: 1, color: 'var(--app-success)' }), mono('pass', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Card', { flex: 2 }), mono('3 · spotlight', { flex: 1.2 }), mono('shipped', { flex: 1, color: 'var(--app-success)' }), mono('pass', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Terminal', { flex: 2 }), mono('live · cursor', { flex: 1.2 }), mono('in review', { flex: 1, color: 'var(--app-warn)' }), mono('pending', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        Studio: list('Studio', 'Where a token change is tried before it ships', [\n          { title: 'Accent trial · circuit at 0.92 luminance', meta: 'AA contrast holds on basalt-deep · 4 surfaces previewed', chip: 'trial', color: 'var(--app-warn)' },\n          { title: 'Radius sweep · 0.75rem vs 0.6rem', meta: 'buttons only · shipped as --radius-btn', chip: 'shipped', color: 'var(--app-success)' },\n          { title: 'Grain opacity 1.5% → 1.2%', meta: 'rejected — texture disappears on light theme', chip: 'rejected', color: 'var(--app-fail)' }\n        ]),\n        'Brand book': list('Brand book', 'The written half of the system', [\n          { title: 'Logos', meta: 'black + white PNG · hex orb glyph · never recoloured', chip: 'locked', color: 'var(--app-accent)' },\n          { title: 'Voice + tone', meta: 'technical · confident · terse · use/don\\'t-use table', chip: 'locked', color: 'var(--app-accent)' },\n          { title: 'Palette', meta: 'two co-leads, three supportive, basalt surfaces', chip: 'locked', color: 'var(--app-accent)' },\n          { title: 'Contract version', meta: 'v4 · consumers pin this in ds-base.js', chip: 'v4', color: 'var(--app-success)' }\n        ])\n      },\n      _quality_extra: {\n        'Delivery health': table('Delivery health', 'DORA-style delivery metrics', [C('Metric', 2), C('Now', 1, 'right'), C('30d', 1, 'right'), C('Trend', 1, 'right')], [\n          row([tCell('Deploy frequency', { flex: 2 }), mono('3.1 / day', { flex: 1, align: 'right' }), mono('2.6 / day', { flex: 1, align: 'right' }), mono('up', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Lead time', { flex: 2 }), mono('9h', { flex: 1, align: 'right' }), mono('14h', { flex: 1, align: 'right' }), mono('down', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Change-fail rate', { flex: 2 }), mono('4%', { flex: 1, align: 'right' }), mono('6%', { flex: 1, align: 'right' }), mono('down', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('MTTR', { flex: 2 }), mono('38m', { flex: 1, align: 'right' }), mono('52m', { flex: 1, align: 'right' }), mono('down', { flex: 1, align: 'right', color: 'var(--app-success)' })])\n        ]),\n        'Review queue': list('Review queue', '14 open · moderation, abuse, support and issues in one queue', [\n          { title: 'Moderation · flagged .pics upload', meta: 'auto-flag · 2h · needs a human call', chip: 'moderation', color: 'var(--app-warn)' },\n          { title: 'Abuse · repeated signup from one IP', meta: 'rate-limiter tripped 14×', chip: 'abuse', color: 'var(--app-fail)' },\n          { title: 'Support chat · billing question', meta: 'waiting 41m · Pro customer', chip: 'support', color: 'var(--app-warn)' },\n          { title: 'GH issue #218 · replay export', meta: 'triaged to product · P2', chip: 'gh issues', color: 'var(--app-accent)' }\n        ]),\n        'Product health': table('Product health', 'What users actually experience', [C('Signal', 2), C('Value', 1, 'right'), C('Target', 1, 'right')], [\n          row([tCell('NPS', { flex: 2 }), mono('61', { flex: 1, align: 'right', color: 'var(--app-success)' }), mono('55', { flex: 1, align: 'right' })]),\n          row([tCell('PSI mobile', { flex: 2 }), mono('92', { flex: 1, align: 'right', color: 'var(--app-success)' }), mono('90', { flex: 1, align: 'right' })]),\n          row([tCell('PSI desktop', { flex: 2 }), mono('98', { flex: 1, align: 'right', color: 'var(--app-success)' }), mono('95', { flex: 1, align: 'right' })]),\n          row([tCell('Error budget burn', { flex: 2 }), mono('18%', { flex: 1, align: 'right' }), mono('< 50%', { flex: 1, align: 'right' })])\n        ])\n      },\n      _costs_extra: {\n        LLM: table('LLM', '€1,464 MTD · 61% of platform spend', [C('Model', 2), C('Tokens', 1.2, 'right'), C('MTD', 1, 'right'), C('Share', 1, 'right')], [\n          row([tCell('sonnet 4.6', { flex: 2 }), mono('19.4M', { flex: 1.2, align: 'right' }), mono('€842', { flex: 1, align: 'right' }), mono('58%', { flex: 1, align: 'right' })]),\n          row([tCell('opus 4.6', { flex: 2 }), mono('2.1M', { flex: 1.2, align: 'right' }), mono('€498', { flex: 1, align: 'right', color: 'var(--app-fail)' }), mono('34%', { flex: 1, align: 'right' })]),\n          row([tCell('embeddings', { flex: 2 }), mono('41.2M', { flex: 1.2, align: 'right' }), mono('€124', { flex: 1, align: 'right' }), mono('8%', { flex: 1, align: 'right' })])\n        ]),\n        GCP: table('GCP', '€612 MTD · fleet compute and storage', [C('Service', 2), C('Usage', 1.4, 'right'), C('MTD', 1, 'right')], [\n          row([tCell('Cloud Run', { flex: 2 }), mono('1,842 vCPU-h', { flex: 1.4, align: 'right' }), mono('€388', { flex: 1, align: 'right' })]),\n          row([tCell('Firestore', { flex: 2 }), mono('12.4M reads', { flex: 1.4, align: 'right' }), mono('€132', { flex: 1, align: 'right' })]),\n          row([tCell('Storage', { flex: 2 }), mono('1.4 TB', { flex: 1.4, align: 'right' }), mono('€92', { flex: 1, align: 'right' })])\n        ]),\n        Stripe: table('Stripe', '€238 MTD in fees · 3 refunds', [C('Line', 2.4), C('Count', 1, 'right'), C('MTD', 1, 'right')], [\n          row([tCell('Processing fees', { flex: 2.4 }), mono('148', { flex: 1, align: 'right' }), mono('€214', { flex: 1, align: 'right' })]),\n          row([tCell('Refunds', { flex: 2.4 }), mono('3', { flex: 1, align: 'right' }), mono('€18', { flex: 1, align: 'right', color: 'var(--app-fail)' })]),\n          row([tCell('Disputes', { flex: 2.4 }), mono('1', { flex: 1, align: 'right' }), mono('€6', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        'Rev-rec': table('Rev-rec', 'Recognised vs deferred revenue', [C('Bucket', 2.4), C('MTD', 1, 'right'), C('Deferred', 1, 'right')], [\n          row([tCell('Subscriptions', { flex: 2.4 }), mono('€14,200', { flex: 1, align: 'right' }), mono('€3,800', { flex: 1, align: 'right' })]),\n          row([tCell('Credit packs', { flex: 2.4 }), mono('€2,400', { flex: 1, align: 'right' }), mono('€8,120', { flex: 1, align: 'right' })]),\n          row([tCell('Retainers', { flex: 2.4 }), mono('€1,600', { flex: 1, align: 'right' }), mono('€0', { flex: 1, align: 'right' })])\n        ]),\n        Subscriptions: table('Subscriptions', 'Vendor subscriptions charged to the platform', [C('Vendor', 2), C('Plan', 1.6), C('Renews', 1.2, 'right'), C('Month', 1, 'right')], [\n          row([tCell('Anthropic', { flex: 2 }), mono('scale', { flex: 1.6 }), mono('06-14', { flex: 1.2, align: 'right' }), mono('€1,464', { flex: 1, align: 'right' })]),\n          row([tCell('Typesense Cloud', { flex: 2 }), mono('standard', { flex: 1.6 }), mono('06-02', { flex: 1.2, align: 'right' }), mono('€89', { flex: 1, align: 'right' })]),\n          row([tCell('Figma', { flex: 2 }), mono('org · 4 seats', { flex: 1.6 }), mono('06-21', { flex: 1.2, align: 'right' }), mono('€60', { flex: 1, align: 'right' })])\n        ])\n      },\n      _finances_extra: {\n        // Credits · Invoice detail · EÜR report render natively (kind: 'fin') — the fin- spec.\n        Subscriptions: table('Subscriptions', 'Recurring revenue by plan', [C('Plan', 2), C('Accounts', 1, 'right'), C('MRR', 1, 'right'), C('Churn 30d', 1, 'right')], [\n          row([tCell('Operator', { flex: 2 }), mono('2', { flex: 1, align: 'right' }), mono('€8,400', { flex: 1, align: 'right' }), mono('0', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Pro', { flex: 2 }), mono('31', { flex: 1, align: 'right' }), mono('€6,169', { flex: 1, align: 'right' }), mono('1', { flex: 1, align: 'right', color: 'var(--app-warn)' })]),\n          row([tCell('Starter', { flex: 2 }), mono('74', { flex: 1, align: 'right' }), mono('€3,626', { flex: 1, align: 'right' }), mono('4', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        Settings: list('Finance settings', 'How the books are kept', [\n          { title: 'Method', meta: 'EÜR — cash basis · EUR · Kleinunternehmer: no', chip: 'set', color: 'var(--app-accent)' },\n          { title: 'Invoice numbering', meta: '{year}-{seq} · next 2026-122', chip: 'set', color: 'var(--app-accent)' },\n          { title: 'Payment terms', meta: 'net 14 · reminder at +3, +10, +21 days', chip: 'set', color: 'var(--app-accent)' },\n          { title: 'Bookkeeper access', meta: 'kanzlei@steuer.de · finance role · read + export', chip: 'granted', color: 'var(--app-success)' }\n        ])\n      },\n      _platform_extra: {\n        Integrations: table('Integrations', '8 connected · 1 needs attention', [C('Integration', 2), C('Scope', 2), C('State', 1, 'right')], [\n          row([tCell('GitHub', { flex: 2 }), mono('repos · PRs · checks', { flex: 2 }), mono('ok', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Stripe', { flex: 2 }), mono('billing · webhooks', { flex: 2 }), mono('ok', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Slack', { flex: 2 }), mono('alerts · /run', { flex: 2 }), mono('ok', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('OpenRouter', { flex: 2 }), mono('fallback models', { flex: 2 }), mono('quota low', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ])\n      },\n      _select_extra: {\n        'Needs review': list('Needs review', '4 decisions the system will not close on its own', [\n          { title: '#4825 · vendor match ambiguous', meta: 'two candidates within 0.02 confidence', chip: 'review', color: 'var(--app-warn)' },\n          { title: '#4819 · invoice above auto-post limit', meta: '€8,400 · policy caps auto-post at €5,000', chip: 'review', color: 'var(--app-warn)' },\n          { title: '#4811 · new vendor, no history', meta: 'first invoice from Postiz BV', chip: 'review', color: 'var(--app-warn)' },\n          { title: '#4802 · duplicate suspicion', meta: '92% match with #4788', chip: 'review', color: 'var(--app-fail)' }\n        ])\n      },\n      _intelligence_extra: {\n        // I-3/I-4 (2026-09-05): Graph/_VaultLensRetired/NotebookLM/Audit removed — none matched a\n        // real tab label ('Entity graph'/'Vault'/'NotebookLM sync'/'Query audit'), confirmed dead.\n      },\n      _product_extra: {\n        Pipelines: table('Pipelines', 'Standing pipelines and what is in each', [C('Pipeline', 2.2), C('In flight', 1, 'right'), C('Blocked', 1, 'right'), C('Spend today', 1, 'right')], [\n          row([tCell('content → publish', { flex: 2.2 }), mono('6', { flex: 1, align: 'right' }), mono('1', { flex: 1, align: 'right', color: 'var(--app-warn)' }), mono('€12', { flex: 1, align: 'right' })]),\n          row([tCell('lead → outreach', { flex: 2.2 }), mono('8', { flex: 1, align: 'right' }), mono('2', { flex: 1, align: 'right', color: 'var(--app-warn)' }), mono('€18', { flex: 1, align: 'right' })]),\n          row([tCell('invoice → ledger', { flex: 2.2 }), mono('5', { flex: 1, align: 'right' }), mono('0', { flex: 1, align: 'right', color: 'var(--app-success)' }), mono('€11', { flex: 1, align: 'right' })])\n        ]),\n        Work: table('Work', '19 items in flight across backlog, tasks and threads', [C('Item', 2.8), C('Type', 1), C('Owner', 1.2), C('State', 1, 'right')], [\n          row([tCell('Replay export spec', { flex: 2.8 }), mono('backlog', { flex: 1 }), mono('product', { flex: 1.2 }), mono('next', { flex: 1, align: 'right' })]),\n          row([tCell('Wire engine C8 run API', { flex: 2.8 }), mono('cc-task', { flex: 1 }), mono('claude-code', { flex: 1.2 }), mono('running', { flex: 1, align: 'right', color: 'var(--app-running)' })]),\n          row([tCell('Helsing pilot scope thread', { flex: 2.8 }), mono('thread', { flex: 1 }), mono('sales', { flex: 1.2 }), mono('open', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        Sales: table('Sales', 'Deals and orders the product side is on the hook for', [C('Deal / order', 2.6), C('Stage', 1.2), C('Value', 1, 'right'), C('Close', 1, 'right')], [\n          row([tCell('Helsing — pilot expansion', { flex: 2.6 }), mono('proposal', { flex: 1.2 }), mono('€24k', { flex: 1, align: 'right' }), mono('06-30', { flex: 1, align: 'right' })]),\n          row([tCell('Studio Nord — retainer renewal', { flex: 2.6 }), mono('verbal yes', { flex: 1.2 }), mono('€50k', { flex: 1, align: 'right' }), mono('07-15', { flex: 1, align: 'right' })]),\n          row([tCell('Order · credit pack ×3', { flex: 2.6 }), mono('fulfilled', { flex: 1.2 }), mono('€597', { flex: 1, align: 'right' }), mono('paid', { flex: 1, align: 'right', color: 'var(--app-success)' })])\n        ])\n      },\n      _marketing_extra: {\n        Social: table('Social', 'Batch queue across channels — schedule, approve, publish', [C('Post', 2.6), C('Channel', 1.2), C('Slot', 1.2), C('State', 1, 'right')], [\n          row([tCell('Relay handoff — 3-part thread', { flex: 2.6 }), mono('x', { flex: 1.2 }), mono('today 17:00', { flex: 1.2 }), mono('approved', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Cost per decision −18%', { flex: 2.6 }), mono('linkedin', { flex: 1.2 }), mono('tomorrow 09:00', { flex: 1.2 }), mono('needs approval', { flex: 1, align: 'right', color: 'var(--app-warn)' })]),\n          row([tCell('Episode 128 clip', { flex: 2.6 }), mono('x', { flex: 1.2 }), mono('queued', { flex: 1.2 }), mono('rate-limited', { flex: 1, align: 'right', color: 'var(--app-fail)' })])\n        ]),\n        Email: list('Email', 'Funnel, variants, reputation and the follow-up ladder', [\n          { title: 'Funnel · activate', meta: 'welcome → feature launch → digest · 3 sends', chip: 'live', color: 'var(--app-success)' },\n          { title: 'Variants', meta: 'subject A/B on relay-launch · A leads 4.1% vs 3.2%', chip: 'testing', color: 'var(--app-accent)' },\n          { title: 'Reputation', meta: 'SPF/DKIM/DMARC pass · complaint rate 0.02%', chip: 'healthy', color: 'var(--app-success)' },\n          { title: 'Follow-up ladder', meta: '3 steps · stops on reply or click-through', chip: 'set', color: 'var(--app-accent)' }\n        ])\n      },\n      _content_extra: {\n        Viewer: cards('Viewer', 'Two reading modes, same content record', [\n          { title: 'Readable', tag: 'text-first', meta: 'Long-form column, 68ch measure, no chrome.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Rich media', tag: 'embeds', meta: 'Audio player, image packs and code blocks inline.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Provenance', tag: 'always', meta: 'Every claim links its source id from the context package.', tagColor: 'rgb(var(--accent-teal) / 0.5)' }\n        ])\n      },\n      _podcast_extra: {\n        Prompts: list('Prompts', 'The chain that turns sources into an episode', [\n          { title: 'Score sources', meta: 'rank 42 feeds → top 8 items', chip: 'step 1', color: 'var(--app-accent)' },\n          { title: 'Draft script', meta: 'production protocol applied · 5 voice pillars', chip: 'step 2', color: 'var(--app-accent)' },\n          { title: 'Tighten', meta: 'cut to 5 minutes · one idea per sentence', chip: 'step 3', color: 'var(--app-accent)' },\n          { title: 'Self-audit', meta: 'banned words, citations, length', chip: 'gate', color: 'var(--app-warn)' }\n        ]),\n        Generate: tail('Generate', 'Last build · episode 129', 'aaas://podcast/generate', [\n          { time: '00.0s', prefix: '[src]', msg: '42 feeds swept · 128 items scored', color: 'var(--app-accent)' },\n          { time: '00.9s', prefix: '[gen]', msg: 'Script drafted · 812 words', color: 'var(--app-accent)' },\n          { time: '02.1s', prefix: '[gate]', msg: 'Self-audit passed · 0 banned words', color: 'var(--app-success)' },\n          { time: '03.2s', prefix: '[tts]', msg: 'Voice rendered 5m 12s · €0.14', color: 'var(--app-success)' }\n        ]),\n        Episode: list('Episode 129', 'Ready for your approval', [\n          { title: 'Context packages ship to every surface', meta: '5:12 · 812 words · 8 sources cited', chip: 'draft', color: 'var(--app-warn)' },\n          { title: 'Cold open', meta: '“Twelve seconds. That is how long a context package takes.”', chip: 'copy', color: 'var(--app-accent)' },\n          { title: 'Sources', meta: '8 links · freshest 2h, oldest 3d', chip: 'ok', color: 'var(--app-success)' }\n        ]),\n        Publish: list('Publish', 'Where episode 129 goes when you approve', [\n          { title: 'aaas.today', meta: 'primary · RSS + web player', chip: 'ready', color: 'var(--app-success)' },\n          { title: 'Newsletter', meta: '12.4k subscribers · same script, read version', chip: 'ready', color: 'var(--app-success)' },\n          { title: 'Social clips', meta: '3 clips cut · x + linkedin', chip: 'queued', color: 'var(--app-warn)' },\n          { title: 'Changelog', meta: 'skipped — no product change this episode', chip: 'skip', color: 'var(--app-ink-faint)' }\n        ])\n      }\n    };\n    Object.assign(this.DEV_PANES.design, IMPORT_PANES._design_extra);\n    Object.assign(this.DEV_PANES.quality, IMPORT_PANES._quality_extra);\n    Object.assign(this.DEV_PANES.costs, IMPORT_PANES._costs_extra);\n    Object.assign(this.DEV_PANES.finances, IMPORT_PANES._finances_extra);\n    Object.assign(this.DEV_PANES.platform, IMPORT_PANES._platform_extra);\n    Object.assign(this.DEV_PANES.select, IMPORT_PANES._select_extra);\n    Object.assign(this.DEV_PANES.intelligence, IMPORT_PANES._intelligence_extra);\n    Object.assign(this.DEV_PANES.product, IMPORT_PANES._product_extra);\n    Object.assign(this.DEV_PANES.marketing, IMPORT_PANES._marketing_extra);\n    Object.assign(this.DEV_PANES.content, IMPORT_PANES._content_extra);\n    Object.assign(this.DEV_PANES.podcast, IMPORT_PANES._podcast_extra);\n    // Podcast's Overview / Sources / Prompts / Episodes render as the native `pd` pane.\n    // Podcast/Design/Costs/Inbox tab ownership is now declared via STREAMS.<id>.ownsTabs (WP-06),\n    // which bypasses the spec unconditionally — no runtime deletion, no merge-order dependency.\n    this.DEV_PANES.design.Catalog = this.DEV_PANES.design.Catalog || this.DEV_PANES.design.Components;\n    ['projects', 'deliverables', 'billing', 'team', 'protocol', 'context'].forEach(id => { this.DEV_PANES[id] = IMPORT_PANES[id]; });\n\n\n    // ══ Second import pass — panes from the remaining handoffs ══\n    const bars = (title, note, bs) => ({ view: 'bars', title, note, bars: bs });\n    const IMPORT2_PANES = {\n      onboardingV2: {\n        Connect: cards('Layer 01 · Connect', 'Sign in to connect — OAuth or key, nothing is written until you approve', [\n          { title: 'Stripe', tag: 'oauth', meta: 'Charges, invoices, payouts — read + webhook.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'HubSpot', tag: 'oauth', meta: 'Contacts, deals and pipeline stages.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Notion', tag: 'oauth', meta: 'Workspace pages become context documents.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Google Ads', tag: 'oauth', meta: 'Campaigns, spend and conversion actions.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Salesforce', tag: 'oauth', meta: 'Accounts, opportunities and activity.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Custom REST', tag: 'key', meta: 'Any JSON endpoint with an auth header.', tagColor: 'rgb(var(--pastel-gold) / 0.5)' }\n        ]),\n        Contextualize: tail('Layer 02 · Contextualize', 'What the engine does the moment an API is connected', 'aaas://onboarding/contextualize', [\n          { time: '00.0s', prefix: '[api]', msg: 'Stripe connected · 4,120 objects discovered', color: 'var(--app-accent)' },\n          { time: '00.8s', prefix: '[map]', msg: 'Customers → entities · invoices → facts', color: 'var(--app-accent)' },\n          { time: '02.4s', prefix: '[emb]', msg: '2,310 documents embedded', color: 'var(--app-accent)' },\n          { time: '03.1s', prefix: '[ctx]', msg: 'Context package ready · 18k tokens', color: 'var(--app-success)' }\n        ]),\n        Automate: list('Layer 03 · Automate', 'What runs once the context exists', [\n          { title: 'Spawn a demo agent', meta: 'rate-limited · 3 turns · no card needed', chip: 'demo', color: 'var(--app-accent)' },\n          { title: 'Daily digest', meta: 'one brief a day from your own data', chip: 'automation', color: 'var(--app-accent)' },\n          { title: 'Run alerts', meta: 'ping when a run blocks or fails', chip: 'automation', color: 'var(--app-accent)' },\n          { title: 'Create an account', meta: 'keeps the context you just built', chip: 'next', color: 'var(--app-success)' }\n        ]),\n        'Intro tour': list('Intro tour', 'The five-step value tour — it opens as an overlay on first visit, and the ? button re-opens it', [\n          { title: '1 · Connect any API. The engine does the rest.', meta: 'Connect → Contextualize → Automate, the whole loop in one frame', chip: 'step', color: 'var(--app-accent)' },\n          { title: '2 · Point the canvas at any API', meta: 'Layer 01 · Connect — analytics, ads, commerce, product, finance, or any REST', chip: 'step', color: 'var(--app-accent)' },\n          { title: '3 · The engine combines everything', meta: 'Layer 02 · Contextualize — one live context, deduplicated and current', chip: 'step', color: 'var(--app-accent)' },\n          { title: '4 · AI runs the operation', meta: 'Layer 03 · Automate — agents on that context, cost and decisions visible', chip: 'step', color: 'var(--app-accent)' },\n          { title: '5 · Connect your first API', meta: 'closes on Platform → Integrations · ?intro=1 replays it', chip: 'step', color: 'var(--app-success)' }\n        ]),\n        'First run': list('First run', 'Under five minutes from landing to output', [\n          { title: 'Confirm your account', meta: 'workspace name + billing account', chip: 'done', color: 'var(--app-success)' },\n          { title: 'Pick your services', meta: '3 of 6 connected', chip: 'in progress', color: 'var(--app-warn)' },\n          { title: 'Spawn your first run', meta: 'demo agent · no credits charged', chip: 'next', color: 'var(--app-accent)' },\n          { title: 'Land on Home', meta: 'your inbox across every stream', chip: 'then', color: 'var(--app-ink-faint)' }\n        ])\n      },\n      growth: {\n        // ── ARCHIVED 2026-08-06 · superseded, kept not deleted ─────────────────────────\n        // These 6 static lens panes were the old Growth Fleet: hand-written tables with\n        // FABRICATED numbers that did not match the source patch\n        // (05_Canvas/system-growth-dashboards-patch/app/kinds-inventory-growth.jsx).\n        // The stream is now a native branch rendering PaneGrowth.dc.html, which ports the\n        // source verbatim and derives its counts. Kept per the archive-don't-delete rule;\n        // superseded, NOT spare parts - do not re-point tabs at them.\n        'System inventory': table('System inventory', '% greenfield 34% · what exists, at what maturity', [C('Asset', 2.4), C('Layer', 1.4), C('Domain', 1.2), C('Maturity', 1, 'right')], [\n          row([tCell('Data backbone', { flex: 2.4 }), mono('infra', { flex: 1.4 }), mono('platform', { flex: 1.2 }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Compute + identity', { flex: 2.4 }), mono('infra', { flex: 1.4 }), mono('platform', { flex: 1.2 }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Canvas 2.0', { flex: 2.4 }), mono('surface', { flex: 1.4 }), mono('canvas', { flex: 1.2 }), mono('built', { flex: 1, align: 'right', color: 'var(--app-accent)' })]),\n          row([tCell('aaas.blog', { flex: 2.4 }), mono('surface', { flex: 1.4 }), mono('content', { flex: 1.2 }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('aaa engine', { flex: 2.4 }), mono('engine', { flex: 1.4 }), mono('platform', { flex: 1.2 }), mono('scaffolded', { flex: 1, align: 'right', color: 'var(--app-warn)' })]),\n          row([tCell('secrets / IAM', { flex: 2.4 }), mono('infra', { flex: 1.4 }), mono('platform', { flex: 1.2 }), mono('planned', { flex: 1, align: 'right', color: 'var(--app-ink-faint)' })])\n        ]),\n        'Ops console': table('Ops console', 'Agents on the growth fleet right now', [C('Agent', 2), C('Status', 1.2), C('Metric', 1.6), C('Spend', 1, 'right')], [\n          row([tCell('scout-agt', { flex: 2 }), mono('running', { flex: 1.2, color: 'var(--app-running)' }), mono('142 prospects / h', { flex: 1.6 }), mono('€18', { flex: 1, align: 'right' })]),\n          row([tCell('send-agt', { flex: 2 }), mono('running', { flex: 1.2, color: 'var(--app-running)' }), mono('1,240 sends today', { flex: 1.6 }), mono('€9', { flex: 1, align: 'right' })]),\n          row([tCell('warmup-agt', { flex: 2 }), mono('idle', { flex: 1.2 }), mono('6 inboxes warming', { flex: 1.6 }), mono('€2', { flex: 1, align: 'right' })])\n        ]),\n        'Send console': table('Send console', '1,240 sends today · bounce 1.8% · cap 2,000/day', [C('Send', 2.2), C('Inbox', 1.6), C('Sent', 1, 'right'), C('Bounce', 1, 'right')], [\n          row([tCell('cold-email · pr-1039', { flex: 2.2 }), mono('jorian@aaas.love', { flex: 1.6 }), mono('412', { flex: 1, align: 'right' }), mono('1.2%', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('relay-launch', { flex: 2.2 }), mono('news@aaas.love', { flex: 1.6 }), mono('748', { flex: 1, align: 'right' }), mono('2.1%', { flex: 1, align: 'right', color: 'var(--app-warn)' })]),\n          row([tCell('warmup pool', { flex: 2.2 }), mono('6 inboxes', { flex: 1.6 }), mono('80', { flex: 1, align: 'right' }), mono('0.0%', { flex: 1, align: 'right', color: 'var(--app-success)' })])\n        ]),\n        Analytics: bars('Analytics', 'Visit → signup → activate → first send → paid', [\n          { label: 'Visit', value: '12,400', meta: '100%', pct: 100, color: 'var(--app-accent)' },\n          { label: 'Signup', value: '1,860', meta: '15%', pct: 15, color: 'var(--app-accent)' },\n          { label: 'Activate', value: '742', meta: '6%', pct: 6, color: 'var(--app-warn)' },\n          { label: 'First send', value: '318', meta: '2.6%', pct: 3, color: 'var(--app-warn)' },\n          { label: 'Paid', value: '96', meta: '0.8%', pct: 1, color: 'var(--app-success)' }\n        ]),\n        'Content calendar': table('Content calendar', '9 scheduled this week · 3 awaiting review', [C('Piece', 2.6), C('Channel', 1.2), C('Slot', 1.2), C('Gate', 1, 'right')], [\n          row([tCell('Context packages, explained', { flex: 2.6 }), mono('.blog', { flex: 1.2 }), mono('Wed 09:00', { flex: 1.2 }), mono('approved', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Episode 129', { flex: 2.6 }), mono('.today', { flex: 1.2 }), mono('Tue 06:05', { flex: 1.2 }), mono('review', { flex: 1, align: 'right', color: 'var(--app-warn)' })]),\n          row([tCell('Cost per decision −18%', { flex: 2.6 }), mono('linkedin', { flex: 1.2 }), mono('Thu 09:00', { flex: 1.2 }), mono('review', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        'Experiment ratchet': list('Experiment ratchet', 'A/B lift only ratchets up — losers are reverted automatically', [\n          { title: 'Subject line A vs B', meta: 'relay-launch · +0.9pp CTR · 96% confidence', chip: 'ratcheted', color: 'var(--app-success)' },\n          { title: 'Landing hero — three variants', meta: 'variant C · +2.1pp signup', chip: 'ratcheted', color: 'var(--app-success)' },\n          { title: 'Send window 09:00 vs 17:00', meta: 'inconclusive after 2 weeks · reverted', chip: 'reverted', color: 'var(--app-warn)' }\n        ])\n      },\n      websites: {\n        Overview: { view: 'frames', segs: [{ id: 'sports', label: 'Sports vertical' }, { id: 'aaas', label: 'AaaS estate' }], title: 'Website templates · live pages', note: 'The AaaS estate and the sports vertical re-cut from it. Open ↗ for full size.',\n          frames: [{ id: 'W1', grp: 'sports', label: 'sports.love', meta: 'Sports universe home · from aaas.love', w: 1440, h: 2600, src: '../domains/sports-love/AaaS%20Love%20Home.dc.html' },\n            { id: 'W2', grp: 'sports', label: 'sports.diy', meta: 'Sport video → training packages · from aaas.diy', w: 1440, h: 2000, src: '../domains/sports-diy/AaaS%20DIY%20Pages.dc.html' },\n            { id: 'W3', grp: 'sports', label: 'sports.blog', meta: 'Sport knowledge base · from aaas.blog', w: 1440, h: 2400, src: '../domains/sports-blog/AaaS%20Knowledge%20Index.dc.html' },\n            { id: 'W4', grp: 'sports', label: 'sports.builders', meta: 'Sport-business agents · from aaas.builders', w: 1440, h: 2600, src: '../domains/sports-builders/aaas.builders.dc.html' },\n            { id: 'W5', grp: 'sports', label: 'sports.store', meta: 'Sports articles · from store.aaas.love', w: 1440, h: 2200, src: '../domains/sports-store/index.html' }].concat((this.DOMAIN_SITES || []).map((d, i) => ({ id: 'A' + (i + 1), grp: 'aaas', label: d.tld, meta: d.label + ' · AaaS estate', w: 1440, h: 2200, src: '../' + encodeURI(String(d.pages[0].file).replace(/^assets\\//, '')) }))) },\n        'sports.love': { view: 'stack', title: 'sports.love', note: 'Sports universe home · from aaas.love', frames: [{ id: 'W1', grp: 'sports', label: 'sports.love', meta: 'Sports universe home · from aaas.love', w: 1440, h: 2600, src: '../domains/sports-love/AaaS%20Love%20Home.dc.html' }] },\n        'sports.diy': { view: 'stack', title: 'sports.diy', note: 'Sport video → training packages · from aaas.diy', frames: [{ id: 'W2', grp: 'sports', label: 'sports.diy', meta: 'Sport video → training packages · from aaas.diy', w: 1440, h: 2000, src: '../domains/sports-diy/AaaS%20DIY%20Pages.dc.html' }] },\n        'sports.blog': { view: 'stack', title: 'sports.blog', note: 'Sport knowledge base · from aaas.blog', frames: [{ id: 'W3', grp: 'sports', label: 'sports.blog', meta: 'Sport knowledge base · from aaas.blog', w: 1440, h: 2400, src: '../domains/sports-blog/AaaS%20Knowledge%20Index.dc.html' }] },\n        'sports.builders': { view: 'stack', title: 'sports.builders', note: 'Sport-business agents · from aaas.builders', frames: [{ id: 'W4', grp: 'sports', label: 'sports.builders', meta: 'Sport-business agents · from aaas.builders', w: 1440, h: 2600, src: '../domains/sports-builders/aaas.builders.dc.html' }] },\n        'sports.store': { view: 'stack', title: 'sports.store', note: 'Sports articles · from store.aaas.love', frames: [{ id: 'W5', grp: 'sports', label: 'sports.store', meta: 'Sports articles · from store.aaas.love', w: 1440, h: 2200, src: '../domains/sports-store/index.html' }] },\n        'AaaS estate': { view: 'frames', title: 'AaaS estate · every domain', note: 'The source templates every vertical is cut from', frames: (this.DOMAIN_SITES || []).map((d, i) => ({ id: 'A' + (i + 1), grp: 'aaas', label: d.tld, meta: d.label + ' · AaaS estate', w: 1440, h: 2200, src: '../' + encodeURI(String(d.pages[0].file).replace(/^assets\\//, '')) })) },\n      },\n      agents: {\n        All: cards('Agents', '12 available · 6 services connected · avg 3 credits per run', [\n          { title: 'Client Onboarder', tag: '2 cr / run', meta: 'Intake → context → kickoff brief.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Prospect Finder', tag: '4 cr / run', meta: 'Strategy → sourcing → verified list.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Cold-Email Personalizer', tag: '3 cr / run', meta: 'Sales · one sequence per prospect.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Brand Canvas', tag: '6 cr / run', meta: 'Content · voice, audience and visual cues.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'Podcast Producer', tag: '5 cr / run', meta: 'Sources → script → voice → publish.', tagColor: 'rgb(var(--lead) / 0.5)' },\n          { title: 'AP Reconciler', tag: '2 cr / run', meta: 'Finance · invoice match and ledger post.', tagColor: 'rgb(var(--lead) / 0.5)' }\n        ]),\n        Favourites: list('Favourites', '4 pinned to your project picker', [\n          { title: 'Brand Canvas', meta: 'used on 3 projects · 6 cr / run', chip: 'pinned', color: 'var(--app-accent)' },\n          { title: 'Cold-Email Personalizer', meta: 'used on 2 projects · 3 cr / run', chip: 'pinned', color: 'var(--app-accent)' },\n          { title: 'Podcast Producer', meta: 'weekly · 5 cr / run', chip: 'pinned', color: 'var(--app-accent)' },\n          { title: 'AP Reconciler', meta: 'monthly · 2 cr / run', chip: 'pinned', color: 'var(--app-accent)' }\n        ]),\n        Connected: table('Connected', 'What each agent can reach on your behalf', [C('Agent', 2), C('Service', 1.6), C('Scope', 1.8), C('State', 1, 'right')], [\n          row([tCell('AP Reconciler', { flex: 2 }), mono('Stripe', { flex: 1.6 }), mono('read + webhook', { flex: 1.8 }), mono('ok', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Prospect Finder', { flex: 2 }), mono('Apollo, Clay', { flex: 1.6 }), mono('search + enrich', { flex: 1.8 }), mono('ok', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Brand Canvas', { flex: 2 }), mono('Notion', { flex: 1.6 }), mono('read pages', { flex: 1.8 }), mono('ok', { flex: 1, align: 'right', color: 'var(--app-success)' })])\n        ]),\n        Services: list('Services', '6 of 12 connected — an agent will not run without its service', [\n          { title: 'Stripe', meta: 'connected · billing + AP reconciliation', chip: 'connected', color: 'var(--app-success)' },\n          { title: 'Notion', meta: 'connected · 1,204 pages indexed', chip: 'connected', color: 'var(--app-success)' },\n          { title: 'Apollo', meta: 'connected · 40 credits left this month', chip: 'connected', color: 'var(--app-warn)' },\n          { title: 'HubSpot', meta: 'not connected · blocks Prospect Finder handoff', chip: 'missing', color: 'var(--app-fail)' }\n        ])\n      },\n      _costs2: {\n        Overview: bars('Spend composition', 'MTD €2.4k · run-rate close €2.6k · headroom €520', [\n          { label: 'LLM tokens', value: '€1,464', meta: '61%', pct: 61, color: 'var(--app-accent)' },\n          { label: 'GCP compute', value: '€612', meta: '24%', pct: 24, color: 'var(--app-accent)' },\n          { label: 'Stripe fees', value: '€238', meta: '10%', pct: 10, color: 'var(--app-warn)' },\n          { label: 'Third-party APIs', value: '€128', meta: '5%', pct: 5, color: 'var(--app-ink-faint)' }\n        ]),\n        Sources: table('Cost sources', 'Where the ledger pulls from', [C('Source', 2), C('Mode', 1.2), C('Last sync', 1.2), C('State', 1, 'right')], [\n          row([tCell('Anthropic', { flex: 2 }), mono('api', { flex: 1.2 }), mono('4m ago', { flex: 1.2 }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Google Cloud', { flex: 2 }), mono('billing export', { flex: 1.2 }), mono('1h ago', { flex: 1.2 }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Stripe', { flex: 2 }), mono('webhook', { flex: 1.2 }), mono('12m ago', { flex: 1.2 }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Darvis', { flex: 2 }), mono('csv drop', { flex: 1.2 }), mono('3d ago', { flex: 1.2 }), mono('modeled', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        'AP inbox': table('AP inbox', '7 documents · OCR-matched against the ledger', [C('Doc', 1.4), C('Vendor', 1.8), C('Category', 1.4), C('OCR', 0.8, 'right'), C('Amount', 1, 'right'), C('Status', 1, 'right')], [\n          row([mono('ap-0412', { flex: 1.4 }), tCell('Google Cloud', { flex: 1.8 }), mono('compute', { flex: 1.4 }), mono('0.99', { flex: 0.8, align: 'right' }), mono('€612', { flex: 1, align: 'right' }), mono('matched', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([mono('ap-0411', { flex: 1.4 }), tCell('OpenRouter', { flex: 1.8 }), mono('LLM', { flex: 1.4 }), mono('0.86', { flex: 0.8, align: 'right' }), mono('€94', { flex: 1, align: 'right' }), mono('review', { flex: 1, align: 'right', color: 'var(--app-warn)' })]),\n          row([mono('ap-0409', { flex: 1.4 }), tCell('Notion Labs Inc', { flex: 1.8 }), mono('software', { flex: 1.4 }), mono('0.71', { flex: 0.8, align: 'right' }), mono('€48', { flex: 1, align: 'right' }), mono('unmatched', { flex: 1, align: 'right', color: 'var(--app-fail)' })]),\n          row([mono('ap-0404', { flex: 1.4 }), tCell('Figma', { flex: 1.8 }), mono('software', { flex: 1.4 }), mono('0.94', { flex: 0.8, align: 'right' }), mono('€60', { flex: 1, align: 'right' }), mono('refunded', { flex: 1, align: 'right', color: 'var(--app-ink-faint)' })])\n        ]),\n        FX: table('FX', 'USD-billed vendors converted at month-end rate', [C('Vendor', 2), C('USD', 1, 'right'), C('Rate', 1, 'right'), C('EUR', 1, 'right')], [\n          row([tCell('Anthropic', { flex: 2 }), mono('$1,592', { flex: 1, align: 'right' }), mono('0.92', { flex: 1, align: 'right' }), mono('€1,464', { flex: 1, align: 'right' })]),\n          row([tCell('OpenRouter', { flex: 2 }), mono('$102', { flex: 1, align: 'right' }), mono('0.92', { flex: 1, align: 'right' }), mono('€94', { flex: 1, align: 'right' })])\n        ]),\n        'Free users': table('Free users', 'What the free tier costs to serve', [C('Cohort', 2), C('Users', 1, 'right'), C('Cost', 1, 'right'), C('Cost / user', 1, 'right')], [\n          row([tCell('Demo agent (3 turns)', { flex: 2 }), mono('412', { flex: 1, align: 'right' }), mono('€84', { flex: 1, align: 'right' }), mono('€0.20', { flex: 1, align: 'right' })]),\n          row([tCell('Free context package', { flex: 2 }), mono('186', { flex: 1, align: 'right' }), mono('€62', { flex: 1, align: 'right' }), mono('€0.33', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ])\n      },\n      _marketing2: {\n        Funnel: bars('Funnel', 'New → qualified → demo → won', [\n          { label: 'New', value: '318', meta: '100%', pct: 100, color: 'var(--app-accent)' },\n          { label: 'Qualified', value: '142', meta: '45%', pct: 45, color: 'var(--app-accent)' },\n          { label: 'Demo', value: '38', meta: '12%', pct: 12, color: 'var(--app-warn)' },\n          { label: 'Won', value: '11', meta: '3.5%', pct: 4, color: 'var(--app-success)' }\n        ]),\n        Leads: table('Leads · scout', 'Discovered prospects, enriched and fit-scored', [C('Company', 2), C('Source', 1.2), C('Signal', 2.2), C('Fit', 0.8, 'right')], [\n          row([tCell('Helsing GmbH', { flex: 2 }), mono('apollo', { flex: 1.2 }), tCell('hiring 3 ops roles', { flex: 2.2, color: 'var(--app-ink-soft)' }), mono('0.91', { flex: 0.8, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Postiz BV', { flex: 2 }), mono('clay', { flex: 1.2 }), tCell('switched billing to usage-based', { flex: 2.2, color: 'var(--app-ink-soft)' }), mono('0.84', { flex: 0.8, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Studio Nord', { flex: 2 }), mono('linkedin', { flex: 1.2 }), tCell('published an AI workflow post', { flex: 2.2, color: 'var(--app-ink-soft)' }), mono('0.72', { flex: 0.8, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        'AI citations': table('AI citations', 'Where the models cite us — visibility score 62', [C('Query', 2.6), C('Top model', 1.4), C('Cited', 0.8, 'right'), C('Rank', 0.8, 'right')], [\n          row([tCell('best AI agent platform for ops', { flex: 2.6 }), mono('claude', { flex: 1.4 }), mono('yes', { flex: 0.8, align: 'right', color: 'var(--app-success)' }), mono('2', { flex: 0.8, align: 'right' })]),\n          row([tCell('agents as a service pricing', { flex: 2.6 }), mono('gpt', { flex: 1.4 }), mono('yes', { flex: 0.8, align: 'right', color: 'var(--app-success)' }), mono('4', { flex: 0.8, align: 'right' })]),\n          row([tCell('autonomous finance agent', { flex: 2.6 }), mono('gemini', { flex: 1.4 }), mono('no', { flex: 0.8, align: 'right', color: 'var(--app-fail)' }), mono('—', { flex: 0.8, align: 'right' })])\n        ]),\n        Calendar: table('Calendar', 'What publishes this week, and what gates it', [C('Piece', 2.6), C('Channel', 1.2), C('Slot', 1.2), C('Gate', 1, 'right')], [\n          row([tCell('Context packages, explained', { flex: 2.6 }), mono('.blog', { flex: 1.2 }), mono('Wed 09:00', { flex: 1.2 }), mono('approved', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Relay handoff thread', { flex: 2.6 }), mono('x', { flex: 1.2 }), mono('Today 17:00', { flex: 1.2 }), mono('approved', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([tCell('Cost per decision −18%', { flex: 2.6 }), mono('linkedin', { flex: 1.2 }), mono('Thu 09:00', { flex: 1.2 }), mono('review', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        Audience: bars('Audience', '12.4k total · 4 active segments', [\n          { label: 'Newsletter subscribers', value: '8,420', meta: '68%', pct: 68, color: 'var(--app-accent)' },\n          { label: 'Product users', value: '2,180', meta: '18%', pct: 18, color: 'var(--app-accent)' },\n          { label: 'Trial · not activated', value: '1,140', meta: '9%', pct: 9, color: 'var(--app-warn)' },\n          { label: 'Churned · win-back', value: '660', meta: '5%', pct: 5, color: 'var(--app-fail)' }\n        ])\n      },\n      _quality2: {\n        Overview: bars('DORA four keys', 'Elite band on three of four', [\n          { label: 'Deploy frequency · 3.1 / day', value: 'elite', meta: '', pct: 92, color: 'var(--app-success)' },\n          { label: 'Lead time · 9h', value: 'high', meta: '', pct: 74, color: 'var(--app-success)' },\n          { label: 'Change-fail rate · 4%', value: 'elite', meta: '', pct: 88, color: 'var(--app-success)' },\n          { label: 'MTTR · 38m', value: 'high', meta: '', pct: 70, color: 'var(--app-warn)' }\n        ]),\n        Consolidation: list('Consolidation', 'One queue, many sources — what feeds Quality', [\n          { title: 'GitHub issues', meta: '8 open · auto-triaged by severity', chip: 'source', color: 'var(--app-accent)' },\n          { title: 'Support chat', meta: '4 open · SLA 4h · 1 breaching', chip: 'source', color: 'var(--app-warn)' },\n          { title: 'Moderation queue', meta: '2 flagged uploads', chip: 'source', color: 'var(--app-warn)' },\n          { title: 'Eval regressions', meta: '3 open · feeds the ratchet', chip: 'source', color: 'var(--app-fail)' }\n        ])\n      },\n      _product2: {\n        Orders: table('Orders', 'Today · €597 · refund rate 0.4%', [C('Order', 1.4), C('Customer', 2), C('Value', 1, 'right'), C('Status', 1, 'right')], [\n          row([mono('or-2291', { flex: 1.4 }), tCell('Kanzlei Steuer', { flex: 2 }), mono('€199', { flex: 1, align: 'right' }), mono('paid', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([mono('or-2290', { flex: 1.4 }), tCell('Studio Nord', { flex: 2 }), mono('€199', { flex: 1, align: 'right' }), mono('paid', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([mono('or-2288', { flex: 1.4 }), tCell('Postiz BV', { flex: 2 }), mono('€199', { flex: 1, align: 'right' }), mono('pending', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        Threads: list('Threads', '11 open · 4 unread · agents answered 7 first', [\n          { title: 'Helsing — pilot scope', meta: 'sales · 3 unread · first reply 12m', chip: 'open', color: 'var(--app-warn)' },\n          { title: 'Postiz — invoice question', meta: 'finance · agent replied · awaiting customer', chip: 'agent', color: 'var(--app-accent)' },\n          { title: 'Studio Nord — renewal', meta: 'sales · human owned · reply due today', chip: 'you', color: 'var(--app-fail)' }\n        ])\n      },\n      _home2: {\n        Services: list('Your services', '3 of 6 connected · a run stops if its service is missing', [\n          { title: 'Stripe', meta: 'connected · billing + reconciliation', chip: 'connected', color: 'var(--app-success)' },\n          { title: 'Notion', meta: 'connected · 1,204 pages', chip: 'connected', color: 'var(--app-success)' },\n          { title: 'Apollo', meta: 'connected · 40 credits left', chip: 'connected', color: 'var(--app-warn)' },\n          { title: 'HubSpot', meta: 'not connected', chip: 'add', color: 'var(--app-ink-faint)' }\n        ]),\n        Threads: list('Conversations', '4 unread across streams', [\n          { title: 'codex — migration approval', meta: 'agentRuns · 46m · needs your yes/no', chip: 'you', color: 'var(--app-fail)' },\n          { title: 'Helsing — pilot scope', meta: 'sales thread · 3 unread', chip: 'unread', color: 'var(--app-warn)' },\n          { title: 'gen-agt — episode 129', meta: 'podcast · draft ready', chip: 'unread', color: 'var(--app-warn)' }\n        ]),\n        Tasks: list('Tasks due', '2 due today · nothing overdue', [\n          { title: 'Approve episode 129', meta: 'podcast · due today', chip: 'today', color: 'var(--app-warn)' },\n          { title: 'Sign Helsing DPA', meta: 'legal · due today', chip: 'today', color: 'var(--app-warn)' },\n          { title: 'Review Q3 budget', meta: 'costs · due Friday', chip: 'this week', color: 'var(--app-ink-faint)' }\n        ])\n      },\n      _deliverables2: {\n        Brief: list('Brief', 'What the agent was told — mission, audience, voice, visual', [\n          { title: 'Mission', meta: 'Give solo operators an autonomous AI workforce — agents that do the work end-to-end.', chip: 'set', color: 'var(--app-accent)' },\n          { title: 'Audience', meta: 'Indie builders and small ops teams already wiring AI into daily workflows.', chip: 'set', color: 'var(--app-accent)' },\n          { title: 'Voice', meta: 'Technical, confident, terse. Numerals, precise times, no hype words.', chip: 'set', color: 'var(--app-accent)' },\n          { title: 'Visual', meta: 'Basalt surfaces, circuit accent, mono system labels. No stock photography.', chip: 'set', color: 'var(--app-accent)' }\n        ])\n      },\n      _domains2: {\n        // Derived (2026-09-06): this subtitle read '15 sub-domains · 14 live' one click from an Overview\n        // saying 16 — the exact defect this file already records for IMPORT2_PANES._finances2.Overview\n        // (\"Editing one site and declaring it done was the error\"). MRR stays literal: it is a fixture\n        // figure, not a count, and deriving it would be typing a number twice.\n        Products: table('Products', (this._domCounts ? this._domCounts.mounted + ' sub-domains · ' + this._domCounts.live + ' live' : 'sub-domains') + ' · portfolio MRR €4.1k', [C('Domain', 1.8), C('What it is', 2.6), C('Reach', 1, 'right'), C('State', 1, 'right')], [\n          row([mono('.blog', { flex: 1.8, color: 'var(--app-accent)' }), tCell('Long-form essays, weekly', { flex: 2.6 }), mono('12.4k', { flex: 1, align: 'right' }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([mono('.today', { flex: 1.8, color: 'var(--app-accent)' }), tCell('Daily AI brief · 3 picks', { flex: 2.6 }), mono('8.2k', { flex: 1, align: 'right' }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([mono('.pics', { flex: 1.8, color: 'var(--app-accent)' }), tCell('AI image packs · €19 / €99', { flex: 2.6 }), mono('3.1k', { flex: 1, align: 'right' }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n          row([mono('.builders', { flex: 1.8, color: 'var(--app-accent)' }), tCell('The workshop — build canvas', { flex: 2.6 }), mono('—', { flex: 1, align: 'right' }), mono('building', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n        ]),\n        Workshop: list('Workshop', '.builders — where a new product gets proposed and built', [\n          { title: 'Propose a product', meta: 'anyone in the org · goes to the build canvas', chip: 'open', color: 'var(--app-accent)' },\n          { title: 'aaas.quest — in build', meta: 'ideation → shipping · 2 agents attached', chip: 'building', color: 'var(--app-warn)' },\n          { title: 'aaas.forum — planned', meta: 'waitlist 214 · not started', chip: 'planned', color: 'var(--app-ink-faint)' }\n        ]),\nDigest: list('Today · portfolio digest', 'What every live domain produced today', [\n          { title: '.today — 3 picks published', meta: '06:05 · sourced and linked', chip: 'ok', color: 'var(--app-success)' },\n          { title: '.podcast — episode 129 drafted', meta: 'awaiting your approval', chip: 'you', color: 'var(--app-warn)' },\n          { title: '.blog — 1 draft in review', meta: '“Why we killed the chat window”', chip: 'review', color: 'var(--app-warn)' },\n          { title: '.pics — 2 packs sold', meta: '€38 · basalt-textures', chip: 'ok', color: 'var(--app-success)' }\n        ])\n      },\n      _finances2: {\n        // Corrected 2026-09-04 (D19, second pass — verifier-caught). This card, NOT the `table()`\n        // Overview above, is what Finances actually renders: `_finances2` is merged over `DEV_PANES.\n        // finances` and wins. It carried the same mislabel-then-subtract defect as the KPI strip, more\n        // prominently (first card, default tab): `€18,200` is MRR, `€18,400` is burn, `€7,140` is\n        // overdue, and the subtitle subtracted the first two into a `Result −€0.2k` loss headline.\n        // Labels now state what the values are and the derived Result is deleted, not recomputed (D20:\n        // a real one comes from the ledgers). `Forecast close` has no counterpart in any other spec —\n        // it is authored, carries its own `proj` marker, and is owed derivation under D20.\n        Overview: bars('Recurring vs burn · MTD', 'cash €254.8k · runway 14 months', [\n          { label: 'MRR', value: '€18,200', meta: '', pct: 99, color: 'var(--app-success)' },\n          { label: 'Burn · month', value: '€18,400', meta: '', pct: 100, color: 'var(--app-fail)' },\n          { label: 'Overdue', value: '€7,140', meta: '2 inv', pct: 39, color: 'var(--app-warn)' },\n          { label: 'Forecast close', value: '€19,600', meta: 'proj', pct: 100, color: 'var(--app-accent)' }\n        ]),\n        Expenses: table('Expenses', 'By SKR-style category · EÜR cash basis', [C('Category', 2.4), C('Vendor', 1.8), C('Month', 1, 'right')], [\n          row([tCell('IT & Software', { flex: 2.4 }), mono('Anthropic, GCP, Figma', { flex: 1.8 }), mono('€2,136', { flex: 1, align: 'right' })]),\n          row([tCell('Dienstleistung', { flex: 2.4 }), mono('Kanzlei Steuer', { flex: 1.8 }), mono('€480', { flex: 1, align: 'right' })]),\n          row([tCell('Raumkosten', { flex: 2.4 }), mono('Studio Nord', { flex: 1.8 }), mono('€620', { flex: 1, align: 'right' })]),\n          row([tCell('Reisekosten', { flex: 2.4 }), mono('DB, Lufthansa', { flex: 1.8 }), mono('€310', { flex: 1, align: 'right' })]),\n          row([tCell('Bankgebühren & Sonstiges', { flex: 2.4 }), mono('Stripe, N26', { flex: 1.8 }), mono('€238', { flex: 1, align: 'right' })])\n        ])\n      },\n      _intelligence2: {\n        // I-3/I-4 (2026-09-05): 'Graph' removed — never matched the real 'Entity graph' label.\n      },\n      _content2: {\n        Viewer: list('Viewer', 'One content record, two render targets, one brand check', [\n          { title: 'Readable — markdown · text · image · code · data', meta: '68ch measure · on-this-page rail · raw / rendered toggle', chip: 'text-first', color: 'var(--app-accent)' },\n          { title: 'Rich media — web · social · audio · video · PDF', meta: 'chapters + transcript, LinkedIn / Bluesky / Threads / Mastodon previews', chip: 'embeds', color: 'var(--app-accent)' },\n          { title: 'Brand check', meta: 'voice, banned words and visual tokens · PASS', chip: 'pass', color: 'var(--app-success)' },\n          { title: 'Render targets', meta: 'desktop · tablet · mobile previewed before publish', chip: '3 sizes', color: 'var(--app-accent)' }\n        ])\n      },\n      _design2: {\n        Studio: list('Tokens studio', 'A token change is modeled, validated, then held-to-publish', [\n          { title: 'Token editor', meta: 'modeled value · shows every surface it affects before publish', chip: 'modeled', color: 'var(--app-warn)' },\n          { title: 'Validate change', meta: 'AA contrast, PSI gate and drift check run on the proposal', chip: 'gate', color: 'var(--app-accent)' },\n          { title: 'Hold to publish', meta: '3-second hold · writes to the token contract', chip: 'v4', color: 'var(--app-success)' },\n          { title: 'Recent proposals', meta: '2 published · 1 rejected (grain opacity)', chip: '3', color: 'var(--app-ink-faint)' }\n        ])\n      }\n    };\n    Object.assign(this.DEV_PANES.costs, IMPORT2_PANES._costs2);\n    Object.assign(this.DEV_PANES.marketing, IMPORT2_PANES._marketing2);\n    // Marketing's Overview / Funnel / Leads / AI citations render as the native `mk` pane.\n    // Marketing's tabs render as the native `mk` pane via STREAMS.marketing.ownsTabs (WP-06) —\n    // the owns-bypass in the kind expression above, not a deletion ritual.\n    Object.assign(this.DEV_PANES.quality, IMPORT2_PANES._quality2);\n    Object.assign(this.DEV_PANES.product, IMPORT2_PANES._product2);\n    Object.assign(this.DEV_PANES.home, IMPORT2_PANES._home2);\n    Object.assign(this.DEV_PANES.deliverables, IMPORT2_PANES._deliverables2);\n    Object.assign(this.DEV_PANES.finances, IMPORT2_PANES._finances2);\n    Object.assign(this.DEV_PANES.intelligence, IMPORT2_PANES._intelligence2);\n    Object.assign(this.DEV_PANES.content, IMPORT2_PANES._content2);\n    // D200 (NIGHT-PLAN P2b): Outbound · v2 Overview shows the handoff verdict — outputs of node N must cover the\n    // inputs of N+1, checked per edge here and by plane-validator's validateTemplate (ancestor rule). Never a default pass.\n    { const ob = this.DEV_PANES.outbound2 && this.DEV_PANES.outbound2.Overview;\n      if (ob && Array.isArray(ob.cards)) { const base = ob.cards;\n        Object.defineProperty(ob, 'cards', { enumerable: true, get: () => {\n          const PS = window.ProcessSpec; const tpl = PS && PS.TEMPLATES ? PS.TEMPLATES.find(t => t.id === 'outbound') : null; const V = this._planeValidator;\n          if (!tpl || !V) return [{ title: 'Handoffs', tag: 'not checked', meta: 'plane-validator not loaded yet — verdict pending', tagColor: 'var(--app-ink-faint)' }].concat(base);\n          const byId = {}; tpl.nodes.forEach(n => { byId[n.id] = n; });\n          const edges = []; tpl.nodes.forEach(n => (n.next || []).forEach(m => edges.push([n, byId[m]])));\n          const broken = edges.filter(e => !e[1] || !(e[1].inputs || []).every(i => (e[0].outputs || []).indexOf(i) > -1));\n          const r = V.validateTemplate(tpl); const ok = r.ok && broken.length === 0;\n          return [{ title: 'Handoffs · ' + (edges.length - broken.length) + ' of ' + edges.length, tag: ok ? 'pass' : 'fail',\n            meta: ok ? 'every node\\'s outputs satisfy the next node\\'s inputs · validateTemplate: 0 errors · ' + tpl.nodes.length + ' nodes'\n              : (broken.length ? 'broken: ' + broken.map(e => e[0].label + ' → ' + (e[1] ? e[1].label : '?')).join(', ') : r.errors[0].message),\n            tagColor: ok ? 'var(--app-success)' : 'var(--app-fail)' }].concat(base);\n        } }); } }\n    // Content · 15 surfaces (upstream kinds-content-presentation.jsx). Overview / Designs /\n    // Social posts render natively; these twelve are the lens tabs. Old .blog/.podcast lens\n    // panes retired — the domain surfaces live in the Domains stream now (HANDOVER §2c).\n    this.DEV_PANES.content = {\n      'Toolbox · 267': table('Toolbox docs', '267 pages across 21 categories · authoring via PR, OSS-style',\n        [C('Category', 2.2), C('Pages', 0.8, 'right'), C('Deprecated', 1, 'right'), C('Last PR', 1.2, 'right')], [\n        row([tCell('Getting started', { flex: 2.2 }), mono('18', { flex: 0.8, align: 'right' }), mono('0', { flex: 1, align: 'right' }), mono('2d ago', { flex: 1.2, align: 'right' })]),\n        row([tCell('Agents · reference', { flex: 2.2 }), mono('42', { flex: 0.8, align: 'right' }), mono('4', { flex: 1, align: 'right', color: 'var(--app-warn)' }), mono('4h ago', { flex: 1.2, align: 'right' })]),\n        row([tCell('Context engine', { flex: 2.2 }), mono('34', { flex: 0.8, align: 'right' }), mono('2', { flex: 1, align: 'right', color: 'var(--app-warn)' }), mono('1d ago', { flex: 1.2, align: 'right' })]),\n        row([tCell('Skills · vault', { flex: 2.2 }), mono('56', { flex: 0.8, align: 'right' }), mono('8', { flex: 1, align: 'right', color: 'var(--app-warn)' }), mono('6h ago', { flex: 1.2, align: 'right' })]),\n        row([tCell('Integrations', { flex: 2.2 }), mono('38', { flex: 0.8, align: 'right' }), mono('6', { flex: 1, align: 'right', color: 'var(--app-warn)' }), mono('3d ago', { flex: 1.2, align: 'right' })]),\n        row([tCell('CLI + API', { flex: 2.2 }), mono('44', { flex: 0.8, align: 'right' }), mono('2', { flex: 1, align: 'right', color: 'var(--app-warn)' }), mono('12h ago', { flex: 1.2, align: 'right' })]),\n        row([tCell('Compliance · EU', { flex: 2.2 }), mono('35', { flex: 0.8, align: 'right' }), mono('2', { flex: 1, align: 'right', color: 'var(--app-warn)' }), mono('5d ago', { flex: 1.2, align: 'right' })])\n      ]),\n      'Brand book': cards('Brand book · v3.2', 'Type, colour, voice and logo — one versioned document, updated 24.05.2026', [\n        { title: 'Type', tag: '2 families', meta: 'Inter for everything visual, JetBrains Mono for system labels. H1 900 uppercase, −0.05em, trailing period.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: 'Colour', tag: '2 leads', meta: 'Circuit glow and accent red co-lead. Teal, lavender and gold are contextual only — never dominant.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: 'Voice', tag: 'terse', meta: 'Technical, confident, terse. Agents not chatbots. Builders not users. Deploy not launch.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: 'Logo', tag: 'hex orb', meta: 'Line geometry, never illustrative. The only branded icon. Never recolour or distort.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: 'Surfaces', tag: 'glass', meta: 'Layered basalt + grain + sheen. Glass nav and cards at 0.6 opacity, 24px blur.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: 'Motion', tag: 'liquid', meta: 'cubic-bezier(0.23, 1, 0.32, 1) for everything. 200ms nav, 300ms hover, 600ms fade-up.', tagColor: 'rgb(var(--lead) / 0.5)' }\n      ]),\n      'Email templates · 67': table('Email templates', '67 templates · MJML + React-Email · de-DE and en-US, live client preview',\n        [C('Template', 2.6), C('Category', 1.4), C('Opens', 1, 'right'), C('Locale', 1, 'right')], [\n        row([tCell('Invoice ready', { flex: 2.6 }), mono('transactional', { flex: 1.4 }), mono('62 %', { flex: 1, align: 'right', color: 'var(--app-success)' }), mono('de · en', { flex: 1, align: 'right' })]),\n        row([tCell('Welcome · aaas.blog', { flex: 2.6 }), mono('lifecycle', { flex: 1.4 }), mono('48 %', { flex: 1, align: 'right', color: 'var(--app-success)' }), mono('en', { flex: 1, align: 'right' })]),\n        row([tCell('Feature launch · boutique', { flex: 2.6 }), mono('announcement', { flex: 1.4 }), mono('38 %', { flex: 1, align: 'right' }), mono('de · en', { flex: 1, align: 'right' })]),\n        row([tCell('Lifecycle digest · aaas.com', { flex: 2.6 }), mono('digest', { flex: 1.4 }), mono('34 %', { flex: 1, align: 'right' }), mono('de · en', { flex: 1, align: 'right' })]),\n        row([tCell('Password reset', { flex: 2.6 }), mono('transactional', { flex: 1.4 }), mono('88 %', { flex: 1, align: 'right', color: 'var(--app-success)' }), mono('de · en', { flex: 1, align: 'right' })]),\n        row([tCell('GDPR export ready', { flex: 2.6 }), mono('transactional', { flex: 1.4 }), mono('74 %', { flex: 1, align: 'right', color: 'var(--app-success)' }), mono('de · en', { flex: 1, align: 'right' })]),\n        row([tCell('Agent published', { flex: 2.6 }), mono('announcement', { flex: 1.4 }), mono('41 %', { flex: 1, align: 'right' }), mono('en', { flex: 1, align: 'right' })])\n      ]),\n      'Landing pages': table('Marketing pages', '12 pages in their actual browser frame · 4 in draft',\n        [C('Page', 2.2), C('Route', 2), C('Lighthouse', 1, 'right'), C('State', 1, 'right')], [\n        row([tCell('Homepage', { flex: 2.2 }), mono('/', { flex: 2, color: 'var(--app-accent)' }), mono('98', { flex: 1, align: 'right', color: 'var(--app-success)' }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('Pricing', { flex: 2.2 }), mono('/pricing', { flex: 2, color: 'var(--app-accent)' }), mono('96', { flex: 1, align: 'right', color: 'var(--app-success)' }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('Decisions launch', { flex: 2.2 }), mono('/decisions', { flex: 2, color: 'var(--app-accent)' }), mono('94', { flex: 1, align: 'right', color: 'var(--app-success)' }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('Agent roster', { flex: 2.2 }), mono('/agents', { flex: 2, color: 'var(--app-accent)' }), mono('92', { flex: 1, align: 'right' }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('About', { flex: 2.2 }), mono('/about', { flex: 2, color: 'var(--app-accent)' }), mono('99', { flex: 1, align: 'right', color: 'var(--app-success)' }), mono('draft', { flex: 1, align: 'right', color: 'var(--app-warn)' })]),\n        row([tCell('Impressum', { flex: 2.2 }), mono('/impressum', { flex: 2, color: 'var(--app-accent)' }), mono('100', { flex: 1, align: 'right', color: 'var(--app-success)' }), mono('live', { flex: 1, align: 'right', color: 'var(--app-success)' })])\n      ]),\n      'Blog drafts': list('Blog drafts', '38 drafts · LLM-generated, awaiting human review · 12 ready to publish', [\n        { title: 'Why we killed the chat window', meta: 'ready · 1,840 words · reviewed by jorian', chip: 'ready', color: 'var(--app-success)' },\n        { title: 'The cost of an idle agent', meta: 'ready · 1,240 words · needs a final read', chip: 'ready', color: 'var(--app-success)' },\n        { title: 'Prompt caching, measured', meta: 'in review · 2,110 words · 3 claims unsourced', chip: 'review', color: 'var(--app-warn)' },\n        { title: 'EU AI Act · what changes in Q3', meta: 'in review · 1,680 words · legal read pending', chip: 'legal', color: 'var(--app-warn)' },\n        { title: 'Reranking beats embedding', meta: 'draft · 940 words · thin on evidence', chip: 'draft', color: 'var(--app-ink-faint)' },\n        { title: 'Context engineering in practice', meta: 'draft · 1,420 words · generated 4h ago', chip: 'draft', color: 'var(--app-ink-faint)' }\n      ]),\n      'Documentation': table('Documentation', '84 pages · developer and customer facing',\n        [C('Section', 2.2), C('Audience', 1.6), C('Pages', 0.8, 'right'), C('Freshness', 1.2, 'right')], [\n        row([tCell('API reference', { flex: 2.2 }), mono('developer', { flex: 1.6 }), mono('32', { flex: 0.8, align: 'right' }), mono('auto · 1h', { flex: 1.2, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('CLI reference', { flex: 2.2 }), mono('developer', { flex: 1.6 }), mono('18', { flex: 0.8, align: 'right' }), mono('auto · 1h', { flex: 1.2, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('UX guides', { flex: 2.2 }), mono('customer', { flex: 1.6 }), mono('22', { flex: 0.8, align: 'right' }), mono('4d ago', { flex: 1.2, align: 'right' })]),\n        row([tCell('Concepts', { flex: 2.2 }), mono('both', { flex: 1.6 }), mono('12', { flex: 0.8, align: 'right' }), mono('2w ago', { flex: 1.2, align: 'right', color: 'var(--app-warn)' })])\n      ]),\n      'Banners': cards('Banners', '38 banners across 6 ratios · 8 campaigns, same message every size', [\n        { title: 'Decisions launch', tag: '6 sizes', meta: '1080×1080 · 1200×630 · 1600×900 · 1920×1080 · 300×250 · 728×90.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: 'Free context package', tag: '6 sizes', meta: 'Activation campaign. Black text on circuit — the locked CTA treatment.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: 'EU-hosted, GDPR-native', tag: '4 sizes', meta: 'Compliance angle for the German market. de-DE copy only.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: '.diy Pro launch', tag: '6 sizes', meta: 'Paywall campaign · gold PRO flag on every size.', tagColor: 'rgb(var(--pastel-gold) / 0.5)' },\n        { title: 'Podcast · daily brief', tag: '4 sizes', meta: 'Lavender treatment, waveform motif, 5-minute promise.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: 'Agent roster · 142', tag: '6 sizes', meta: 'Marketplace campaign. Numerals lead, no product shot.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: 'Vault · 5,400 skills', tag: '4 sizes', meta: 'Gold/silver/bronze tier bar as the hero graphic.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: 'Q3 all-hands', tag: '2 sizes', meta: 'Internal only. Screen ratios, no print.', tagColor: 'rgb(var(--text) / 0.3)' }\n      ]),\n      'Posters': table('Posters', '24 posters · A0 / A1 / A2 / A3, print and screen',\n        [C('Poster', 2.4), C('Size', 1), C('Use', 1.8), C('State', 1, 'right')], [\n        row([tCell('Autonomous AI Workforce', { flex: 2.4 }), mono('A1', { flex: 1 }), mono('trade show', { flex: 1.8 }), mono('print-ready', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('Context engine · how it works', { flex: 2.4 }), mono('A1', { flex: 1 }), mono('trade show', { flex: 1.8 }), mono('print-ready', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('5,400 skills', { flex: 2.4 }), mono('A2', { flex: 1 }), mono('conference', { flex: 1.8 }), mono('print-ready', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('Deploy in minutes', { flex: 2.4 }), mono('A2', { flex: 1 }), mono('conference', { flex: 1.8 }), mono('print-ready', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('Decisions · the audit trail', { flex: 2.4 }), mono('A2', { flex: 1 }), mono('office', { flex: 1.8 }), mono('screen only', { flex: 1, align: 'right', color: 'var(--app-warn)' })]),\n        row([tCell('EU data residency', { flex: 2.4 }), mono('A3', { flex: 1 }), mono('office', { flex: 1.8 }), mono('print-ready', { flex: 1, align: 'right', color: 'var(--app-success)' })])\n      ]),\n      'Presentations': cards('Presentations', '18 decks · investor, sales, all-hands, workshop · 216 slides total', [\n        { title: 'Investor · Series A', tag: '18 slides', meta: 'The current raise deck. Market, product, traction, team, ask.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: 'Investor · one-pager', tag: '4 slides', meta: 'Cut-down for first calls. Numbers only.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: 'Sales · enterprise', tag: '14 slides', meta: 'Compliance-first framing for German mid-market.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: 'Sales · SMB', tag: '9 slides', meta: 'Price-led. Free context package as the opener.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: 'All-hands · Q3', tag: '22 slides', meta: 'Roadmap, hiring, the two-canvas merge.', tagColor: 'rgb(var(--lead) / 0.5)' },\n        { title: 'Workshop · context engineering', tag: '32 slides', meta: 'Half-day builder workshop. Exercises included.', tagColor: 'rgb(var(--lead) / 0.5)' }\n      ]),\n      'Videos': table('Videos', '12 videos · demos, explainers, social shorts · 90s average',\n        [C('Video', 2.6), C('Kind', 1.4), C('Length', 1, 'right'), C('Views 30d', 1.2, 'right')], [\n        row([tCell('AP-Reconciliation in 90 seconds', { flex: 2.6 }), mono('demo', { flex: 1.4 }), mono('1:32', { flex: 1, align: 'right' }), mono('6.2k', { flex: 1.2, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('What Decisions actually shows you', { flex: 2.6 }), mono('explainer', { flex: 1.4 }), mono('2:14', { flex: 1, align: 'right' }), mono('4.8k', { flex: 1.2, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('Context engine · the 2-minute version', { flex: 2.6 }), mono('explainer', { flex: 1.4 }), mono('2:04', { flex: 1, align: 'right' }), mono('3.1k', { flex: 1.2, align: 'right' })]),\n        row([tCell('Vault tour', { flex: 2.6 }), mono('demo', { flex: 1.4 }), mono('1:48', { flex: 1, align: 'right' }), mono('2.4k', { flex: 1.2, align: 'right' })]),\n        row([tCell('Why agents, not chatbots', { flex: 2.6 }), mono('social', { flex: 1.4 }), mono('0:44', { flex: 1, align: 'right' }), mono('1.2k', { flex: 1.2, align: 'right' })]),\n        row([tCell('Deploy in minutes', { flex: 2.6 }), mono('social', { flex: 1.4 }), mono('0:38', { flex: 1, align: 'right' }), mono('0.7k', { flex: 1.2, align: 'right' })])\n      ]),\n      'Snippets': list('Snippets', '48 reusable copy blocks · one source, every surface · 4 orphaned', [\n        { title: 'Primary CTA · Activate', meta: '\"Free context package in 2 min\" · used on 8 surfaces', chip: 'in use', color: 'var(--app-success)' },\n        { title: 'Compliance line · EU', meta: '\"EU-hosted, GDPR-native\" · used on 12 surfaces', chip: 'in use', color: 'var(--app-success)' },\n        { title: 'Vault claim', meta: '\"5,400+ structured AI skills\" · used on 6 surfaces', chip: 'in use', color: 'var(--app-success)' },\n        { title: 'Pricing footnote', meta: 'VAT boilerplate · used on 4 surfaces', chip: 'in use', color: 'var(--app-success)' },\n        { title: 'Beta disclaimer', meta: 'no surface references this · candidate for deletion', chip: 'orphaned', color: 'var(--app-warn)' },\n        { title: 'Old tagline · \"AI that works\"', meta: 'retired in v3.0 brand update · still in the store', chip: 'orphaned', color: 'var(--app-warn)' }\n      ]),\n      'Public KB articles': table('Public KB articles', '24 articles · published from Documentation, tracked for search hits',\n        [C('Article', 2.8), C('Hits 30d', 1, 'right'), C('Zero-result', 1.2, 'right'), C('State', 1, 'right')], [\n        row([tCell('How billing works', { flex: 2.8 }), mono('842', { flex: 1, align: 'right' }), mono('2', { flex: 1.2, align: 'right' }), mono('fresh', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('Connecting Google Photos', { flex: 2.8 }), mono('612', { flex: 1, align: 'right' }), mono('0', { flex: 1.2, align: 'right' }), mono('fresh', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('What agents can and cannot do', { flex: 2.8 }), mono('584', { flex: 1, align: 'right' }), mono('4', { flex: 1.2, align: 'right' }), mono('fresh', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('Exporting your data (GDPR)', { flex: 2.8 }), mono('418', { flex: 1, align: 'right' }), mono('1', { flex: 1.2, align: 'right' }), mono('fresh', { flex: 1, align: 'right', color: 'var(--app-success)' })]),\n        row([tCell('Cancelling a subscription', { flex: 2.8 }), mono('386', { flex: 1, align: 'right' }), mono('6', { flex: 1.2, align: 'right', color: 'var(--app-warn)' }), mono('stale', { flex: 1, align: 'right', color: 'var(--app-warn)' })]),\n        row([tCell('Pro vs Team tiers', { flex: 2.8 }), mono('294', { flex: 1, align: 'right' }), mono('5', { flex: 1.2, align: 'right', color: 'var(--app-warn)' }), mono('stale', { flex: 1, align: 'right', color: 'var(--app-warn)' })])\n      ])\n    };\n    Object.assign(this.DEV_PANES.design, IMPORT2_PANES._design2);\n    this.DEV_PANES.domains = IMPORT2_PANES._domains2;\n    ['onboardingV2', 'growth', 'agents', 'websites'].forEach(id => { this.DEV_PANES[id] = IMPORT2_PANES[id]; });\n\n\n    // ══ Design asset gallery — AaaS Design Asset Catalogue (399 assets, 57 families) ══\n    const famCard = (id, name, count, done, meta) => ({\n      title: id + ' · ' + name,\n      tag: done === count ? count + ' · shipped' : done + ' / ' + count,\n      meta: meta,\n      tagColor: done === count ? 'rgb(var(--accent-teal) / 0.5)' : done === 0 ? 'rgb(var(--text) / 0.62)' : 'rgb(var(--pastel-gold) / 0.5)'\n    });\n    const GALLERY_PANES = {\n      Catalog: bars('Design asset catalogue', '399 templates · 160 shipped · every one re-skins from the token contract', [\n        { label: 'Template · Social — 20 families', value: '92 / 192', meta: '48%', pct: 48, color: 'var(--app-accent)' },\n        { label: 'Marketing Asset — 30 families', value: '0 / 116', meta: 'specced', pct: 4, color: 'var(--app-ink-faint)' },\n        { label: 'Email Templates — 5 series', value: '54 / 68', meta: '79%', pct: 79, color: 'var(--app-success)' },\n        { label: 'Paid & Organic — P0 launch set', value: '12 / 20', meta: '60%', pct: 60, color: 'var(--app-warn)' },\n        { label: 'Foundation · Web · Cross-project', value: '3 / 3', meta: '100%', pct: 100, color: 'var(--app-success)' }\n      ]),\n      Email: cards('Email templates', '68 templates in five series · 600px canonical, light + dark via prefers-color-scheme', [\n        famCard('A', 'Welcome — one per property', 12, 12, 'aaas.com, select, diy, builders, forum, name, company, business, today, pics, blog, academy.'),\n        famCard('B', 'Explainers & product update', 10, 4, 'Monthly product update, context-engine, skills and vault offering explainers.'),\n        famCard('C', 'Lifecycle', 12, 8, 'Trial expiring, first-run success, 7-day win-back, referral, receipt, eval summary, weekly digest, NPS.'),\n        famCard('D', 'Announcements', 10, 6, 'Security advisory, founder letter, case study, beta invite, agent published, earnings payout.'),\n        famCard('E', 'Transactional', 8, 4, 'Password reset, GDPR export, cancellation confirmed, waitlist position.'),\n        famCard('L', 'Lifecycle digests · per property', 16, 16, 'One digest layout per surface — aaas.com, blog, academy, boutique, builders, business, canvas, diy, forum, name, hub, dispatcher, cross-property.')\n      ]),\n      Social: cards('Social templates', '192 templates across 20 platform families · sized per network, variants where the network needs them', [\n        famCard('S1', 'LinkedIn · squares + carousels', 49, 49, 'Announcement, whitepaper drop, contrarian take, milestone, stat of the week, 10 carousels.'),\n        famCard('S2', 'LinkedIn · native formats', 12, 12, 'Document carousel cover/slide/CTA, video cover, personal + company banner, event, article hero, poll, newsletter.'),\n        famCard('S3', 'X', 12, 12, 'Single image, summary card, quote square, numbered thread card, profile header, poll, Spaces promo, launch.'),\n        famCard('S4', 'Instagram', 14, 1, 'Feed square + portrait, stories, reels covers, carousel cover and end-CTA.'),\n        famCard('S5', 'TikTok', 8, 0, 'Vertical static hook, talking-head frame, caption plates.'),\n        famCard('S6', 'Facebook', 8, 0, 'Feed square + landscape, page cover, event cover.'),\n        famCard('S7', 'YouTube', 10, 0, 'Thumbnails standard + podcast, channel art, end screens.'),\n        famCard('S8', 'Threads', 6, 0, 'Square and portrait posts, reply cards.'),\n        famCard('S9', 'Pinterest', 6, 5, 'Standard pin, long pin, idea-pin cover + inner, board cover, tutorial pin.'),\n        famCard('S10', 'Reddit', 5, 1, 'Subreddit banner + icon, post image, AMA card.'),\n        famCard('S11', 'Substack', 5, 0, 'Post hero, publication cover, section art.'),\n        famCard('S12', 'Discord', 6, 1, 'Server banner, pinned channel art, event cover, sticker pack cover.'),\n        famCard('S13', 'Snapchat', 3, 3, 'Snap vertical, story tile, discover tile.'),\n        famCard('S14', 'WhatsApp', 3, 3, 'Status vertical, business profile, broadcast card.'),\n        famCard('S15', 'Dev communities', 4, 4, 'HN og-card, Lobsters og-card, dev.to cover + social share.'),\n        famCard('S16', 'Quote cards', 10, 0, 'Attributed quote squares, portraits and landscapes.'),\n        famCard('S17', 'Polls & AMA', 5, 0, 'Poll companion, AMA announce, Q&A recap.'),\n        famCard('S18', 'Podcast audiograms', 5, 0, 'Square, vertical and landscape audiogram frames.'),\n        famCard('S19', 'People', 5, 0, 'Founder portrait frame, team group frame, hire announce.'),\n        famCard('S20', 'OG share images', 4, 0, '1200×630 share images for site, blog and docs.')\n      ]),\n      Paid: cards('Paid & organic · P0 launch set', '20 templates · the highest-traffic surfaces, each with 2–4 variants', [\n        famCard('P0.1', 'LinkedIn carousel · architecture 5-slide', 2, 2, '5400×1350 · the architecture story in five frames.'),\n        famCard('P0.2', 'Instagram carousel · agent anatomy 7-slide', 2, 2, '7560×1080 · what an agent is made of.'),\n        famCard('P0.3', 'X profile banner · visual hook', 3, 3, '1500×500.'),\n        famCard('P0.4', 'Vault skill repost card', 3, 3, '1200×1200 · one card per published skill.'),\n        famCard('P0.5', 'YouTube thumbnail · architecture deep-dive', 3, 0, '1280×720.'),\n        famCard('P0.6', 'GitHub social preview', 2, 2, '1280×640 · repo og-image.'),\n        famCard('P0.7', 'Product Hunt launch card', 2, 0, '1200×675.'),\n        famCard('P0.8', 'Before/after architecture banner', 2, 0, '1600×900 · the setup-cost story.'),\n        famCard('P0.9', 'Stat drop poster', 4, 0, '1080×1080 · one number, one claim.'),\n        famCard('P0.10', 'Threads / Bluesky vertical hook', 3, 0, '1080×1350.'),\n        famCard('P0.11', 'Reels / TikTok static cover', 2, 0, '1080×1920.'),\n        famCard('P0.12', 'Hacker News og-card', 2, 0, '1200×630.')\n      ]),\n      Marketing: cards('Marketing assets', '116 templates in 30 families · specced against the token contract, none produced yet', [\n        famCard('M1', 'Whitepaper', 5, 0, 'Cover letter + A4, inside spread, callout pull-quote, back-cover CTA.'),\n        famCard('M2', 'Case study', 5, 0, 'Cover, hero-quote spread, results panel, one-pager, web hero.'),\n        famCard('M3', 'Ebook', 4, 0, 'Web + KDP covers, chapter opener, back matter.'),\n        famCard('M4', 'One-pagers', 3, 0, 'Sales letter, product spec, elevator summary.'),\n        famCard('M5', 'Brochures', 3, 0, 'Trifold outside + inside, bifold spread.'),\n        famCard('M6', 'Pitch deck', 10, 0, 'Cover, problem, solution, market, screens, traction, model, competition, team, ask.'),\n        famCard('M7', 'Investor', 3, 0, 'One-pager, KPI summary, update newsletter.'),\n        famCard('M8', 'Reports', 5, 0, 'Annual + quarterly covers, state-of-industry, data spread, exec summary.'),\n        famCard('M9', 'Infographics', 6, 0, 'Vertical long, square summary, comparison, process.'),\n        famCard('M10', 'Roadmaps', 4, 0, 'Timeline quarters, now/next/later.'),\n        famCard('M11', 'Pricing', 4, 0, '3-up cards, single card, table.'),\n        famCard('M12', 'Feature comparison', 3, 0, 'Vs competitor, 3-option, matrix.'),\n        famCard('M13', 'Webinar', 4, 0, 'Posters landscape + portrait, holding slide.'),\n        famCard('M14', 'Conference', 3, 0, 'Roll-up pull-up, booth back wall, table runner.'),\n        famCard('M15', 'Swag', 6, 0, 'T-shirt front + back, cap, sticker sheet, tote, mug.'),\n        famCard('M16', 'Press kit', 3, 0, 'Cover, fact sheet, logo sheet.'),\n        famCard('M17', 'Email signature', 2, 0, 'Banner strip, event banner.'),\n        famCard('M18', 'Blog', 3, 0, 'Hero landscape, article og-card, inline figure.'),\n        famCard('M19', 'Newsletter blocks', 4, 0, 'Modular hero, feature, list and CTA blocks.'),\n        famCard('M20', 'Landing heroes', 3, 0, 'Above the fold, with form, with product shot.'),\n        famCard('M21', 'Product Hunt gallery', 3, 0, 'Cover, screen, GIF still.'),\n        famCard('M22', 'App store', 4, 0, 'iPhone 6.7\", iPad 12.9\", feature graphic.'),\n        famCard('M23', 'Docs diagrams', 3, 0, 'Architecture, sequence, data-flow.'),\n        famCard('M24', 'Tutorials', 3, 0, 'Step graphic, before/after split, callout.'),\n        famCard('M25', 'FAQ / help', 2, 0, 'Help-card og, help-card social.'),\n        famCard('M26', 'Testimonials', 4, 0, 'Single square, 4-up grid, logo wall.'),\n        famCard('M27', 'Logo lockups', 3, 0, 'Horizontal, stacked, partner co-brand.'),\n        famCard('M28', 'Badges', 3, 0, 'Award recognition, trust + security, compliance.'),\n        famCard('M29', 'QR', 2, 0, 'Business card, event poster A3.'),\n        famCard('M30', 'Seasonal', 6, 0, 'New year, launch anniversary, holiday, milestone.')\n      ])\n    };\n    Object.assign(this.DEV_PANES.design, GALLERY_PANES);\n    this.DEV_PANES.design.Components = this.DEV_PANES.design.Components || IMPORT_PANES._design_extra.Catalog;\n\n\n// ══ Gallery · real design files under assets/gallery/ (extracted from the handoff bundles) ══\n    const GALLERY_FRAMES = { view: 'frames', title: 'Design gallery · live files', note: '85 of 399 templates extracted — real files, not previews. Open ↗ for full size.',\n      frames: [\n        { id: 'A2', label: \"Welcome · aaas.select\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a2-welcome-select/welcome-aaas-select.html\" },\n        { id: 'A3', label: \"Welcome · aaas.diy\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a3-welcome-diy/welcome-email.html\" },\n        { id: 'A4', label: \"Welcome · aaas.builders\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a4-welcome-builders/welcome-aaas-builders.html\" },\n        { id: 'A6', label: \"Welcome · aaas.name\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a6-welcome-name/welcome-aaas-name.html\" },\n        { id: 'A7', label: \"Welcome · aaas.company\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a7-welcome-company/welcome-aaas-company.html\" },\n        { id: 'A8', label: \"Welcome · aaas.business\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a8-welcome-business/welcome-aaas-business.html\" },\n        { id: 'A10', label: \"Welcome · aaas.pics\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a10-welcome-pics/welcome-aaas-pics.html\" },\n        { id: 'A11', label: \"Welcome · aaas.blog\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a11-welcome-blog/welcome-aaas-blog.html\" },\n        { id: 'A12', label: \"Welcome · aaas.academy\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a12-welcome-academy/welcome-email.html\" },\n        { id: 'B1', label: \"Product update · monthly\", meta: 'Email · 600px', w: 700, h: 1500, src: \"b1-product-update/Product%20Update%20Monthly.html\" },\n        { id: 'B2', label: \"Context engine explainer\", meta: 'Email · 600px', w: 700, h: 1500, src: \"b2-context-engine/Context%20Engine%20Email.html\" },\n        { id: 'B3', label: \"Skills offering explainer\", meta: 'Email · 600px', w: 700, h: 1500, src: \"b3-skills-offering/skills-explainer.html\" },\n        { id: 'B4', label: \"Vault offering explainer\", meta: 'Email · 600px', w: 700, h: 1500, src: \"b4-vault-offering/Vault%20Explainer%20Email.html\" },\n        { id: 'C1', label: \"Trial expiring soon\", meta: 'Email · 600px', w: 700, h: 1500, src: \"c1-trial-expiring/index.html\" },\n        { id: 'C2', label: \"First run success\", meta: 'Email · 600px', w: 700, h: 1500, src: \"c2-first-run-success/First%20Run%20Success%20Email.html\" },\n        { id: 'C3', label: \"Dormant 7d win-back\", meta: 'Email · 600px', w: 700, h: 1500, src: \"c3-dormant-winback/Dormant%207d%20Winback%20Email.html\" },\n        { id: 'C4', label: \"Referral invite\", meta: 'Email · 600px', w: 700, h: 1500, src: \"c4-referral-invite/Referral%20Invite%20Email.html\" },\n        { id: 'C6', label: \"Eval result summary\", meta: 'Email · 600px', w: 700, h: 1500, src: \"c6-eval-summary/index.html\" },\n        { id: 'C7', label: \"Weekly digest\", meta: 'Email · 600px', w: 700, h: 1500, src: \"c7-weekly-digest/Weekly%20Digest%20Email.html\" },\n        { id: 'C8', label: \"Feedback · NPS\", meta: 'Email · 600px', w: 700, h: 1500, src: \"c8-feedback-nps/Feedback%20NPS%20Email.html\" },\n        { id: 'D1', label: \"Security advisory\", meta: 'Email · 600px', w: 700, h: 1500, src: \"d1-security-advisory/Security%20Advisory%20Email.html\" },\n        { id: 'D3', label: \"Case study spotlight\", meta: 'Email · 600px', w: 700, h: 1500, src: \"d3-case-study/case-study-email.html\" },\n        { id: 'D4', label: \"Beta feature invite\", meta: 'Email · 600px', w: 700, h: 1500, src: \"d4-beta-invite/Beta%20Feature%20Invite.html\" },\n        { id: 'D5', label: \"Agent published\", meta: 'Email · 600px', w: 700, h: 1500, src: \"d5-agent-published/Agent%20Published%20Email.html\" },\n        { id: 'D6', label: \"Earnings payout\", meta: 'Email · 600px', w: 700, h: 1500, src: \"d6-earnings-payout/Payout%20Notification.html\" },\n        { id: 'E1', label: \"Password reset\", meta: 'Email · 600px', w: 700, h: 1500, src: \"e1-password-reset/Password%20Reset.html\" },\n        { id: 'E2', label: \"GDPR export ready\", meta: 'Email · 600px', w: 700, h: 1500, src: \"e2-gdpr-export/GDPR%20Data%20Export%20Email.html\" },\n        { id: 'E3', label: \"Cancellation confirmed\", meta: 'Email · 600px', w: 700, h: 1500, src: \"e3-cancellation/Subscription%20Cancellation%20Confirmed.html\" },\n        { id: 'E4', label: \"Waitlist position\", meta: 'Email · 600px', w: 700, h: 1500, src: \"e4-waitlist-position/Waitlist%20Position%20Update%20Email.html\" },\n        { id: 'S1.2', label: \"LinkedIn · whitepaper drop\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-2-whitepaper-drop/LinkedIn%20Whitepaper%20Drop.html\" },\n        { id: 'S1.3', label: \"LinkedIn · new-LLM take\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-3-new-llm-take/Reaction%20Post.html\" },\n        { id: 'S1.5', label: \"LinkedIn · contrarian take\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-5-contrarian-take/linkedin-contrarian-take.html\" },\n        { id: 'S1.6', label: \"LinkedIn · customer quote\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-6-customer-quote/Customer%20Quote%20Square.html\" },\n        { id: 'S1.7', label: \"LinkedIn · milestone\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-7-milestone/Milestone%20Post.html\" },\n        { id: 'S1.10', label: \"LinkedIn · postmortem\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-10-postmortem/Postmortem%20Square.html\" },\n        { id: 'S1.12', label: \"LinkedIn · tool comparison\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-12-tool-comparison/Comparison%20Post.html\" },\n        { id: 'S1.14', label: \"LinkedIn · blog promo\", meta: 'Social template', w: 1500, h: 950, src: \"s1-14-blog-promo/Stat%20of%20the%20Week.html\" },\n        { id: 'S1.19', label: \"LinkedIn carousel · 7 mistakes\", meta: 'Social template', w: 1500, h: 950, src: \"s1-19-carousel-7-mistakes/7%20Mistakes%20Carousel.html\" },\n        { id: 'S1.21', label: \"LinkedIn carousel · case-study numbers\", meta: 'Social template', w: 1500, h: 950, src: \"s1-21-carousel-case-numbers/LinkedIn%20Carousel%20-%20AaaS%20Case%20Study.html\" },\n        { id: 'S1.26', label: \"X · launch card\", meta: 'Social template', w: 1500, h: 950, src: \"s1-26-x-launch-card/Launch%20Card.html\" },\n        { id: 'S1.28', label: \"X · stat bomb\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-28-x-stat-bomb/stat-bomb.html\" },\n        { id: 'S1.30', label: \"X · thread cover\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-30-x-thread-cover/Launch%20Card.html\" },\n        { id: 'S1.33', label: \"X · product update\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-33-x-product-update/Launch%20Card.html\" },\n        { id: 'S1.37', label: \"X · shipped this week\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-37-x-shipped-week/Recruiting%20Card.html\" },\n        { id: 'S1.41', label: \"Instagram · announcement\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-41-ig-announcement/Instagram%20Announcement%20Card.html\" },\n        { id: 'S1.43', label: \"Instagram story · stat\", meta: 'Social template', w: 1200, h: 1650, src: \"s1-43-ig-story-stat/Instagram%20Story%20Stat.html\" },\n        { id: 'S1.45', label: \"Instagram reels cover\", meta: 'Social template', w: 1200, h: 1650, src: \"s1-45-ig-reels-cover/Reels%20Cover.html\" },\n        { id: 'S1.49', label: \"Instagram carousel cover\", meta: 'Social template', w: 1500, h: 950, src: \"s1-49-ig-carousel-cover/Instagram%20Carousel%20Cover.html\" },\n        { id: 'S2.1', label: \"LinkedIn · document carousel cover\", meta: 'Social template', w: 1500, h: 950, src: \"s2-1-doc-carousel-cover/index.html\" },\n        { id: 'S2.4', label: \"LinkedIn · video cover\", meta: 'Social template', w: 1400, h: 1400, src: \"s2-4-linkedin-video-cover/S2.4%20LinkedIn%20video-cover-frame.html\" },\n        { id: 'S2.7', label: \"LinkedIn · event cover\", meta: 'Social template', w: 1400, h: 1400, src: \"s2-7-linkedin-event-cover/LinkedIn%20event%20banner.html\" },\n        { id: 'S2.8', label: \"LinkedIn · article hero\", meta: 'Social template', w: 1400, h: 1400, src: \"s2-8-linkedin-article-hero/index.html\" },\n        { id: 'S2.10', label: \"LinkedIn · newsletter cover\", meta: 'Social template', w: 1400, h: 1400, src: \"s2-10-linkedin-newsletter/linkedin-newsletter-cover.html\" },\n        { id: 'S3.2', label: \"X · summary large card\", meta: 'Social template', w: 1500, h: 950, src: \"s3-2-x-summary-large/S3.2%20X%20summary-large-card.html\" },\n        { id: 'S3.3', label: \"X · quote square\", meta: 'Social template', w: 1400, h: 1400, src: \"s3-3-x-quote-square/S3.3%20-%20x-quote-card-square.html\" },\n        { id: 'S3.4', label: \"X · thread card numbered\", meta: 'Social template', w: 1400, h: 1400, src: \"s3-4-x-thread-numbered/index.html\" },\n        { id: 'S3.9', label: \"X · launch announce\", meta: 'Social template', w: 1400, h: 1400, src: \"s3-9-x-launch-announce/S3.9%20-%20x-launch-announce.html\" },\n        { id: 'S3.10', label: \"X · stat of the week\", meta: 'Social template', w: 1400, h: 1400, src: \"s3-10-x-stat-week/index.html\" },\n        { id: 'S4.1', label: \"Instagram · feed square\", meta: 'Social template', w: 1400, h: 1400, src: \"s4-1-ig-feed-square/S4.1%20-%20ig-feed-square.html\" },\n        { id: 'P0.1', label: \"LinkedIn carousel · architecture 5-slide\", meta: 'Paid & organic', w: 1500, h: 950, src: \"p0-1-li-carousel-arch/LinkedIn%20Carousel.html\" },\n        { id: 'P0.2', label: \"Instagram carousel · agent anatomy\", meta: 'Paid & organic', w: 1500, h: 950, src: \"p0-2-ig-carousel-anatomy/Anatomy%20Carousel.html\" },\n        { id: 'P0.3', label: \"X profile banner · visual hook\", meta: 'Paid & organic', w: 1500, h: 950, src: \"p0-3-x-banner-hook/Banner.html\" },\n        { id: 'P0.5', label: \"YouTube thumbnail · deep dive\", meta: 'Paid & organic', w: 1500, h: 950, src: \"p0-5-yt-thumbnail/Thumbnail%20Template.html\" },\n        { id: 'P0.6', label: \"GitHub social preview\", meta: 'Paid & organic', w: 1500, h: 950, src: \"p0-6-github-preview/index.html\" },\n        { id: 'P0.7', label: \"Product Hunt launch card\", meta: 'Paid & organic', w: 1400, h: 1400, src: \"p0-7-producthunt-card/launch-card.html\" },\n        { id: 'P0.8', label: \"Before/after architecture banner\", meta: 'Paid & organic', w: 1500, h: 950, src: \"p0-8-before-after-banner/Before-After%20Banner.html\" },\n        { id: 'P0.10', label: \"Threads / Bluesky vertical hook\", meta: 'Paid & organic', w: 1400, h: 1400, src: \"p0-10-threads-hook/index.html\" },\n        { id: 'P0.11', label: \"Reels / TikTok static cover\", meta: 'Paid & organic', w: 1200, h: 1650, src: \"p0-11-reels-cover/Reels%20Cover.html\" },\n        { id: 'P0.12', label: \"Hacker News og-card\", meta: 'Paid & organic', w: 1500, h: 950, src: \"p0-12-hn-og-card/og-card.html\" },\n        { id: 'EH', label: \"Email header\", meta: 'Social template', w: 700, h: 1500, src: \"email-header/Email%20Header.html\" },\n        { id: 'LI', label: \"LinkedIn post · base template\", meta: 'Social template', w: 1400, h: 1400, src: \"linkedin-post/linkedin-post.html\" },\n        { id: 'S9.3', label: \"Pinterest · idea-pin cover\", meta: 'Social template', w: 1400, h: 1400, src: \"s9-3-pin-idea-cover/Pinterest%20Idea%20Pin%20Cover.html\" },\n        { id: 'S9.4', label: \"Pinterest · idea-pin inner\", meta: 'Social template', w: 1200, h: 1650, src: \"s9-4-pin-idea-inner/S9.4%20Pinterest%20idea-pin-inner.html\" },\n        { id: 'S9.5', label: \"Pinterest · board cover\", meta: 'Social template', w: 1400, h: 1400, src: \"s9-5-pin-board-cover/index.html\" },\n        { id: 'S9.6', label: \"Pinterest · tutorial pin\", meta: 'Social template', w: 1200, h: 1650, src: \"s9-6-pin-tutorial/Pinterest%20tutorial%20pin.html\" },\n        { id: 'S10.1', label: \"Reddit · subreddit banner\", meta: 'Social template', w: 1500, h: 950, src: \"s10-1-reddit-banner/Reddit%20Subreddit%20Banner.html\" },\n        { id: 'S12.6', label: \"Telegram · sticker pack cover\", meta: 'Social template', w: 1400, h: 1400, src: \"s12-6-telegram-sticker/Telegram%20Sticker%20Pack%20Cover.html\" },\n        { id: 'S13.2', label: \"Snapchat · story tile\", meta: 'Social template', w: 1200, h: 1650, src: \"s13-2-snap-story-tile/index.html\" },\n        { id: 'S13.3', label: \"Snapchat · discover tile\", meta: 'Social template', w: 1200, h: 1650, src: \"s13-3-snap-discover/index.html\" },\n        { id: 'S14.2', label: \"WhatsApp · business profile\", meta: 'Social template', w: 1400, h: 1400, src: \"s14-2-wa-business-profile/profile-photo.html\" },\n        { id: 'S14.3', label: \"WhatsApp · broadcast card\", meta: 'Social template', w: 1400, h: 1400, src: \"s14-3-wa-broadcast/index.html\" },\n        { id: 'S15.1', label: \"Hacker News · og card\", meta: 'Social template', w: 1500, h: 950, src: \"s15-1-hn-og/og-cards.html\" },\n        { id: 'S15.2', label: \"Lobsters · og card\", meta: 'Social template', w: 1500, h: 950, src: \"s15-2-lobsters-og/Lobsters%20-%20S15.2%20-%20OG%20Card.html\" },\n        { id: 'S15.4', label: \"dev.to · social share\", meta: 'Social template', w: 1500, h: 950, src: \"s15-4-devto-share/Dev.to%20Social%20Share%20Card.html\" }\n      ] };\n    // Explainer tours — one list, two homes (Onboarding › Explainers, Design › Videos). Tracks are the\n    // CanvasTour rig ids; CX-02 is the authored animation and opens its own file. Built with the same\n    // table()/tCell()/mono() helpers every other lens table uses, so it renders through the shared lens.\n    const EXPL = [\n      ['CX-01 · Admit the scale', 'I · the whole surface', '0:26', 'tour', './CanvasTour.dc.html#CX-01'],\n      ['CX-02 · Four fixed regions', 'II · navigation model', '0:25', 'authored animation', this.rootPrefix + 'assets/videos/CX-02 Four Regions.dc.html'],\n      ['CX-03 · Roles are filters', 'II · navigation model', '1:08', 'tour', './CanvasTour.dc.html#CX-03'],\n      ['CX-04 · A stream is one job', 'II · navigation model', '0:45', 'tour', './CanvasTour.dc.html#CX-04'],\n      ['CX-05 · Two ways in', 'III · two ways in', '0:34', 'tour', './CanvasTour.dc.html#CX-05'],\n      ['CX-06 · Why you can trust it', 'IV · honesty badges', '0:44', 'tour', './CanvasTour.dc.html#CX-06'],\n      ['CX-07 · One product, for anyone', 'V · tenancy', '0:30', 'tour', './CanvasTour.dc.html#CX-07'],\n      ['08 · Step plan · intro tour', 'in-canvas step plan (the ? button)', '6 steps', 'in-canvas', 'tour:intro'],\n      ['SX-01 · You are the inbox', 'stream · Inbox', '~1:05', 'tour · VO pending', null],\n      ['SX-02 · The GTM fleet', 'stream · GrowthRun', '~1:29', 'tour · VO pending', null]\n    ];\n    const EXPLAINERS = table('Canvas explainers · click-through tours over the real shell', 'Each one plays its recorded voice-over and drives this canvas. Click a row to open it on that track.',\n      [{ label: 'Piece', flex: 2.2, align: 'left' }, { label: 'Movement', flex: 1.5, align: 'left' }, { label: 'Length', flex: 0.7, align: 'right' }, { label: 'Picture', flex: 1.4, align: 'left' }, { label: '', flex: 0.7, align: 'right' }],\n      EXPL.map(([piece, mv, len, pic, href]) => ({\n        cells: [tCell(piece, { flex: 2.2 }), mono(mv, { flex: 1.5 }), mono(len, { flex: 0.7, align: 'right' }), mono(pic, { flex: 1.4, color: href ? 'var(--app-ink-soft)' : 'var(--app-warn)' }), mono(href ? 'Open ↗' : '—', { flex: 0.7, align: 'right', color: href ? 'var(--app-accent)' : 'var(--app-ink-faint)' })],\n        // Same-tab navigation: window.open surfaced the raw serve URL in a new browser tab (reported\n        // 2026-09-03). The tour imports this shell, so it replaces the current document; the browser's\n        // Back returns here. Never a popup.\n        onClick: href === 'tour:intro' ? () => this.setState({ tour: 0, settings: false }) : href ? () => { try { window.location.assign(href); } catch (e) {} } : undefined\n      })));\n    // ── Toolbox · table plane per phase, from the tenant toolset pack (O1.1) ──────────────\n    // One row per template. Clicking a row opens the Studio on that document (O1b); the ↗ cell opens\n    // the template itself. Counts are never typed — every number here comes from the manifest.\n    const TS = window.__dcToolset;\n    if (TS && TS.items && TS.items.length) {\n      const tCols = [{ label: 'Template', flex: 2.4, align: 'left' }, { label: 'Phase', flex: 1.5, align: 'left' },\n        { label: 'What it does', flex: 3.2, align: 'left' }, { label: '', flex: 0.6, align: 'right' }];\n      const tRow = (it) => ({\n        cells: [tCell(it.title, { flex: 2.4 }), mono(it.meta, { flex: 1.5 }),\n          tCell((it.desc || '').slice(0, 92) + ((it.desc || '').length > 92 ? '…' : ''), { flex: 3.2, color: 'var(--app-ink-soft)' }),\n          mono('Open ↗', { flex: 0.6, align: 'right', color: 'var(--app-accent)' })],\n        // Carried through the table lens (it rebuilds onClick from the cells) — see the lens wiring's\n        // `doc: r.doc` pass-through. Opening the row opens the document's context fields, editable.\n        doc: this.toolboxDoc(it), href: it.href\n      });\n      const tb = { Overview: table('Toolbox · ' + TS.n + ' templates across ' + TS.ph + ' phases',\n        'The tenant toolset pack. Open one in the Studio to edit its context fields and optimise the document.',\n        tCols, TS.items.map(tRow)) };\n      TS.phases.forEach(p => {\n        const rows = TS.items.filter(i => i.phase === p.id);\n        tb[p.id + ' ' + p.name] = table(p.name + ' · ' + rows.length + ' templates',\n          'Phase ' + p.id + ' of the startup journey · ' + p.dir, tCols, rows.map(tRow));\n      });\n      // Studio (O1b) is a native branch — until it lands, say so honestly rather than render an empty tab.\n      tb.Studio = list('Document Studio', 'Open a document, edit the context fields its schema declares, optimise, finalise — canvas/20-plans/TOOLBOX-DOCUMENT-STUDIO.md', [\n        { title: 'Context fields are predetermined by the template', meta: 'schema.context_keys — e.g. company.legal.dpo_name, company.market.target_audience', chip: 'contract ready', color: 'var(--app-success)' },\n        { title: 'Auto-fill from the tenant context pack', meta: 'fields[].context_map → tenants/' + TS.tenant + '/context/company-context.json', chip: 'contract ready', color: 'var(--app-success)' },\n        { title: 'Optimise writes the rest', meta: 'fields[].ai_prompt against the company context; diff before accept', chip: 'planned', color: 'var(--app-warn)' },\n        { title: 'Finalise snapshots the document', meta: '.data.json v<n> + an Inbox thread — reply revises, forward hands off', chip: 'planned', color: 'var(--app-warn)' },\n        { title: 'Schemas on disk', meta: '3 of ' + TS.n + ' seeded at tenants/' + TS.tenant + '/toolset/schemas/ — the rest is one copy batch', chip: 'partial', color: 'var(--app-warn)' }\n      ]);\n      this.DEV_PANES.toolbox = tb;\n    }\n    this.DEV_PANES.onboarding = Object.assign(this.DEV_PANES.onboarding || {}, { Explainers: EXPLAINERS });\n    this.DEV_PANES.design.Videos = EXPLAINERS;\n    this.DEV_PANES.design.Gallery = GALLERY_FRAMES;\n    // 2026-09-03 consolidation: the gallery is index-driven. The 85 hand-sized frames above keep their\n    // authored labels/sizes; every other item in assets/index.json (generated from disk by\n    // assets/build-index.js) is appended at load with a class default size. manifest.json is retired.\n    // Lifecycle emails are grouped into their own segment so 34 near-identical previews don't drown the rest.\n    /* Track 2 · fetch the storefront's own catalog. Same pattern as _loadGalleryIndex: one fetch, a\n       named source on the result, and a failure that is stated rather than hidden. `store-data` is\n       registered as a publish resource so the bundled build resolves it too. */\n    this._loadStoreCatalog = () => {\n      const url = (window.__resources && window.__resources['store-data']) || (this.rootPrefix + 'assets/domains/store/data.jsx');\n      return fetch(url).then(r => r.ok ? r.text() : null).then(text => {\n        if (!text) throw new Error('HTTP miss');\n        const items = this.parseStoreCatalog(text);\n        if (!items.length) throw new Error('CATALOG scan matched 0 items');\n        this._storeCatalog = { items: items, from: url, at: new Date().toISOString() };\n      }).catch(e => {\n        console.warn('[store] catalog not read:', e && e.message);\n        this._storeCatalog = { items: [], from: url, error: (e && e.message) || 'unknown' };\n      }).then(() => {\n        window.__storeCatalog = this._storeCatalog;\n        this.applyStoreCatalog();\n        this.forceUpdate();\n      });\n    };\n    this._loadGalleryIndex = () => {\n      const url = (window.__resources && window.__resources['asset-index']) || '../../assets/index.json';\n      return fetch(url).then(r => r.ok ? r.json() : null).then(idx => {\n        // domains count: the one number the shell cannot compute from itself (see applyDomainCounts).\n        if (idx && idx.classes && idx.classes.domains && typeof idx.classes.domains.count === 'number' && this._domCounts) {\n          this._domCounts.pulled = idx.classes.domains.count;\n          this._domCounts.pulledFrom = 'assets/index.json';\n          this.applyDomainCounts();\n          this.forceUpdate();\n        }\n        if (!idx || !idx.classes || !idx.classes.gallery) return;\n        const have = new Set(GALLERY_FRAMES.frames.map(f => f.src.replace(/%20/g, ' ')));\n        const SIZE = { emails: [700, 1500], social: [1200, 1200], misc: [1200, 900] };\n        const extra = [];\n        for (const it of idx.classes.gallery.items) {\n          const rel = it.src.replace(/^gallery\\//, '');\n          if (have.has(rel)) continue;\n          const [w, h] = SIZE[it.class] || SIZE.misc;\n          const label = it.series === 'LIFECYCLE' ? `${it.kind} · ${it.property}` : (it.slug.replace(/^social\\//, '').replace(/^[a-z]+\\d+(-\\d+)?-/, '').replace(/-/g, ' '));\n          extra.push({ id: it.id, label, meta: (it.series === 'LIFECYCLE' ? 'Lifecycle email' : it.class === 'emails' ? 'Email' : 'Social template') + (it.done === 'FALSE' ? ' · PLANNED' : ''), w, h, src: rel, seg: it.series === 'LIFECYCLE' ? 'lifecycle' : it.class, planned: it.done === 'FALSE' });\n        }\n        if (!extra.length || window.__dcGalleryExtra) return;\n        // Cache on the window; renderVals rebuilds GALLERY_FRAMES on every pass and re-applies from here.\n        window.__dcGalleryExtra = { extra, generated: idx.generated.slice(0, 10) };\n        this.forceUpdate();\n      }).catch(() => {});\n    };\n    if (window.__dcGalleryExtra) {\n      const x = window.__dcGalleryExtra;\n      GALLERY_FRAMES.frames = GALLERY_FRAMES.frames.concat(x.extra);\n      GALLERY_FRAMES.note = `${GALLERY_FRAMES.frames.length} live files, indexed from disk (assets/index.json · ${x.generated}). Open ↗ for full size.`;\n    } else if (!window.__dcGalleryIndexScheduled) {\n      // Once per document — this block runs on every renderVals pass.\n      window.__dcGalleryIndexScheduled = true; setTimeout(() => this._loadGalleryIndex(), 0);\n    }\n    this.DEV_PANES.design.Templates = { view: 'stack', segs: [{ id: 'email', label: 'Email' }, { id: 'social', label: 'Social' }, { id: 'brand', label: 'Launch & brand' }], title: \"Template Library · T1–T20\", note: \"20 brand templates — email shells, social formats, launch set, pitch-deck kit, brand book, logo lockup. Each one re-skins from the token contract.\",\n      frames: [\n        { id: 'T1', grp: 'email', label: \"Lifecycle Email Shell\", meta: 'Template Library · token-bound', w: 900, h: 1500, src: \"../templates/T1%20Lifecycle%20Email%20Shell.dc.html\" },\n        { id: 'T2', grp: 'email', label: \"Announcement Email\", meta: 'Template Library · token-bound', w: 900, h: 1500, src: \"../templates/T2%20Announcement%20Email.dc.html\" },\n        { id: 'T3', grp: 'email', label: \"Digest Newsletter Email\", meta: 'Template Library · token-bound', w: 900, h: 1500, src: \"../templates/T3%20Digest%20Newsletter%20Email.dc.html\" },\n        { id: 'T4', grp: 'social', label: \"LinkedIn Square Post\", meta: 'Template Library · token-bound', w: 1400, h: 1300, src: \"../templates/T4%20LinkedIn%20Square%20Post.dc.html\" },\n        { id: 'T5', grp: 'social', label: \"LinkedIn Carousel\", meta: 'Template Library · token-bound', w: 1400, h: 1300, src: \"../templates/T5%20LinkedIn%20Carousel.dc.html\" },\n        { id: 'T6', grp: 'social', label: \"LinkedIn Banner\", meta: 'Template Library · token-bound', w: 1500, h: 950, src: \"../templates/T6%20LinkedIn%20Banner.dc.html\" },\n        { id: 'T7', grp: 'social', label: \"X Twitter Card\", meta: 'Template Library · token-bound', w: 1500, h: 950, src: \"../templates/T7%20X%20Twitter%20Card.dc.html\" },\n        { id: 'T8', grp: 'social', label: \"X Profile Header\", meta: 'Template Library · token-bound', w: 1500, h: 950, src: \"../templates/T8%20X%20Profile%20Header.dc.html\" },\n        { id: 'T9', grp: 'social', label: \"Instagram Post Set\", meta: 'Template Library · token-bound', w: 1500, h: 950, src: \"../templates/T9%20Instagram%20Post%20Set.dc.html\" },\n        { id: 'T10', grp: 'social', label: \"TikTok Hook Cover\", meta: 'Template Library · token-bound', w: 1200, h: 1650, src: \"../templates/T10%20TikTok%20Hook%20Cover.dc.html\" },\n        { id: 'T11', grp: 'social', label: \"YouTube Thumbnail\", meta: 'Template Library · token-bound', w: 1500, h: 950, src: \"../templates/T11%20YouTube%20Thumbnail.dc.html\" },\n        { id: 'T12', grp: 'social', label: \"Pinterest Pin\", meta: 'Template Library · token-bound', w: 1200, h: 1650, src: \"../templates/T12%20Pinterest%20Pin.dc.html\" },\n        { id: 'T13', grp: 'social', label: \"Open Graph Card\", meta: 'Template Library · token-bound', w: 1500, h: 950, src: \"../templates/T13%20Open%20Graph%20Card.dc.html\" },\n        { id: 'T14', grp: 'social', label: \"Snapchat Tile\", meta: 'Template Library · token-bound', w: 1200, h: 1650, src: \"../templates/T14%20Snapchat%20Tile.dc.html\" },\n        { id: 'T15', grp: 'social', label: \"WhatsApp Asset Set\", meta: 'Template Library · token-bound', w: 1500, h: 950, src: \"../templates/T15%20WhatsApp%20Asset%20Set.dc.html\" },\n        { id: 'T16', grp: 'social', label: \"Reddit Telegram Set\", meta: 'Template Library · token-bound', w: 1500, h: 950, src: \"../templates/T16%20Reddit%20Telegram%20Set.dc.html\" },\n        { id: 'T17', grp: 'brand', label: \"Launch Set\", meta: 'Template Library · token-bound', w: 1500, h: 950, src: \"../templates/T17%20Launch%20Set.dc.html\" },\n        { id: 'T18', grp: 'brand', label: \"Pitch Deck Kit\", meta: 'Template Library · token-bound', w: 900, h: 1500, src: \"../templates/T18%20Pitch%20Deck%20Kit.dc.html\" },\n        { id: 'T19', grp: 'brand', label: \"Brand Book One-Pager\", meta: 'Template Library · token-bound', w: 900, h: 1500, src: \"../templates/T19%20Brand%20Book%20One-Pager.dc.html\" },\n        { id: 'T20', grp: 'brand', label: \"Favicon Logo Lockup\", meta: 'Template Library · token-bound', w: 1400, h: 1300, src: \"../templates/T20%20Favicon%20Logo%20Lockup.dc.html\" }\n      ] };\n    this.DEV_PANES.design['Building Blocks'] = { view: 'stack', segs: [{ id: 'archetypes', label: 'View archetypes' }, { id: 'charts', label: 'Charts' }, { id: 'diagrams', label: 'Diagrams' }, { id: 'icons', label: 'Icon sets' }, { id: 'patterns', label: 'Patterns' }], title: \"Canvas 2.0 building blocks\", note: \"38 block families — charts (C), diagrams (D), icon sets (I) and dashboard patterns (P) the Canvas composes from.\",\n      frames: [\n        { id: 'V1', grp: 'archetypes', label: \"Collection Archetypes\", meta: 'View archetypes · board · cards · calendar · swimlanes · thread', w: 1400, h: 1500, src: \"../blocks/V1-Collection-Archetypes.dc.html\" },\n        { id: '00', grp: 'charts', label: \"Catalog Index\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/00-Catalog-Index.dc.html\" },\n        { id: 'C1', grp: 'charts', label: \"Line Area Family\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/C1-Line-Area-Family.dc.html\" },\n        { id: 'C2', grp: 'charts', label: \"Bar Column Family\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/C2-Bar-Column-Family.dc.html\" },\n        { id: 'C3', grp: 'charts', label: \"Part to Whole\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/C3-Part-to-Whole.dc.html\" },\n        { id: 'C4', grp: 'charts', label: \"Distribution Correlation\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/C4-Distribution-Correlation.dc.html\" },\n        { id: 'C5', grp: 'charts', label: \"Advanced Analytics\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/C5-Advanced-Analytics.dc.html\" },\n        { id: 'C6', grp: 'charts', label: \"Financial\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/C6-Financial.dc.html\" },\n        { id: 'C7', grp: 'charts', label: \"KPI Stat Tiles\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/C7-KPI-Stat-Tiles.dc.html\" },\n        { id: 'C8', grp: 'charts', label: \"Geo Specialty Charts\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/C8-Geo-Specialty-Charts.dc.html\" },\n        { id: 'D0', grp: 'diagrams', label: \"Mermaid Flow Family\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/D0-Mermaid-Flow-Family.dc.html\" },\n        { id: 'D1', grp: 'diagrams', label: \"Relational\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/D1-Relational.dc.html\" },\n        { id: 'D2', grp: 'diagrams', label: \"Set Matrix\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/D2-Set-Matrix.dc.html\" },\n        { id: 'D3', grp: 'diagrams', label: \"Process Temporal\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/D3-Process-Temporal.dc.html\" },\n        { id: 'D4', grp: 'diagrams', label: \"Persuasion Exec\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/D4-Persuasion-Exec.dc.html\" },\n        { id: 'D5', grp: 'diagrams', label: \"Timeline Diff\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/D5-Timeline-Diff.dc.html\" },\n        { id: 'I0', grp: 'icons', label: \"Icon System Foundation\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/I0-Icon-System-Foundation.dc.html\" },\n        { id: 'I1', grp: 'icons', label: \"Action Set\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/I1-Action-Set.dc.html\" },\n        { id: 'I2', grp: 'icons', label: \"Navigation Set\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/I2-Navigation-Set.dc.html\" },\n        { id: 'I3', grp: 'icons', label: \"Status Signal Set\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/I3-Status-Signal-Set.dc.html\" },\n        { id: 'I4', grp: 'icons', label: \"Object Entity Set\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/I4-Object-Entity-Set.dc.html\" },\n        { id: 'I5', grp: 'icons', label: \"Domain Set\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/I5-Domain-Set.dc.html\" },\n        { id: 'I6', grp: 'icons', label: \"Brand Glyph Set\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/I6-Brand-Glyph-Set.dc.html\" },\n        { id: 'P1', grp: 'patterns', label: \"Indicators Atoms\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/P1-Indicators-Atoms.dc.html\" },\n        { id: 'P10', grp: 'patterns', label: \"Department Specialty Widgets\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/P10-Department-Specialty-Widgets.dc.html\" },\n        { id: 'P11', grp: 'patterns', label: \"Industry Specialty Widgets\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/P11-Industry-Specialty-Widgets.dc.html\" },\n        { id: 'P12', grp: 'patterns', label: \"Composed Dashboard Templates\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/P12-Composed-Dashboard-Templates.dc.html\" },\n        { id: 'P13', grp: 'patterns', label: \"Motion Extended States\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/P13-Motion-Extended-States.dc.html\" },\n        { id: 'P14', grp: 'patterns', label: \"Responsive Print Embed Proof\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/P14-Responsive-Print-Embed-Proof.dc.html\" },\n        { id: 'P15', grp: 'patterns', label: \"Signature Visual Treatments\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/P15-Signature-Visual-Treatments.dc.html\" },\n        { id: 'P2', grp: 'patterns', label: \"Controls\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/P2-Controls.dc.html\" },\n        { id: 'P3', grp: 'patterns', label: \"Containers State Blocks\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/P3-Containers-State-Blocks.dc.html\" },\n        { id: 'P4', grp: 'patterns', label: \"Form Fields\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/P4-Form-Fields.dc.html\" },\n        { id: 'P5', grp: 'patterns', label: \"Data Table Collab\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/P5-Data-Table-Collab.dc.html\" },\n        { id: 'P6', grp: 'patterns', label: \"Utility\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/P6-Utility.dc.html\" },\n        { id: 'P7', grp: 'patterns', label: \"Media Realtime\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/P7-Media-Realtime.dc.html\" },\n        { id: 'P8', grp: 'patterns', label: \"Commerce Compliance\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/P8-Commerce-Compliance.dc.html\" },\n        { id: 'P9', grp: 'patterns', label: \"RTL Whitelabel Proof\", meta: 'Canvas 2.0 building blocks', w: 1400, h: 1300, src: \"../blocks/P9-RTL-Whitelabel-Proof.dc.html\" }\n      ] };\n\n    this.DEV_PANES.design.Email = { view: 'frames', title: 'Email templates · live files', note: '31 extracted · welcome, explainer, lifecycle, announcement and transactional series',\n      frames: [\n        { id: 'A2', label: \"Welcome · aaas.select\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a2-welcome-select/welcome-aaas-select.html\" },\n        { id: 'A3', label: \"Welcome · aaas.diy\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a3-welcome-diy/welcome-email.html\" },\n        { id: 'A4', label: \"Welcome · aaas.builders\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a4-welcome-builders/welcome-aaas-builders.html\" },\n        { id: 'A6', label: \"Welcome · aaas.name\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a6-welcome-name/welcome-aaas-name.html\" },\n        { id: 'A7', label: \"Welcome · aaas.company\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a7-welcome-company/welcome-aaas-company.html\" },\n        { id: 'A8', label: \"Welcome · aaas.business\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a8-welcome-business/welcome-aaas-business.html\" },\n        { id: 'A10', label: \"Welcome · aaas.pics\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a10-welcome-pics/welcome-aaas-pics.html\" },\n        { id: 'A11', label: \"Welcome · aaas.blog\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a11-welcome-blog/welcome-aaas-blog.html\" },\n        { id: 'A12', label: \"Welcome · aaas.academy\", meta: 'Email · 600px', w: 700, h: 1500, src: \"a12-welcome-academy/welcome-email.html\" },\n        { id: 'B1', label: \"Product update · monthly\", meta: 'Email · 600px', w: 700, h: 1500, src: \"b1-product-update/Product%20Update%20Monthly.html\" },\n        { id: 'B2', label: \"Context engine explainer\", meta: 'Email · 600px', w: 700, h: 1500, src: \"b2-context-engine/Context%20Engine%20Email.html\" },\n        { id: 'B3', label: \"Skills offering explainer\", meta: 'Email · 600px', w: 700, h: 1500, src: \"b3-skills-offering/skills-explainer.html\" },\n        { id: 'B4', label: \"Vault offering explainer\", meta: 'Email · 600px', w: 700, h: 1500, src: \"b4-vault-offering/Vault%20Explainer%20Email.html\" },\n        { id: 'C1', label: \"Trial expiring soon\", meta: 'Email · 600px', w: 700, h: 1500, src: \"c1-trial-expiring/index.html\" },\n        { id: 'C2', label: \"First run success\", meta: 'Email · 600px', w: 700, h: 1500, src: \"c2-first-run-success/First%20Run%20Success%20Email.html\" },\n        { id: 'C3', label: \"Dormant 7d win-back\", meta: 'Email · 600px', w: 700, h: 1500, src: \"c3-dormant-winback/Dormant%207d%20Winback%20Email.html\" },\n        { id: 'C4', label: \"Referral invite\", meta: 'Email · 600px', w: 700, h: 1500, src: \"c4-referral-invite/Referral%20Invite%20Email.html\" },\n        { id: 'C6', label: \"Eval result summary\", meta: 'Email · 600px', w: 700, h: 1500, src: \"c6-eval-summary/index.html\" },\n        { id: 'C7', label: \"Weekly digest\", meta: 'Email · 600px', w: 700, h: 1500, src: \"c7-weekly-digest/Weekly%20Digest%20Email.html\" },\n        { id: 'C8', label: \"Feedback · NPS\", meta: 'Email · 600px', w: 700, h: 1500, src: \"c8-feedback-nps/Feedback%20NPS%20Email.html\" },\n        { id: 'D1', label: \"Security advisory\", meta: 'Email · 600px', w: 700, h: 1500, src: \"d1-security-advisory/Security%20Advisory%20Email.html\" },\n        { id: 'D3', label: \"Case study spotlight\", meta: 'Email · 600px', w: 700, h: 1500, src: \"d3-case-study/case-study-email.html\" },\n        { id: 'D4', label: \"Beta feature invite\", meta: 'Email · 600px', w: 700, h: 1500, src: \"d4-beta-invite/Beta%20Feature%20Invite.html\" },\n        { id: 'D5', label: \"Agent published\", meta: 'Email · 600px', w: 700, h: 1500, src: \"d5-agent-published/Agent%20Published%20Email.html\" },\n        { id: 'D6', label: \"Earnings payout\", meta: 'Email · 600px', w: 700, h: 1500, src: \"d6-earnings-payout/Payout%20Notification.html\" },\n        { id: 'E1', label: \"Password reset\", meta: 'Email · 600px', w: 700, h: 1500, src: \"e1-password-reset/Password%20Reset.html\" },\n        { id: 'E2', label: \"GDPR export ready\", meta: 'Email · 600px', w: 700, h: 1500, src: \"e2-gdpr-export/GDPR%20Data%20Export%20Email.html\" },\n        { id: 'E3', label: \"Cancellation confirmed\", meta: 'Email · 600px', w: 700, h: 1500, src: \"e3-cancellation/Subscription%20Cancellation%20Confirmed.html\" },\n        { id: 'E4', label: \"Waitlist position\", meta: 'Email · 600px', w: 700, h: 1500, src: \"e4-waitlist-position/Waitlist%20Position%20Update%20Email.html\" },\n        { id: 'EH', label: \"Email header\", meta: 'Social template', w: 700, h: 1500, src: \"email-header/Email%20Header.html\" }\n      ] };\n    this.DEV_PANES.design.Social = { view: 'frames', title: 'Social templates · live files', note: '44 extracted across LinkedIn, X, Instagram, Pinterest, Reddit, Telegram, Snapchat, WhatsApp and dev communities',\n      frames: [\n        { id: 'S1.2', label: \"LinkedIn · whitepaper drop\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-2-whitepaper-drop/LinkedIn%20Whitepaper%20Drop.html\" },\n        { id: 'S1.3', label: \"LinkedIn · new-LLM take\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-3-new-llm-take/Reaction%20Post.html\" },\n        { id: 'S1.5', label: \"LinkedIn · contrarian take\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-5-contrarian-take/linkedin-contrarian-take.html\" },\n        { id: 'S1.6', label: \"LinkedIn · customer quote\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-6-customer-quote/Customer%20Quote%20Square.html\" },\n        { id: 'S1.7', label: \"LinkedIn · milestone\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-7-milestone/Milestone%20Post.html\" },\n        { id: 'S1.10', label: \"LinkedIn · postmortem\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-10-postmortem/Postmortem%20Square.html\" },\n        { id: 'S1.12', label: \"LinkedIn · tool comparison\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-12-tool-comparison/Comparison%20Post.html\" },\n        { id: 'S1.14', label: \"LinkedIn · blog promo\", meta: 'Social template', w: 1500, h: 950, src: \"s1-14-blog-promo/Stat%20of%20the%20Week.html\" },\n        { id: 'S1.19', label: \"LinkedIn carousel · 7 mistakes\", meta: 'Social template', w: 1500, h: 950, src: \"s1-19-carousel-7-mistakes/7%20Mistakes%20Carousel.html\" },\n        { id: 'S1.21', label: \"LinkedIn carousel · case-study numbers\", meta: 'Social template', w: 1500, h: 950, src: \"s1-21-carousel-case-numbers/LinkedIn%20Carousel%20-%20AaaS%20Case%20Study.html\" },\n        { id: 'S1.26', label: \"X · launch card\", meta: 'Social template', w: 1500, h: 950, src: \"s1-26-x-launch-card/Launch%20Card.html\" },\n        { id: 'S1.28', label: \"X · stat bomb\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-28-x-stat-bomb/stat-bomb.html\" },\n        { id: 'S1.30', label: \"X · thread cover\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-30-x-thread-cover/Launch%20Card.html\" },\n        { id: 'S1.33', label: \"X · product update\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-33-x-product-update/Launch%20Card.html\" },\n        { id: 'S1.37', label: \"X · shipped this week\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-37-x-shipped-week/Recruiting%20Card.html\" },\n        { id: 'S1.41', label: \"Instagram · announcement\", meta: 'Social template', w: 1400, h: 1400, src: \"s1-41-ig-announcement/Instagram%20Announcement%20Card.html\" },\n        { id: 'S1.43', label: \"Instagram story · stat\", meta: 'Social template', w: 1200, h: 1650, src: \"s1-43-ig-story-stat/Instagram%20Story%20Stat.html\" },\n        { id: 'S1.45', label: \"Instagram reels cover\", meta: 'Social template', w: 1200, h: 1650, src: \"s1-45-ig-reels-cover/Reels%20Cover.html\" },\n        { id: 'S1.49', label: \"Instagram carousel cover\", meta: 'Social template', w: 1500, h: 950, src: \"s1-49-ig-carousel-cover/Instagram%20Carousel%20Cover.html\" },\n        { id: 'S2.1', label: \"LinkedIn · document carousel cover\", meta: 'Social template', w: 1500, h: 950, src: \"s2-1-doc-carousel-cover/index.html\" },\n        { id: 'S2.4', label: \"LinkedIn · video cover\", meta: 'Social template', w: 1400, h: 1400, src: \"s2-4-linkedin-video-cover/S2.4%20LinkedIn%20video-cover-frame.html\" },\n        { id: 'S2.7', label: \"LinkedIn · event cover\", meta: 'Social template', w: 1400, h: 1400, src: \"s2-7-linkedin-event-cover/LinkedIn%20event%20banner.html\" },\n        { id: 'S2.8', label: \"LinkedIn · article hero\", meta: 'Social template', w: 1400, h: 1400, src: \"s2-8-linkedin-article-hero/index.html\" },\n        { id: 'S2.10', label: \"LinkedIn · newsletter cover\", meta: 'Social template', w: 1400, h: 1400, src: \"s2-10-linkedin-newsletter/linkedin-newsletter-cover.html\" },\n        { id: 'S3.2', label: \"X · summary large card\", meta: 'Social template', w: 1500, h: 950, src: \"s3-2-x-summary-large/S3.2%20X%20summary-large-card.html\" },\n        { id: 'S3.3', label: \"X · quote square\", meta: 'Social template', w: 1400, h: 1400, src: \"s3-3-x-quote-square/S3.3%20-%20x-quote-card-square.html\" },\n        { id: 'S3.4', label: \"X · thread card numbered\", meta: 'Social template', w: 1400, h: 1400, src: \"s3-4-x-thread-numbered/index.html\" },\n        { id: 'S3.9', label: \"X · launch announce\", meta: 'Social template', w: 1400, h: 1400, src: \"s3-9-x-launch-announce/S3.9%20-%20x-launch-announce.html\" },\n        { id: 'S3.10', label: \"X · stat of the week\", meta: 'Social template', w: 1400, h: 1400, src: \"s3-10-x-stat-week/index.html\" },\n        { id: 'S4.1', label: \"Instagram · feed square\", meta: 'Social template', w: 1400, h: 1400, src: \"s4-1-ig-feed-square/S4.1%20-%20ig-feed-square.html\" },\n        { id: 'LI', label: \"LinkedIn post · base template\", meta: 'Social template', w: 1400, h: 1400, src: \"linkedin-post/linkedin-post.html\" },\n        { id: 'S9.3', label: \"Pinterest · idea-pin cover\", meta: 'Social template', w: 1400, h: 1400, src: \"s9-3-pin-idea-cover/Pinterest%20Idea%20Pin%20Cover.html\" },\n        { id: 'S9.4', label: \"Pinterest · idea-pin inner\", meta: 'Social template', w: 1200, h: 1650, src: \"s9-4-pin-idea-inner/S9.4%20Pinterest%20idea-pin-inner.html\" },\n        { id: 'S9.5', label: \"Pinterest · board cover\", meta: 'Social template', w: 1400, h: 1400, src: \"s9-5-pin-board-cover/index.html\" },\n        { id: 'S9.6', label: \"Pinterest · tutorial pin\", meta: 'Social template', w: 1200, h: 1650, src: \"s9-6-pin-tutorial/Pinterest%20tutorial%20pin.html\" },\n        { id: 'S10.1', label: \"Reddit · subreddit banner\", meta: 'Social template', w: 1500, h: 950, src: \"s10-1-reddit-banner/Reddit%20Subreddit%20Banner.html\" },\n        { id: 'S12.6', label: \"Telegram · sticker pack cover\", meta: 'Social template', w: 1400, h: 1400, src: \"s12-6-telegram-sticker/Telegram%20Sticker%20Pack%20Cover.html\" },\n        { id: 'S13.2', label: \"Snapchat · story tile\", meta: 'Social template', w: 1200, h: 1650, src: \"s13-2-snap-story-tile/index.html\" },\n        { id: 'S13.3', label: \"Snapchat · discover tile\", meta: 'Social template', w: 1200, h: 1650, src: \"s13-3-snap-discover/index.html\" },\n        { id: 'S14.2', label: \"WhatsApp · business profile\", meta: 'Social template', w: 1400, h: 1400, src: \"s14-2-wa-business-profile/profile-photo.html\" },\n        { id: 'S14.3', label: \"WhatsApp · broadcast card\", meta: 'Social template', w: 1400, h: 1400, src: \"s14-3-wa-broadcast/index.html\" },\n        { id: 'S15.1', label: \"Hacker News · og card\", meta: 'Social template', w: 1500, h: 950, src: \"s15-1-hn-og/og-cards.html\" },\n        { id: 'S15.2', label: \"Lobsters · og card\", meta: 'Social template', w: 1500, h: 950, src: \"s15-2-lobsters-og/Lobsters%20-%20S15.2%20-%20OG%20Card.html\" },\n        { id: 'S15.4', label: \"dev.to · social share\", meta: 'Social template', w: 1500, h: 950, src: \"s15-4-devto-share/Dev.to%20Social%20Share%20Card.html\" }\n      ] };\n    this.DEV_PANES.design.Paid = { view: 'frames', title: 'Paid & organic · live files', note: '10 of the P0 launch set extracted',\n      frames: [\n        { id: 'P0.1', label: \"LinkedIn carousel · architecture 5-slide\", meta: 'Paid & organic', w: 1500, h: 950, src: \"p0-1-li-carousel-arch/LinkedIn%20Carousel.html\" },\n        { id: 'P0.2', label: \"Instagram carousel · agent anatomy\", meta: 'Paid & organic', w: 1500, h: 950, src: \"p0-2-ig-carousel-anatomy/Anatomy%20Carousel.html\" },\n        { id: 'P0.3', label: \"X profile banner · visual hook\", meta: 'Paid & organic', w: 1500, h: 950, src: \"p0-3-x-banner-hook/Banner.html\" },\n        { id: 'P0.5', label: \"YouTube thumbnail · deep dive\", meta: 'Paid & organic', w: 1500, h: 950, src: \"p0-5-yt-thumbnail/Thumbnail%20Template.html\" },\n        { id: 'P0.6', label: \"GitHub social preview\", meta: 'Paid & organic', w: 1500, h: 950, src: \"p0-6-github-preview/index.html\" },\n        { id: 'P0.7', label: \"Product Hunt launch card\", meta: 'Paid & organic', w: 1400, h: 1400, src: \"p0-7-producthunt-card/launch-card.html\" },\n        { id: 'P0.8', label: \"Before/after architecture banner\", meta: 'Paid & organic', w: 1500, h: 950, src: \"p0-8-before-after-banner/Before-After%20Banner.html\" },\n        { id: 'P0.10', label: \"Threads / Bluesky vertical hook\", meta: 'Paid & organic', w: 1400, h: 1400, src: \"p0-10-threads-hook/index.html\" },\n        { id: 'P0.11', label: \"Reels / TikTok static cover\", meta: 'Paid & organic', w: 1200, h: 1650, src: \"p0-11-reels-cover/Reels%20Cover.html\" },\n        { id: 'P0.12', label: \"Hacker News og-card\", meta: 'Paid & organic', w: 1500, h: 950, src: \"p0-12-hn-og-card/og-card.html\" }\n      ] };\n\n    // ══ GTM outreach · one stream, two densities: native mail client on desktop, lenses on a phone ══\n    const scolor = { queued: 'var(--app-ink-faint)', sent: 'var(--app-accent)', opened: 'rgb(var(--pastel-lavender,147 154 255))', clicked: 'var(--app-success)', replied: 'var(--app-success)', bounced: 'var(--app-fail)' };\n    const gtmFrame = (id, label, meta, src) => ({ id, label, meta, w: 660, h: 1180, src: 'gtm/' + src });\n    this.DEV_PANES.outreach = {\n      'Delivery emails': list('Delivery emails · one per step', 'Four transactional sends, one per thing the client gets. Same 600px table, same single CTA into their canvas.', [\n        { title: 'Script — \"Your script is ready\"', meta: 'Fires when the read lands · CTA → Your project › Script · preheader \"45 seconds, written from your own pages.\"', chip: 'built', color: 'var(--app-success)' },\n        { title: 'Storyboard — \"Your storyboard is ready\"', meta: 'Fires on style selection · CTA → Your project › Storyboard · preheader \"Six shots, on your brand.\"', chip: 'built', color: 'var(--app-success)' },\n        { title: 'Snippet — \"Your 5-second preview is ready\"', meta: 'Fires when the preview renders · CTA → Your project › Snippet · preheader \"Watch it before you buy the film.\"', chip: 'draft', color: 'var(--app-accent)' },\n        { title: 'Film — \"Your film is ready\"', meta: 'Fires on delivery · CTA → Your project › Delivery · preheader \"Masters, cutdowns and captions.\"', chip: 'live file', color: 'var(--app-success)' }\n      ]),\n      'Delivery page': { view: 'stack', title: 'Delivery page · the customer surface', note: 'Public, no login. Brand tokens cascade per ADR-032 — not fixed to Canvas basalt.',\n        segs: [{ id: 'states', label: 'States' }, { id: 'brands', label: 'Brand tokens' }, { id: 'types', label: 'Deliverable' }],\n        frames: [\n          { id: 'D1', grp: 'states', label: 'offer · preview, not paid', meta: 'Stripe Payment Link CTA · watermarked viewer · notes field', w: 1280, h: 1020, src: 'gtm/delivery-page.html?state=offer&chrome=0' },\n          { id: 'D2', grp: 'states', label: 'paid_composing · agent working', meta: 'Payment cleared · render log · files locked', w: 1280, h: 1020, src: 'gtm/delivery-page.html?state=paid_composing&chrome=0' },\n          { id: 'D3', grp: 'states', label: 'delivered · files released', meta: 'Clean master · downloads · 2 revision rounds · upsell', w: 1280, h: 1020, src: 'gtm/delivery-page.html?state=delivered&chrome=0' },\n          { id: 'D4', grp: 'states', label: 'offer_expired · revoked', meta: 'Preview deleted · rebuild path · nothing charged', w: 1280, h: 1020, src: 'gtm/delivery-page.html?state=expired&chrome=0' },\n          { id: 'B1', grp: 'brands', label: 'AaaS tokens · basalt + circuit', meta: 'Platform default — 12px radius, cyan accent', w: 1280, h: 1020, src: 'gtm/delivery-page.html?state=delivered&brand=aaas&chrome=0' },\n          { id: 'B2', grp: 'brands', label: 'Client tokens · Nordlicht', meta: 'White-label — warm light surface, 4px radius, forest accent', w: 1280, h: 1020, src: 'gtm/delivery-page.html?state=delivered&brand=nordlicht&chrome=0' },\n          { id: 'B3', grp: 'brands', label: 'Client tokens · Studio Grau', meta: 'White-label — neutral dark, 22px radius, bone accent', w: 1280, h: 1020, src: 'gtm/delivery-page.html?state=delivered&brand=studio&chrome=0' },\n          { id: 'T1', grp: 'types', label: 'video · 1080p master', meta: 'Poster frame, spec strip, per-format downloads', w: 1280, h: 1020, src: 'gtm/delivery-page.html?state=delivered&type=video&chrome=0' },\n          { id: 'T2', grp: 'types', label: 'image · infographic', meta: 'PNG + SVG, same offer and review contract', w: 1280, h: 1020, src: 'gtm/delivery-page.html?state=offer&type=image&chrome=0' },\n          { id: 'T3', grp: 'types', label: 'text · page copy', meta: 'Readable target — blurred tail until paid', w: 1280, h: 1020, src: 'gtm/delivery-page.html?state=offer&type=text&chrome=0' }\n        ] },\n      Sent: table('Sent · last 24 hours', 'Every send is one file, one lead, one activation link',\n        [{ label: 'When', flex: 0.7, align: 'left' }, { label: 'Company', flex: 1.7, align: 'left' }, { label: 'Recipient', flex: 2, align: 'left' },\n         { label: 'Variant', flex: 0.9, align: 'left' }, { label: 'Status', flex: 0.9, align: 'left' }, { label: 'Opens', flex: 0.6, align: 'right' }, { label: 'Clicks', flex: 0.6, align: 'right' }],\n        this.GTM_THREADS.filter(t => t.status !== 'queued').map(t => ({ cells: [\n          mono(t.when, { flex: 0.7 }), tCell(t.company, { flex: 1.7 }), mono(t.to, { flex: 2 }),\n          mono(t.variant, { flex: 0.9 }), mono(t.status, { flex: 0.9, color: scolor[t.status] }),\n          mono(t.status === 'bounced' ? '—' : '1', { flex: 0.6, align: 'right' }),\n          mono(t.status === 'clicked' ? '1' : '0', { flex: 0.6, align: 'right' })\n        ] })).concat([\n          { cells: [mono('2 d', { flex: 0.7 }), tCell('Seilerei Nord', { flex: 1.7 }), mono('kontakt@seilerei-nord.de', { flex: 2 }), mono('Cold A', { flex: 0.9 }), mono('replied', { flex: 0.9, color: scolor.replied }), mono('3', { flex: 0.6, align: 'right' }), mono('1', { flex: 0.6, align: 'right' })] },\n          { cells: [mono('2 d', { flex: 0.7 }), tCell('Moorlicht Kerzen', { flex: 1.7 }), mono('info@moorlicht.de', { flex: 2 }), mono('Cold B', { flex: 0.9 }), mono('opened', { flex: 0.9, color: scolor.opened }), mono('2', { flex: 0.6, align: 'right' }), mono('0', { flex: 0.6, align: 'right' })] }\n        ])),\n      Templates: { view: 'frames', title: 'Email templates · live files', note: 'The same HTML the sender ships',\n        frames: [\n          gtmFrame('C-A', 'Cold A · conversion angle', 'Cold outreach', 'cold-a-conversion.html'),\n          gtmFrame('C-B', 'Cold B · web-video service', 'Cold outreach', 'cold-b-web-video.html'),\n          gtmFrame('DLV', 'Delivery · your film is ready', 'Transactional', 'delivery-ready.html')\n        ] }\n    };\n    // Phone: the three-column mail client does not fit — the same threads render through the lenses.\n    if (mob) Object.assign(this.DEV_PANES.outreach, {\n      Queue: list('Send queue', 'Read-only here — approve a send on a wide screen',\n        this.GTM_THREADS.map(t => ({ title: t.subject, meta: t.company + ' · ' + t.to + ' · ' + t.when, chip: t.variant, color: scolor[t.status] }))),\n      'Cold A': { view: 'frames', title: 'Cold A · conversion angle', note: 'The live file — 600px table, inline styles, merge-ready',\n        frames: [gtmFrame('C-A', 'Cold A · conversion angle', 'Cold outreach', 'cold-a-conversion.html')] },\n      'Cold B': { view: 'frames', title: 'Cold B · web-video service', note: 'Same base template, different angle and CTA',\n        frames: [gtmFrame('C-B', 'Cold B · web-video service', 'Cold outreach', 'cold-b-web-video.html')] },\n      Sent: table('Sent · last 24 hours', 'One file, one lead, one activation link',\n        [{ label: 'When', flex: 0.7, align: 'left' }, { label: 'Company', flex: 1.7, align: 'left' }, { label: 'Variant', flex: 0.9, align: 'left' }, { label: 'Status', flex: 0.9, align: 'left' }],\n        this.GTM_THREADS.filter(t => t.status !== 'queued').map(t => ({ cells: [\n          mono(t.when, { flex: 0.7 }), tCell(t.company, { flex: 1.7 }), mono(t.variant, { flex: 0.9 }), mono(t.status, { flex: 0.9, color: scolor[t.status] })\n        ] })))\n    });\n\n    // ══ MVP client canvas · four small streams, all from the same lenses ══\n    // cdelivery and cbrief render natively (kind: 'client') — no lenses, or the\n    // lens would win over the native plane. See `const kind = spec ? null : …`.\n    this.DEV_PANES.cbilling = {\n      Invoices: table('Invoices', 'One line per delivery · no subscription',\n        [{ label: 'Date', flex: 0.9, align: 'left' }, { label: 'Item', flex: 2.2, align: 'left' }, { label: 'Amount', flex: 0.8, align: 'right' }, { label: 'State', flex: 0.9, align: 'left' }],\n        [\n          { cells: [mono('01 Aug 2026', { flex: 0.9 }), tCell('Web film · cargo line 0:32', { flex: 2.2 }), mono('€149', { flex: 0.8, align: 'right' }), mono('due', { flex: 0.9, color: 'var(--app-fail)' })] },\n          { cells: [mono('12 Jun 2026', { flex: 0.9 }), tCell('Spec infographic · pentest report', { flex: 2.2 }), mono('€99', { flex: 0.8, align: 'right' }), mono('paid', { flex: 0.9, color: 'var(--app-success)' })] },\n          { cells: [mono('04 Mar 2026', { flex: 0.9 }), tCell('Landing hero · attack surface', { flex: 2.2 }), mono('€199', { flex: 0.8, align: 'right' }), mono('paid', { flex: 0.9, color: 'var(--app-success)' })] }\n        ]),\n      Plan: cards('Plan', 'Pay for what you keep · nothing recurring', [\n        { title: 'Pay per delivery', tag: 'active', meta: 'No seats, no minimum. A delivery is priced before work starts.', tagColor: 'rgb(var(--accent-teal) / 0.5)' },\n        { title: 'Payment method', tag: 'card', meta: 'Visa ···· 4417 — charged only when you accept an offer.', tagColor: 'rgb(var(--circuit-glow) / 0.5)' },\n        { title: 'Billing contact', tag: 'you', meta: 'finance@client-site.example · VAT DE317… · invoices arrive as PDF.', tagColor: 'rgb(var(--circuit-glow) / 0.5)' }\n      ])\n    };\n    this.DEV_PANES.csupport = {\n      Messages: list('Your thread', 'One thread, a human at the end · median reply 3 h', [\n        { title: 'Can the closing frame use our new wordmark?', meta: 'you · 2 h ago · waiting on us', chip: 'open', color: 'var(--app-fail)' },\n        { title: 'Your film is ready — offer attached', meta: 'AaaS · 4 h ago', chip: 'answered', color: 'var(--app-success)' },\n        { title: 'We pulled 11 images from client-site.example, nothing else', meta: 'AaaS · 1 d ago', chip: 'answered', color: 'var(--app-success)' },\n        { title: 'Who owns the footage afterwards?', meta: 'you · 1 d ago · answered in 41 m', chip: 'answered', color: 'var(--app-success)' }\n      ])\n    };\n\n    const paneSpecs = this.DEV_PANES[st.id] || null;\n    const rawSubLabel = (st.subs && st.subs[this.state.sub]) ? st.subs[this.state.sub].label : null;\n    // D185: a v2 node tab mounts the v1 pane; the pane still receives the v1 tab label it knows.\n    const V2_TAB = {\n      outbound2: { ICP: 'Builder', Compose: 'Composer', Send: 'Queue', Score: 'Scores' },\n      ads2: { Brief: 'Launch Readiness', Build: 'Campaign Builder', 'Brand safety': 'Assets & Brand Safety', Launch: 'Launch Readiness', Measure: 'Conversions', 'Kill-switch': 'Budget & Kill-Switch' },\n      growth2: { Inventory: 'System inventory', Send: 'Send console', Measure: 'Analytics', Ratchet: 'Experiment ratchet', Run: 'GrowthRun' },\n      // D205: Infra mounts Platform's native pane, which owns Overview + Integrations — 'Infra' alone rendered its empty state.\n      ops2: { Runs: 'Board', Infra: 'Overview', Cost: 'Overview' },\n      knowledge2: { Ask: 'Ask anything', Context: 'Company', Decide: 'Needs review', SOP: 'Protocol' },\n      money2: { Costs: 'Overview', Revenue: 'Rev-rec', Report: 'EÜR report' },\n      account2: { Identity: 'Settings', Plan: 'Billing', People: 'Team', Projects: 'Catalog', Start: 'Walkthrough' },\n      foundry2: { Compose: 'Roster', Docs: 'Overview' },\n      estate2: { Review: 'Mirrors', Publish: 'PSI Gate', Source: 'Overview' },\n      podcast2: { Generate: 'Overview' }\n    };\n    const subLabel = (st.v2 && V2_TAB[st.id] && V2_TAB[st.id][rawSubLabel]) || rawSubLabel;\n    let spec = (paneSpecs && subLabel) ? (paneSpecs[subLabel] || null) : null;\n    // View-mode switcher (docs/CANVAS-IMPROVEMENT-PLAN.md #1): swap in the tab's Board alternate\n    // when picked. viewMode is keyed by stream+tab so different tabs remember their own choice.\n    const viewModeKey = st.id + '·' + subLabel;\n    // Multi-view strip (#1): the tab declares which archetypes it can render; the strip shows one\n    // icon each. state.viewMode is the same per-stream+tab store the Table/Board toggle uses, so a\n    // tab remembers its pick independently. Deliberately NOT persisted to localStorage — that would\n    // be a third ad-hoc preference key; the shared store owed in FABLE-BRIEF.md §7.3 should own it.\n    const declaredViews = (st.tabViews && st.tabViews[subLabel]) || null;\n    const activeView = declaredViews ? (declaredViews.indexOf(this.state.viewMode[viewModeKey]) > -1 ? this.state.viewMode[viewModeKey] : declaredViews[0]) : null;\n    const viewStrip = declaredViews ? declaredViews.map(id => {\n      const o = this.VIEW_OPTIONS.filter(v => v.id === id)[0] || { id, label: id, icon: 'grid' };\n      const on = id === activeView;\n      return {\n        id: o.id, label: o.label, title: 'View → ' + o.label, iconEl: this.ic(o.icon, 15),\n        ariaPressed: on ? 'true' : 'false',\n        // 44px floor, not 34 — same tap-target rule applied to PaneInbox in WP-15 batch 1 (D5).\n        btnStyle: 'display:inline-flex;align-items:center;justify-content:center;min-width:44px;min-height:44px;flex-shrink:0;border-radius:8px;cursor:pointer;background:' +\n          (on ? 'rgb(var(--lead) / 0.14)' : 'transparent') + ';border:1px solid ' + (on ? 'rgb(var(--lead))' : 'transparent') +\n          ';color:' + (on ? 'rgb(var(--lead))' : 'var(--app-ink-faint)'),\n        onClick: () => this.setState(s2 => ({ viewMode: { ...s2.viewMode, [viewModeKey]: id } }))\n      };\n    }) : [];\n    const hasBoardAlt = !!(spec && spec.boardAlt);\n    const viewModeIsBoard = hasBoardAlt && this.state.viewMode[viewModeKey] === 'board';\n    if (viewModeIsBoard) spec = spec.boardAlt;\n    const segActive = spec && spec.segs ? (this.state.galSeg[spec.title] || spec.segs[0].id) : null;\n    let devPane = spec ? {\n      title: spec.title, note: spec.note,\n      showViewSwitcher: hasBoardAlt,\n      viewModeIsBoard,\n      tableBtnBg: viewModeIsBoard ? 'transparent' : 'rgb(var(--lead) / 0.14)',\n      tableBtnColor: viewModeIsBoard ? 'var(--app-ink-soft)' : 'rgb(var(--lead))',\n      boardBtnBg: viewModeIsBoard ? 'rgb(var(--lead) / 0.14)' : 'transparent',\n      boardBtnColor: viewModeIsBoard ? 'rgb(var(--lead))' : 'var(--app-ink-soft)',\n      setViewTable: () => this.setState(s => ({ viewMode: { ...s.viewMode, [viewModeKey]: 'table' } })),\n      setViewBoard: () => this.setState(s => ({ viewMode: { ...s.viewMode, [viewModeKey]: 'board' } })),\n      isTable: spec.view === 'table', isBoard: spec.view === 'board', isCards: spec.view === 'cards',\n      isTail: spec.view === 'tail', isList: spec.view === 'list', isBars: spec.view === 'bars', isFrames: spec.view === 'frames' || spec.view === 'stack',\n      grid: spec.view === 'stack' ? '1fr' : 'repeat(auto-fill,minmax(268px,1fr))',\n      hasSegs: !!(spec.segs && spec.segs.length),\n      segs: (spec.segs || []).map(sg => ({ label: sg.label, count: (spec.frames || []).filter(f => f.grp === sg.id).length,\n        border: segActive === sg.id ? 'rgb(var(--lead) / 0.45)' : 'var(--border)',\n        bg: segActive === sg.id ? 'rgb(var(--lead) / 0.12)' : 'transparent',\n        color: segActive === sg.id ? 'rgb(var(--lead))' : 'var(--app-ink-soft)',\n        onClick: () => this.setState(st5 => ({ galSeg: { ...st5.galSeg, [spec.title]: sg.id } })) })),\n      frames: (spec.frames || []).filter(f => !spec.segs || !f.grp || f.grp === segActive).map(f => { const w = f.w || 1200, h = f.h || 1200, tw = spec.view === 'stack' ? 880 : 268, sc = tw / w; return { key: (spec.title || '') + '·' + f.src, id: f.id, label: f.label, meta: f.meta, src: this.resolveGallerySrc(f.src), srcdoc: this.dcSrcdocFor(f.src), setFrameRef: (el) => { if (!el || el.__dcBound) return; el.__dcBound = true; const tenant = /copy=tenant/.test(f.src);\n            el.addEventListener('load', () => { this.postBrandToFrame({ target: el }); if (tenant) { setTimeout(() => this.recutFrameCopy(el), 400); setTimeout(() => this.recutFrameCopy(el), 1600); } if (f.slots && el.contentWindow) { const post = () => el.contentWindow.postMessage({ type: '__dc_slots', slots: f.slots }, '*'); post(); setTimeout(post, 500); } }); }, w: w + 'px', h: h + 'px', scale: String(Math.round(sc * 1000) / 1000), tileH: Math.round(h * sc) + 'px' }; }),\n      bars: (spec.bars || []).map(b => ({ label: b.label, value: b.value, meta: b.meta || '', pct: b.pct + '%', color: b.color || 'var(--app-accent)' })),\n      cols: spec.cols || [], rows: (spec.rows || []).map((r, ri) => Object.assign({}, r, {\n        // WP-20: table rows open the record-detail drawer. Title = first cell's value (generic\n        // spec rows carry no semantic id field), chip = last cell's value (the state column, by\n        // the table lens's own convention — see the sc-if markup at line ~401).\n        onClick: () => this.openDetail({ doc: r.doc || null, kind: spec.title || st.label, id: '#' + (ri + 1),\n          selId: (spec.title||'') + '·row·' + ri, title: (r.cells[0] || {}).v || 'Record',\n          chip: (r.cells[r.cells.length - 1] || {}).v || '', chipColor: (r.cells[r.cells.length - 1] || {}).color || 'var(--app-ink-soft)',\n          sections: (spec.cols || []).map((c2, ci) => ({ label: c2.label, value: (r.cells[ci] || {}).v || '—' })) })\n      })),\n      columns: (spec.columns || []).map(c => ({\n        label: c.label, count: String(c.items.length), color: dcol(c.state),\n        items: c.items.map(i => ({ title: i.title, meta: (i.harness ? i.harness + ' · ' : '') + (i.note || i.meta || ''),\n          // WP-20: board cards open the record-detail drawer. selId ties selection state to the\n          // literal item identity (title+column), stable across a view/tab switch back to this data.\n          onClick: () => this.openDetail({ kind: spec.title || st.label, id: i.title, selId: (spec.title||'') + '·' + c.label + '·' + i.title,\n            title: i.title, chip: c.label, chipColor: dcol(c.state),\n            sections: [{ label: 'Column', value: c.label }, { label: 'Detail', value: (i.harness ? i.harness + ' · ' : '') + (i.note || i.meta || '') }] }) }))\n      })),\n      // D194 standard: an Overview card that names a tab of this stream opens it (title or explicit `tab`).\n      cards: (spec.cards || []).map(c => { const subsL = (st.subs || []).map(x => typeof x === 'string' ? x : (x && x.label)); const tgt = c.tab || (subsL.indexOf(c.title) > -1 ? c.title : null); const idx = tgt ? subsL.indexOf(tgt) : -1;\n        if (c.stream && this.STREAMS[c.stream]) { const lab = this.STREAMS[c.stream].label + (c.tab ? ' › ' + c.tab : ''); return Object.assign({}, c, { linked: true, cursor: 'pointer', hint: 'Open ' + lab + ' →', onOpen: () => this.goTo(c.stream, c.tab || '') }); }\n        return Object.assign({}, c, { linked: idx > -1, cursor: idx > -1 ? 'pointer' : 'default', hint: idx > -1 ? 'Open ' + tgt + ' →' : '', onOpen: idx > -1 ? () => this.setState({ sub: idx }) : undefined }); }),\n      lines: spec.lines || [], items: spec.items || [],\n      tailTitle: spec.tailTitle || 'aaas://system-log'\n    } : null;\n\n    const slots = this.SLOTS.map(s => ({ name: s.name, desc: s.desc, iconEl: this.ic(s.icon, 18) }));\n\n    // ═══ Canvas-native panes ═══\n    // A stream can be native AND carry lens tabs — the lens wins on tabs that define a pane.\n    // Ads is one stream with six native surfaces; the tab picks which one renders.\n    const ADS_KIND = { 'Launch Readiness': 'launch', 'Campaign Builder': 'builder', 'Assets & Brand Safety': 'assets', Conversions: 'conv', 'Budget & Kill-Switch': 'pacing', 'Account Overview': 'adsAccount' };\n    // Content: three tabs render natively; the rest fall through to the lens panes below.\n    const cntNative = st.id === 'content' && ['Overview', 'Designs', 'Social posts'].indexOf(subLabel) >= 0;\n    // Twelve bespoke lenses, keyed stream›tab. Same bypass rule as cntNative (HANDOVER §2c).\n    const LENS_ROUTE = {\n      'content\\u203aEmail templates \\u00b7 67': 'email', 'content\\u203aLanding pages': 'pages',\n      'content\\u203aDocumentation': 'docs', 'content\\u203aBanners': 'banners',\n      'content\\u203aPosters': 'posters', 'content\\u203aPresentations': 'decks',\n      'content\\u203aVideos': 'videos', 'content\\u203aBlog drafts': 'drafts',\n      'quality\\u203aSupport chat': 'support', 'select\\u203aReplays': 'replays',\n      'ops\\u203aOnboarding': 'flow', 'ops\\u203aImpersonate': 'imp'\n    };\n    const lensKey = LENS_ROUTE[st.id + '\\u203a' + subLabel] || null;\n    // WP-06: ownsTabs bypasses the spec entirely for a stream's declared tabs — the pane renders\n    // regardless of merge order, so the delete-after-both-assigns ritual is gone (docs/PANE-RESOLUTION.md).\n    const owns = !!(st.ownsTabs && st.ownsTabs.indexOf(subLabel) > -1);\n    if (cntNative || lensKey || owns) devPane = null;\n    // D185: v2 streams mount the REAL v1 panes as their nodes (tab → pane kind). Overview and any tab not\n    // listed here keep the lens spec (the template map). Nothing about the v1 pane or stream changes.\n    const V2_KIND = {\n      outbound2: { ICP: 'icp', Scout: 'scout', Leads: 'leads', Compose: 'composer', Send: 'mail', Convert: 'convert', Score: 'cro' },\n      ads2: { Brief: 'launch', Build: 'builder', 'Brand safety': 'assets', Launch: 'launch', Measure: 'conv', 'Kill-switch': 'pacing' },\n      growth2: { Inventory: 'growth', Send: 'growth', Measure: 'growth', Ratchet: 'growth', Run: 'growthrun' },\n      ops2: { Runs: 'wk', Alerts: 'ql', Infra: 'pl', Cost: 'cs' },\n      knowledge2: { Ask: 'ask', Context: 'ctx', Impact: 'ctx', Decide: 'runtrace', SOP: 'protocol' },\n      money2: { Invoices: 'fin', Costs: 'cs', Budgets: 'cs', Revenue: 'cs', Report: 'fin', Billing: 'account' },\n      account2: { Identity: 'account', Plan: 'account', People: 'account', Projects: 'packages', Start: 'onboarding' },\n      foundry2: { Compose: 'foundry', Docs: 'cro' },\n      estate2: { Tokens: 'ds', Review: 'ds', Publish: 'ds', Source: 'domainsHub' },\n      podcast2: { Sources: 'pd', Prompts: 'pd', Generate: 'pd', Trigger: 'pd', Alerts: 'pd', Episodes: 'pd' }\n    };\n    const v2Kind = st.v2 && V2_KIND[st.id] ? (V2_KIND[st.id][rawSubLabel] || null) : null;\n    if (v2Kind) devPane = null;\n    // D192 · v2 nodes with an engine archetype. Data is the v2 fixture set; every proposal declares its source\n    // status so the gate ladder renders \"not checked\" (never a default pass, WP-54) until checkGates runs.\n    const v2Arch = (st.v2 && this.v2ArchFor(st.id, rawSubLabel)) || null;\n    if (v2Arch) devPane = null;\n    // D195: \"moved\" tabs render as a link-card strip on Overview (native or lens) so nothing is lost when a tab leaves the strip.\n    // D205: streams without an Overview tab show the strip on their first tab instead.\n    const movedHome = (st.subs || []).some(s => s.label === 'Overview') ? 'Overview' : ((st.subs && st.subs[0]) ? st.subs[0].label : null);\n    const movedCards = (st.moved && (rawSubLabel === movedHome || !rawSubLabel)) ? st.moved.map(m => ({ key: m[0], title: m[0], meta: 'now in ' + ((this.STREAMS[m[1]] || {}).label || m[1]) + (m[2] ? ' › ' + m[2] : ''), onOpen: () => this.goTo(m[1], m[2] || '') })) : [];\n    const kind = v2Arch ? null : v2Kind ? v2Kind : lensKey ? 'lens' : cntNative ? 'cnt' : (owns ? (st.kind || null) : (spec ? null : (st.id === 'ads' ? (ADS_KIND[subLabel] || 'launch')\n      : st.id === 'domains' ? (this.DOMAINS[subLabel] ? 'domain' : 'domainsHub')\n      : (st.kind || null))));\n    const pane = this.state.pane;\n    // WP-03: paneStates/skeletons now come from the shared state-layer.js helper (window.StateLayer)\n    // rather than being hand-rolled here — this is the parity proof that the extraction didn't change\n    // behaviour. None of these 5 kinds declare a dataContract yet, so StateLayer.resolve() never\n    // overrides the manually-picked pane and showModeled stays false — same 4 buttons as before.\n    // F-7a fix (4.17a): pass the stream's contract, so the 5th state (`modeled`) is offered on the\n    // planes that declare a non-live feed — it was unreachable from the control before.\n    const paneStates = window.StateLayer.paneStates(pane, { onPick: (p) => this.setState({ pane: p }), dataContract: st.dataContract || null });\n    const skeletons = window.StateLayer.skeletons();\n    const chip = (s) => {\n      const m = { ok: ['ok', 'success'], run: ['live', 'running'], running: ['live', 'running'], warn: ['warn', 'warn'], blocked: ['blocked', 'warn'], fail: ['fail', 'fail'], failed: ['fail', 'fail'], idle: ['idle', null] };\n      const e = m[s] || [s, null]; const c = e[1] ? 'var(--app-' + e[1] + ')' : 'var(--app-ink-faint)';\n      return { label: e[0], color: c };\n    };\n    // Empty / error copy per kind\n    const emptyCopy = { zui: { t: 'No process tree yet', b: 'Run a plan to generate a process tree for this project.', cta: 'Run a plan' }, foundry: { t: 'No workers running', b: 'Start a fleet to populate the maturity roster and queue.', cta: 'Start a fleet' }, cro: { t: 'No CRO data yet', b: 'Sync from the CRO API to score this property.', cta: 'Sync from API' } };\n    const errorCopy = { zui: 'TREE_UNAVAILABLE', foundry: 'FOUNDRY_UNAVAILABLE', cro: 'CRO_API_TIMEOUT', icp: 'ICP_SERVICE_UNAVAILABLE', leads: 'DISCOVERY_SERVICE_UNAVAILABLE', pacing: 'METRICS_API_TIMEOUT', builder: 'SPECS_UNAVAILABLE', assets: 'CONFIG_READ_ERROR', conv: 'CONVERSIONS_API_TIMEOUT', launch: 'SIGNALS_UNREACHABLE', adsAccount: 'CAMPAIGNS_API_TIMEOUT' };\n    emptyCopy.launch = { t: 'No launch signals yet', b: 'The gate reads live config from the account. Recheck signals to hydrate the prerequisite list.', cta: 'Recheck signals' };\n    emptyCopy.adsAccount = { t: 'No campaigns in this account', b: 'Eight specs are staged in the repo. Create them from Campaign Builder — they ship PAUSED.', cta: 'Open Campaign Builder' };\n    emptyCopy.pacing = { t: 'No live metrics yet', b: 'Budget pacing and kill-switch thresholds compute against /campaigns/metrics. Enable a campaign to start the meter.', cta: 'View checklist' };\n    emptyCopy.builder = { t: 'No spec selected', b: 'Pick a campaign spec from the Day-0 or Phase-2 tier to preview its operations and validation.', cta: 'Browse specs' };\n    emptyCopy.assets = { t: 'No configs loaded', b: 'Ad extensions and brand-safety exclusions live in the repo. Point the reader at a config path to display them.', cta: 'Reload configs' };\n    emptyCopy.conv = { t: 'No conversion actions', b: 'Six conversion actions are defined but all resource names are REPLACE_ME. Wire them in Google Ads to go live.', cta: 'View contract' };\n    emptyCopy.icp = { t: 'No ICP yet — define a product first', b: 'Your ideal-customer profile is built from a product. Define one to unlock firmographics, personas, and country gating.', cta: 'Build ICP' };\n    emptyCopy.leads = { t: 'No leads yet — run discovery', b: 'Pick a saved ICP and run discovery to populate your lead list with verified, ICP-scored contacts.', cta: 'Run discovery' };\n    const squrKinds = ['pacing', 'builder', 'assets', 'conv', 'launch', 'adsAccount'];\n    // F-7a (D27) · participation, \"where it's of value\". Was a hard-coded 11-kind allowlist, which\n    // made the state contract a property of five hand-picked panes instead of a shell rule. Now:\n    //   · `stateAware: false` on a stream is the explicit opt-out (pure-chrome planes);\n    //   · otherwise a plane participates if it has bespoke state copy below, OR it declares a\n    //     `dataContract` — which IS the declaration that its content comes from a fetch.\n    // A participating plane with no bespoke copy renders StateLayer.build()'s honest defaults, so\n    // opting in costs nothing and no plane needs its own empty state (that is the D27 contract).\n    const bespokeStates = ['zui', 'foundry', 'cro', 'icp', 'leads'].concat(squrKinds);\n    const hasStates = st.stateAware === false ? false\n      : (bespokeStates.indexOf(kind) >= 0 || !!st.dataContract);\n    const stateIcon = { zui: 'tree', foundry: 'foundry', icp: 'crosshair', leads: 'contacts', pacing: 'gauge2', builder: 'megaphone', assets: 'shield', conv: 'convert', launch: 'shield', adsAccount: 'chart' }[kind] || 'trending';\n    const ec = emptyCopy[kind];\n    const stateBox = hasStates ? window.StateLayer.build({\n      pane,\n      copy: {\n        emptyTitle: ec && ec.t, emptyBody: ec && ec.b, emptyCta: ec && ec.cta,\n        errCode: errorCopy[kind]\n      },\n      icons: { empty: this.ic(stateIcon, 30) }\n    }).stateBox : null;\n    // Phase 3 note (2026-08-06): dataContract declarations are KEPT as honest metadata (Phase 4's\n    // write-path reads them), but they no longer force a full-pane 'modeled' takeover — a blocking\n    // box that hides the entire dashboard to announce stub data is a bad trade, and there is no\n    // legal requirement to label demo data this way. resolve() is passed null so only an explicit\n    // manual pane pick can reach 'modeled'.\n    // RULED 2026-09-10 — canvas/10-contracts/DATA-CONTRACTS.md § CR-5. This note is the contract, not\n    // a deviation: non-live forces the always-on contract badge below plus value-level modeled\n    // treatment, never a full-pane takeover. Do NOT re-fuse resolve() with autoModeled() — that is\n    // what put a MODELED box on 244 of 359 planes (PR #31, reverted by #33); the 62-check probe\n    // canvas/30-audits/probes/T6-STATE-LAYER-CONTRACT.ut.cjs pins both sides.\n    const streamContract = st.dataContract || null;\n    // W-0 honesty pass (GTM-6): an UNCONDITIONAL, operator-visible contract marker. The older\n    // `modeledBox` only renders when StateLayer resolves to 'modeled', which never happens on a\n    // normal visit — so a snapshot/planned surface looked live. This badge always renders.\n    const contractBadge = (streamContract && (streamContract.status === 'planned' || streamContract.status === 'stub')) ? {\n      label: streamContract.status === 'planned' ? 'PLANNED' : 'SNAPSHOT',\n      variant: streamContract.status === 'planned' ? 'gold' : 'outline',\n      note: streamContract.status === 'planned'\n        ? 'No live backend — every figure here is modeled'\n        : 'Snapshot-backed — not reading live data'\n      // AW-7: an ABSENT contract used to render no badge at all, and no badge reads as live — so the\n      // shell was quietest exactly where it knew least (23 of 61 streams). Absence now states itself.\n      // Lavender, not red: per TOKEN-MAP §4 that token is governance-in-flight, which is what an\n      // unstated feed is — not an error. WP-39 deliberately left these undeclared rather than guess a\n      // contract, and that stands; this makes the gap visible instead of inventing 23 endpoints.\n      // Suppressed in the MVP client canvas: it is an internal governance marker, not customer copy.\n    } : (!streamContract && !st.archived && !mvp && this.MVP_IDS.indexOf(st.id) < 0) ? {\n      label: 'UNDECLARED', variant: 'lavender',\n      note: 'No data contract declared — liveness unstated, so treat every figure as unverified'\n    } : null;\n    const isModeled = hasStates && window.StateLayer.resolve(pane, null) === 'modeled';\n    const modeledBox = isModeled ? window.StateLayer.build({ pane, dataContract: streamContract }).modeledBox : null;\n    const retry = () => this.setState({ pane: 'default' });\n\n    // 01 · ZUI\n    const zSel = this.ZUI_NODES.find(n => n.id === this.state.zuiSel) || this.ZUI_NODES[0];\n    const zc = chip(zSel.state);\n    const zui = kind === 'zui' ? {\n      canvasEl: this.zuiCanvas(this.state.zuiZoom / 100),\n      minimapEl: this.zuiMinimap(),\n      zoom: this.state.zuiZoom,\n      zoomOut: () => this.setState(s => ({ zuiZoom: Math.max(40, s.zuiZoom - 15) })),\n      zoomIn: () => this.setState(s => ({ zuiZoom: Math.min(160, s.zuiZoom + 15) })),\n      zoomFit: () => this.setState({ zuiZoom: 100 }),\n      plusEl: this.ic('plus', 15), minusEl: this.ic('minus', 15), fitEl: this.ic('fit', 15),\n      sel: { title: zSel.title, type: zSel.type, chipLabel: zc.label, chipColor: zc.color,\n        started: zSel.started, dur: zSel.durationMs ? (zSel.durationMs / 1000).toFixed(1) + 's' : '—',\n        output: zSel.output, summary: zSel.summary,\n        canRerun: zSel.state === 'failed' || zSel.state === 'ok' },\n      outlineCount: this.ZUI_NODES.length,\n      outline: this.ZUI_NODES.map(n => ({ id: n.id, title: n.title, type: n.type, chipColor: chip(n.state).color, bg: n.id === zSel.id ? 'var(--app-accent-dim)' : 'transparent', onClick: () => this.setState({ zuiSel: n.id }) })),\n      rerun: () => this.setState({ confirm: { title: 'Re-run node: ' + zSel.title, verb: 'RE-RUN' } })\n    } : null;\n\n    // 02 · Foundry\n    let foundry = null;\n    if (kind === 'foundry') {\n      const cols = [1, 2, 3, 4, 5, 6].map(m => ({\n        m, label: 'F' + m, count: this.FOUNDRY_WORKERS.filter(w => w.maturity === m).length,\n        workers: this.FOUNDRY_WORKERS.filter(w => w.maturity === m).map(w => ({\n          id: w.id, name: w.name, task: w.currentTask, state: w.state,\n          border: w.state === 'idle' ? 'var(--app-rule)' : this.appState(w.state),\n          dot: w.state === 'idle' ? 'var(--app-ink-faint)' : this.appState(w.state),\n          pulse: w.state === 'running', chipLabel: chip(w.state).label, chipColor: chip(w.state).color,\n          sparkEl: this.sparkline(w.tp, 88, 22, 'var(--app-accent)', true),\n          promote: () => this.setState({ confirm: { title: 'Promote ' + w.name + ' → F' + Math.min(6, w.maturity + 1), verb: 'PROMOTE' } }),\n          demote: () => this.setState({ confirm: { title: 'Demote ' + w.name + ' → F' + Math.max(1, w.maturity - 1), verb: 'DEMOTE' } })\n        }))\n      }));\n      const queue = this.FOUNDRY_QUEUE.map(r => ({ id: r.id, task: r.task, worker: r.worker, stream: r.stream, age: r.age, attempts: r.attempts, chipLabel: chip(r.state).label, chipColor: chip(r.state).color, blockReason: r.blockReason || null }));\n      const esc = this.FOUNDRY_ESC.map(e => ({ id: e.id, ask: e.ask, owner: e.owner, waiting: e.waiting, sevColor: this.appState(e.severity === 'ok' ? 'ok' : e.severity === 'warn' ? 'blocked' : 'failed'),\n        approve: () => this.setState({ confirm: { title: 'Approve: ' + e.ask, verb: 'APPROVE' } }),\n        reject: () => this.setState({ confirm: { title: 'Reject: ' + e.ask, verb: 'REJECT' } }) }));\n      foundry = { cols, queue, esc, escCount: this.FOUNDRY_ESC.length };\n    }\n\n    // 03c · Podcast · daily-brief pipeline (data lifted from the A-Podcast Stream reference)\n    // P-1/P-2 (2026-09-05): Trigger + Alerts ported from the source's isTrigger/isAlerts branches —\n    // the stream's only write surfaces (podcast.run.create, podcast.alert.upsert), previously absent\n    // from the shell entirely (no tab, not even a lens fallback).\n    this.PD_TRIGGER_HISTORY = this.PD_TRIGGER_HISTORY || [\n      { t: '06:00', d: '2026-06-30', by: 'schedule', ok: true },\n      { t: '06:00', d: '2026-06-29', by: 'schedule', ok: true },\n      { t: '14:22', d: '2026-06-28', by: 'maya · manual', ok: true },\n      { t: '06:00', d: '2026-06-27', by: 'schedule', ok: false }\n    ];\n    this.PD_ALERT_RULES = this.PD_ALERT_RULES || [\n      { name: 'Run failure', desc: 'Notify on any stage error', channel: 'slack · #ai-brief', on: true },\n      { name: 'Publish success', desc: 'Notify when an episode publishes', channel: 'slack · #ai-brief', on: true },\n      { name: 'Gen latency', desc: 'Alert if generate exceeds 8:00', channel: 'email · maya', on: true },\n      { name: 'Source drift', desc: 'Alert if a feed returns 0 items', channel: 'slack · #ai-brief', on: false }\n    ];\n    let pd = null;\n    if (kind === 'pd') {\n      const sub = subLabel || 'Overview';\n      const isSrc = sub === 'Sources', isPr = sub === 'Prompts', isEp = sub === 'Episodes', isTrig = sub === 'Trigger', isAl = sub === 'Alerts';\n      const SC = { ok: 'rgb(var(--accent-teal))', run: 'rgb(var(--circuit-glow))', err: 'rgb(var(--accent-red))', idle: 'rgb(var(--text) / 0.62)' };\n      const pill = (c) => ({ color: c, bg: c === SC.idle ? 'rgb(var(--text) / 0.05)' : c.replace('))', ') / 0.09)') });\n      const stage = (label, state, io, dur) => {\n        const c = state === 'Done' ? SC.ok : state === 'Running' ? SC.run : state === 'Failed' ? SC.err : SC.idle;\n        const p = pill(c);\n        return { label, state, io, dur, dotColor: c, stateColor: p.color, stateBg: p.bg,\n          barColor: state === 'Done' || state === 'Running' ? c : 'rgb(var(--text) / 0.08)' };\n      };\n      pd = {\n        isOverview: !isSrc && !isPr && !isEp && !isTrig && !isAl,\n        isSources: isSrc, isPrompts: isPr, isEpisodes: isEp, isTrigger: isTrig, isAlerts: isAl,\n        runLabel: 'ep-204', runState: 'Published 06:06',\n        stages: [\n          stage('Sources', 'Done', '3 feeds → 42 items', '0:04'),\n          stage('Prompts', 'Done', 'v12 → 4 prompts', '0:01'),\n          stage('Generate', 'Running', '42 items → generating', '3:37'),\n          stage('Episode', 'Pending', 'awaiting draft', '—'),\n          stage('Publish', 'Pending', 'awaiting audio', '—')\n        ],\n        logs: [\n          { time: '06:00', prefix: '[ctx]', msg: 'Sweep started · 3 feeds queued', color: SC.run },\n          { time: '06:00', prefix: '[ctx]', msg: 'arxiv cs.AI → 18 items scored', color: SC.run },\n          { time: '06:01', prefix: '[skl]', msg: 'Prompt chain v12 attached (4 prompts, 820 tokens)', color: SC.run },\n          { time: '06:01', prefix: '[llm]', msg: 'Generate stage · claude-sonnet · streaming', color: SC.run },\n          { time: '06:04', prefix: '[agt]', msg: 'Draft at 1,240 words · est 6:10 runtime', color: SC.ok },\n          { time: '06:04', prefix: '[mem]', msg: 'Episode context cached for ep-205', color: SC.idle }\n        ],\n        sources: [\n          { name: 'arxiv cs.AI', kind: 'academic feed', type: 'arxiv', items: '18', status: 'connected', dotColor: SC.ok, pillColor: SC.ok, pillBg: 'rgb(var(--accent-teal) / 0.09)' },\n          { name: 'Hacker News · front', kind: 'community feed', type: 'hn', items: '14', status: 'connected', dotColor: SC.ok, pillColor: SC.ok, pillBg: 'rgb(var(--accent-teal) / 0.09)' },\n          { name: 'reddit · r/MachineLearning', kind: 'community feed', type: 'reddit', items: '10', status: 'connected', dotColor: SC.ok, pillColor: SC.ok, pillBg: 'rgb(var(--accent-teal) / 0.09)' },\n          { name: 'reddit · r/LocalLLaMA', kind: 'community feed', type: 'reddit', items: '0', status: 'error', dotColor: SC.err, pillColor: SC.err, pillBg: 'rgb(var(--accent-red) / 0.09)' },\n          { name: 'Papers with Code', kind: 'rss feed', type: 'rss', items: '—', status: 'paused', dotColor: SC.idle, pillColor: 'rgb(var(--text) / 0.5)', pillBg: 'rgb(var(--text) / 0.04)' }\n        ],\n        prompts: [\n          { name: 'Intro', tokens: '120 tok', body: 'Open with a 15-second hook on the single most important story. Name the model or paper, why it matters, and who it affects.' },\n          { name: 'Research Roundup', tokens: '340 tok', body: 'Summarize the top 5 arxiv + HN items. One sentence each. Lead with the finding, not the authors. Numerals for all metrics.' },\n          { name: 'Deep Dive', tokens: '260 tok', body: 'Pick one item for a 90-second deep dive. Explain the method plainly, then the result, then the limitation.' },\n          { name: 'Outro', tokens: '100 tok', body: 'One forward-looking line. No sign-off fluff. State tomorrow’s trigger time.' }\n        ],\n        versions: [\n          { name: 'v12', date: 'Jun 28', tag: 'current', color: 'rgb(var(--circuit-glow))', bd: 'rgb(var(--circuit-glow) / 0.25)', bg: 'rgb(var(--circuit-glow) / 0.06)' },\n          { name: 'v11', date: 'Jun 14', tag: 'archived', color: 'var(--app-ink-soft)', bd: 'var(--app-rule)', bg: 'transparent' },\n          { name: 'v10', date: 'May 30', tag: 'archived', color: 'var(--app-ink-soft)', bd: 'var(--app-rule)', bg: 'transparent' },\n          { name: 'v9', date: 'May 12', tag: 'archived', color: 'var(--app-ink-soft)', bd: 'var(--app-rule)', bg: 'transparent' }\n        ],\n        episodes: [\n          { id: 'ep-204', title: 'Daily AI Brief — 2026-06-30', dur: '6:12', date: 'Jun 30', state: 'published', ok: true, pillColor: SC.ok, pillBg: 'rgb(var(--accent-teal) / 0.09)' },\n          { id: 'ep-203', title: 'Daily AI Brief — 2026-06-29', dur: '5:48', date: 'Jun 29', state: 'published', ok: true, pillColor: SC.ok, pillBg: 'rgb(var(--accent-teal) / 0.09)' },\n          { id: 'ep-202', title: 'Daily AI Brief — 2026-06-28', dur: '6:41', date: 'Jun 28', state: 'published', ok: true, pillColor: SC.ok, pillBg: 'rgb(var(--accent-teal) / 0.09)' },\n          { id: 'ep-201', title: 'Daily AI Brief — 2026-06-27', dur: '—', date: 'Jun 27', state: 'failed', ok: false, pillColor: SC.err, pillBg: 'rgb(var(--accent-red) / 0.09)' },\n          { id: 'ep-200', title: 'Daily AI Brief — 2026-06-26', dur: '5:59', date: 'Jun 26', state: 'published', ok: true, pillColor: SC.ok, pillBg: 'rgb(var(--accent-teal) / 0.09)' }\n        ],\n        playIconEl: this.ic('play', 15),\n        // Trigger (P-1): manual fire + schedule + history. Real write: onConfirm unshifts a new\n        // history row and flips Overview's Generate stage to Running — not a closed-modal no-op.\n        promptSetVersion: 'v12 · current',\n        sourcesSummary: '3 connected · 1 paused · 1 error',\n        nextFireLabel: 'tomorrow · 06:00 UTC',\n        triggerHistory: this.PD_TRIGGER_HISTORY.map(h => ({ t: h.t, d: h.d, by: h.by, dotColor: h.ok ? SC.ok : SC.err })),\n        triggerBtnText: 'Run now',\n        fireTrigger: () => this.setState({ confirm: { title: 'Fire the daily-brief pipeline now with prompt set v12 and the 3 connected sources?', verb: 'RUN', onConfirm: () => {\n          const now = new Date();\n          this.PD_TRIGGER_HISTORY = [{ t: now.toTimeString().slice(0, 5), d: 'manual trigger', by: (this.PERMISSIONS ? this.state.role : 'operator') || 'operator', ok: true }].concat(this.PD_TRIGGER_HISTORY);\n          this.setState({});\n        } } }),\n        // Alerts (P-2): rule toggles mutate this.PD_ALERT_RULES directly (podcast.alert.upsert) —\n        // no hold-to-confirm needed, matches the source's own plain-toggle design.\n        alertRules: this.PD_ALERT_RULES.map((a, i) => ({\n          name: a.name, desc: a.desc, channel: a.channel,\n          toggleBg: a.on ? 'rgb(var(--accent-teal) / 0.25)' : 'rgb(var(--text) / 0.06)',\n          toggleBd: a.on ? 'rgb(var(--accent-teal) / 0.4)' : 'var(--app-rule)',\n          knobLeft: a.on ? '17px' : '2px', knobColor: a.on ? 'rgb(var(--accent-teal))' : 'rgb(var(--text) / 0.5)',\n          toggle: () => { this.PD_ALERT_RULES[i] = Object.assign({}, a, { on: !a.on }); this.setState({}); }\n        })),\n        alertFeed: [\n          { m: 'ep-204 published · 6:12 audio', t: '06:06 · today', color: SC.ok },\n          { m: 'r/LocalLLaMA returned 0 items — feed may be down', t: '06:00 · today', color: 'var(--app-warn)' }\n        ]\n      };\n    }\n\n    // 03 · CRO\n    let cro = null;\n    if (kind === 'cro') {\n      const dims = this.CRO_DIMS.slice().sort((a, b) => a.score - b.score).map(d => ({\n        name: d.name, score: d.score, band: d.band, topIssue: d.topIssue,\n        barColor: 'var(--app-' + d.band + ')', barPct: d.score + '%',\n        delta: (d.deltaPct >= 0 ? '+' : '') + d.deltaPct + '%', deltaColor: d.deltaPct >= 0 ? 'var(--app-success)' : 'var(--app-fail)',\n        deltaIcon: this.ic(d.deltaPct >= 0 ? 'up' : 'down', 12),\n        sparkEl: this.sparkline(d.spark, 120, 30, 'var(--app-' + d.band + ')', true),\n        active: this.state.croDim === d.name,\n        cardBorder: this.state.croDim === d.name ? 'var(--app-accent)' : 'var(--app-rule)',\n        onClick: () => this.setState(s => ({ croDim: s.croDim === d.name ? null : d.name }))\n      }));\n      const selDim = this.CRO_DIMS.find(d => d.name === this.state.croDim) || null;\n      cro = {\n        gaugeEl: this.gauge(this.CRO_OVERALL.score, this.CRO_OVERALL.band),\n        band: this.CRO_OVERALL.band, bandLabel: this.CRO_OVERALL.band === 'ok' ? 'GOOD' : this.CRO_OVERALL.band === 'warn' ? 'NEEDS WORK' : 'POOR',\n        overallDelta: (this.CRO_OVERALL.deltaPct >= 0 ? '+' : '') + this.CRO_OVERALL.deltaPct + '% vs prior window',\n        dims, funnelEl: this.funnelEl(),\n        selDim: selDim ? { name: selDim.name, sub: selDim.sub.map(s => ({ c: s.c, impact: s.impact, chipLabel: chip(s.s).label, chipColor: chip(s.s).color })) } : null\n      };\n    }\n\n    // ── Onboarding pane ──\n    let onb = null;\n    if (kind === 'onboarding') {\n      const cur = this.state.onbStep, done = this.state.onbDone;\n      // FQ-10 (D156/D158): the founder's two Run steps sit before Land — a Founder is a Member who also runs\n      // a business. Ids 6/7 keep the six canonical ids stable; order and numbering come from SRC's index.\n      // D164: the Business step left with the Business picker.\n      const SRC = this.ONB_STEPS.slice(0, 5).concat([\n        { id: 7, label: 'Roles', title: 'Pick your role templates', blurb: 'A role template is a rail: the streams a seat sees in Run. Pick the ones your business needs; permissions still decide access.', kind: 'roles' }\n      ], this.ONB_STEPS.slice(5));\n      const idx = Math.max(0, SRC.findIndex(x => x.id === cur));\n      const steps = SRC.map((s, i) => {\n        const isDone = !!done[s.id], isCur = s.id === cur;\n        const state = isDone ? 'done' : isCur ? 'current' : 'todo';\n        return { id: s.id, n: i + 1, label: s.label, state,\n          numBg: isDone ? 'var(--app-success)' : isCur ? 'var(--app-accent)' : 'var(--app-rule-soft)',\n          numColor: isDone || isCur ? 'rgb(var(--basalt-deep))' : 'var(--app-ink-faint)',\n          ring: isCur ? '0 0 0 4px var(--app-accent-dim)' : 'none',\n          labelColor: isCur ? 'var(--app-ink)' : isDone ? 'var(--app-ink-soft)' : 'var(--app-ink-faint)',\n          leftLine: i === 0 ? 'transparent' : (done[SRC[i - 1].id] ? 'var(--app-success)' : 'var(--app-rule)'),\n          connectColor: i === SRC.length - 1 ? 'transparent' : (isDone ? 'var(--app-success)' : 'var(--app-rule)'),\n          showCheck: isDone, showNum: !isDone,\n          onClick: () => this.setState({ onbStep: s.id }) };\n      });\n      const s = SRC[idx];\n      const pct = Math.round(Object.keys(done).length / SRC.length * 100);\n      onb = {\n        steps, pctLabel: pct + '% complete',\n        active: { title: s.title, blurb: s.blurb, kind: s.kind, label: s.label, stepOf: (idx + 1) + ' of ' + SRC.length },\n        showContext: s.kind === 'context', showDetect: s.kind === 'detect', showConnect: s.kind === 'connect', showSpawn: s.kind === 'spawn', showLand: s.kind === 'land', showRead: s.kind === 'read',\n        showBusiness: s.kind === 'business', showRoles: s.kind === 'roles',\n        businesses: (this.state.businessIds || ['aaas']).map(id => { const on = (this.state.business || 'aaas') === id; const B = (window.AAAS_BRANDS || {})[id];\n          return { id, label: (B && B.name) || id.toUpperCase(), tag: (B && B.tag) || '', border: on ? 'var(--app-accent)' : 'var(--app-rule)', chip: on ? 'Selected' : 'Select', chipColor: on ? 'var(--app-accent)' : 'var(--app-ink-faint)',\n            onClick: () => this.setState({ business: id }, () => this.loadToolset()) }; }),\n        roleTemplates: this.ROLES.map(r => { const rail = r === 'Owner' ? this.RAIL_GROUPS : (this.ROLE_RAIL[r] || []); const on = (this.state.onbRoles || ['Owner']).indexOf(r) > -1;\n          return { name: r, groups: rail.map(g => g.label).join(' · '), streams: rail.reduce((n, g) => n + g.ids.length, 0) + ' streams', border: on ? 'var(--app-accent)' : 'var(--app-rule)', chip: on ? 'Picked' : 'Pick', chipColor: on ? 'var(--app-accent)' : 'var(--app-ink-faint)',\n            onClick: () => this.setState(st => { const cur2 = st.onbRoles || ['Owner']; return { onbRoles: cur2.indexOf(r) > -1 ? cur2.filter(x => x !== r) : cur2.concat([r]) }; }) }; }),\n        // D208 · one concept, one list: a tile's state is read from ACC_SERVICES (the Settings › Connected services table) by id;\n        // 'ok' or 'warn' = connected. Tiles with no ACC_SERVICES row (hubspot) keep their own flag. ONB_CONNECT keeps name/tag only.\n        connectTiles: this.ONB_CONNECT.map(c => { const svc = (this.ACC_SERVICES || []).find(v => v.id === c.id); const connected = svc ? (svc.state === 'ok' || svc.state === 'warn') : !!c.connected; return { id: c.id, name: c.name, tag: c.tag, connected,\n          border: connected ? 'var(--app-success)' : 'var(--app-rule)', chip: connected ? 'Connected' : 'Connect',\n          chipColor: connected ? 'var(--app-success)' : 'var(--app-ink-faint)' }; }),\n        checklist: this.ONB_CHECKLIST.map(c => ({ id: c.id, label: c.label, done: c.done,\n          box: c.done ? 'var(--app-success)' : 'transparent', boxBorder: c.done ? 'var(--app-success)' : 'var(--app-rule)',\n          textColor: c.done ? 'var(--app-ink-soft)' : 'var(--app-ink)', showCheck: c.done })),\n        checkIcon: this.ic('check', 12), targetIcon: this.ic('target', 20), sparkIcon: this.ic('spark', 26),\n        canBack: idx > 0, canNext: idx < SRC.length - 1,\n        backCursor: idx > 0 ? 'pointer' : 'not-allowed', backOpacity: idx > 0 ? '1' : '0.4',\n        nextLabel: idx < SRC.length - 1 ? 'Next' : 'Finish',\n        back: () => this.setState({ onbStep: SRC[Math.max(0, idx - 1)].id }),\n        next: () => this.setState(st => { const nd = Object.assign({}, st.onbDone); nd[SRC[idx].id] = true; return { onbDone: nd, onbStep: SRC[Math.min(SRC.length - 1, idx + 1)].id }; }),\n        restart: () => this.setState({ onbStep: 0, onbDone: {} }),\n        openCoach: () => this.setState({ coach: true }),\n        spawnConfirm: () => this.setState({ confirm: { title: 'Spawn demo agent against your context', verb: 'SPAWN' } })\n      };\n    }\n\n    // ── Account & Settings pane ──\n    let acc = null;\n    if (kind === 'account') {\n      const sec = this.state.accSection;\n      acc = {\n        sections: this.ACC_SECTIONS.map(name => ({ name, active: name === sec, danger: name === 'Danger zone',\n          bg: name === sec ? 'var(--app-accent-dim)' : 'transparent',\n          color: name === 'Danger zone' ? 'var(--app-fail)' : name === sec ? 'var(--app-accent)' : 'var(--app-ink-soft)',\n          onClick: () => this.setState({ accSection: name }) })),\n        section: sec,\n        isProfile: sec === 'Profile', isLanding: sec === 'Home landing', isEmail: sec === 'Email & notifications',\n        isServices: sec === 'Connected services', isBrand: sec === 'Brand', isReferral: sec === 'Referral', isDanger: sec === 'Danger zone',\n        landingOpts: this.LANDING_OPTS.map(o => ({ id: o.id, label: o.label, desc: o.desc, dest: o.dest, active: this.state.landing === o.id,\n          cardBg: this.state.landing === o.id ? 'var(--app-accent)' : 'var(--app-surface)',\n          cardBorder: this.state.landing === o.id ? 'var(--app-accent)' : 'var(--app-rule)',\n          textColor: this.state.landing === o.id ? 'rgb(var(--basalt-deep))' : 'var(--app-ink)',\n          subColor: this.state.landing === o.id ? 'rgb(var(--basalt-deep) / 0.7)' : 'var(--app-ink-faint)',\n          onClick: () => this.setState({ landing: o.id }) })),\n        landingDest: (this.LANDING_OPTS.find(o => o.id === this.state.landing) || {}).dest,\n        services: this.ACC_SERVICES.map(s => ({ id: s.id, name: s.name, meta: s.meta, chipLabel: chip(s.state).label, chipColor: chip(s.state).color, dot: this.appState(s.state === 'idle' ? 'idle' : s.state === 'warn' ? 'blocked' : s.state === 'fail' ? 'failed' : 'ok') })),\n        notifs: this.ACC_NOTIFS.map(n => ({ id: n.id, label: n.label, desc: n.desc, on: this.state.notifs[n.id],\n          track: this.state.notifs[n.id] ? 'var(--app-accent)' : 'rgba(255,255,255,0.15)', knobLeft: this.state.notifs[n.id] ? '16px' : '2px',\n          toggle: () => this.setState(st => ({ notifs: Object.assign({}, st.notifs, { [n.id]: !st.notifs[n.id] }) })) })),\n        userIcon: this.ic('user', 26), linkIcon: this.ic('link', 18), brushIcon: this.ic('brush', 18), giftIcon: this.ic('gift', 18), trashIcon: this.ic('trash', 16), previewIcon: this.ic('target', 14),\n        deleteConfirm: () => this.setState({ confirm: { title: 'Delete account and export all data', verb: 'DELETE' } })\n      };\n      // W2 · tab routing. ownsTabs keeps kind === 'account' on all three tabs; the pane branches on these.\n      const accTab = subLabel || 'Settings';\n      acc.tab = accTab; acc.isBilling = accTab === 'Billing'; acc.isTeam = accTab === 'Team'; acc.isSettings = accTab === 'Settings';\n      if (acc.isBilling) {\n        const B = this.ACC_KINDS.billing;\n        const eur = (s) => parseFloat(String(s).replace(/\\./g, '').replace(',', '.')) || 0;\n        const mx = Math.max.apply(null, B.rows.map(r => eur(r.revenue)).concat([1]));\n        acc.billing = {\n          label: B.label, description: B.description,\n          balanceEur: '€ ' + B.balance.eur, balanceCredits: B.balance.credits + ' credits', rate: B.balance.rate,\n          topups: { title: 'Top-ups', balance: '€ ' + B.balance.eur + ' · ' + B.balance.credits + ' credits',\n            rows: B.topups.map(t => ({ label: t.pack, amount: '€ ' + t.amount, tone: 'under', state: 'modeled', note: t.date + ' · ' + t.method })) },\n          spend: { title: 'Spend over time · ' + B.year + ' — € ' + B.totals.revenue + ' total',\n            rows: B.rows.map(r => ({ label: r.month + ' ' + B.year, amount: '€ ' + r.revenue, spentPct: Math.round(eur(r.revenue) / mx * 100), tone: 'under', state: 'modeled' })) }\n        };\n      }\n      if (acc.isTeam) {\n        const T = this.ACC_KINDS.orgTeam;\n        // Row drawer = the source's `preview` block, one section per line (`Label     value`).\n        const drawer = (m) => this.openDetail({ kind: 'TEAM · ' + m.role.toUpperCase(), id: m.id, title: m.title,\n          chip: m.statusLabel, chipColor: m.status === 'ok' ? 'rgb(var(--accent-teal))' : 'var(--app-warn)',\n          sections: [{ label: 'Summary', value: m.subtitle }].concat(m.preview.split('\\n').map(l => { const mm = l.match(/^(\\S+)\\s+(.*)$/); return mm ? { label: mm[1], value: mm[2] } : { label: '', value: l }; })) });\n        acc.team = {\n          label: T.label, description: T.description,\n          cols: T.schema.map(c => ({ label: c.label })),\n          note: this.state.accInviteNote || '',\n          hasNote: !!this.state.accInviteNote,\n          rows: T.mockItems.map(m => {\n            const pending = m.status === 'idle';\n            return { id: m.id, name: m.name, email: m.email, role: m.role, statusLabel: m.statusLabel, joinedAt: m.joinedAt, pending,\n              dot: pending ? 'var(--app-warn)' : 'rgb(var(--accent-teal))',\n              open: () => drawer(m),\n              resend: (e) => { if (e) e.stopPropagation(); this.setState({ accInviteNote: 'Invite resent to ' + m.email }); },\n              revoke: (e) => { if (e) e.stopPropagation(); this.setState({ confirm: { title: 'Revoke invite for ' + m.email, verb: 'REVOKE' } }); } };\n          })\n        };\n      }\n    }\n\n    // ── ICP Builder pane ──\n    let icp = null;\n    if (kind === 'icp') {\n      const roleColor = { economic: 'var(--app-success)', champion: 'var(--app-accent)', user: 'rgb(var(--pastel-lavender,147 154 255))', blocker: 'var(--app-fail)' };\n      const gate = this.state.icpGate || (() => { const g = { allow: [], consent: [], block: [] }; this.ICP_COUNTRIES.forEach(c => g[c.bucket].push(c.code)); return g; })();\n      const moveCountry = (code, from, to) => {\n        const doMove = () => this.setState(s => {\n          const g = s.icpGate ? { allow: s.icpGate.allow.slice(), consent: s.icpGate.consent.slice(), block: s.icpGate.block.slice() } : (() => { const gg = { allow: [], consent: [], block: [] }; this.ICP_COUNTRIES.forEach(c => gg[c.bucket].push(c.code)); return gg; })();\n          ['allow', 'consent', 'block'].forEach(b => { g[b] = g[b].filter(x => x !== code); });\n          g[to].push(code);\n          return { icpGate: g };\n        });\n        if (to === 'block') this.setState({ confirm: { title: 'Block ' + code + ' — no outreach, ever. This is a compliance action.', verb: 'BLOCK', onConfirm: doMove } });\n        else doMove();\n      };\n      const bucketDef = [\n        { id: 'allow', label: 'allow', tone: 'var(--app-success)', hint: 'Outreach permitted' },\n        { id: 'consent', label: 'consent_required', tone: 'var(--app-warn)', hint: 'Explicit opt-in first' },\n        { id: 'block', label: 'block', tone: 'var(--app-fail)', hint: 'Hold to confirm · danger' }\n      ];\n      const buckets = bucketDef.map(b => ({\n        id: b.id, label: b.label, tone: b.tone, hint: b.hint, count: gate[b.id].length,\n        countries: gate[b.id].map(code => ({ code,\n          next: b.id === 'allow' ? 'consent' : b.id === 'consent' ? 'block' : 'allow',\n          nextLabel: b.id === 'allow' ? 'require consent' : b.id === 'consent' ? 'block' : 'allow',\n          onMove: () => moveCountry(code, b.id, b.id === 'allow' ? 'consent' : b.id === 'consent' ? 'block' : 'allow') }))\n      }));\n      const sel = this.ICP_PERSONAS.find(p => p.id === this.state.icpDrawer) || null;\n      icp = {\n        firmo: this.ICP_FIRMO.map(f => ({ key: f.key, label: f.label, chips: f.values.map(v => ({ v, on: f.selected.indexOf(v) >= 0,\n          bg: f.selected.indexOf(v) >= 0 ? 'var(--app-accent-dim)' : 'transparent',\n          border: f.selected.indexOf(v) >= 0 ? 'var(--app-accent)' : 'var(--app-rule)',\n          color: f.selected.indexOf(v) >= 0 ? 'var(--app-accent)' : 'var(--app-ink-soft)' })) })),\n        ranges: this.ICP_RANGES,\n        segments: this.ICP_SEGMENTS.map(s => ({ name: s.name, priority: s.priority, rationale: s.rationale, tam: s.tam,\n          prioColor: s.priority === 'P1' ? 'var(--app-success)' : s.priority === 'P2' ? 'var(--app-warn)' : 'var(--app-ink-faint)' })),\n        personas: this.ICP_PERSONAS.map(p => ({ id: p.id, name: p.name, roles: p.role_titles.join(' · '), buying_role: p.buying_role, tam: p.tam,\n          roleColor: roleColor[p.buying_role], sparkEl: this.sparkline(p.spark, 96, 26, roleColor[p.buying_role], true),\n          onOpen: () => this.setState({ icpDrawer: p.id }) })),\n        buckets,\n        drawer: sel ? { name: sel.name, roles: sel.role_titles.join(' · '), buying_role: sel.buying_role, roleColor: roleColor[sel.buying_role],\n          goals: sel.goals, watering_holes: sel.watering_holes, pains: sel.pains } : null,\n        closeDrawer: () => this.setState({ icpDrawer: null }),\n        crosshairIcon: this.ic('crosshair', 18), arrowIcon: this.ic('arrowRight', 14)\n      };\n    }\n\n    // ── Leads pane ──\n    let leads = null;\n    if (kind === 'leads') {\n      const vmap = {\n        deliverable: { label: 'deliverable', color: 'var(--app-success)', bg: 'rgb(var(--accent-teal) / 0.12)' },\n        risky: { label: 'risky', color: 'var(--app-warn)', bg: 'rgb(var(--pastel-gold) / 0.12)' },\n        'catch-all': { label: 'catch-all', color: 'rgb(var(--pastel-gold))', bg: 'rgb(var(--text) / 0.06)' },\n        undeliverable: { label: 'undeliverable', color: 'var(--app-fail)', bg: 'rgb(var(--accent-red) / 0.1)' }\n      };\n      const selArr = this.state.leadSel;\n      const toggleRow = (id) => this.setState(s => ({ leadSel: s.leadSel.indexOf(id) >= 0 ? s.leadSel.filter(x => x !== id) : s.leadSel.concat(id) }));\n      const allOn = selArr.length === this.LEADS.length;\n      leads = {\n        allChecked: allOn,\n        allBorder: allOn ? 'var(--app-accent)' : 'var(--app-rule)', allBg: allOn ? 'var(--app-accent)' : 'transparent',\n        toggleAll: () => this.setState({ leadSel: allOn ? [] : this.LEADS.map(l => l.id) }),\n        selCount: selArr.length, hasSel: selArr.length > 0,\n        clearSel: () => this.setState({ leadSel: [] }),\n        rows: this.LEADS.map(l => {\n          const v = vmap[l.verify]; const on = selArr.indexOf(l.id) >= 0;\n          return { id: l.id, company: l.company, contact: l.contact, title: l.title, email: l.email,\n            li: l.li, liShort: l.li.length > 26 ? l.li.slice(0, 26) + '…' : l.li,\n            verifyLabel: v.label, verifyColor: v.color, verifyBg: v.bg,\n            icp: l.icp.toFixed(2), icpColor: l.icp >= 0.75 ? 'var(--app-success)' : l.icp >= 0.6 ? 'var(--app-ink)' : 'var(--app-fail)',\n            checked: on, rowBg: on ? 'var(--app-accent-dim)' : 'transparent',\n            box: on ? 'var(--app-accent)' : 'transparent', boxBorder: on ? 'var(--app-accent)' : 'var(--app-rule)',\n            onToggle: () => toggleRow(l.id),\n            onCopy: () => { try { navigator.clipboard && navigator.clipboard.writeText(l.li); } catch (e) {} },\n            approve: () => this.setState(s => ({ leadSel: s.leadSel.filter(x => x !== l.id) })),\n            reject: () => this.setState(s => ({ leadSel: s.leadSel.filter(x => x !== l.id) })) };\n        }),\n        copyIcon: this.ic('copy', 13), checkIcon: this.ic('check', 13), xIcon: this.ic('x', 13),\n        runDiscovery: () => this.setState({ disco: { step: 0, icp: this.DISCO_ICPS[0], maxLeads: 250, sources: ['apollo', 'clay'], status: null } }),\n        discoveryLabel: 'Run discovery',\n        approveSelected: () => this.setState({ leadSel: [] }),\n        rejectSelected: () => this.setState({ leadSel: [] })\n      };\n      // Run-discovery modal\n      const d = this.state.disco;\n      if (d) {\n        const steps = ['ICP', 'Criteria', 'Preview', 'Confirm', 'Run'];\n        leads.disco = {\n          open: true, step: d.step, stepName: steps[d.step],\n          steps: steps.map((s, i) => ({ label: s, n: i + 1, active: i === d.step, done: i < d.step,\n            numBg: i < d.step ? 'var(--app-success)' : i === d.step ? 'var(--app-accent)' : 'var(--app-rule)',\n            numColor: i <= d.step ? 'rgb(var(--basalt-deep))' : 'var(--app-ink-faint)',\n            glyph: i < d.step ? '✓' : String(i + 1) })),\n          isICP: d.step === 0, isCriteria: d.step === 1, isPreview: d.step === 2, isConfirm: d.step === 3, isRun: d.step === 4,\n          icps: this.DISCO_ICPS.map(name => ({ name, active: d.icp === name,\n            border: d.icp === name ? 'var(--app-accent)' : 'var(--app-rule)', bg: d.icp === name ? 'var(--app-accent-dim)' : 'var(--app-surface)',\n            color: d.icp === name ? 'var(--app-accent)' : 'var(--app-ink)',\n            onClick: () => this.setState(s => ({ disco: Object.assign({}, s.disco, { icp: name }) })) })),\n          maxLeads: d.maxLeads,\n          sources: this.DISCO_SOURCES.map(src => ({ id: src.id, label: src.label, on: d.sources.indexOf(src.id) >= 0,\n            track: d.sources.indexOf(src.id) >= 0 ? 'var(--app-accent)' : 'rgba(255,255,255,0.15)', knob: d.sources.indexOf(src.id) >= 0 ? '16px' : '2px',\n            toggle: () => this.setState(s => { const has = s.disco.sources.indexOf(src.id) >= 0; return { disco: Object.assign({}, s.disco, { sources: has ? s.disco.sources.filter(x => x !== src.id) : s.disco.sources.concat(src.id) }) }; }) })),\n          statusLabel: d.status || 'queued',\n          statusColor: d.status === 'done' ? 'var(--app-success)' : d.status === 'running' ? 'var(--app-running)' : 'var(--app-warn)',\n          canPrev: d.step > 0, canNext: d.step < 4,\n          nextLabel: d.step === 2 ? 'Confirm' : d.step === 3 ? 'Run discovery' : 'Next',\n          prev: () => this.setState(s => ({ disco: Object.assign({}, s.disco, { step: Math.max(0, s.disco.step - 1) }) })),\n          next: () => this.setState(s => {\n            const cur = s.disco.step;\n            if (cur < 4) {\n              const ns = Object.assign({}, s.disco, { step: cur + 1 });\n              if (cur + 1 === 4) { ns.status = 'queued'; setTimeout(() => this.setState(x => x.disco ? { disco: Object.assign({}, x.disco, { status: 'running' }) } : {}), 500); setTimeout(() => this.setState(x => x.disco ? { disco: Object.assign({}, x.disco, { status: 'done' }) } : {}), 1800); }\n              return { disco: ns };\n            }\n            return {};\n          }),\n          close: () => this.setState({ disco: null }),\n          maxDown: () => this.setState(s => ({ disco: Object.assign({}, s.disco, { maxLeads: Math.max(50, s.disco.maxLeads - 50) }) })),\n          maxUp: () => this.setState(s => ({ disco: Object.assign({}, s.disco, { maxLeads: Math.min(2000, s.disco.maxLeads + 50) }) })),\n          radarIcon: this.ic('radar', 18)\n        };\n      }\n      leads.openDisco = () => this.setState({ disco: { step: 0, icp: this.DISCO_ICPS[0], maxLeads: 250, sources: ['apollo', 'clay'], status: null } });\n    }\n\n    // ── GTM outreach · mail client (native branch: Queue / Cold A / Cold B / Delivery email) ──\n    let mail = null;\n    if (kind === 'mail') {\n      const byTab = { 'Cold A': 'cold-a-conversion.html', 'Cold B': 'cold-b-web-video.html' };\n      const isQueue = !byTab[subLabel];\n      const sel = Math.min(this.state.mailSel || 0, this.GTM_THREADS.length - 1);\n      const doc = isQueue ? this.GTM_THREADS[sel].doc : byTab[subLabel];\n      const shown = isQueue ? this.GTM_THREADS[sel] : (this.GTM_THREADS.find(t => t.doc === doc) || this.GTM_THREADS[0]);\n      const meta = this.GTM_DOCS[doc];\n      const queued = shown.status === 'queued';\n      mail = {\n        showList: isQueue,\n        queueCount: String(this.GTM_THREADS.filter(t => t.status === 'queued').length),\n        threads: this.GTM_THREADS.map((t, i) => ({\n          company: t.company, subject: t.subject, when: t.when, variant: t.variant, status: t.status,\n          color: scolor[t.status],\n          bg: i === sel ? 'var(--app-accent-dim)' : 'transparent',\n          edge: i === sel ? 'var(--app-accent)' : 'transparent',\n          onSelect: () => this.setState({ mailSel: i })\n        })),\n        src: this.resolveGallerySrc('gtm/' + doc),\n        file: 'assets/gallery/gtm/' + doc,\n        docLabel: meta.label, docKind: meta.kind, preheader: meta.preheader,\n        subject: shown.subject, to: shown.to, company: shown.company,\n        from: 'Jonas Reiter <jonas@aaas.love>',\n        statusLabel: shown.status, statusColor: scolor[shown.status],\n        queued, sendLabel: queued ? 'Approve and send' : 'Duplicate to a new lead',\n        vars: meta.vars, lead: this.GTM_LEAD, checks: this.GTM_CHECKS,\n        mailIcon: this.ic('mail', 15), sendIcon: this.ic('send', 15),\n        approve: () => this.setState({ confirm: { title: (queued ? 'Send ' : 'Duplicate ') + '\"' + shown.subject + '\" → ' + shown.to, verb: queued ? 'SEND' : 'COPY' } }),\n        test: () => this.setState({ confirm: { title: 'Send a test copy to jonas@aaas.love', verb: 'TEST' } }),\n        hold: () => this.setState({ confirm: { title: 'Hold this thread — no further sends to ' + shown.company, verb: 'HOLD' } })\n      };\n    }\n\n    // ── Decisions · Zooey run trace (native; the dt- spec from the handoff) ──\n    let dt = null;\n    let sel = null;\n    // S-1: clicking any run row (Overview's Recent or the Runs table) sets dtRunId and jumps to the\n    // 'Run detail' sub-tab, so the trace shown always matches the row the operator actually clicked.\n    const goToRun = (id) => {\n      const idx = (st.subs || []).findIndex(s => s.label === 'Run detail');\n      this.setState({ dtRunId: id, sub: idx >= 0 ? idx : this.state.sub });\n    };\n    if (kind === 'runtrace' && subLabel === 'Overview') {\n      const runs = this.SELECT_RUNS, live = runs.find(r => r.live);\n      const sCol = { running: 'var(--app-accent)', success: 'var(--app-success)', fail: 'var(--app-fail)', awaiting: 'var(--app-warn)' };\n      sel = { isOverview: true,\n        live: live ? { id: live.id, agent: live.agent, started: live.started, decisions: live.decisions } : null,\n        stats: [\n          { label: 'Runs · 24h', value: String(runs.length) },\n          { label: 'Success rate', value: Math.round(runs.filter(r => r.status === 'success').length / runs.filter(r => r.status !== 'running').length * 100) + '%' },\n          { label: 'Agents', value: String(this.SELECT_AGENTS.length) },\n          { label: 'Avg decisions/run', value: (runs.reduce((a, r) => a + r.decisions, 0) / runs.length).toFixed(1) }\n        ],\n        recent: runs.slice(0, 5).map(r => ({ id: r.id, agent: r.agent, status: r.status.toUpperCase(), color: sCol[r.status], started: r.started, go: () => goToRun(r.id) })) };\n    } else if (kind === 'runtrace' && subLabel === 'Runs') {\n      const sCol = { running: 'var(--app-accent)', success: 'var(--app-success)', fail: 'var(--app-fail)', awaiting: 'var(--app-warn)' };\n      sel = { isRuns: true, runs: this.SELECT_RUNS.map(r => ({\n        id: r.id, agent: r.agent, status: r.status.toUpperCase(), color: sCol[r.status], started: r.started, dur: r.dur, decisions: String(r.decisions), cost: r.cost, live: r.live, go: () => goToRun(r.id) })) };\n    } else if (kind === 'runtrace' && subLabel === 'Agents') {\n      const aCol = { HITL: 'var(--app-fail)', Supervised: 'var(--app-warn)', Autonomous: 'var(--app-success)' };\n      sel = { isAgents: true, agents: this.SELECT_AGENTS.map(a => ({\n        name: a.name, autonomy: a.autonomy, autonomyColor: aCol[a.autonomy], runs: String(a.runs), successRate: a.successRate })) };\n    }\n    if (kind === 'runtrace' && !sel) {\n      const activeId = this.state.dtRunId || (this.SELECT_RUNS.find(r => r.live) || this.SELECT_RUNS[0]).id;\n      const entry = activeId === '#4826' ? { run: this.DT_RUN, trace: this.DT_TRACE } : this.DT_RUNS_OTHER[activeId];\n      const R = entry.run, T = entry.trace;\n      const tone = { llm: 'var(--app-accent)', tool: 'var(--app-ink-soft)', decision: 'var(--app-accent)', critique: 'var(--app-warn)', soft: 'var(--app-ink-soft)' };\n      const typeLabel = { plan: 'PLAN', llm_call: 'LLM CALL', tool_call: 'TOOL CALL', decision_branch: 'DECISION', self_critique: 'CRITIQUE', phase: 'PHASE' };\n      const maxDur = Math.max.apply(null, T.map(s => s.dur));\n      const totalCost = T.reduce((a, s) => a + parseFloat(s.cost), 0);\n      const open = this.state.dtOpen || {};\n      const tabIdx = this.state.dtTab || 0;\n      const fmt = (ms) => ms >= 1000 ? (ms / 1000).toFixed(1) + ' s' : ms + ' ms';\n      const banner = { ok: { c: 'var(--app-success)', bg: 'var(--app-surface)' }, running: { c: 'var(--app-accent)', bg: 'rgb(var(--lead, var(--circuit-glow)) / 0.06)' }, fail: { c: 'var(--app-fail)', bg: 'rgb(var(--accent-red) / 0.06)' } }[R.status];\n      dt = {\n        bannerColor: banner.c, bannerBg: banner.bg, bannerLabel: R.statusLabel, schema: 'aaas-trace-' + R.schema,\n        tabs: ['Decisions', 'Output', 'Logs', 'Costs'].map((t, i) => ({\n          label: t, iconEl: this.ic(['relayout', 'content', 'table', 'costs'][i], 14),\n          badge: t === 'Decisions' ? '3' : '', showBadge: t === 'Decisions' ? 'inline-flex' : 'none',\n          bg: i === tabIdx ? 'var(--app-surface)' : 'transparent',\n          ink: i === tabIdx ? 'var(--app-accent)' : 'var(--app-ink-soft)',\n          shadow: i === tabIdx ? 'inset 0 -2px 0 var(--app-accent)' : 'none',\n          go: () => this.setState({ dtTab: i })\n        })),\n        isTimeline: tabIdx === 0, isOutput: tabIdx === 1, isLogs: tabIdx === 2, isCosts: tabIdx === 3,\n        steps: T.map((s, i) => {\n          const expandable = s.type === 'decision_branch' || s.type === 'self_critique';\n          const isOpen = expandable && !!open[s.id];\n          return {\n            id: s.id, label: s.label, type: typeLabel[s.type], summary: s.summary || '', hasSummary: !!s.summary,\n            iconEl: this.ic(s.icon, 12), tone: tone[s.tone],\n            dotBg: s.tone === 'decision' ? 'rgb(var(--lead, var(--circuit-glow)) / 0.18)' : 'var(--app-elevated)',\n            dotBd: s.tone === 'decision' ? 'var(--app-accent)' : s.tone === 'critique' ? 'rgb(var(--pastel-gold) / 0.5)' : s.tone === 'llm' ? 'rgb(var(--lead, var(--circuit-glow)) / 0.4)' : 'var(--app-rule)',\n            dotGlow: s.tone === 'decision' ? '0 0 8px rgb(var(--lead, var(--circuit-glow)) / 0.35)' : 'none',\n            typeInk: s.tone === 'decision' ? 'var(--app-accent)' : s.tone === 'critique' ? 'var(--app-warn)' : 'var(--app-ink-faint)',\n            typeBg: s.tone === 'decision' ? 'rgb(var(--lead, var(--circuit-glow)) / 0.15)' : s.tone === 'critique' ? 'rgb(var(--pastel-gold) / 0.15)' : 'var(--app-elevated)',\n            dur: fmt(s.dur), tok: s.tin ? s.tin + '/' + s.tout + ' tok' : '', hasTok: !!s.tin,\n            cost: '€ ' + s.cost,\n            pct: Math.max(4, Math.round(s.dur / maxDur * 100)) + '%',\n            fill: s.tone === 'decision' ? 'var(--app-accent)' : s.tone === 'critique' ? 'var(--app-warn)' : 'linear-gradient(90deg, var(--app-accent-dim), var(--app-accent))',\n            conf: s.conf != null ? Math.round(s.conf * 100) + ' %' : '', showConf: s.conf != null ? 'inline-flex' : 'none',\n            hasPreview: !!(s.pin || s.pout), pin: s.pin || '', hasPin: !!s.pin, pout: s.pout || '', hasPout: !!s.pout,\n            line: i < T.length - 1 ? 'block' : 'none',\n            cursor: expandable ? 'pointer' : 'default',\n            caretEl: this.ic(isOpen ? 'chevronUp' : 'chevronDown', 15), showCaret: expandable ? 'inline-flex' : 'none',\n            toggle: expandable ? () => this.setState(st6 => ({ dtOpen: Object.assign({}, st6.dtOpen, { [s.id]: !(st6.dtOpen || {})[s.id] }) })) : () => {},\n            showAlts: (isOpen && s.alts) ? true : false,\n            altCount: s.alts ? String(s.alts.length) : '0',\n            alts: (s.alts || []).map(a => {\n              const on = a.label === s.chosen;\n              return { label: a.label, why: a.why, rejected: a.rejected || '', showRejected: a.rejected ? 'block' : 'none',\n                score: 'Score ' + a.score.toFixed(2),\n                bd: on ? 'rgb(var(--lead, var(--circuit-glow)) / 0.6)' : 'var(--app-rule)',\n                bg: on ? 'rgb(var(--lead, var(--circuit-glow)) / 0.08)' : 'var(--app-surface)',\n                op: on ? '1' : '0.78',\n                ink: on ? 'var(--app-accent)' : 'var(--app-ink)',\n                weight: on ? '600' : '400',\n                markBg: on ? 'var(--app-accent)' : 'var(--app-elevated)',\n                markInk: on ? 'rgb(var(--basalt-deep))' : 'var(--app-fail)',\n                markEl: this.ic(on ? 'check' : 'x', 14),\n                showChosen: on ? 'inline-flex' : 'none' };\n            }),\n            showCrit: (isOpen && s.crit) ? true : false,\n            crit: s.crit ? { before: s.crit.before, critique: s.crit.critique, after: s.crit.after,\n              afterTag: 'AFTER ' + (s.crit.accepted ? 'ACCEPTED' : 'REJECTED'),\n              afterColor: s.crit.accepted ? 'var(--app-success)' : 'var(--app-fail)' } : null\n          };\n        }),\n        output: this.DT_OUTPUT,\n        logs: this.DT_LOGS.map(l => ({ t: l.t, tag: l.tag, msg: l.msg,\n          color: { accent: 'var(--app-accent)', success: 'var(--app-success)', warn: 'var(--app-warn)', lav: 'rgb(var(--pastel-lavender))', soft: 'var(--app-ink-soft)' }[l.tone] })),\n        costs: T.filter(s => parseFloat(s.cost) > 0).map(s => ({\n          label: s.label, dur: fmt(s.dur), cost: '€ ' + s.cost,\n          pct: Math.round(parseFloat(s.cost) / totalCost * 100) + '%'\n        })),\n        costTotal: '€ ' + totalCost.toFixed(3),\n        meta: [\n          { k: 'Run', v: R.label }, { k: 'Agent', v: R.agent }, { k: 'Owner', v: R.owner },\n          { k: 'Tier', v: R.tier }, { k: 'Started', v: R.started }, { k: 'Duration', v: R.duration },\n          { k: 'Tokens in / out', v: R.tokIn + ' / ' + R.tokOut }, { k: 'Retention until', v: R.retention }\n        ],\n        replayEl: this.ic('refresh', 14)\n      };\n    }\n\n    // ── Growth Fleet · native branch (docs/SCREEN-INTEGRATION-PROTOCOL.md). Source declares 8\n    // archetypes the lens contract doesn't have, so per HANDOVER.md it is a native branch.\n    const isGrowth = kind === 'growth';\n    const growthSub = subLabel || 'System inventory';\n\n    // ── Personal Home · native branch (O5). Source declares four views with their own live/loading/\n    // empty/error copy per view; the lens contract has no such per-view phase axis, so per HANDOVER §2\n    // this is a native branch. Inbox/Saved stay lens — they are not in ownsTabs.\n    const isHome = kind === 'home' && ['Overview', 'Services', 'Threads', 'Tasks'].indexOf(subLabel || 'Overview') > -1;\n    const homeTab = subLabel || 'Overview';\n    const homeGoTab = (label) => { const i = (st.subs || []).findIndex(s => s.label === label); if (i >= 0) this.setState({ sub: i, pane: 'default' }); };\n    const homeGoStream = (id) => this.selectStream(id);\n\n\n    const PROP_KINDS = ['proposalRequest', 'offeringMatch', 'proposalBuilder', 'proposalReview', 'proposalPipeline'];\n    const isProposals = PROP_KINDS.indexOf(kind) > -1;\n    const isClientIndex = kind === 'clientIndex' && (subLabel || 'Index') === 'Index';\n    // WP-51 · Process engine P3 — the whole-tree overview, mounted through ArchetypeHost's new\n    // 'process' archetype. Validated before mount, same pattern as WP-37/38 (fail visibly).\n    const isClientProcess = kind === 'clientIndex' && (subLabel || '') === 'Process';\n    // W2 · Onboarding › Getting started — K.servicesWizard as a `process` plane. Template in process-spec.js\n    // ('services-activation'); the instance is derived from UI state (svcWizStep), never a typed progress.\n    const isOnbWizard = kind === 'onboarding' && (subLabel || '') === 'Getting started';\n    let wizTemplate = null, wizInstance = null, wizValidation = { ok: true, errors: [] }, wizStep = null;\n    if (isOnbWizard && window.ProcessSpec) {\n      wizTemplate = window.ProcessSpec.templateById('services-activation');\n      const cur = this.state.svcWizStep || 0;\n      if (wizTemplate) {\n        const nodes = {};\n        wizTemplate.nodes.forEach((n, i) => { nodes[n.id] = { state: i < cur ? 'done' : i === cur ? 'active' : 'todo', owner: i === cur ? 'you' : null,\n          startedAt: null, doneAt: null, artifacts: [], blockedOn: null, dataContract: { endpoint: null, status: 'planned' } }; });\n        wizInstance = { id: 'inst-services-activation', templateId: 'services-activation', client: 'You', owner: 'you', startedAt: null, nodes };\n        const st6 = (window.ProcessSpec.ONBOARDING_STEPS || [])[cur] || {};\n        wizStep = { of: 'Step ' + (cur + 1) + ' of ' + wizTemplate.nodes.length + ' · ' + (st6.label || ''), title: st6.title || '', tagline: st6.tagline || '',\n          canBack: cur > 0, canNext: cur < wizTemplate.nodes.length - 1,\n          back: () => this.setState(x => ({ svcWizStep: Math.max(0, (x.svcWizStep || 0) - 1) })),\n          next: () => this.setState(x => ({ svcWizStep: Math.min(wizTemplate.nodes.length - 1, (x.svcWizStep || 0) + 1) })),\n          openServices: () => this.selectStream('services') };\n        if (this._planeValidator && wizInstance) {\n          const a = this._planeValidator.validateTemplate(wizTemplate), b = this._planeValidator.validateInstance(wizInstance, wizTemplate);\n          wizValidation = { ok: a.ok && b.ok, errors: a.errors.concat(b.errors) };\n        }\n      }\n    }\n    let processTemplate = null, processInstance = null, processValidation = { ok: true, errors: [] };\n    if (isClientProcess && window.ProcessSpec) {\n      processTemplate = window.ProcessSpec.templateById('client-engagement');\n      processInstance = (window.ProcessInstantiate && window.ProcessInstantiate.current('inst-helsing-afo'))\n        || window.ProcessSpec.instanceById('inst-helsing-afo');\n      if (this._planeValidator && processTemplate && processInstance) {\n        const tplCheck = this._planeValidator.validateTemplate(processTemplate);\n        const instCheck = this._planeValidator.validateInstance(processInstance, processTemplate);\n        processValidation = { ok: tplCheck.ok && instCheck.ok, errors: tplCheck.errors.concat(instCheck.errors) };\n      }\n    }\n    /* CM-H · the Flows tab. Same archetype, same validator-before-mount rule as WP-51's Client Index\n       Process. The instance is DERIVED from ContextStore every render (ProcessSpec.gtmInstance) — there\n       is no stored GTM instance and there must not be one: typed flow progress would disagree with the\n       blocks it claims to have produced the moment either changed. */\n    const isCtxFlows = kind === 'ctx' && (subLabel || '') === 'Flows';\n    let gtmTemplate = null, gtmInstance = null, gtmValidation = { ok: true, errors: [] }, gtmNote = '';\n    if (isCtxFlows && window.ProcessSpec) {\n      const PS = window.ProcessSpec, CS = window.ContextStore;\n      gtmTemplate = PS.templateById('strategic-gtm');\n      gtmInstance = CS ? PS.gtmInstance(CS, (window.__dcCtxPacks || {})['aaas'] || null, 'aaas') : null;\n      if (this._planeValidator && gtmTemplate && gtmInstance) {\n        const t = this._planeValidator.validateTemplate(gtmTemplate);\n        const i = this._planeValidator.validateInstance(gtmInstance, gtmTemplate);\n        gtmValidation = { ok: t.ok && i.ok, errors: t.errors.concat(i.errors) };\n      }\n      const rows = (CS && PS.gtmRows) ? PS.gtmRows(CS, (window.__dcCtxPacks || {})['aaas'] || null, 'aaas') : [];\n      const derivable = rows.filter(r => !r.present && r.derivable).length;\n      gtmNote = 'Node state is read from ContextStore, not stored: done = the block exists with at least one ' +\n        'filled field, active = derivable from company intake now, blocked = the reason is stated on the node. ' +\n        derivable + ' of ' + rows.length + ' block types are derivable right now — derive them in Domains, ' +\n        'where the write is confirmed and raises an acceptance thread (CM-6).';\n    }\n\n    // WP-58 · the trigger that was missing: accepting an offering starts its process.\n    // The write is operator-initiated, so gate 2 is checked against the OPERATOR's own permission\n    // (PERMISSIONS), not AGENT_WRITE — a person is not an agent. The consequential-write guard is\n    // the shell's existing HoldToConfirm, not a new dialog.\n    const startInstanceId = 'inst-helsing-afo';\n    const storeHasInstance = !!(window.ProcessInstantiate && window.ProcessInstantiate.current(startInstanceId));\n    const runStart = () => {\n      const PI = window.ProcessInstantiate, C = this._catalog;\n      if (!PI || !C) return;\n      const pkg = C.PACKAGES.find(p => p.id === 'k1');\n      const perms = this.PERMISSIONS[this.state.role];\n      const verdict = PI.instantiateFromOffering(pkg, {\n        client: 'Helsing GmbH', owner: (window.Fixtures || {}).OPERATOR && window.Fixtures.OPERATOR.addr,\n        agent: null, streamId: 'clientIndex'\n      }, this._planeValidator || {});\n      const gated = PI.checkGates({ template: verdict.template, instance: verdict.instance,\n        actor: (window.Fixtures || {}).OPERATOR ? window.Fixtures.OPERATOR.addr : 'operator',\n        scope: perms === null ? null : (perms || []), streamId: 'clientIndex' }, this._planeValidator || {});\n      if (gated.route === 'blocked') { this.setState({ procNote: gated.reason, procNoteTone: 'fail' }); return; }\n      const inst = Object.assign({}, verdict.instance, { id: startInstanceId });\n      PI.apply(inst, 'accepted ' + pkg.name);\n      this.setState({ procNote: 'Engagement started from “' + pkg.name + '” — applied by ' +\n        ((window.Fixtures || {}).OPERATOR ? window.Fixtures.OPERATOR.addr : 'the operator') +\n        ' after hold-to-confirm. Every node starts at todo; undo is available.', procNoteTone: 'ok' });\n    };\n    const startEngagement = () => this.setState({ confirm: {\n      title: 'Start the Autonomous Finance Ops engagement for Helsing GmbH', verb: 'START', onConfirm: runStart } });\n    const undoEngagement = () => {\n      const PI = window.ProcessInstantiate;\n      if (!PI) return;\n      PI.undo(startInstanceId);\n      this.setState({ procNote: 'Reverted to the previous instance.', procNoteTone: 'ok' });\n    };\n    // WP-52 · Process engine P4 — the 'gate' archetype, generalizing PaneApprovals' rendering.\n    // approvals.js stays the state owner (raise/resolve) — this tab reads its REAL pending queue\n    // and shows the one item that is actually this client's (ap-303's own preview text names\n    // \"Client Index → Helsing GmbH\"). No fabricated proposal: if none is pending, empty state.\n    const isClientGate = kind === 'clientIndex' && (subLabel || '') === 'Approve';\n    let gateProposal = null, gateRowsReal = null, gateBlockedReason = null;\n    if (isClientGate && this._approvals) {\n      const found = this._approvals.pending().find(a => /Helsing/i.test(a.preview || '') || a.id === 'ap-303');\n      if (found) {\n        gateProposal = { id: found.id, title: found.title, origin: found.origin, agent: found.agent,\n          raised: found.raised, summary: found.summary, preview: found.preview, status: found.status };\n        // WP-54 (verifier-caught): run the REAL predicate rather than letting ArchetypeHost's\n        // default ladder render an unchecked gate as a pass. The target stream is derived from the\n        // proposal's own `origin` field, and its liveness from that stream's declared dataContract.\n        const originStream = { 'Deliverables': 'deliverables', 'Proposal Pipeline': 'proposalPipeline',\n          'Package Builder': 'packages', 'Client Index': 'clientIndex' }[found.origin] || 'clientIndex';\n        const targetContract = (this.STREAMS[originStream] || {}).dataContract || null;\n        if (window.ProcessInstantiate) {\n          const verdict = window.ProcessInstantiate.checkGates(\n            { agent: found.agent, streamId: originStream, dataContract: targetContract },\n            this._planeValidator || {});\n          gateRowsReal = verdict.gates;\n          if (verdict.route === 'blocked') gateBlockedReason = verdict.reason;\n        }\n      }\n    }\n    const gateApprove = (p) => { if (this._approvals) this._approvals.resolve(p.id, 'approved'); };\n    const gateReject = (p, reason) => { if (this._approvals) this._approvals.resolve(p.id, 'rejected'); };\n\n    // WP-55/56/57 · diff · brief · ledger — each fed from data that already exists in this project.\n    // diff: approvals.js ap-302's own preview is a real before/after config change.\n    const isPkgDiff = kind === 'packages' && (subLabel || '') === 'Change review';\n    let diffSpec = null;\n    if (isPkgDiff && this._approvals) {\n      const c = this._approvals.all().find(a => a.kind === 'catalog.change');\n      if (c) {\n        const rows = String(c.preview || '').split('\\n');\n        const before = rows.map(r => r.includes(' → ') ? r.split(' → ')[0] : r);\n        const after = rows.map(r => r.includes(' → ') ? r.replace(/: .*? → /, ': ') : r);\n        diffSpec = { title: c.title, before: before.join('\\n'), after: after.join('\\n'),\n          checks: [\n            { label: 'Raised with a stated reason', state: c.raised ? 'pass' : 'fail', detail: c.raised || '—' },\n            { label: 'Human verdict recorded', state: c.status === 'pending' ? 'pending' : 'pass', detail: c.status }\n          ],\n          history: [{ actor: c.agent, action: 'proposed this change', timestamp: c.raised, tone: 'accent' }] };\n      }\n    }\n\n    // brief + ledger: both derived from catalog.js — the tier the client's open proposal quotes\n    // (k1 · Growth). Every figure is computed from that module, none authored here.\n    const isClientBrief = kind === 'clientIndex' && (subLabel || '') === 'Brief';\n    const isClientLedger = kind === 'clientIndex' && (subLabel || '') === 'Ledger';\n    let briefSpec = null, ledgerSpec = null;\n    if ((isClientBrief || isClientLedger) && this._catalog) {\n      const C = this._catalog;\n      const tier = C.TIERS.find(t => t.id === 't2');\n      const offerings = C.deriveOfferings('t2');\n      const F = window.Fixtures;\n      if (isClientBrief) {\n        const listOf = (state) => offerings.filter(o => o.state === state).map(o => o.name + ' — ' + o.req).join('\\n');\n        briefSpec = {\n          title: 'Autonomous Finance Ops — engagement brief',\n          reference: 'REQ-2026-0142 · servicesProposal',\n          meta: [{ k: 'operator', v: F ? F.OPERATOR.addr : '' }, { k: 'tier', v: tier.name },\n            { k: 'quoted', v: C.eur(tier.base) + ' ' + tier.unit }],\n          sections: [\n            { label: 'What the client asked for', body: C.REQUESTED.map(r => r.label).join('\\n'), required: true },\n            { label: 'Matched from the catalog', body: listOf('matched'), required: true },\n            { label: 'Gaps — needs a new line', body: listOf('gap') },\n            { label: 'Out of scope', body: listOf('out') },\n            { label: 'Success criteria', body: '', required: true }\n          ]\n        };\n      }\n      if (isClientLedger) {\n        const enabled = tier.lines.filter(l => l.on);\n        const quoted = enabled.reduce((s, l) => s + l.cost, 0);\n        ledgerSpec = {\n          title: tier.name + ' tier — quoted lines',\n          cap: C.eur(tier.base), actual: C.eur(quoted),\n          actualPct: Math.min(100, Math.round(quoted / tier.base * 100)),\n          projectionPct: 0,\n          rows: tier.lines.map(l => ({\n            label: l.label, amount: l.on ? C.eur(l.cost) : '—',\n            spentPct: l.on ? Math.round(l.cost / tier.base * 100) : 0,\n            tone: 'under',\n            state: l.on ? 'modeled' : 'empty',\n            note: l.on ? l.basis + ' · covers ' + l.covers : 'switched off in the Composer — not quoted'\n          })),\n          balance: C.eur(quoted) + ' of ' + C.eur(tier.base) + ' base'\n        };\n      }\n    }\n    const showDealChip = PROP_KINDS.indexOf(kind) > -1 || kind === 'clientIndex';\n    const dealChip = 'Helsing GmbH · REQ-2026-0142';\n    const proposalKind = isProposals ? kind : '';\n    // PT-1: all six tabs are in ownsTabs, so `kind` is 'pitch' on every one of them and the pane\n    // owns its own tab routing — no per-tab branch here.\n    const isPitches = kind === 'pitch';\n    const isVox = kind === 'vox';\n    // AW-7 · the per-stream contracts, handed to PaneFeeds so the reconciliation reads the LIVE map\n    // rather than a copy of it. Memoised: STREAMS never changes after construction while renderVals\n    // runs on every keystroke. The shape is deliberately minimal — the pane needs endpoint + status\n    // and must not be able to reach anything else in the map.\n    if (!this._feedContracts) {\n      this._feedContracts = {};\n      Object.keys(this.STREAMS).forEach((sid) => {\n        const dc = this.STREAMS[sid].dataContract;\n        this._feedContracts[sid] = dc ? { endpoint: dc.endpoint || null, status: dc.status || 'unknown' } : null;\n      });\n    }\n    const isFeeds = kind === 'feeds';\n    const isVid = kind === 'vid';\n    const isDelivery = kind === 'delivery';\n    const isPackages = kind === 'packages' && (subLabel || 'Catalog') !== 'Change review';\n\n    // ── Production Protocol · native branch (docs/SCREEN-INTEGRATION-PROTOCOL.md).\n    // The source screen's chrome (its own left nav + top bar) is deliberately NOT ported — the\n    // Canvas rail and heartbeat bar already provide it. Only the content plane crosses over.\n    const isProto = kind === 'protocol' && (subLabel || 'Protocol') === 'Protocol';\n\n    // ── Phase 2 Batch 3 migration target: onboardingV2's 'Tour steps' tab, mounted through\n    // ArchetypeHost as a real { archetype, items } plane instead of hand-written pane code\n    // (docs/BLOCK-HOST-FINDING.md). Data below is the SAME 5 steps the archived tour always had —\n    // ownership is scoped via ownsTabs so only this one tab is affected.\n    const isTourSteps = st.id === 'onboardingV2' && (subLabel || '') === 'Tour steps';\n    const tourStepsItems = [\n      { id: 'ts-1', title: 'Connect an API', subtitle: 'OAuth or key — your data never leaves your account', group: 'Setup', owner: 'you', date: '2026-08-01', status: 'ok', statusLabel: 'done' },\n      { id: 'ts-2', title: 'Contextualize', subtitle: 'The engine reads your API schema and builds working context', group: 'Setup', owner: 'engine', date: '2026-08-01', status: 'ok', statusLabel: 'done' },\n      { id: 'ts-3', title: 'Pick a starting agent', subtitle: 'One of 18 integrations has a matching agent template', group: 'Configure', owner: 'you', date: '2026-08-02', status: 'warn', statusLabel: 'in progress' },\n      { id: 'ts-4', title: 'Automate a first task', subtitle: 'Point the agent at a real, low-risk job', group: 'Configure', owner: 'you', date: '2026-08-02', status: 'idle', statusLabel: 'not started' },\n      { id: 'ts-5', title: 'First run', subtitle: 'Watch it execute — under 5 min from connect to result', group: 'Run', owner: 'engine', date: '2026-08-03', status: 'idle', statusLabel: 'not started' }\n    ];\n    // Validate the plane BEFORE it mounts, per Phase 2's \"fail visibly, never silently\" rule\n    // (docs/BLOCK-HOST-FINDING.md §4). archetypeValidation is set only when isTourSteps is true —\n    // cheap to extend to future ArchetypeHost mounts by repeating this pattern per plane.\n    let archetypeValidation = null;\n    if (isTourSteps && this._planeValidator) {\n      archetypeValidation = this._planeValidator.validatePlane(\n        { id: 'V1.BoardView', props: { items: tourStepsItems, groups: [], onSelect: () => {}, emptyLabel: 'Nothing here yet.', ariaLabel: 'Tour steps board' } },\n        this._descriptorsIndex || { descriptors: [], $types: {} }\n      );\n    }\n\n    // ── Live-data proof (Phase 2, 2026-08-06): same real Content-calendar data Growth Fleet's\n    // hand-written pane already renders (PaneGrowth.dc.html's own CALENDAR array), mapped into the\n    // shared plane contract and mounted through ArchetypeHost as 'calendar'. Additive — a new\n    // sibling tab, the original 'Content calendar' tab and PaneGrowth are untouched.\n    // W3 (D152): the '· auto' sibling tab is folded — the archetype calendar is now the Content calendar tab's primary view,\n    // PaneGrowth's board renders below it as the source's secondary ('Review gate'). One surface, one tab (B9).\n    // D199b: Content · v2 › Publish mounts the same list through v2ArchFor (this flag sits inside the `kind` block,\n    // which content2's lens tabs never reach — verifier round 1). One list: growthCalItemsList().\n    const isGrowthCalConfig = st.id === 'growth' && (subLabel || '') === 'Content calendar';\n    const growthCalItems = this.growthCalItemsList();\n    let growthCalValidation = null;\n    if (isGrowthCalConfig && this._planeValidator) {\n      growthCalValidation = this._planeValidator.validatePlane(\n        { id: 'V1.CalendarView', props: { items: growthCalItems, dateKey: 'date', range: 'week', onSelect: () => {}, ariaLabel: 'Content calendar' } },\n        this._descriptorsIndex || { descriptors: [], $types: {} }\n      );\n    }\n\n    // ── Intelligence · Unified Ask (native; the ua- spec) ──\n    let ua = null;\n    if (kind === 'ask') {\n      const iSub = subLabel || 'Overview';\n      const U = this.UA;\n      const srcCol = { blog: 'var(--app-accent)', vault: 'var(--app-warn)', notebooklm: 'rgb(var(--pastel-lavender))', user_kb: 'var(--app-success)' };\n      const srcBg = { blog: 'rgb(var(--lead, var(--circuit-glow)) / 0.12)', vault: 'rgb(var(--pastel-gold) / 0.14)', notebooklm: 'rgb(var(--pastel-lavender) / 0.14)', user_kb: 'rgb(var(--accent-teal) / 0.14)' };\n      const off = this.state.uaOff || {};\n      ua = {\n        question: U.question, latency: U.latency, cost: U.cost, model: U.model,\n        answer: U.answer.map(a => ({ t: a.t, color: a.em ? 'var(--app-ink)' : 'var(--app-ink-soft)', weight: a.em ? '500' : '400' })),\n        sources: U.sources.map(s => {\n          const on = !off[s.id];\n          return { label: s.label, weight: s.weight,\n            bd: on ? 'var(--app-accent)' : 'var(--app-rule)',\n            ink: on ? 'var(--app-accent)' : 'var(--app-ink-soft)',\n            bg: on ? 'rgb(var(--lead, var(--circuit-glow)) / 0.08)' : 'var(--app-elevated)',\n            toggle: () => this.setState(st8 => ({ uaOff: Object.assign({}, st8.uaOff, { [s.id]: !(st8.uaOff || {})[s.id] }) })) };\n        }),\n        citations: U.citations.map(c => ({ n: c.n, title: c.title, quote: c.quote, url: c.url, conf: c.conf,\n          srcLabel: c.src === 'user_kb' ? 'WORKSPACE KB' : c.src.toUpperCase(),\n          srcInk: srcCol[c.src], srcBg: srcBg[c.src] })),\n        recent: U.recent.map(r => ({ q: r.q, meta: r.ago + ' · € ' + r.cost,\n          ask: () => this.setState({ confirm: { title: 'Re-run \"' + r.q + '\" across the four sources', verb: 'ASK' } }) })),\n        suggested: U.suggested.map(s => ({ q: s,\n          ask: () => this.setState({ confirm: { title: 'Ask \"' + s + '\"', verb: 'ASK' } }) })),\n        budgetPct: Math.round(U.budget.used / U.budget.of * 100) + '%',\n        budgetLabel: U.budget.used + ' / ' + U.budget.of,\n        budgetSub: U.budget.label,\n        sendEl: this.ic('send', 16), sparkEl: this.ic('spark', 13),\n        send: () => this.setState({ confirm: { title: 'Send this question to the four-source router', verb: 'ASK' } })\n      };\n      ua.isAsk = iSub === 'Ask anything';\n      ua.isGraph = iSub === 'Entity graph';\n      ua.isVault = iSub === 'Vault';\n      ua.isPrompts = iSub === 'Prompts';\n      ua.isNb = iSub === 'NotebookLM sync';\n      ua.isKb = iSub === 'KB';\n      ua.isAudit = iSub === 'Query audit';\n      ua.isCloseout = iSub === 'Closeout · 14 briefs';\n\n      const TINT = { cy: 'var(--app-accent)', red: 'rgb(var(--accent-red))', gd: 'rgb(var(--pastel-gold))', lav: 'rgb(var(--pastel-lavender))', tl: 'rgb(var(--accent-teal))' };\n\n      // ── Entity graph ──\n      if (ua.isGraph) {\n        const G = this.IG;\n        const gf = this.state.igFilter || 'all';\n        const gp = this.state.igPick || null;\n        const ct = (id) => { const c = G.collections.find(x => x.id === id); return TINT[(c || {}).tint] || 'var(--app-accent)'; };\n        const visN = gf === 'all' ? G.nodes : G.nodes.filter(n => n.collection === gf);\n        const ids = visN.map(n => n.id);\n        const pk = G.nodes.find(n => n.id === gp) || null;\n        const setF = (id) => () => this.setState({ igFilter: id, igPick: null });\n        ua.graph = {\n          chips: [{ label: 'All', count: String(G.nodes.length), sw: null, bd: gf === 'all' ? 'var(--app-accent)' : 'var(--app-rule)', ink: gf === 'all' ? 'var(--app-accent)' : 'var(--app-ink-soft)', pick: setF('all') }]\n            .concat(G.collections.map(c => ({ label: c.label, count: String(c.count), sw: TINT[c.tint],\n              bd: gf === c.id ? TINT[c.tint] : 'var(--app-rule)', ink: gf === c.id ? TINT[c.tint] : 'var(--app-ink-soft)', pick: setF(c.id) }))),\n          nodes: visN.map(n => {\n            const r = 6 + Math.min(14, n.edges / 4);\n            const on = n.id === gp;\n            return { label: n.label, size: (r * 2) + 'px', left: n.x + '%', top: n.y + '%',\n              bg: ct(n.collection), ink: ct(n.collection),\n              ring: on ? '0 0 0 3px ' + ct(n.collection) + ', 0 0 22px ' + ct(n.collection) : '0 0 12px ' + ct(n.collection),\n              pick: () => this.setState({ igPick: n.id }) };\n          }),\n          lines: G.edges.filter(e => ids.indexOf(e.src) >= 0 && ids.indexOf(e.tgt) >= 0).map(e => {\n            const a = G.nodes.find(n => n.id === e.src), b = G.nodes.find(n => n.id === e.tgt);\n            const hot = gp && (e.src === gp || e.tgt === gp);\n            return { x1: a.x, y1: a.y, x2: b.x, y2: b.y,\n              stroke: hot ? 'var(--app-accent)' : 'var(--app-rule)',\n              w: hot ? 1.4 : 0.8, dash: e.confidence < 0.9 ? '3 3' : '0' };\n          }),\n          hasPick: !!pk,\n          empty: !pk,\n          p: pk ? {\n            label: pk.label, id: pk.id, ink: ct(pk.collection),\n            collection: (G.collections.find(c => c.id === pk.collection) || {}).label,\n            edges: String(pk.edges),\n            out: String(G.edges.filter(e => e.src === pk.id).length),\n            inn: String(G.edges.filter(e => e.tgt === pk.id).length),\n            rows: G.edges.filter(e => e.src === pk.id || e.tgt === pk.id).map(e => ({\n              pred: e.predicate, dir: e.src === pk.id ? '→' : '←',\n              tgt: (G.nodes.find(n => n.id === (e.src === pk.id ? e.tgt : e.src)) || {}).label,\n              conf: e.confidence.toFixed(2) })),\n            close: () => this.setState({ igPick: null })\n          } : null,\n          hitl: G.hitl.map(h => ({ src: (G.nodes.find(n => n.id === h.src) || {}).label, tgt: (G.nodes.find(n => n.id === h.tgt) || {}).label,\n            pred: h.predicate, conf: h.confidence.toFixed(2), quote: h.quote, url: h.url,\n            accept: () => this.setState({ confirm: { title: 'Accept edge “' + h.src + ' ' + h.predicate + ' ' + h.tgt + '” into the graph', verb: 'ACCEPT' } }),\n            reject: () => this.setState({ confirm: { title: 'Reject edge “' + h.src + ' ' + h.predicate + ' ' + h.tgt + '”', verb: 'REJECT' } }) })),\n          hitlCount: G.hitl.length + ' of 24 shown'\n        };\n        // I-1 (2026-09-05): per-source relevance scores behind a picked node — genuinely missing\n        // before (only relationship confidence existed, not provenance). Mirrors the Ask surface's\n        // own citation pattern (src/title/conf/url) so the two surfaces read consistently.\n        if (pk) {\n          const SRC_TINT = { blog: 'var(--app-accent)', vault: 'rgb(var(--accent-teal))', notebooklm: 'rgb(var(--pastel-lavender))', user_kb: 'var(--app-warn)' };\n          const provenance = {\n            agent: [{ src: 'vault', label: pk.label + '/spec', conf: '0.94' }, { src: 'notebooklm', label: 'MEGADOC §4.1 Agent roster', conf: '0.87' }],\n            dataset: [{ src: 'blog', label: pk.label + ' schema notes', conf: '0.9' }, { src: 'user_kb', label: 'Workspace data dictionary', conf: '0.81' }],\n            'pull req': [{ src: 'vault', label: 'PR review skill output', conf: '0.92' }],\n            run: [{ src: 'vault', label: 'Run trace export', conf: '0.96' }],\n            policy: [{ src: 'notebooklm', label: 'MEGADOC §2.3 Compliance', conf: '0.89' }]\n          }[pk.collection] || [{ src: 'blog', label: pk.label + ' reference', conf: '0.85' }];\n          ua.graph.p.sources = provenance.map(s => ({ src: s.src, label: s.label, conf: s.conf, ink: SRC_TINT[s.src] || 'var(--app-ink-soft)' }));\n        }\n      }\n\n      // ── Vault ──\n      if (ua.isVault) {\n        const V = this.VL, D = V.dist, tot = D.gold + D.silver + D.bronze;\n        const tier = this.state.vlTier || 'all';\n        const view = this.state.vlView || 'grid';\n        const TB = { gold: 'rgb(var(--pastel-gold))', silver: 'rgba(160,160,160,0.45)', bronze: '#8b5a2b' };\n        const TI = { gold: 'rgb(var(--basalt-deep))', silver: 'rgb(var(--text))', bronze: '#fff' };\n        ua.vault = {\n          head: 'Tier distribution · ' + tot.toLocaleString('de-DE') + ' skills',\n          isGrid: view === 'grid', isList: view === 'list',\n          viewToggle: [['grid', 'Grid'], ['list', 'List']].map(([id, label]) => ({\n            label, on: view === id,\n            bg: view === id ? 'var(--app-surface)' : 'transparent', ink: view === id ? 'var(--app-ink)' : 'var(--app-ink-soft)',\n            pick: () => this.setState({ vlView: id }) })),\n          segs: [\n            { label: 'Gold · ' + D.gold, w: (D.gold / tot * 100) + '%', bg: TB.gold, ink: TI.gold },\n            { label: 'Silver · ' + D.silver, w: (D.silver / tot * 100) + '%', bg: TB.silver, ink: TI.silver },\n            { label: 'Bronze · ' + D.bronze, w: (D.bronze / tot * 100) + '%', bg: TB.bronze, ink: TI.bronze }\n          ],\n          cats: V.cats.map(c => ({ label: c.label, count: c.count.toLocaleString('de-DE'),\n            gw: (c.gold / c.count * 100) + '%', sw: (c.silver / c.count * 100) + '%', bw: (c.bronze / c.count * 100) + '%',\n            mini: c.gold + ' g · ' + c.silver + ' s · ' + c.bronze + ' b' })),\n          tabs: ['all', 'gold', 'silver', 'bronze'].map(t => ({\n            label: t === 'all' ? 'All' : t.charAt(0).toUpperCase() + t.slice(1),\n            bg: tier !== t ? 'transparent' : (t === 'all' ? 'var(--app-surface)' : TB[t]),\n            ink: tier !== t ? 'var(--app-ink-soft)' : (t === 'all' ? 'var(--app-ink)' : TI[t]),\n            pick: () => this.setState({ vlTier: t }) })),\n          skills: V.featured.filter(s => tier === 'all' || s.tier === tier).map(s => ({\n            name: s.name, cat: s.cat, tier: s.tier.toUpperCase(), score: s.score, uses: s.uses, up: s.up, ctx: s.ctx,\n            bd: s.tier === 'gold' ? 'rgb(var(--pastel-gold, 248 217 116) / 0.42)' : s.tier === 'silver' ? 'rgba(160,160,160,0.3)' : 'rgba(139,90,43,0.62)',\n            badgeBg: TB[s.tier], badgeInk: TI[s.tier],\n            promote: () => this.setState({ confirm: { title: 'Open a promotion PR for ' + s.name, verb: 'PROMOTE' } }),\n            tryIt: () => this.setState({ confirm: { title: 'Run ' + s.name + ' against a sample payload', verb: 'RUN' } })\n          })),\n          boltEl: this.ic('spark', 10), playEl: this.ic('play', 10), upEl: this.ic('arrow-up-right', 10)\n        };\n      }\n\n      // ── Prompt library ──\n      if (ua.isPrompts) {\n        const P = this.PLL;\n        const kf = this.state.pllKind || 'all', tf = this.state.pllTag || 'all';\n        const KC = { system: 'var(--app-accent)', template: 'rgb(var(--accent-teal))', per_step: 'rgb(var(--pastel-gold))',\n          fewshot_example: 'rgb(var(--pastel-lavender))', output_schema: 'var(--app-ink-soft)', guardrail: 'rgb(var(--accent-red))' };\n        const list = P.filter(p => (kf === 'all' || p.kind === kf) && (tf === 'all' || p.tags.indexOf(tf) >= 0));\n        const sel = list.find(p => p.id === this.state.pllPick) || list[0] || P[0];\n        const chip = (val, cur, set, label) => ({ label, on: cur === val,\n          bg: cur === val ? 'var(--app-accent)' : 'var(--app-elevated)',\n          ink: cur === val ? 'rgb(var(--basalt-deep))' : 'var(--app-ink-soft)',\n          bd: cur === val ? 'var(--app-accent)' : 'var(--app-rule)', pick: set });\n        ua.prompts = {\n          count: P.length + ' prompts · ' + P.filter(p => p.ab).length + ' A/B tests active · last run 2h ago',\n          kinds: ['all', 'system', 'template', 'per_step', 'fewshot_example', 'output_schema', 'guardrail'].map(k2 =>\n            chip(k2, kf, () => this.setState({ pllKind: k2, pllPick: null }), k2 === 'all' ? 'all' : k2.replace(/_/g, ' '))),\n          tags: ['all', 'brand', 'voice', 'legal', 'safety', 'experimental'].map(t2 =>\n            chip(t2, tf, () => this.setState({ pllTag: t2, pllPick: null }), t2)),\n          items: list.map(p => ({\n            slug: p.slug, kind: p.kind.replace(/_/g, ' ').toUpperCase(), kindInk: KC[p.kind], ab: !!p.ab,\n            kindBg: 'color-mix(in srgb, ' + KC[p.kind] + ' 18%, transparent)',\n            tags: p.tags, stats: p.agents + ' agents · ' + p.runs + ' · ' + p.tokens,\n            bg: p.id === sel.id ? 'var(--app-elevated)' : 'transparent',\n            bd: p.id === sel.id ? 'var(--app-accent)' : 'transparent',\n            pick: () => this.setState({ pllPick: p.id }) })),\n          d: {\n            slug: sel.slug, kind: sel.kind.replace(/_/g, ' ').toUpperCase(), kindInk: KC[sel.kind],\n            kindBg: 'color-mix(in srgb, ' + KC[sel.kind] + ' 18%, transparent)',\n            tags: sel.tags, lang: 'lang ' + sel.lang, body: sel.body,\n            bodyHead: 'BODY · ' + sel.active + ' active · ' + sel.tokens.replace('t', ' tokens'),\n            vars: 'Variables: ' + sel.vars, models: 'Models: ' + sel.models,\n            hasTest: !!sel.test, test: sel.test || null,\n            versions: sel.versions.map(v => ({ v: v.v, on: !!v.on, meta: v.ago + ' · ' + v.by, note: '“' + v.note + '”',\n              mark: v.on ? '●' : '○', ink: v.on ? 'var(--app-accent)' : 'var(--app-ink-faint)' })),\n            usedBy: sel.usedBy.map(u2 => ({ name: u2 })),\n            usedByHead: 'USED BY · ' + sel.usedBy.length + (sel.usedBy.length === 1 ? ' agent' : ' agents'),\n            promote: () => this.setState({ confirm: { title: 'Promote the winning arm of ' + sel.slug + ' to active', verb: 'PROMOTE' } }),\n            edit: () => this.setState({ confirm: { title: 'Open ' + sel.slug + ' for editing (RBAC-gated)', verb: 'EDIT' } })\n          },\n          editEl: this.ic('palette', 12), diffEl: this.ic('columns', 12), sandboxEl: this.ic('spark', 12), agentEl: this.ic('intelligence', 12)\n        };\n      }\n\n      // ── NotebookLM sync ──\n      if (ua.isNb) {\n        const M = this.MS;\n        ua.nb = {\n          health: M.health, docs: M.docs,\n          notebooks: M.notebooks.map(n => ({ name: n.name, uuid: n.uuid, sources: n.sources, primary: n.primary,\n            bd: n.primary ? 'var(--app-accent)' : 'var(--app-rule)',\n            bg: n.primary ? 'rgb(var(--circuit-glow, 0 243 255) / 0.07)' : 'var(--app-elevated)',\n            open: () => this.setState({ confirm: { title: 'Open “' + n.name + '” in NotebookLM', verb: 'OPEN' } }) })),\n          bookEl: this.ic('content', 14), fileEl: this.ic('content', 12), upEl: this.ic('arrow-up-right', 12),\n          trigger: () => this.setState({ confirm: { title: 'Trigger sync-now.sh via the Cloud Function — audit-logged', verb: 'SYNC' } })\n        };\n      }\n\n      // ── KB tree ──\n      if (ua.isKb) {\n        const closed = this.state.kbShut || {};\n        ua.kb = {\n          groups: this.KBT.map(g => ({ label: g.label, count: g.count, open: !closed[g.label],\n            iconEl: this.ic(g.icon, 14), caret: closed[g.label] ? '›' : '⌄',\n            docs: g.docs.map(d2 => ({ f: d2.f, tag: d2.tag.toUpperCase() })),\n            toggle: () => this.setState(s9 => ({ kbShut: Object.assign({}, s9.kbShut, { [g.label]: !(s9.kbShut || {})[g.label] }) })) })),\n          fileEl: this.ic('content', 12)\n        };\n      }\n\n      // ── Query audit ──\n      if (ua.isAudit) {\n        ua.audit = this.IAUD.map(e => ({ ts: e.ts, actor: e.actor, verb: e.verb, target: e.target, meta: e.meta,\n          dot: e.tone === 'warn' ? 'var(--app-warn)' : 'var(--app-success)' }));\n      }\n\n      // ── Closeout board ──\n      if (ua.isCloseout) {\n        const C = this.COB;\n        const tab = this.state.cobTab || 'themes';\n        const dec = this.state.cobDec || {};\n        const SC = { 'needs-brief': ['rgb(var(--pastel-gold, 248 217 116) / 0.18)', 'rgb(var(--pastel-gold))'],\n          inventory: ['rgb(var(--circuit-glow, 0 243 255) / 0.14)', 'var(--app-accent)'],\n          compliance: ['rgb(var(--accent-red, 244 63 108) / 0.16)', 'rgb(var(--accent-red))'],\n          extend: ['rgb(var(--accent-teal, 0 212 184) / 0.16)', 'rgb(var(--accent-teal))'],\n          cleanup: ['rgba(128,128,128,0.16)', 'var(--app-ink-soft)'] };\n        const dCol = { accept: 'rgb(var(--accent-teal))', modify: 'rgb(var(--pastel-gold))', reject: 'rgb(var(--accent-red))' };\n        const set = (id, v) => () => this.setState(s10 => {\n          const nx = Object.assign({}, s10.cobDec);\n          if (nx['T' + id] === v) { delete nx['T' + id]; } else { nx['T' + id] = v; }\n          return { cobDec: nx };\n        });\n        const done = Object.keys(dec).length;\n        ua.cob = {\n          isThemes: tab === 'themes', isSurfaces: tab === 'surfaces', isPhases: tab === 'phases',\n          ratified: 'RATIFIED · ' + done + ' / 17',\n          accepted: String(Object.keys(dec).filter(x => dec[x] === 'accept').length),\n          modified: String(Object.keys(dec).filter(x => dec[x] === 'modify').length),\n          rejected: String(Object.keys(dec).filter(x => dec[x] === 'reject').length),\n          tabs: [\n            { label: 'Themes', count: '17', on: tab === 'themes', pick: () => this.setState({ cobTab: 'themes' }) },\n            { label: 'Surface sweep', count: '21', on: tab === 'surfaces', pick: () => this.setState({ cobTab: 'surfaces' }) },\n            { label: 'P1 / P2 / P3', count: '3', on: tab === 'phases', pick: () => this.setState({ cobTab: 'phases' }) }\n          ].map(t3 => Object.assign(t3, {\n            bg: t3.on ? 'var(--app-accent)' : 'var(--app-elevated)',\n            ink: t3.on ? 'rgb(var(--basalt-deep))' : 'var(--app-ink-soft)',\n            bd: t3.on ? 'var(--app-accent)' : 'var(--app-rule)' })),\n          themes: C.themes.map(t4 => {\n            const d3 = dec['T' + t4.id] || null;\n            return { id: 'T' + t4.id, title: t4.title, q: t4.q, rec: t4.rec, touches: 'touches: ' + t4.touches,\n              edge: d3 ? dCol[d3] : 'var(--app-rule)',\n              btns: [['accept', 'Accept'], ['modify', 'Modify'], ['reject', 'Push back']].map(p2 => ({\n                label: p2[1], on: d3 === p2[0],\n                bg: d3 === p2[0] ? dCol[p2[0]] : 'var(--app-elevated)',\n                ink: d3 === p2[0] ? 'rgb(var(--basalt-deep))' : 'var(--app-ink-soft)',\n                bd: d3 === p2[0] ? dCol[p2[0]] : 'var(--app-rule)',\n                pick: set(t4.id, p2[0]) })) };\n          }),\n          surfaces: C.surfaces.map(s2 => ({ id: '#' + s2.id, title: s2.title, route: s2.route, body: s2.body,\n            status: s2.status.replace('-', ' ').toUpperCase(),\n            sBg: (SC[s2.status] || SC.cleanup)[0], sInk: (SC[s2.status] || SC.cleanup)[1] })),\n          phases: C.phases.map((p3, i) => ({ id: p3.id, title: p3.title, window: p3.window,\n            top: i === 0 ? 'rgb(var(--accent-teal))' : i === 1 ? 'rgb(var(--pastel-gold))' : 'rgb(var(--pastel-lavender))',\n            items: p3.items.map(x2 => ({ t: x2 })) })),\n          checkEl: this.ic('check', 11), penEl: this.ic('palette', 11), xEl: this.ic('x', 11)\n        };\n      }\n    }\n\n    // ── Work · agent swimlanes + selected-run detail (native; the sl- spec) ──\n    let wk = null;\n    if (kind === 'wk') {\n      const L = this.WK_LANES;\n      const sel = Math.min(this.state.wkSel || 0, L.length - 1);\n      const lane = L[sel];\n      const sCol = { running: 'var(--app-accent)', awaiting: 'var(--app-warn)', blocked: 'var(--app-warn)', success: 'var(--app-success)', fail: 'var(--app-fail)' };\n      const cellCol = { ok: 'var(--app-success)', warn: 'var(--app-warn)', fail: 'var(--app-fail)', empty: 'var(--app-rule)' };\n      const stepCol = { done: 'var(--app-success)', active: 'var(--app-accent)', failed: 'var(--app-fail)', todo: 'var(--app-ink-faint)' };\n      const roleCol = { agent: 'var(--app-accent)', tool: 'var(--app-ink-soft)', system: 'var(--app-warn)' };\n      wk = {\n        isLanes: (subLabel || 'Swimlanes') === 'Swimlanes',\n        count: L.length + ' runs · ' + L.filter(l => l.status === 'running').length + ' running · ' + L.filter(l => l.status === 'fail' || l.status === 'blocked').length + ' need a human',\n        lanes: L.map((l, i) => ({\n          agent: l.agent, task: l.task, id: l.id,\n          stages: l.status.toUpperCase() + ' · ' + l.model + ' · ' + l.tier.toUpperCase(),\n          statusColor: sCol[l.status],\n          bg: i === sel ? 'var(--app-accent-dim)' : 'transparent',\n          cells: l.cells.map(c => ({ bg: cellCol[c], op: c === 'empty' ? '0.35' : '1' })),\n          cost: '€ ' + l.cost, dur: l.dur, last: l.last,\n          pick: () => this.setState({ wkSel: i })\n        })),\n        d: {\n          agent: lane.agent, id: lane.id, task: lane.task, status: lane.status.toUpperCase(),\n          statusColor: sCol[lane.status],\n          rows: [\n            { k: 'Worktree', v: lane.worktree, link: () => this.setState({ confirm: { title: 'Open worktree ' + lane.worktree + ' in a new terminal tab', verb: 'OPEN' } }), plain: false },\n            { k: 'Working tree', v: lane.dirty, link: null, plain: true },\n            { k: 'Model', v: lane.model + ' · ' + lane.tier, link: null, plain: true }, { k: 'Project', v: lane.project, link: null, plain: true },\n            { k: 'Pull request', v: lane.pr, link: () => this.setState({ confirm: { title: 'Open ' + lane.pr + ' on GitHub in a new tab', verb: 'OPEN' } }), plain: false },\n            { k: 'Started', v: lane.started + ' · ' + lane.dur, link: null, plain: true }, { k: 'Cost so far', v: '€ ' + lane.cost, link: null, plain: true }\n          ],\n          steps: lane.steps.map(s => ({ l: s.l, color: stepCol[s.s],\n            bg: s.s === 'done' ? 'rgb(var(--accent-teal) / 0.14)' : s.s === 'active' ? 'rgb(var(--lead, var(--circuit-glow)) / 0.14)' : s.s === 'failed' ? 'rgb(var(--accent-red) / 0.14)' : 'var(--app-elevated)' })),\n          msgs: lane.msgs.map(m => ({ role: m.r.toUpperCase(), text: m.t, color: roleCol[m.r],\n            mono: m.r === 'tool' ? 'var(--font-jetbrains)' : 'var(--font-inter)' })),\n          isRunning: lane.status === 'running', isAwaiting: lane.status === 'awaiting', canReplay: lane.status === 'fail'\n        },\n        steer: () => this.setState({ confirm: { title: 'Send a steer to ' + lane.agent + ' (' + lane.id + ')', verb: 'STEER', onConfirm: () => {\n          lane.msgs = lane.msgs.concat([{ r: 'system', t: 'Operator steer sent — awaiting agent acknowledgement.' }]);\n          lane.last = 'steer received · reprioritizing'; this.setState({});\n        } } }),\n        halt: () => this.setState({ confirm: { title: 'Halt ' + lane.id + ' and keep the worktree for inspection', verb: 'HALT', onConfirm: () => {\n          lane.status = 'blocked'; lane.last = 'halted by operator · worktree kept for inspection'; this.setState({});\n        } } }),\n        stop: () => this.setState({ confirm: { title: 'Stop ' + lane.id + ' now — in-progress work is kept, nothing further runs', verb: 'STOP', onConfirm: () => {\n          lane.status = 'blocked'; lane.last = 'stopped by operator'; this.setState({});\n        } } }),\n        approve: () => this.setState({ confirm: { title: 'Approve ' + lane.id + ' — ' + lane.task, verb: 'APPROVE', onConfirm: () => {\n          lane.status = 'running'; lane.last = 'approved by operator · resuming'; this.setState({});\n        } } }),\n        deny: () => this.setState({ confirm: { title: 'Deny ' + lane.id + ' and send it back to the agent', verb: 'DENY', onConfirm: () => {\n          lane.status = 'blocked'; lane.last = 'denied by operator · sent back for revision'; this.setState({});\n        } } }),\n        replay: () => this.setState({ confirm: { title: 'Replay ' + lane.id + ' from its last checkpoint', verb: 'REPLAY', onConfirm: () => {\n          lane.status = 'running'; lane.last = 'replaying from last checkpoint'; this.setState({});\n        } } }),\n        isQueue: subLabel === 'Operator queue',\n        // NIGHT-PLAN P1 §2: Operator queue now reads the SAME projection PaneInbox reads (one\n        // projection, INBOX-CONTRACT §1) instead of its own WK_QUEUE/WK_QUEUE_SRC fixtures — every\n        // row here is provably a subset of that projection. WK_QUEUE/WK_QUEUE_SRC stay in place\n        // (this stream's own SUBHERO KPI still reads them) but no longer feed this tab.\n        queueGroups: (() => {\n          const IT = window.InboxThreads; if (!IT) return [];\n          const proj = IT.projectThreads(IT.rawThreads(this.state.ctxThreads));\n          const GROUP_OF = { needs: 'Blocking now', approval: 'Needs decision', draft: 'Needs decision' };\n          const TONE_COLOR = { fail: 'var(--app-fail)', pending: 'rgb(var(--pastel-lavender))', accent: 'var(--app-accent)' };\n          const rows = proj.filter(t => GROUP_OF[t.state]);\n          return ['Blocking now', 'Needs decision'].map(g => ({\n            label: g,\n            items: rows.filter(t => GROUP_OF[t.state] === g).map(t => ({\n              id: t.id, title: t.subject, meta: t.sourceStream + ' \\u00b7 ' + t.who + ' \\u00b7 ' + t.meta,\n              typeLabel: t.category.replace('_', ' ').toUpperCase(), typeColor: TONE_COLOR[t.tone] || 'var(--app-ink-faint)',\n              approve: () => this.setState({ confirm: { title: 'Approve: ' + t.subject, verb: 'APPROVE', onConfirm: () => { if (t.route) this.goTo(t.route[0], t.route[1]); } } }),\n              deny: () => { if (t.route) this.goTo(t.route[0], t.route[1]); else this.goTo('inbox', 'Needs your input'); }\n            }))\n          })).filter(g => g.items.length).map(g => Object.assign(g, { count: String(g.items.length) }));\n        })(),\n        queueEmpty: (() => { const IT = window.InboxThreads; if (!IT) return true;\n          const proj = IT.projectThreads(IT.rawThreads(this.state.ctxThreads));\n          return !proj.some(t => t.state === 'needs' || t.state === 'approval' || t.state === 'draft'); })(),\n        queueCount: (() => { const IT = window.InboxThreads; if (!IT) return '0 open loops';\n          const proj = IT.projectThreads(IT.rawThreads(this.state.ctxThreads));\n          const n = proj.filter(t => t.state === 'needs' || t.state === 'approval' || t.state === 'draft').length;\n          return n + ' open loops \\u00b7 reading the one Inbox projection'; })()\n      };\n    }\n\n    // ── Review · native split-diff + PR panel (native; WK-2) ──\n    let rev = null;\n    if (kind === 'rev') {\n      const F = this.AW_DIFF;\n      const fi = Math.min(this.state.revFile || 0, F.length - 1);\n      const file = F[fi];\n      const tCol = { add: 'var(--app-success)', del: 'var(--app-fail)', ctx: 'var(--app-ink-soft)' };\n      const tBg = { add: 'rgb(var(--accent-teal) / 0.08)', del: 'rgb(var(--accent-red) / 0.06)', ctx: 'transparent' };\n      rev = {\n        isDiff: (subLabel || 'Diff') === 'Diff', isPr: subLabel === 'PR', isDeliverable: subLabel === 'Deliverable',\n        // Verbatim from pane-sources/A-Review.dc.html (the source's d1 deliverable). Modeled: no\n        // deliverable endpoint exists yet (dataContract planned). Approve / Request changes go\n        // through the same confirm path as PR — one approval path, not two (B9).\n        deliverable: {\n          title: 'Mid-market AI-agent competitive scan', run: 'run #a7f2', modeled: true,\n          meta: 'Prepared for the Q3 planning cycle · confidence high · 14 sources cited',\n          sections: [\n            { label: 'Executive summary', lines: [\n              'The mid-market AI-agent category consolidated around four incumbents; net-new entrants fell 38% QoQ.',\n              'Vellum is the fastest-growing challenger at an estimated $4.2M ARR, up from a standing start last cycle.',\n              'Pricing pressure is concentrated in per-seat plans; usage-based tiers held margin.'] },\n            { label: 'Competitive landscape', lines: [\n              'Recommendation: hold per-seat list price, lead with a usage-based tier for teams under 20 seats, and counter Vellum on context-depth rather than price.'] },\n            { label: 'Recommendations', lines: [\n              'Ship the usage-based tier before the September pricing review.',\n              'Publish a context-depth benchmark against the top two challengers.',\n              'Reprice the enterprise floor to protect the mid-market from downmarket incursion.'] }\n          ],\n          sources: 'Sources: G2 category export · Crunchbase · 3 analyst calls',\n          approve: () => this.setState({ confirm: { title: 'Approve deliverable · run #a7f2', verb: 'APPROVE' } }),\n          request: () => this.setState({ confirm: { title: 'Request changes on deliverable · run #a7f2', verb: 'REQUEST' } })\n        },\n        files: F.map((f, i) => ({ file: f.file, add: f.add, del: f.del,\n          bg: i === fi ? 'var(--app-accent-dim)' : 'transparent',\n          pick: () => this.setState({ revFile: i }) })),\n        file: { name: file.file, note: file.note, add: file.add, del: file.del,\n          lines: file.lines.map(l => ({\n            oldNo: l.o != null ? String(l.o) : '', newNo: l.n != null ? String(l.n) : '',\n            marker: l.t === 'add' ? '+' : l.t === 'del' ? '−' : ' ',\n            text: l.s, ink: tCol[l.t], bg: tBg[l.t]\n          })) },\n        pr: {\n          title: 'feat(engine): extract context-loop driver', number: 'PR #402', branch: 'canvas#372',\n          state: 'DRAFT', stateColor: 'var(--app-warn)',\n          meta: '5 commits · 2 reviewers requested · draft until the last check goes green',\n          checks: [\n            { label: 'unit · engine', color: 'var(--app-success)', state: 'pass' },\n            { label: 'unit · shared', color: 'var(--app-success)', state: 'pass' },\n            { label: 'lint · oxlint', color: 'var(--app-success)', state: 'pass' },\n            { label: 'types · tsc', color: 'var(--app-success)', state: 'pass' },\n            { label: 'e2e · playwright', color: 'var(--app-running)', state: 'running · 14/18' },\n            { label: 'cost · per-run budget', color: 'var(--app-success)', state: 'pass' }\n          ],\n          gates: [\n            { label: 'Coverage gate', color: 'var(--app-success)', verdict: 'approve · 82% → 84%' },\n            { label: 'Public API gate', color: 'var(--app-success)', verdict: 'approve with note' },\n            { label: 'Migration gate', color: 'var(--app-warn)', verdict: 'pending human' }\n          ],\n          reviewer: 'tobias', reviewerNote: 'requested 12m ago · no comments yet',\n          merge: 'squash · deletes worktree on merge',\n          openGithub: () => this.setState({ confirm: { title: 'Open PR #402 on GitHub in a new tab', verb: 'OPEN' } }),\n          approve: () => this.setState({ confirm: { title: 'Approve and merge PR #402', verb: 'MERGE' } }),\n          request: () => this.setState({ confirm: { title: 'Request changes on PR #402', verb: 'REQUEST' } })\n        }\n      };\n    }\n\n    // ── Services · toggle grid + feature rows + audit log (native; the sg-/svc- spec) ──\n    let svc = null;\n    if (kind === 'svc') {\n      const S = this.state.svcOn || {};\n      const isOn = (x, base) => (S[x] === undefined ? base : S[x]);\n      const pb = { free: { label: 'FREE', c: 'var(--app-success)', bg: 'rgb(var(--accent-teal) / 0.14)' },\n        freemium: { label: 'FREEMIUM', c: 'var(--app-warn)', bg: 'rgb(var(--pastel-gold) / 0.18)' },\n        builder_only: { label: 'BUILDER', c: 'rgb(var(--pastel-lavender))', bg: 'rgb(var(--pastel-lavender) / 0.18)' } };\n      const enabled = this.SVC_CATALOG.filter(s => isOn(s.id, s.on)).length;\n      const toggle = (id, base, always) => () => { if (always) return;\n        this.setState(st7 => ({ svcOn: Object.assign({}, st7.svcOn, { [id]: !((st7.svcOn || {})[id] === undefined ? base : (st7.svcOn || {})[id]) }) })); };\n      svc = {\n        isGrid: (subLabel || 'Services') === 'Services', isAudit: subLabel === 'Audit',\n        summary: enabled + ' of ' + this.SVC_CATALOG.length + ' services enabled',\n        enableAll: () => this.setState({ confirm: { title: 'Enable the ' + this.SVC_CATALOG.filter(x => x.on).length + ' recommended services for this workspace', verb: 'ENABLE' } }),\n        resetAll: () => this.setState({ svcOn: {} }),\n        cats: this.SVC_CATS.map(c => {\n          const items = this.SVC_CATALOG.filter(s => s.cat === c.id);\n          return { label: c.label, tag: c.tag, count: items.length + ' services',\n            items: items.map(s => {\n              const on = isOn(s.id, s.on), p = pb[s.tier];\n              return { name: s.name, domain: s.domain, desc: s.desc,\n                iconEl: this.ic(s.icon, 16),\n                shadow: on ? '0 0 0 1px var(--app-accent)' : 'none',\n                pbLabel: p.label, pbInk: p.c, pbBg: p.bg,\n                limit: s.limit || '', showLimit: s.limit ? 'inline' : 'none',\n                pinned: s.alwaysOn ? 'inline-flex' : 'none',\n                trackBg: on ? 'var(--app-accent)' : 'var(--app-elevated)',\n                trackBd: on ? 'var(--app-accent)' : 'var(--app-rule)',\n                knobLeft: on ? '20px' : '4px',\n                knobBg: on ? 'rgb(var(--basalt-deep))' : 'var(--app-ink)',\n                op: s.alwaysOn ? '0.5' : '1', cursor: s.alwaysOn ? 'not-allowed' : 'pointer',\n                toggle: toggle(s.id, s.on, s.alwaysOn),\n                showFeatures: (on && s.features.length) ? true : false,\n                features: s.features.map(f => {\n                  const fid = s.id + '.' + f.id, fon = isOn(fid, f.on), fp = f.tier ? pb[f.tier] : null;\n                  return { label: f.label,\n                    limit: f.limit || '', showLimit: f.limit ? 'inline' : 'none',\n                    pbLabel: fp ? fp.label : '', pbInk: fp ? fp.c : 'transparent', pbBg: fp ? fp.bg : 'transparent',\n                    showPb: fp ? 'inline-flex' : 'none',\n                    trackBg: fon ? 'var(--app-accent)' : 'var(--app-elevated)',\n                    trackBd: fon ? 'var(--app-accent)' : 'var(--app-rule)',\n                    knobLeft: fon ? '20px' : '4px',\n                    knobBg: fon ? 'rgb(var(--basalt-deep))' : 'var(--app-ink)',\n                    toggle: toggle(fid, f.on, false) };\n                }) };\n            }) };\n        }),\n        audit: this.SVC_AUDIT.map(a => ({ ts: a.ts, actor: a.actor, action: a.action.replace(/_/g, ' '), target: a.target,\n          color: { ok: 'var(--app-accent)', warn: 'var(--app-warn)', fail: 'var(--app-fail)' }[a.tone] }))\n      };\n    }\n\n    // ── Finance · EÜR grid · credits · invoice detail (native; the fin- spec) ──\n    let fin = null;\n    if (kind === 'fin') {\n      const E = this.FIN_EUR, G = this.FIN_GUTSCHRIFTEN, I = this.FIN_INVOICE;\n      const num = (s) => s === '—' ? 0 : parseFloat(s.replace(/\\./g, '').replace(',', '.'));\n      const maxRev = Math.max.apply(null, E.rows.map(r => num(r.rev)));\n      const deFmt = (n) => n.toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n      const revSum = E.rows.reduce((a, r) => a + num(r.rev), 0);\n      const expSum = E.rows.reduce((a, r) => a + num(r.exp), 0);\n      fin = {\n        isEur: subLabel === 'EÜR report', isGutschriften: subLabel === 'Gutschriften', isInvoice: subLabel === 'Invoice detail',\n        year: E.year,\n        eurRows: E.rows.map(r => ({ m: r.m, rev: r.rev, exp: r.exp, pro: r.pro,\n          pct: (maxRev ? Math.round(num(r.rev) / maxRev * 100) : 0) + '%',\n          ink: r.rev === '—' ? 'var(--app-ink-faint)' : 'var(--app-ink)' })),\n        eurTotals: { rev: deFmt(revSum), exp: deFmt(expSum), pro: deFmt(revSum - expSum) },\n        gutschriften: G.map(g => ({ number: g.number, client: g.client, issued: g.issued, amt: g.amt, reason: g.reason, ref: g.ref })),\n        gutschriftenTotal: deFmt(G.reduce((a, g) => a + num(g.amt), 0)),\n        inv: { number: I.number, client: I.client, issued: I.issued, due: I.due, delivered: I.delivered,\n          state: I.state, net: I.net, vat: I.vat, gross: I.gross, rate: I.rate,\n          lines: I.lines, issuer: I.issuer, chips: I.chips,\n          fields: [\n            { k: 'Invoice number', v: I.number }, { k: 'Client', v: I.client },\n            { k: 'Issue date', v: I.issued }, { k: 'Due date', v: I.due },\n            { k: 'Delivery date', v: I.delivered }, { k: 'Default VAT rate', v: I.rate + ' %' }\n          ],\n          steps: [\n            { n: '1', label: 'Header', hint: 'issuer + client' },\n            { n: '2', label: 'Line items', hint: '1 line' },\n            { n: '3', label: 'Totals', hint: 'net · VAT · gross' },\n            { n: '4', label: 'Send', hint: 'email + PDF' }\n          ] },\n        topup: () => this.setState({ confirm: { title: 'Top up 2,500 credits — € 25.00 via Stripe', verb: 'TOP UP' } }),\n        pricing: () => this.setState({ confirm: { title: 'Open the public /pricing page in a new tab', verb: 'OPEN' } }),\n        exportCsv: () => this.setState({ confirm: { title: 'Export the EÜR report for ' + E.year + ' as CSV', verb: 'EXPORT' } }),\n        send: () => this.setState({ confirm: { title: 'Send invoice ' + I.number + ' to ' + I.client + ' as PDF', verb: 'SEND' } })\n      };\n    }\n\n    // ── MVP client canvas · native planes only (no page-in-a-page) ──\n    let client = null;\n    if (kind === 'client') {\n      const isBriefStream = st.id === 'cbrief';\n      const tab = subLabel || (isBriefStream ? 'Brief' : 'Overview');\n      const toneCol = { circuit: 'var(--app-accent)', teal: 'var(--app-success)', red: 'var(--app-fail)', muted: 'var(--app-ink-faint)' };\n      const sc = this.CLIENT_SCRIPT;\n      const goTabC = (label) => () => {\n        const i = (st.subs || []).findIndex(s => s.label === label);\n        if (i >= 0) this.setState({ sub: i, briefOpen: null });\n        else this.selectStream(isBriefStream ? 'cdelivery' : 'cbrief');\n      };\n\n      // ── Context topics · confirm state, hover state, deep-dive ──\n      const okMap = this.state.briefOK || {};\n      const isOK = (t) => (okMap[t.id] === undefined ? t.ok : okMap[t.id]);\n      const nOK = this.CLIENT_BRIEF.filter(isOK).length;\n      const nAll = this.CLIENT_BRIEF.length;\n      const confirmBtn = (t, big) => {\n        const ok = isOK(t), hov = this.state.hoverConf === t.id;\n        return {\n          label: ok ? (hov ? 'Unconfirm' : 'Confirmed') : 'Confirm',\n          iconEl: this.ic(ok && hov ? 'x' : 'check', big ? 16 : 14),\n          h: big ? '48px' : '38px', pad: big ? '0 22px' : '0 15px', fs: big ? '0.68rem' : '0.6rem',\n          bg: ok ? 'transparent' : 'var(--app-accent)',\n          bd: ok ? (hov ? 'var(--app-fail)' : 'var(--app-success)') : 'var(--app-accent)',\n          ink: ok ? (hov ? 'var(--app-fail)' : 'var(--app-success)') : 'rgb(var(--basalt-deep))',\n          shadow: (!ok && hov) ? '-4px 4px 0 var(--app-fail)' : 'none',\n          lift: hov ? 'translate(2px,-2px)' : 'none',\n          on: () => this.setState({ hoverConf: t.id }),\n          off: () => this.setState({ hoverConf: null }),\n          toggle: (e) => { if (e && e.stopPropagation) e.stopPropagation();\n            this.setState(s => ({ briefOK: Object.assign({}, s.briefOK, { [t.id]: !isOK(t) }) })); }\n        };\n      };\n      const openId = this.state.briefOpen;\n      const openT = openId ? this.CLIENT_BRIEF.find(t => t.id === openId) : null;\n\n      // ── Storyboard · style carousel ──\n      const L = this.SB_STYLES.length;\n      const sbIdx = ((this.state.sbIdx || 0) % L + L) % L;\n      const sbs = this.SB_STYLES[sbIdx];\n      const pickId = this.state.sbPick;\n      const pickS = pickId ? this.SB_STYLES.find(s => s.id === pickId) : null;\n      const shotS = pickS || sbs;\n\n      // D204 (NIGHT-PLAN P3a): the `client` template's six nodes as link cards — each node IS a rail stream, so the\n      // card opens the stream (D194 card-link standard). Node meta is derived, never typed: brief = confirmed\n      // topics, support = open messages, the rest = the stream's own tag. Verdict = validateTemplate, or \"not\n      // checked\" until the validator loads (WP-54 — never a default pass).\n      const cTpl = window.ProcessSpec && window.ProcessSpec.templateById ? window.ProcessSpec.templateById('client') : null;\n      const cV = this._planeValidator && cTpl ? this._planeValidator.validateTemplate(cTpl) : null;\n      const supOpen = ((this.DEV_PANES.csupport && this.DEV_PANES.csupport.Messages && this.DEV_PANES.csupport.Messages.items) || []).filter(m => m.chip === 'open').length;\n      const cMeta = { cbrief: nOK + ' of ' + nAll + ' confirmed', csupport: supOpen + ' open', cdelivery: 'Offer open' };\n      const cProcess = cTpl ? cTpl.nodes.map((n, i) => {\n        const s = this.STREAMS[n.streamId] || {};\n        const cur = st.id === n.streamId;\n        return { n: String(i + 1).padStart(2, '0'), title: s.label || n.label, stage: n.stage, arche: n.archetype + (n.gate ? ' · human gate' : ''),\n          meta: cMeta[n.streamId] || s.tag || '', cur,\n          edge: cur ? 'var(--app-accent)' : 'var(--app-rule)', ink: cur ? 'var(--app-accent)' : 'var(--app-ink-faint)',\n          go: () => this.selectStream(n.streamId) };\n      }) : [];\n      const cVerdict = !cTpl ? 'template not loaded' : !cV ? 'not checked — validator loading' : cV.ok ? 'chain valid · ' + cTpl.nodes.length + ' nodes · 1 human gate' : (cV.errors[0] && cV.errors[0].message) || 'invalid';\n\n      client = {\n        process: cProcess, hasProcess: cProcess.length > 0, processVerdict: cVerdict,\n        processVerdictColor: cV && cV.ok ? 'var(--app-success)' : (cV ? 'var(--app-fail)' : 'var(--app-ink-faint)'),\n        isOverview: tab === 'Overview',\n        isScriptView: tab === 'Overview' || tab === 'Script',\n        isRevisions: tab === 'Script',\n        isBrief: (tab === 'Context' || tab === 'Brief') && !openT,\n        isDetail: (tab === 'Context' || tab === 'Brief') && !!openT,\n        isSources: tab === 'Sources',\n        isStory: tab === 'Storyboard', isSnippet: tab === 'Snippet',\n        isReview: tab === 'Review', isDelivery: tab === 'Delivery',\n        statusLabel: 'Offer open', statusColor: 'var(--app-accent)',\n        spec: sc.duration + ' · ' + sc.words + ' words · ' + sc.voice,\n        steps: this.CLIENT_STEPS.map(s => ({\n          n: s.n, title: s.title, body: s.body, state: s.state,\n          iconEl: this.ic(s.icon, 21), tone: toneCol[s.tone],\n          ring: '1px solid ' + (s.tone === 'muted' ? 'var(--app-rule)' : toneCol[s.tone]),\n          glow: s.tone === 'muted' ? 'none' : '0 0 24px ' + toneCol[s.tone] + '33',\n          go: goTabC(s.tab)\n        })),\n\n        // Context · grid\n        briefCount: nOK + ' of ' + nAll + ' confirmed',\n        briefNeed: (nAll - nOK) + ' need you',\n        briefNeedColor: nAll - nOK ? 'var(--app-fail)' : 'var(--app-success)',\n        topics: this.CLIENT_BRIEF.map(t => {\n          const ok = isOK(t), b = confirmBtn(t);\n          return { id: t.id, grp: t.grp, title: t.title, line: t.line, src: t.src,\n            dot: ok ? 'var(--app-success)' : 'var(--app-fail)',\n            edge: ok ? 'var(--app-rule)' : 'var(--app-fail)',\n            open: () => this.setState({ briefOpen: t.id }), btn: b };\n        }),\n\n        // Context · deep dive\n        d: openT ? {\n          grp: openT.grp, title: openT.title, line: openT.line, src: openT.src, why: openT.why, used: openT.used,\n          state: isOK(openT) ? 'Confirmed' : 'Needs you',\n          stateColor: isOK(openT) ? 'var(--app-success)' : 'var(--app-fail)',\n          fields: openT.fields.map((f, i) => ({ k: f.k, v: f.v, rows: Math.max(2, Math.ceil(f.v.length / 58)), id: openT.id + i })),\n          back: () => this.setState({ briefOpen: null }),\n          backEl: this.ic('arrowRight', 14),\n          ask: () => { if (!mvp && !clientChrome) { this.setState({ rosterOpen: true, askOn: true, askDraft: 'Correct ' + openT.title.toLowerCase() + ': ' }); return; }\n            this.setState({ chat: 'open', chatDraft: '', chatTurns: 3,\n            chatMsgs: [{ from: 'agent', text: 'What would you like to correct about ' + openT.title.toLowerCase() + ', John?' }] });\n            setTimeout(() => this._chatIn && this._chatIn.focus(), 60); },\n          btn: confirmBtn(openT, true)\n        } : null,\n\n        sources: [\n          { t: 'client-site.example — home', m: 'hero, claim, primary CTA', chip: 'read', c: 'var(--app-success)' },\n          { t: 'client-site.example/pentest', m: 'scope, 24h promise, deliverables', chip: 'read', c: 'var(--app-success)' },\n          { t: 'client-site.example/attack-surface', m: 'free 60-second scan flow', chip: 'read', c: 'var(--app-success)' },\n          { t: 'client-site.example/pricing', m: 'tiers and what a report contains', chip: 'read', c: 'var(--app-success)' },\n          { t: 'client-site.example/compliance', m: 'NIS2 and ISO language', chip: 'read', c: 'var(--app-success)' },\n          { t: 'Brand assets', m: 'logo, type, two brand colours', chip: 'used', c: 'var(--app-accent)' },\n          { t: 'Press mentions', m: 'left out — nothing verifiable in the last 12 months', chip: 'skipped', c: 'var(--app-ink-faint)' }\n        ],\n\n        // Storyboard\n        sb: {\n          idx: String(sbIdx + 1), total: String(L), name: sbs.name, spec: sbs.spec, body: sbs.body,\n          bg: sbs.bg, deco: sbs.deco, decoOp: sbs.decoOp, justify: sbs.justify,\n          title: sbs.title, titleFont: sbs.titleFont, titleSize: sbs.titleSize, titleWeight: sbs.titleWeight,\n          titleTrack: sbs.titleTrack, titleCase: sbs.titleCase, titleColor: sbs.titleColor, titleMax: sbs.titleMax,\n          sub: sbs.sub,\n          prevEl: this.ic('chevronLeft', 18), nextEl: this.ic('chevronRight', 18),\n          prev: () => this.setState(s => ({ sbIdx: (s.sbIdx || 0) - 1 })),\n          next: () => this.setState(s => ({ sbIdx: (s.sbIdx || 0) + 1 })),\n          picked: pickId === sbs.id,\n          selLabel: pickId === sbs.id ? 'Selected' : 'Select this style',\n          selBg: pickId === sbs.id ? 'transparent' : 'var(--app-accent)',\n          selBd: pickId === sbs.id ? 'var(--app-success)' : 'var(--app-accent)',\n          selInk: pickId === sbs.id ? 'var(--app-success)' : 'rgb(var(--basalt-deep))',\n          selEl: this.ic('check', 15),\n          select: () => this.setState({ sbPick: sbs.id }),\n          dots: this.SB_STYLES.map((s, i) => ({\n            name: s.name, active: i === sbIdx,\n            bd: i === sbIdx ? 'var(--app-accent)' : 'var(--app-rule)',\n            ink: i === sbIdx ? 'var(--app-accent)' : 'var(--app-ink-faint)',\n            mark: s.id === pickId ? 'inline-flex' : 'none',\n            go: () => this.setState({ sbIdx: i })\n          })),\n          pickedName: pickS ? pickS.name : 'none yet',\n          hasPick: !!pickS\n        },\n        shots: this.SB_SHOTS.map(s => ({ n: s.n, t: s.t, title: s.title, dir: s.dir, vo: s.vo })),\n        shotStyle: shotS.name,\n\n        // Snippet\n        snip: {\n          styleName: shotS.name, bg: shotS.bg, deco: shotS.deco, justify: shotS.justify,\n          title: shotS.title, titleFont: shotS.titleFont, titleSize: shotS.titleSize, titleWeight: shotS.titleWeight,\n          titleTrack: shotS.titleTrack, titleCase: shotS.titleCase, titleColor: shotS.titleColor, titleMax: shotS.titleMax,\n          playEl: this.ic('play', 20),\n          play: () => this.setState({ confirm: { title: 'Play the 5-second preview · shot 01, ' + shotS.name.toLowerCase(), verb: 'PLAY' } }),\n          buy: () => this.setState({ confirm: { title: 'Produce the full 45-second film in ' + shotS.name.toLowerCase() + ' — €49', verb: 'PRODUCE' } }),\n          rows: [\n            { k: 'Preview length', v: '5 seconds · shot 01' },\n            { k: 'Full film', v: '45 seconds + 30s and 15s cutdowns' },\n            { k: 'Style', v: shotS.name },\n            { k: 'Price', v: '€49' },\n            { k: 'Turnaround', v: '24 hours from approval' }\n          ]\n        },\n\n        // Review\n        checks: this.REVIEW_CHECKS.map(c => ({ title: c.title, meta: c.meta, chip: c.chip, color: toneCol[c.tone] })),\n\n        // Delivery\n        files: this.DELIVERY_FILES.map(f => ({ name: f.name, spec: f.spec, size: f.size, state: f.state,\n          color: f.state === 'preview' ? 'var(--app-accent)' : 'var(--app-ink-faint)' })),\n        lines: sc.lines.map(l => ({ t: l.t, text: l.text,\n          blur: l.locked ? 'blur(4.5px)' : 'none',\n          select: l.locked ? 'none' : 'auto',\n          color: l.locked ? 'var(--app-ink-faint)' : 'var(--app-ink)' })),\n        duration: sc.duration, words: sc.words, voice: sc.voice,\n        bars: [10, 18, 26, 14, 30, 22, 34, 18, 26, 12, 30, 20, 36, 24, 14, 28, 18, 32, 22, 12, 26, 16, 30, 20, 34, 18, 24, 12, 28, 16].map((h, i) => ({ h: h + 'px', op: i < 9 ? '1' : '0.35' })),\n        playIconEl: this.ic('play', 18),\n        rows: [\n          { k: 'Script', v: '€149' }, { k: 'Storyboard', v: 'Included' },\n          { k: 'Snippet', v: 'Free · 5 seconds' }, { k: 'Full film', v: '€49 after the snippet' },\n          { k: 'Licence', v: 'Perpetual, commercial' }, { k: 'Revisions', v: '2 rounds included' },\n          { k: 'Offer expires', v: '08 Aug 2026' }\n        ],\n        notes: [\n          { when: '2 h ago', who: 'You', text: 'Line 3 says “zero noise” twice — use “verified” once instead.', color: 'var(--app-fail)', chip: 'open' },\n          { when: '3 h ago', who: 'script-agt', text: 'Rewritten and re-read. New take lands in 40 m.', color: 'var(--app-accent)', chip: 'working' },\n          { when: '5 h ago', who: 'You', text: 'The read is a touch fast under the KASTEL line.', color: 'var(--app-success)', chip: 'done' },\n          { when: '5 h ago', who: 'script-agt', text: 'Slowed to 138 wpm and held the pause before the CTA.', color: 'var(--app-success)', chip: 'done' }\n        ],\n        revisionsLeft: '2 of 2 left',\n        play: () => this.setState({ confirm: { title: 'Play the read — preview stops after line 2', verb: 'PLAY' } }),\n        pay: () => this.setState({ confirm: { title: 'Approve this script and start the storyboard — €149', verb: 'PRODUCE' } }),\n        goStory: goTabC('Storyboard'), goContext: goTabC('Context'),\n        ask: () => this.setState({ confirm: { title: 'Send this revision request to the agent', verb: 'SEND' } })\n      };\n    }\n\n    // ── Floating agent chat (repo: overlay/vendor-app/agent-support.jsx) ──\n    const chatSeed = mvp\n      ? [{ from: 'agent', text: 'Hi — I wrote your script. Ask me anything about it, the offer, or what happens after you pay.' }]\n      : [{ from: 'agent', text: \"Hi — I'm a demo agent. Ask me anything about the streams, runs or the vault.\" }];\n    // P4 · the plane the operator is actually on, read from canvas.index.json. This is what makes the\n    // agent grounded rather than conversational: purpose/stage/verdict/reads/writes are the index's own\n    // fields, so an answer cannot drift from the ledger. null = no index row → say so, never improvise.\n    const brief = this.planeBrief(this.state.active, subLabel);\n    const briefLines = () => {\n      if (!brief) return null;\n      const L = [];\n      L.push(brief.purpose + ' — stage ' + brief.stage + ' of 7, ' + brief.resolves + ', verdict ' + brief.verdict + '.');\n      if (brief.need) L.push('The need it serves: ' + brief.need.need + ' (' + brief.need.status + ').');\n      if (brief.reads.length) L.push('Reads: ' + brief.reads.map(r => r.id + (r.tenant === 'present' ? ' ✓ in this tenant' : r.tenant === 'missing' ? ' — no value for this tenant yet' : ' (stub endpoint)')).join(' · ') + '.');\n      if (brief.readsScopes.length) L.push('Reads whole context scopes: ' + brief.readsScopes.join(', ') + '.');\n      L.push(brief.writes.length ? 'Writes: ' + brief.writes.join(' · ') + '.'\n        : (brief.resolves === 'lens' ? 'This tab is read-only in the canvas — nothing here writes.' : 'No write action is declared for this tab.'));\n      if (brief.buildItem) L.push('Open build item: ' + brief.buildItem + '.');\n      if (brief.sourceFile) L.push('Built from ' + brief.sourceFile + '.');\n      return L.join(' ');\n    };\n    const chatSeedGrounded = brief\n      ? [{ from: 'agent', text: 'You are on ' + st.label + ' › ' + subLabel + '. ' + briefLines() }]\n      : chatSeed;\n    const msgs = this.state.chatMsgs || chatSeedGrounded;\n    const turns = this.state.chatTurns;\n    const sendChat = () => {\n      const d = (this.state.chatDraft || '').trim();\n      if (!d || turns <= 0) return;\n      const map = mvp ? this.CHAT_REPLIES : this.CHAT_REPLIES_OPS;\n      const hit = map.find(r => r.m.test(d));\n      // P4 · before falling back to \"I don't know\", answer from the index. Three behaviours, all\n      // index-driven: what is this plane · what can I do here (writes[]) · what is missing (gaps).\n      let grounded = null;\n      if (!hit && !mvp && brief) {\n        const q = d.toLowerCase();\n        if (/gap|missing|not built|gaps|gebaut|fehlt/.test(q)) {\n          const g = this.streamGaps(this.state.active);\n          grounded = !g ? null : (g.total\n            ? g.total + ' open on this stream: ' + g.needs.map(n => n.need + ' (' + n.status + ')').concat(g.planes.map(p => p.tab + ' — ' + p.verdict)).join(' · ') + '. ⌘K → \"Gaps for this stream\" prints it as a build brief.'\n            : 'Nothing open on this stream — every need here resolves to an integrated or accepted-as-lens plane. ⌘K → \"Gaps for this stream\" shows the evidence.');\n        } else if (/can i|do here|action|write|what.*happen|change|edit/.test(q)) {\n          grounded = brief.writes.length\n            ? 'On this tab you can: ' + brief.writes.join(' · ') + '. Each one goes through hold-to-confirm.'\n            : (brief.resolves === 'lens'\n              ? 'Nothing here writes — this tab is read-only in the canvas' + (brief.buildItem ? '; its write surface is build item ' + brief.buildItem : '') + '.'\n              : 'No write action is declared for this tab in the index, so treat it as read-only.');\n        } else if (/what is this|where am i|this tab|this plane|purpose|why/.test(q)) {\n          grounded = briefLines();\n        } else if (/read|data|source|context|feed/.test(q)) {\n          grounded = brief.reads.length || brief.readsScopes.length\n            ? 'It reads ' + brief.reads.map(r => r.id + (r.tenant === 'present' ? ' (value present)' : r.tenant === 'missing' ? ' (no tenant value yet)' : ' (stub)')).concat(brief.readsScopes.map(sc => 'the whole ' + sc + ' context scope')).join(', ') + '.'\n            : 'The index declares no data source for this tab — it renders from shell fixtures.';\n        }\n      }\n      const reply = grounded || (hit ? hit.o : (mvp\n        ? 'Good question — that one needs a human. Drop it in Support and you get an answer inside a working day.'\n        : (brief ? \"I only hold this canvas's own record — ask what this plane is, what you can do here, what it reads, or what is missing.\"\n                 : \"The demo agent does not know that one — try runs, costs, outreach or the vault.\")));\n      this.setState({ chatMsgs: msgs.concat([{ from: 'user', text: d }]), chatDraft: '', chatTurns: turns - 1 });\n      setTimeout(() => this.setState(s => ({ chatMsgs: (s.chatMsgs || []).concat([{ from: 'agent', text: reply }]) })), 650);\n    };\n    // D176b: outside the client chrome the roster's Ask is the one conversation. The floating chat stays only\n    // for MVP / client streams; elsewhere its tab is the \"roster closed\" fallback and opens the roster Ask.\n    const askDocked = !mvp && !clientChrome && rightPanel && rosterEff && this.state.askOn !== false;\n    const floatOwn = mvp || clientChrome;\n    const chat = {\n      isTab: floatOwn ? this.state.chat === 'tab' : (mob && !(rightPanel && rosterEff)), isOpen: floatOwn && this.state.chat === 'open',\n      title: mvp ? 'YOUR SCRIPT AGENT' : (brief ? 'GROUNDED IN THIS CANVAS' : 'TRY A DEMO AGENT'),\n      meta: mvp ? 'answers in seconds' : (brief ? 'reads canvas.index.json' : 'rate-limited'),\n      turnsLabel: turns + '/3',\n      draft: this.state.chatDraft,\n      spent: turns <= 0,\n      showSuggests: msgs.length === 1,\n      suggests: (mvp\n        ? ['Can I get a 30-second cut?', 'Who owns the script?', 'What happens after I pay?']\n        : (brief ? ['What can I do on this tab?', 'What does it read?', \"What's missing on this stream?\"]\n                 : ['What is the Vault?', 'Where do runs live?', 'Show me Outreach'])).map(s => ({ label: s, onClick: () => this.setState({ chatDraft: s }) })),\n      msgs: msgs.map(m => ({ text: m.text, isAgent: m.from === 'agent',\n        bg: m.from === 'agent' ? 'rgb(var(--lead) / 0.08)' : 'rgb(var(--text) / 0.06)',\n        border: m.from === 'agent' ? '1px solid rgb(var(--lead) / 0.22)' : '1px solid var(--border)',\n        align: m.from === 'agent' ? 'flex-start' : 'flex-end',\n        color: m.from === 'agent' ? 'rgb(var(--text))' : 'rgb(var(--text) / 0.85)' })),\n      open: () => (floatOwn ? this.setState({ chat: 'open' }) : this.setState({ rosterOpen: true, askOn: true, chat: 'tab' })),\n      close: () => this.setState({ chat: 'tab' }),\n      onDraft: (e) => this.setState({ chatDraft: e.target.value }),\n      onKey: (e) => { if (e.key === 'Enter') sendChat(); },\n      send: sendChat,\n      inRef: (el) => { this._chatIn = el; },\n      sparkEl: this.ic('spark', 16), sendIconEl: this.ic('send', 14), minIconEl: this.ic('chevronRight', 14)\n    };\n\n    // ── Intro tour overlay ──\n    const ts = this.state.tour;\n    const closeTour = () => { try { localStorage.setItem('canvas.introSeen', '1'); } catch (e) {} this.setState({ tour: -1 }); };\n    const openTour = () => this.setState({ tour: 0, settings: false });\n    let tour = null;\n    if (ts >= 0 && !mvp) {\n      const s = this.TOUR_STEPS[Math.min(ts, this.TOUR_STEPS.length - 1)];\n      const last = ts === this.TOUR_STEPS.length - 1;\n      tour = {\n        eyebrow: s.eyebrow, title: s.title, body: s.body, glyphEl: this.ic(s.glyph, 26),\n        isPipeline: s.kind === 'pipeline', isPlatforms: s.kind === 'platforms',\n        isCentral: s.kind === 'central', isAgents: s.kind === 'agents', isFinal: s.kind === 'final',\n        stepLabel: 'STEP ' + (ts + 1) + ' / ' + this.TOUR_STEPS.length,\n        pipeline: this.TOUR_PIPELINE.map((p, i) => ({ label: p.label, n: '0' + (i + 1), iconEl: this.ic(p.icon, 15), arrow: i < 2 ? 'inline-flex' : 'none' })),\n        platforms: this.TOUR_PLATFORMS.map(p => ({ label: p })),\n        central: ['Analytics', 'Ads', 'Commerce', 'Product'].map((c, i) => ({ label: c, op: String(1 - i * 0.12) })),\n        agents: this.TOUR_AGENTS.map(a => ({ name: a.name, pct: a.pct })),\n        dots: this.TOUR_STEPS.map((_, i) => ({\n          bg: i === ts ? 'rgb(var(--lead))' : i < ts ? 'rgb(var(--lead) / 0.45)' : 'rgb(var(--text) / 0.15)',\n          w: i === ts ? '26px' : '8px',\n          onClick: () => this.setState({ tour: i })\n        })),\n        canBack: ts > 0, isLast: last,\n        nextLabel: last ? 'Connect an API' : 'Next',\n        arrowEl: this.ic('arrowRight', 15), boltEl: this.ic('bolt', 15),\n        back: () => this.setState(st2 => ({ tour: Math.max(0, st2.tour - 1) })),\n        next: () => { if (last) { closeTour(); this.selectStream('platform'); this.setState({ sub: 3 }); } else this.setState(st2 => ({ tour: st2.tour + 1 })); },\n        skip: closeTour\n      };\n    }\n\n    // ── Introduction · the public landing page, rendered as a plane on the Landing tab ──\n    let landing = null;\n    if (kind === 'landing') {\n      landing = {\n        eyebrow: 'VISUAL BUSINESS TOOLBOX · ONLINE',\n        l1: 'Connect any API.', l2: 'The engine combines everything.', l3: 'AI runs the operation.',\n        body: \"You're already inside the product. Point the canvas at any source through a visual front-end — a contextual-engineering machine gathers every signal, combines your data into one live context, and puts AI to work as functionally as possible.\",\n        playEl: this.ic('play', 17), linkEl: this.ic('link', 17),\n        takeTour: () => this.setState({ tour: 0 }),\n        connect: () => { this.selectStream('platform'); this.setState({ sub: 3 }); },\n        layers: [\n          { n: 'LAYER 01 · FRONT-END', title: 'Connect', iconEl: this.ic('link', 19), arrow: 'inline-flex',\n            body: 'Point the canvas at any API — no glue code. The front-end you see becomes the control surface for anything.' },\n          { n: 'LAYER 02 · ENGINE', title: 'Contextualize', iconEl: this.ic('tree', 19), arrow: 'inline-flex',\n            body: 'The contextual engine gathers every signal and combines your data across sources into one live context.' },\n          { n: 'LAYER 03 · BACK-END', title: 'Automate with AI', iconEl: this.ic('bolt', 19), arrow: 'none',\n            body: 'AI agents act on that context — content, outreach, ops — always-on, as functionally as possible.' }\n        ],\n        tiles: [\n          { label: 'Integrations', value: '18', unit: 'sources connected', meta: 'Stripe · HubSpot · Notion · +15', dot: 'var(--app-accent)', chip: null, help: true },\n          { label: 'Contextual Engine', value: '1.2M', unit: 'signals combined', meta: 'one live context · updated 3.2s ago', dot: 'var(--app-accent)', chip: null, help: true },\n          { label: 'Agent Ops', value: '42', unit: 'agents live', meta: '[agt] crm-agent · 12 skills attached', dot: 'var(--app-accent)', chip: null, help: true },\n          { label: 'Finances', value: '€184.2K', unit: 'MRR · +8.4% MoM', meta: null, dot: 'var(--app-success)', chip: null, help: false },\n          { label: 'Marketing', value: '12', unit: 'campaigns · 2 need review', meta: null, dot: 'var(--app-warn)', chip: 'warn', help: false },\n          { label: 'Quality', value: '98.7%', unit: 'compliance · 5,400 skills', meta: null, dot: 'var(--app-success)', chip: null, help: false }\n        ],\n        bars: [\n          { label: 'Layer 01 · Connect — 18 integrations live', value: '18', meta: 'live', pct: '100%', color: 'var(--app-accent)' },\n          { label: 'Layer 02 · Contextualize — engine coverage', value: '88%', meta: 'graph', pct: '88%', color: 'var(--app-accent)' },\n          { label: 'Layer 03 · Automate — agents on the account', value: '9', meta: 'online', pct: '60%', color: 'var(--app-success)' }\n        ]\n      };\n    }\n\n    // ── Twelve bespoke lenses ──\n    let lens = null;\n    if (kind === 'lens') {\n      const L = this.LENS, TINT = { cy: 'var(--app-accent)', gd: 'rgb(var(--pastel-gold))',\n        red: 'rgb(var(--accent-red))', lav: 'rgb(var(--pastel-lavender))', tl: 'rgb(var(--accent-teal))' };\n      const grad = t => 'linear-gradient(135deg,' + TINT[t] + ',color-mix(in srgb,' + TINT[t] + ' 24%,var(--app-elevated)))';\n      lens = { isEmail: lensKey === 'email', isPages: lensKey === 'pages', isDocs: lensKey === 'docs',\n        isBanners: lensKey === 'banners', isPosters: lensKey === 'posters', isDecks: lensKey === 'decks',\n        isVideos: lensKey === 'videos', isDrafts: lensKey === 'drafts', isSupport: lensKey === 'support',\n        isReplays: lensKey === 'replays', isFlow: lensKey === 'flow', isImp: lensKey === 'imp' };\n\n      if (lensKey === 'email') {\n        const pick = this.state.lsEmail || 'invoice-issued', s = L.email.sample;\n        lens.email = {\n          list: L.email.templates.map(t => ({ name: t[0], cat: t[1], opens: t[2], on: t[0] === pick,\n            bg: t[0] === pick ? 'var(--app-elevated)' : 'transparent',\n            bd: t[0] === pick ? 'var(--app-accent)' : 'transparent',\n            onClick: () => this.setState({ lsEmail: t[0] }) })),\n          from: s.from, to: s.to, subject: s.subject, preheader: s.preheader,\n          heroLabel: s.heroLabel, heroAmount: s.heroAmount, cta: s.cta, footer: s.footer,\n          body: s.body.map(p => ({ t: p })) };\n      }\n      if (lensKey === 'pages') {\n        const pick = this.state.lsPage || 'Landing', s = L.pages.sample;\n        lens.pages = {\n          list: L.pages.list.map(p => ({ name: p[0], route: p[1], meta: p[2] + ' · ' + p[3], on: p[0] === pick,\n            bg: p[0] === pick ? 'var(--app-elevated)' : 'transparent',\n            bd: p[0] === pick ? 'var(--app-accent)' : 'transparent',\n            onClick: () => this.setState({ lsPage: p[0] }) })),\n          url: s.url, eyebrow: s.heroEyebrow, sub: s.heroSub,\n          lines: s.heroTitle.map((l, i) => ({ t: l, last: i === s.heroTitle.length - 1 })),\n          ctas: s.ctas.map(c => ({ label: c[0],\n            bg: c[1] ? 'var(--app-accent)' : 'transparent',\n            ink: c[1] ? 'rgb(var(--basalt-deep))' : 'var(--app-ink)',\n            bd: c[1] ? 'var(--app-accent)' : 'var(--app-rule)' })),\n          stats: s.stats.map(x => ({ v: x[0], l: x[1] })),\n          features: s.features.map(f => ({ label: f })) };\n      }\n      if (lensKey === 'docs') {\n        const active = this.state.lsDoc || L.docs.active;\n        lens.docs = {\n          tree: L.docs.tree.map(g => ({ head: g[0], items: g[1].map(i => ({ label: i, on: i === active,\n            bg: i === active ? 'var(--app-elevated)' : 'transparent',\n            ink: i === active ? 'var(--app-accent)' : 'var(--app-ink-soft)',\n            onClick: () => this.setState({ lsDoc: i }) })) })),\n          blocks: L.docs.md.map(b => ({ isH1: b[0] === 'h1', isH2: b[0] === 'h2', isP: b[0] === 'p',\n            isCode: b[0] === 'code', isUl: b[0] === 'ul',\n            t: typeof b[1] === 'string' ? b[1] : '',\n            items: Array.isArray(b[1]) ? b[1].map(i => ({ label: i })) : [] })) };\n      }\n      if (lensKey === 'banners') {\n        lens.banners = L.banners.map(c => ({ label: c.label, eyebrow: c.eyebrow, title: c.title, sub: c.sub, cta: c.cta,\n          formats: c.formats.map((f, i) => ({ ratio: f[0], label: f[1], px: f[2],\n            ar: f[0].indexOf('×') >= 0 ? '1200 / 630' : f[0].replace(':', ' / '),\n            ts: i === 0 ? '22px' : '17px', bg: grad(c.accent) })) }));\n      }\n      if (lensKey === 'posters') {\n        lens.posters = L.posters.map(p => ({ size: p[0], orient: p[1], eyebrow: p[2],\n          lines: p[3].map(l => ({ t: l })), sub: p[4], surface: p[6],\n          ar: p[1] === 'landscape' ? '1.414 / 1' : '1 / 1.414', bg: grad(p[5]),\n          ts: p[0] === 'A1' ? '26px' : p[0] === 'A2' ? '22px' : '18px' }));\n      }\n      if (lensKey === 'decks') {\n        lens.decks = L.decks.map(d => ({ title: d.title, kind: d.kind, meta: d.slides + ' slides · ' + d.updated,\n          thumbs: d.thumbs.map((t, i) => ({ n: String(i + 1), label: t[0], bg: grad(t[1]) })) }));\n      }\n      if (lensKey === 'videos') {\n        const li = this.state.lsVid == null ? 0 : this.state.lsVid, v = L.videos[li];\n        lens.videos = { title: v[0], duration: v[1], kind: v[2], views: v[4] + ' views', bg: grad(v[3]),\n          grid: L.videos.map((x, i) => ({ title: x[0], duration: x[1], meta: x[2] + ' · ' + x[4] + ' views',\n            bg: grad(x[3]), bd: i === li ? 'var(--app-accent)' : 'var(--app-rule)',\n            onClick: () => this.setState({ lsVid: i }) })) };\n      }\n      if (lensKey === 'drafts') {\n        const a = L.drafts.article, pick = this.state.lsDraft || a.title;\n        const CH = { ready: 'var(--app-success)', review: 'var(--app-warn)', legal: 'var(--app-warn)', draft: 'var(--app-ink-faint)' };\n        lens.drafts = {\n          title: a.title, author: a.author, meta: a.meta,\n          blocks: a.body.map(b => ({ isH2: b[0] === 'h2', isP: b[0] === 'p', isQuote: b[0] === 'quote', t: b[1] })),\n          outline: a.outline.map(o => ({ label: o })),\n          checks: a.checks.map(c => ({ label: c[0], glyph: c[1] === 'ok' ? '✓' : '!',\n            ink: c[1] === 'ok' ? 'var(--app-success)' : 'var(--app-warn)' })),\n          list: L.drafts.list.map(d => ({ title: d[0], meta: d[2] + ' words', chip: d[1], chipInk: CH[d[1]],\n            on: d[0] === pick, bd: d[0] === pick ? 'var(--app-accent)' : 'transparent',\n            bg: d[0] === pick ? 'var(--app-elevated)' : 'transparent',\n            onClick: () => this.setState({ lsDraft: d[0] }) })) };\n      }\n      if (lensKey === 'support') {\n        const ti = this.state.lsThread == null ? 0 : this.state.lsThread, t = L.support.threads[ti];\n        const PR = { p2: 'var(--app-warn)', p3: 'var(--app-accent)', p4: 'var(--app-ink-soft)' };\n        lens.support = {\n          list: L.support.threads.map((x, i) => ({ customer: x.customer, ago: x.ago, preview: x.preview,\n            prio: x.prio, prioInk: PR[x.prio], prioBg: 'color-mix(in srgb,' + PR[x.prio] + ' 14%,transparent)',\n            tag: x.tag, tier: x.tier,\n            bg: i === ti ? 'var(--app-elevated)' : 'transparent',\n            bd: i === ti ? 'var(--app-accent)' : 'transparent',\n            onClick: () => this.setState({ lsThread: i }) })),\n          customer: t.customer, meta: t.email + ' · ' + t.tier + ' · opened ' + t.opened,\n          messages: t.messages.map(m => ({ author: m[1], av: m[2], ts: m[3], text: m[4],\n            avBg: m[0] === 'us' ? 'var(--app-accent)' : 'var(--app-elevated)',\n            avInk: m[0] === 'us' ? 'rgb(var(--basalt-deep))' : 'var(--app-ink)',\n            bg: m[0] === 'us' ? 'color-mix(in srgb,var(--app-accent) 8%,var(--app-elevated))' : 'var(--app-elevated)',\n            bd: m[0] === 'us' ? 'color-mix(in srgb,var(--app-accent) 30%,var(--app-rule))' : 'var(--app-rule)' })),\n          snippets: L.support.snippets.map(s => ({ label: s })) };\n      }\n      if (lensKey === 'replays') {\n        const ST = { ok: 'var(--app-success)', warn: 'var(--app-warn)', fail: 'var(--app-fail)' };\n        lens.replays = L.replays.map(r => ({ id: r[0], parent: r[1], step: r[2], override: r[3],\n          status: r[5], cost: r[6], ink: ST[r[4]], bg: 'color-mix(in srgb,' + ST[r[4]] + ' 14%,transparent)',\n          costInk: r[6].charAt(0) === '−' ? 'var(--app-success)' : 'var(--app-ink-soft)' }));\n      }\n      if (lensKey === 'flow') {\n        const vi = this.state.lsVar == null ? 0 : this.state.lsVar;\n        lens.flow = {\n          variants: L.flow.variants.map((v, i) => ({ label: v[0], pct: v[1] + ' % completion',\n            bd: i === vi ? 'var(--app-accent)' : 'var(--app-rule)',\n            ink: i === vi ? 'var(--app-accent)' : 'var(--app-ink-soft)',\n            onClick: () => this.setState({ lsVar: i }) })),\n          steps: L.flow.steps.map((s, i) => ({ n: String(i + 1), title: s[0], mode: s[1], tagline: s[2],\n            last: i === L.flow.steps.length - 1,\n            fields: s[3].map(f => ({ label: f })) })) };\n      }\n      if (lensKey === 'imp') {\n        const ri = this.state.lsReason == null ? 1 : this.state.lsReason;\n        lens.imp = {\n          policy: L.imp.policy.map(p => ({ label: p[0], v: p[1] })),\n          reasons: L.imp.reasons.map((r, i) => ({ label: r,\n            bg: i === ri ? 'var(--app-accent)' : 'var(--app-elevated)',\n            ink: i === ri ? 'rgb(var(--basalt-deep))' : 'var(--app-ink-soft)',\n            bd: i === ri ? 'var(--app-accent)' : 'var(--app-rule)',\n            onClick: () => this.setState({ lsReason: i }) })),\n          recent: L.imp.recent.map(r => ({ uid: r[0], actor: r[1], duration: r[2], reason: r[3], ts: r[4] })) };\n      }\n    }\n\n    // ── Content · overview cards, design gallery, social board ──\n    let cnt = null;\n    if (kind === 'cnt') {\n      const C = this.CNT, surf = this.state.cntSurf || 'all';\n      const TINT = { cy: 'var(--app-accent)', gd: 'rgb(var(--pastel-gold))', red: 'rgb(var(--accent-red))',\n        lav: 'rgb(var(--pastel-lavender))', tl: 'rgb(var(--accent-teal))' };\n      const surfaces = ['all', 'marketing', 'product', 'social', 'brand'];\n      const shown = C.designs.filter(d => surf === 'all' || d[1] === surf);\n      const lb = this.state.cntLightbox;\n      cnt = {\n        isOverview: subLabel === 'Overview', isDesigns: subLabel === 'Designs', isSocial: subLabel === 'Social posts',\n        note: 'Open any surface — each one renders its assets at real size, not as a row in a list.',\n        cards: C.cards.map(c => ({ title: c[0], sub: c[1], kpi: c[2], meta: c[3],\n          bd: c[4] === 'warn' ? 'rgb(var(--pastel-gold))' : 'var(--app-rule)',\n          dot: c[4] === 'warn' ? 'rgb(var(--pastel-gold))' : 'var(--accent-teal)',\n          spark: c[4] === 'warn' ? 'rgb(var(--pastel-gold))' : 'var(--accent-teal)' })),\n        surfaces: surfaces.map(s => ({ label: s, count: String(s === 'all' ? C.designs.length : C.designs.filter(d => d[1] === s).length),\n          bg: surf === s ? 'var(--app-accent)' : 'transparent',\n          bd: surf === s ? 'var(--app-accent)' : 'var(--app-rule)',\n          ink: surf === s ? 'rgb(var(--basalt-deep))' : 'var(--app-ink-soft)',\n          onClick: () => this.setState({ cntSurf: s }) })),\n        tiles: shown.map((d, i) => ({ title: d[0], surface: d[1], mark: d[2], ratio: d[3], updated: d[4],\n          bg: 'linear-gradient(135deg,' + TINT[d[5]] + ',color-mix(in srgb,' + TINT[d[5]] + ' 30%,var(--app-elevated)))',\n          onClick: () => this.setState({ cntLightbox: { title: d[0], surface: d[1], mark: d[2], ratio: d[3], updated: d[4],\n            ar: d[3].indexOf('\\u00D7') >= 0 ? '3 / 2' : d[3].replace(':', ' / '),\n            bg: 'linear-gradient(135deg,' + TINT[d[5]] + ',color-mix(in srgb,' + TINT[d[5]] + ' 20%,var(--app-elevated)))',\n            close: () => this.setState({ cntLightbox: null }) } }) })),\n        lightbox: lb || null,\n        posts: C.posts.map(p => ({ ch: p.ch, top: p.top, author: p.author, handle: p.handle, av: p.av, when: p.when,\n          body: p.body, media: p.media || '', hasMedia: !!p.media, stats: p.stats || '', hasStats: !!p.stats,\n          tag: p.live ? '' : 'SCHEDULED', hasTag: !p.live }))\n      };\n    }\n\n    // ── Domains · hub + one content surface per mounted domain (native, dual-mode) ──\n    const goTab = (label) => () => { const i = (st.subs || []).findIndex(s => s.label === label); if (i >= 0) this.setState({ sub: i }); };\n    const DOM_ICONS = {\".blog\":\"book\",\".podcast\":\"podcast\",\".pics\":\"display\",\".academy\":\"sparkles\",\".today\":\"content\",\".toolbox\":\"apps\",\".select\":\"select\",\".builders\":\"foundry\",\".boutique\":\"gift\",\".business\":\"columns\",\".diy\":\"brush\",\".forum\":\"contacts\",\".quest\":\"target\",\".canvas\":\"palette\",\".company\":\"user\",\".store\":\"costs\"};\n    let hub = null, dom = null;\n    if (kind === 'domainsHub') {\n      hub = {\n        buckets: this.DOM_BUCKETS.map(b => {\n          const ids = Object.keys(this.DOMAINS).filter(kk => this.DOMAINS[kk].bucket === b.id);\n          return { label: b.label, tagline: b.tagline, count: ids.length + ' domains',\n            cards: ids.map(kk => { const d = this.DOMAINS[kk];\n              return { label: kk, tld: d.tld, tagline: d.tagline, accent: d.accent, state: d.state,\n                iconEl: this.ic(DOM_ICONS[kk] || 'grid', 20), onClick: goTab(kk) }; }) };\n        }),\n        // Derived from the same counts as the KPI strip (see the DOMAINS derivation) — this line and\n        // the bucket counts beside it can no longer disagree.\n        note: (this._domCounts ? this._domCounts.mounted + ' domains mounted · ' + this._domCounts.live +\n          ' live · ' + this._domCounts.inCanvas + ' already rendering inside Canvas · ' +\n          this._domCounts.pulled + ' pulled into assets/domains/' : ''),\n        rules: this.DOM_RULES.map(r => ({ head: r[0], body: r[1] }))\n      };\n    }\n    if (kind === 'domain') {\n      const d = this.DOMAINS[subLabel], v = d.view, ac = d.accent;\n      dom = {\n        label: subLabel, tld: d.tld, tagline: d.tagline, accent: ac, desc: d.desc, state: d.state,\n        kpis: d.kpis.map(x => ({ label: x[0], value: x[1], delta: x[2] || '', hasDelta: !!x[2] })),\n        showOps: !!this.state.ops,\n        ops: (d.ops || []).map(x => ({ label: x[0], value: x[1] })),\n        isArticle: v === 'article', isVideo: v === 'video', isPodcast: v === 'podcast', isGallery: v === 'gallery',\n        isBrowser: v === 'browser', isListing: v === 'listing', isThread: v === 'thread', isSoon: v === 'soon',\n        isPics: v === 'picsApp', isDiy: v === 'diyApp',\n        isPicks: v === 'dailyPicks', isShop: v === 'builderWorkshop', isWiz: v === 'ceuWizard',\n        isEntity: v === 'entityOverview', isVb: v === 'visualBuilder',\n        hint: d.hint || '',\n        openLabel: 'Open user portal · ' + d.tld,\n        open: () => this.setState({ confirm: { title: 'Open ' + d.tld + ' in a new tab', verb: 'OPEN' } })\n      };\n      if (v === 'dailyPicks' || v === 'builderWorkshop' || v === 'ceuWizard' || v === 'entityOverview' || v === 'visualBuilder') {\n        const S = this.SCAF;\n        if (v === 'dailyPicks') {\n          dom.picks = { day: 'DAILY · 28.05.2026',\n            rows: S.picks.map((p, i) => ({ n: ('0' + (i + 1)).slice(-2), title: p[0],\n              meta: p[1] + ' · ' + p[2] + ' min · ' + p[3], take: p[6], views: p[4], marks: p[5] })) };\n        }\n        if (v === 'builderWorkshop') {\n          const W = S.shop, tab = this.state.bwTab || 'editor';\n          dom.shop = { status: W.status, activeFile: W.activeFile + ' · markdown',\n            tabs: ['editor', 'preview', 'tests', 'history'].map(t => ({ label: t, on: tab === t,\n              bg: tab === t ? 'color-mix(in srgb,var(--app-accent) 8%,var(--app-elevated))' : 'var(--app-elevated)',\n              bd: tab === t ? ac : 'var(--app-rule)', ink: tab === t ? ac : 'var(--app-ink-soft)',\n              onClick: () => this.setState({ bwTab: t }) })),\n            isEditor: tab === 'editor', isPreview: tab === 'preview', isTests: tab === 'tests', isHistory: tab === 'history',\n            files: W.files.map(f => ({ path: f[0], dirty: f[2],\n              bg: f[1] ? 'var(--app-surface)' : 'transparent', ink: f[1] ? ac : 'var(--app-ink-soft)' })),\n            lines: W.code.split('\\n').map((l, i) => ({ n: String(i + 1), text: l })),\n            tests: W.tests.map(t => ({ name: t[0], time: t[2],\n              glyph: t[1] === 'pass' ? '\\u2713' : '\\u2715',\n              ink: t[1] === 'pass' ? 'var(--app-success)' : 'var(--app-fail)',\n              bd: t[1] === 'pass' ? 'var(--app-rule)' : 'var(--app-fail)' })),\n            history: W.history.map(h => ({ sha: h[0], msg: h[1], ts: h[2] })) };\n        }\n        if (v === 'ceuWizard') {\n          const steps = S.wizard, si = this.state.cwStep == null ? 3 : this.state.cwStep, a = steps[si];\n          dom.wiz = { head: 'CV-Wizard · ' + steps.length + ' Schritte',\n            rail: steps.map((s, i) => ({ label: s.label, num: i < si ? '\\u2713' : String(i + 1),\n              bg: i === si ? 'color-mix(in srgb,var(--app-accent) 10%,var(--app-elevated))' : 'transparent',\n              bd: i === si ? ac : 'transparent',\n              ink: i === si ? 'var(--app-ink)' : i < si ? 'var(--app-ink-faint)' : 'var(--app-ink-soft)',\n              numBg: i === si ? ac : i < si ? 'var(--app-success)' : 'var(--app-surface)',\n              numInk: i <= si ? 'rgb(var(--basalt-deep))' : 'var(--app-ink-soft)',\n              onClick: () => this.setState({ cwStep: i }) })),\n            eyebrow: 'Schritt ' + (si + 1) + ' von ' + steps.length, title: a.title, tag: a.tag,\n            hasFields: !!a.fields,\n            fields: (a.fields || []).map((f, i) => ({ label: f[0], v: f[1], rows: f[2], id: 'cw' + si + i, isArea: f[2] > 1 })),\n            hasPreview: !!a.preview, previewLabel: 'Vorschau · ' + (a.previewLabel || 'Auszug'), preview: a.preview || '',\n            nextLabel: si === steps.length - 1 ? 'CV generieren' : 'Weiter',\n            back: () => this.setState({ cwStep: Math.max(0, si - 1) }),\n            next: () => this.setState({ cwStep: Math.min(steps.length - 1, si + 1) }) };\n        }\n        if (v === 'entityOverview') {\n          const e = S.entity;\n          dom.ent = { avatar: e.label.slice(0, 2).toUpperCase(), eyebrow: e.kind + ' · ' + e.id,\n            label: e.label, tagline: e.tagline,\n            badges: e.badges.map(b => ({ label: b })),\n            actions: e.actions.map(a => ({ label: a[0],\n              bg: a[1] ? ac : 'var(--app-elevated)', ink: a[1] ? 'rgb(var(--basalt-deep))' : 'var(--app-ink-soft)',\n              bd: a[1] ? ac : 'var(--app-rule)' })),\n            metrics: e.metrics.map(m => ({ label: m[0], value: m[1], delta: m[2] || '', hasDelta: !!m[2] })),\n            activityHead: 'Activity · last 30 d',\n            activity: e.activity.map(a => ({ ts: a[0], text: a[1],\n              dot: a[2] === 'warn' ? 'var(--app-warn)' : a[2] === 'fail' ? 'var(--app-fail)' : 'var(--app-success)' })),\n            teamHead: 'Team · ' + e.members.length + ' members',\n            members: e.members.map(m => ({ name: m[0], av: m[1], meta: m[2] + ' · ' + m[3], status: m[4],\n              ink: m[4] === 'pending' ? 'var(--app-warn)' : 'var(--app-success)' })),\n            related: e.related.map(r => ({ label: r[0], kind: r[1] })) };\n        }\n        if (v === 'visualBuilder') {\n          dom.vb = { head: 'R1 migration progress · 3 / 8 modules', pct: '37.5%',\n            cards: S.projects.map(p => ({ title: p[0], meta: p[1] + ' nodes · ' + p[2],\n              avatars: p[3].map(c => ({ label: c })),\n              nodes: [0, 1, 2, 3].map(i => ({ left: (10 + (i % 2) * 60) + '%', top: (10 + Math.floor(i / 2) * 70) + '%',\n                bg: 'color-mix(in srgb,var(--app-accent) ' + (30 + i * 15) + '%,transparent)' })) })) };\n        }\n      }\n      if (v === 'diyApp') {\n        const D = this.DIY, tab = this.state.diyTab || 'library';\n        const pkgId = this.state.diyPkg || 'pkg-prompt-cache';\n        const pkg = D.packages.filter(p => p.id === pkgId)[0] || D.packages[0];\n        const filt = this.state.diyFilter || 'all';\n        const fmtId = this.state.diyFmt || (pkg.formats[0] || {}).id;\n        const fmt = pkg.formats.filter(f => f.id === fmtId)[0] || pkg.formats[0] || null;\n        const mode = this.state.diyMode || 'implementation';\n        const chosen = this.state.diyFmts || ['ide_package', 'briefing', 'podcast_notes'];\n        const SI = { youtube: 'display', article: 'book', podcast: 'podcast', reddit: 'contacts', github: 'code', newsletter: 'mail', all: 'apps' };\n        const FI = { ide_package: 'code', briefing: 'book', podcast_notes: 'podcast', social_content: 'share', mcp_server: 'apps' };\n        const tint = t => this.DIY_TINT[t] || 'rgb(var(--basalt-surface, 26 26 28))';\n        dom.diy = {\n          tabs: [['library', 'Library', String(D.packages.length)], ['detail', 'Detail', null], ['ingest', 'Ingest', null], ['runs', 'Runs', String(D.runs.length)]]\n            .map(t => ({ label: t[1], count: t[2] || '', hasCount: !!t[2], on: tab === t[0],\n              bg: tab === t[0] ? 'color-mix(in srgb,var(--app-accent) 8%,var(--app-elevated))' : 'var(--app-elevated)',\n              bd: tab === t[0] ? 'var(--app-accent)' : 'var(--app-rule)',\n              ink: tab === t[0] ? 'var(--app-accent)' : 'var(--app-ink-soft)',\n              onClick: () => this.setState({ diyTab: t[0] }) })),\n          isLibrary: tab === 'library', isDetail: tab === 'detail', isIngest: tab === 'ingest', isRuns: tab === 'runs',\n          filters: ['all', 'youtube', 'article', 'podcast', 'reddit', 'github'].map(s => ({ label: s, on: filt === s,\n            iconEl: this.ic(SI[s] || 'grid', 12),\n            bg: filt === s ? 'var(--app-elevated)' : 'transparent',\n            bd: filt === s ? 'var(--app-accent)' : 'var(--app-rule)',\n            ink: filt === s ? 'var(--app-accent)' : 'var(--app-ink-soft)',\n            onClick: () => this.setState({ diyFilter: s }) })),\n          cards: D.packages.filter(p => filt === 'all' || p.st === filt).map(p => ({\n            name: p.name, creator: '@' + p.creator, st: p.st, installs: p.installs + ' installs',\n            fmts: '· ' + p.nFmt + ' formats', dur: p.duration || '', hasDur: !!p.duration, pro: !!p.proOnly,\n            iconEl: this.ic(SI[p.st] || 'book', 12),\n            thumb: 'linear-gradient(135deg,' + tint(p.st) + ',var(--app-elevated))',\n            onClick: () => this.setState({ diyPkg: p.id, diyTab: 'detail', diyFmt: null }) })),\n          d: {\n            name: pkg.name, creator: '@' + pkg.creator, generated: pkg.generated, mode: pkg.mode,\n            isYt: pkg.src.type === 'youtube', isPod: pkg.src.type === 'podcast', isArt: pkg.src.type === 'article',\n            src: { duration: pkg.src.duration || '', title: pkg.src.title, channel: pkg.src.channel || '',\n              domain: pkg.src.domain || '', lead: pkg.src.lead || '', readMin: pkg.src.readMin || '',\n              lines: (pkg.src.transcript || []).map(t => ({ t: t[0], text: t[1] })),\n              bars: Array.from({ length: 60 }, (_, i) => ({ h: (18 + ((i * 47) % 68)) + '%',\n                bg: i < 12 ? ac : 'var(--app-rule)' })) },\n            hasFmts: pkg.formats.length > 0,\n            fmtTabs: pkg.formats.map(f => ({ label: f.label, iconEl: this.ic(FI[f.id] || 'book', 14),\n              bd: fmtId === f.id ? 'var(--app-accent)' : 'var(--app-rule)',\n              ink: fmtId === f.id ? 'var(--app-accent)' : 'var(--app-ink-soft)',\n              onClick: () => this.setState({ diyFmt: f.id }) })),\n            isIde: fmt && fmt.id === 'ide_package', isBrief: fmt && fmt.id === 'briefing',\n            isNotes: fmt && fmt.id === 'podcast_notes', isSocial: fmt && fmt.id === 'social_content',\n            isMcp: fmt && fmt.id === 'mcp_server',\n            files: fmt && fmt.files ? fmt.files.map(f => ({ path: f, on: f === fmt.activeFile,\n              bg: f === fmt.activeFile ? 'var(--app-surface)' : 'transparent',\n              ink: f === fmt.activeFile ? 'var(--app-accent)' : 'var(--app-ink-soft)' })) : [],\n            fileCount: fmt && fmt.files ? 'FILES · ' + fmt.files.length : '',\n            readme: fmt && fmt.readme ? fmt.readme : '',\n            paras: fmt && fmt.paras ? fmt.paras.map(p => ({ t: p.replace(/\\*\\*/g, '') })) : [],\n            notes: fmt && fmt.notes ? fmt.notes.map(n => ({ t: n[0], text: n[1] })) : [],\n            variants: fmt && fmt.variants ? fmt.variants.map(x => ({ p: x.p, chars: x.chars, top: x.top, text: x.text })) : [],\n            mcpCols: fmt && fmt.mcp ? [['Tools', fmt.mcp.tools], ['Resources', fmt.mcp.resources], ['Prompts', fmt.mcp.prompts]]\n              .map(c => ({ head: c[0] + ' · ' + c[1].length, items: c[1].map(i => ({ label: i })) })) : []\n          },\n          url: this.state.diyUrl == null ? 'https://youtube.com/watch?v=xyz123' : this.state.diyUrl,\n          setUrl: e => this.setState({ diyUrl: e.target.value }),\n          detected: /youtube\\.com|youtu\\.be/.test(this.state.diyUrl == null ? 'youtube.com' : this.state.diyUrl) ? 'youtube'\n            : /reddit\\.com/.test(this.state.diyUrl || '') ? 'reddit' : /github\\.com/.test(this.state.diyUrl || '') ? 'github' : 'article',\n          modes: D.modes.map(m => ({ label: m[0], tag: m[1] || '', hasTag: !!m[1], op: m[1] ? '0.7' : '1',\n            bg: mode === m[0] ? 'var(--app-accent)' : 'var(--app-elevated)',\n            bd: mode === m[0] ? 'var(--app-accent)' : 'var(--app-rule)',\n            ink: mode === m[0] ? 'rgb(var(--basalt-deep))' : 'var(--app-ink-soft)',\n            onClick: () => this.setState({ diyMode: m[0] }) })),\n          fmtOpts: D.fmtOpts.map(f => { const on = chosen.indexOf(f[0]) >= 0;\n            return { label: f[1], tag: f[2] || '', hasTag: !!f[2], op: f[2] ? '0.7' : '1',\n              bg: on ? 'var(--app-accent)' : 'var(--app-elevated)',\n              bd: on ? 'var(--app-accent)' : 'var(--app-rule)',\n              ink: on ? 'rgb(var(--basalt-deep))' : 'var(--app-ink-soft)',\n              onClick: () => this.setState({ diyFmts: on ? chosen.filter(x => x !== f[0]) : chosen.concat([f[0]]) }) }; }),\n          cost: 'est. cost · € 0,038 · ' + chosen.length + ' formats',\n          runsHead: 'Live generation stream · ' + D.runs.length + ' active',\n          runs: D.runs.map(r => ({ id: r.id, stage: r.stage, cost: '€ ' + r.cost, source: r.source,\n            steps: ['transcribe', 'extract', 'generate', 'render'].map((s, i) => {\n              const on = i <= ['transcribe', 'extract', 'generate', 'render'].indexOf(r.stage);\n              return { label: s, bg: on ? 'var(--app-success)' : 'var(--app-elevated)',\n                ink: on ? 'rgb(var(--basalt-deep))' : 'var(--app-ink-faint)' }; }) }))\n        };\n      }\n      if (v === 'picsApp') {\n        const P = this.PICS, scr = this.state.picsScreen || 'landing';\n        const si = this.PICS_FLOW.findIndex(f => f.id === scr);\n        const set = (k, val) => () => this.setState({ [k]: val });\n        const tiles = Array.from({ length: 24 }, (_, i) => {\n          const flag = i === 4 ? 'side-profile' : i === 9 ? 'blurry' : i === 15 ? 'group' : null;\n          return { id: i, score: 0.62 + ((i * 29) % 38) / 100, flag: flag, hue: 200 + (i * 37) % 160 };\n        });\n        const defSel = tiles.filter(t => !t.flag && t.score > 0.7).slice(0, 20).map(t => t.id);\n        const sel = this.state.picsSel || defSel;\n        const pick = this.state.picsPick || { background: 'white seamless', composition: '3/4 portrait', camera: '85mm f/1.4', lighting: 'soft natural' };\n        const lead = this.state.picsLead == null ? 2 : this.state.picsLead;\n        const grad = (h, a, b) => 'linear-gradient(135deg,oklch(' + a + ' 0.07 ' + h + '),oklch(' + b + ' 0.07 ' + h + '))';\n        dom.pics = {\n          rail: this.PICS_FLOW.map((f, i) => ({\n            label: f.label, num: i < si ? '\\u2713' : String(i + 1), iconEl: this.ic(f.icon, 14),\n            bg: i === si ? 'color-mix(in srgb,var(--app-accent) 10%,var(--app-elevated))' : 'transparent',\n            bd: i === si ? 'var(--app-accent)' : 'transparent',\n            ink: i === si ? 'var(--app-ink)' : i < si ? 'var(--app-ink-faint)' : 'var(--app-ink-soft)',\n            numBg: i === si ? 'var(--app-accent)' : i < si ? 'var(--app-success)' : 'var(--app-surface)',\n            numInk: i <= si ? 'rgb(var(--basalt-deep))' : 'var(--app-ink-soft)',\n            onClick: set('picsScreen', f.id)\n          })),\n          isLanding: scr === 'landing', isConnect: scr === 'connect', isSelect: scr === 'select',\n          isTrain: scr === 'train', isStudio: scr === 'studio', isResults: scr === 'results', isBilling: scr === 'billing',\n          go: { connect: set('picsScreen', 'connect'), select: set('picsScreen', 'select'), train: set('picsScreen', 'train'),\n            studio: set('picsScreen', 'studio'), results: set('picsScreen', 'results'), billing: set('picsScreen', 'billing'),\n            landing: set('picsScreen', 'landing') },\n          landTiles: P.landTags.map((t, i) => ({ tag: t,\n            bg: 'linear-gradient(' + (30 + i * 40) + 'deg,color-mix(in srgb,var(--app-accent) ' + (20 + i * 4) + '%,var(--app-elevated)),color-mix(in srgb,' + ac + ' ' + (15 + i * 3) + '%,var(--app-surface)))' })),\n          landSteps: P.landSteps.map((s, i) => ({ n: String(i + 1), h: s[0], s: s[1] })),\n          trust: P.trust.map(t => ({ label: t })),\n          perms: P.perms.map(p => ({ label: p[1], glyph: p[0] === 'ok' ? '\\u2713' : '\\u2715',\n            color: p[0] === 'ok' ? 'var(--app-success)' : 'var(--app-ink-faint)' })),\n          why: P.why.map(w => ({ label: w })),\n          scope: this.state.picsScope !== false,\n          scopeKnob: this.state.picsScope !== false ? 'var(--app-accent)' : 'var(--app-elevated)',\n          scopeDot: this.state.picsScope !== false ? '16px' : '2px',\n          scopeDotInk: this.state.picsScope !== false ? 'rgb(var(--basalt-deep))' : 'var(--app-ink)',\n          toggleScope: () => this.setState({ picsScope: this.state.picsScope === false }),\n          selTiles: tiles.map(t => {\n            const on = sel.indexOf(t.id) >= 0;\n            return { id: t.id, pct: (t.score * 100).toFixed(0) + '%', flag: t.flag, hasFlag: !!t.flag,\n              bg: grad(t.hue, '0.78', '0.40'), on: on,\n              bd: on ? 'var(--app-accent)' : 'transparent', op: t.flag ? '0.45' : '1',\n              shadow: on ? '0 0 0 2px color-mix(in srgb,var(--app-accent) 30%,transparent)' : 'none',\n              onClick: t.flag ? null : () => this.setState({ picsSel: on ? sel.filter(x => x !== t.id) : sel.concat([t.id]) }) };\n          }),\n          selN: String(sel.length), selReady: sel.length === 20,\n          selBtn: 'Train my LoRA · ' + sel.length + '/20',\n          selBtnBg: sel.length === 20 ? 'var(--app-accent)' : 'var(--app-elevated)',\n          selBtnInk: sel.length === 20 ? 'rgb(var(--basalt-deep))' : 'var(--app-ink-faint)',\n          trainPct: 48, ringOff: 2 * Math.PI * 44 * 0.52, ringLen: 2 * Math.PI * 44,\n          trainStages: P.trainStages.map(s => ({ label: s.label, pct: s.pct || '', hasPct: !!s.pct,\n            bd: s.active ? 'var(--app-accent)' : 'var(--app-rule)',\n            ink: s.done ? 'var(--app-success)' : s.active ? 'var(--app-ink)' : 'var(--app-ink-soft)',\n            glyph: s.done ? '\\u2713' : s.active ? '\\u25CF' : '\\u00B7',\n            gInk: s.done ? 'var(--app-success)' : s.active ? 'var(--app-accent)' : 'var(--app-rule)' })),\n          trainCards: P.trainCards.map(c => ({ l: c[0], v: c[1] })),\n          axes: Object.keys(P.axes).map(k => ({ key: k,\n            opts: P.axes[k].map(o => ({ label: o, on: pick[k] === o,\n              bg: pick[k] === o ? 'var(--app-accent)' : 'var(--app-elevated)',\n              ink: pick[k] === o ? 'rgb(var(--basalt-deep))' : 'var(--app-ink-soft)',\n              bd: pick[k] === o ? 'var(--app-accent)' : 'var(--app-rule)',\n              onClick: () => this.setState({ picsPick: Object.assign({}, pick, { [k]: o }) }) })) })),\n          pickComp: pick.composition, pickBg: pick.background, pickCam: pick.camera, pickLight: pick.lighting,\n          previewTiles: [0, 1, 2, 3].map(i => ({ n: String(i + 1),\n            bg: 'linear-gradient(' + (i * 70) + 'deg,color-mix(in srgb,var(--app-accent) ' + (30 + i * 8) + '%,var(--app-elevated)),color-mix(in srgb,' + ac + ' ' + (20 + i * 5) + '%,var(--app-surface)))' })),\n          leadBg: grad(180 + lead * 32, '0.72', '0.30'), leadTag: 'selected · ' + (lead + 1),\n          resThumbs: Array.from({ length: 8 }, (_, i) => ({ id: i, fav: i === 2,\n            bg: grad(180 + i * 32, '0.72', '0.30'), on: i === lead,\n            bd: i === lead ? 'var(--app-accent)' : 'transparent',\n            onClick: () => this.setState({ picsLead: i }) })),\n          resMeta: pick.camera + ' · ' + pick.lighting + ' · ' + pick.composition + ' · ' + pick.background,\n          packs: P.packs.map(p => ({ name: p.name, credits: p.credits, price: p.price, per: p.per, pop: !!p.pop,\n            bd: p.pop ? 'var(--app-accent)' : 'var(--app-rule)',\n            shadow: p.pop ? '0 0 0 1px var(--app-accent)' : 'none' }))\n        };\n      }\n      if (d.article) {\n        const a = d.article;\n        dom.art = { title: a.title, author: a.author, avatar: a.avatar, published: a.published, read: a.read,\n          lead: a.lead, tags: a.tags.map(t => ({ label: t })),\n          sections: a.sections.map((x, i) => ({ n: '0' + (i + 1), h: x.h, b: x.b })),\n          toc: a.sections.map((x, i) => ({ n: '0' + (i + 1), h: x.h })),\n          related: a.related.map(r => ({ t: r.t, m: r.m })) };\n      }\n      if (d.video) {\n        const vd = d.video, ch = Math.min(this.state.domChapter || 0, vd.chapters.length - 1), sp = this.state.domSpeed || '1';\n        dom.vid = { course: vd.course, progress: vd.progress, title: vd.title, duration: vd.duration, at: vd.at,\n          nowChapter: vd.chapters[ch][1], transcript: vd.transcript,\n          chapters: vd.chapters.map((c, i) => ({ t: c[0], label: c[1],\n            border: i === ch ? ac : 'var(--app-rule)', color: i === ch ? ac : 'var(--app-ink-soft)',\n            bg: i === ch ? 'rgb(var(--text) / 0.05)' : 'transparent',\n            onClick: () => this.setState({ domChapter: i }) })),\n          speeds: ['0.5', '1', '1.5', '2'].map(x => ({ label: x + '×', active: x === sp,\n            color: x === sp ? ac : 'var(--app-ink-faint)',\n            onClick: () => this.setState({ domSpeed: x }) })),\n          modules: vd.modules.map(m => ({ label: m.label,\n            lessons: m.lessons.map(l => ({ label: l[1], dur: l[2],\n              glyph: l[0] === 'done' ? '✓' : l[0] === 'now' ? '▶' : '·',\n              color: l[0] === 'done' ? 'var(--app-success)' : l[0] === 'now' ? ac : 'var(--app-ink-faint)',\n              ink: l[0] === 'next' ? 'var(--app-ink-faint)' : 'var(--app-ink)',\n              bg: l[0] === 'now' ? 'rgb(var(--text) / 0.05)' : 'transparent' })) })) };\n      }\n      if (d.podcast) {\n        const p = d.podcast, at = this.state.domAt || p.chapters[1][0];\n        dom.pod = { title: p.title, duration: p.duration, published: p.published, pct: p.pct, at: p.at,\n          bars: Array.from({ length: 72 }).map((_, i) => ({\n            h: (22 + ((i * 37) % 53) / 53 * 62) + '%',\n            bg: i < 13 ? ac : 'rgb(var(--text) / 0.16)' })),\n          chapters: p.chapters.map(c => ({ t: c[0], label: c[1],\n            border: c[0] === at ? ac : 'var(--app-rule)', color: c[0] === at ? ac : 'var(--app-ink-soft)',\n            bg: c[0] === at ? 'rgb(var(--text) / 0.05)' : 'transparent',\n            onClick: () => this.setState({ domAt: c[0] }) })),\n          transcript: p.transcript.map(t => ({ t: t[0], who: t[1], text: t[2],\n            ink: t[0] === at ? 'var(--app-ink)' : 'var(--app-ink-soft)',\n            edge: t[0] === at ? ac : 'transparent' })),\n          recent: p.recent.map(r => ({ id: r[0], title: r[1], meta: r[2] })) };\n      }\n      if (d.gallery) {\n        const g = d.gallery;\n        const tint = { classic: 'oklch(0.72 0.09 240)', casual: 'oklch(0.72 0.09 180)', studio: 'oklch(0.72 0.09 290)', environment: 'oklch(0.72 0.09 140)' };\n        const sel = this.state.domImg;\n        dom.gal = { pack: g.pack, owner: g.owner, model: g.model, generated: g.generated, prompt: g.prompt,\n          eyebrow: g.model + ' · pack ' + g.pack + ' · ' + g.generated,\n          tiles: g.tiles.map((t, i) => ({ n: String(i + 1).padStart(2, '0'), variant: t[0],\n            score: Math.round(parseFloat(t[1]) * 100) + ' %', cost: '€ 0,04',\n            bg: 'linear-gradient(135deg, ' + tint[t[0]] + ', color-mix(in srgb, ' + tint[t[0]] + ' 45%, rgb(var(--basalt-surface))))',\n            onClick: () => this.setState({ domImg: i }) })),\n          lightbox: sel === undefined || sel === null ? null : {\n            id: 'img-' + (sel + 1), variant: g.tiles[sel][0],\n            score: Math.round(parseFloat(g.tiles[sel][1]) * 100) + ' %', cost: '€ 0,04',\n            bg: 'linear-gradient(135deg, ' + tint[g.tiles[sel][0]] + ', color-mix(in srgb, ' + tint[g.tiles[sel][0]] + ' 40%, rgb(var(--basalt-surface))))',\n            close: () => this.setState({ domImg: null }) } };\n      }\n      if (d.browser) {\n        const b = d.browser, bf = this.state.domFilter || 'All', pick = this.state.domPick;\n        const shown = b.items.filter(it => bf === 'All' || it[1].toLowerCase().indexOf(bf.split(' ')[0].toLowerCase()) === 0);\n        const chip = (label, count, on, onClick) => ({ label, count,\n          color: on ? ac : 'var(--app-ink-soft)',\n          bg: on ? 'color-mix(in srgb, ' + ac + ' 8%, transparent)' : 'transparent',\n          edge: on ? '2px solid ' + ac : '2px solid transparent', onClick });\n        const sel = shown.find(it => it[0] === pick);\n        dom.brw = {\n          filters: b.filters.map(f => chip(f[0], f[1], f[0] === bf, () => this.setState({ domFilter: f[0], domPick: null }))),\n          cats: b.cats.map(c => chip(c[0], c[1], false, () => {})),\n          count: shown.length + ' of ' + b.items.length,\n          items: shown.map(it => ({ title: it[0], kind: it[1], cat: it[2], updated: it[3], desc: it[4],\n            star: it[5] ? '♥' : '',\n            border: it[0] === pick ? ac : 'var(--app-rule)',\n            ring: it[0] === pick ? '0 0 0 2px color-mix(in srgb, ' + ac + ' 30%, transparent)' : 'none',\n            onClick: () => this.setState({ domPick: it[0] }) })),\n          hasPick: !!sel, noPick: !sel,\n          pick: sel ? { title: sel[0], kind: sel[1], cat: sel[2], updated: sel[3], desc: sel[4],\n            close: () => this.setState({ domPick: null }) } : null,\n          use: () => this.setState({ confirm: { title: 'Attach \"' + (sel ? sel[0] : '') + '\" to an agent run', verb: 'ATTACH' } }),\n          copy: () => this.setState({ confirm: { title: 'Copy \"' + (sel ? sel[0] : '') + '\" to the clipboard', verb: 'COPY' } })\n        };\n      }\n      if (d.listing) {\n        const L = d.listing;\n        dom.lst = { title: L.title, publisher: L.publisher, verified: L.verified, rating: L.rating,\n          reviews: L.reviews, installs: L.installs, price: L.price, priceChip: L.priceChip,\n          tagline: L.tagline, desc: L.desc,\n          art: 'linear-gradient(135deg, ' + ac + ', color-mix(in srgb, ' + ac + ' 28%, rgb(var(--basalt-surface))))',\n          caps: L.caps.map(c => ({ label: c })), perms: L.perms.map(p => ({ label: p })),\n          hasPerms: L.perms.length > 0,\n          recent: L.recent.map(r => ({ title: r[0], publisher: r[1], rating: r[2], installs: r[3], price: r[4] })) };\n      }\n      if (d.thread) {\n        const t = d.thread;\n        dom.thr = { title: t.title, author: t.author, avatar: t.avatar, flair: t.flair, created: t.created,\n          stats: t.stats, tags: t.tags.map(x => ({ label: x })), body: t.body,\n          posts: t.posts.map(p => ({ name: p.name, avatar: p.avatar, flair: p.flair, at: p.at, votes: p.votes,\n            body: p.body, flairColor: p.op ? ac : 'var(--app-ink-faint)',\n            edge: p.op ? ac : 'var(--app-rule)' })) };\n      }\n    }\n    // ── Ads · Launch Readiness + Account Overview (native) ──\n    let launch = null, adsAcct = null;\n    if (kind === 'launch') {\n      const col = { ok: 'var(--app-success)', warn: 'var(--app-warn)', fail: 'var(--app-fail)' };\n      const met = this.ADS_PREREQS.filter(p => p.state === 'ok').length;\n      launch = {\n        met: met + ' / ' + this.ADS_PREREQS.length,\n        warning: 'Do NOT launch ads into a funnel whose €25 activation step is missing — build the tier first.',\n        prereqs: this.ADS_PREREQS.map(p => ({ label: p.label, note: p.note, color: col[p.state],\n          glyph: p.state === 'ok' ? '✓' : p.state === 'warn' ? '!' : '✕' })),\n        golive: this.ADS_GOLIVE.map(g => ({ label: g.label, applied: g.applied,\n          chip: g.applied ? 'applied' : 'not applied',\n          color: g.applied ? 'var(--app-success)' : 'var(--app-ink-faint)' })),\n        recheck: () => this.setState({ confirm: { title: 'Recheck every launch signal against the live account', verb: 'RECHECK' } })\n      };\n    }\n    if (kind === 'adsAccount') {\n      const f = this.state.adsFilter || 'ALL';\n      const chColor = { SEARCH: 'var(--app-accent)', DISPLAY: 'rgb(var(--pastel-lavender,147 154 255))', DEMAND_GEN: 'rgb(var(--pastel-gold))' };\n      adsAcct = {\n        chips: ['ALL', 'SEARCH', 'DISPLAY', 'DEMAND_GEN'].map(c => ({\n          label: c === 'ALL' ? 'All channels' : c, active: f === c,\n          border: f === c ? 'var(--app-accent)' : 'var(--app-rule)',\n          bg: f === c ? 'var(--app-accent-dim)' : 'transparent',\n          color: f === c ? 'var(--app-accent)' : 'var(--app-ink-soft)',\n          onClick: () => this.setState({ adsFilter: c })\n        })),\n        rows: this.ADS_ACCOUNT.filter(r => f === 'ALL' || r.ch === f).map(r => ({\n          name: r.name, ch: r.ch, chColor: chColor[r.ch] || 'var(--app-ink-soft)',\n          status: r.status, statusColor: r.status === 'enabled' ? 'var(--app-success)' : 'var(--app-ink-faint)',\n          spend: r.spend, conv: r.conv, cpl: r.cpl, ctr: r.ctr\n        })),\n        count: String(this.ADS_ACCOUNT.filter(r => f === 'ALL' || r.ch === f).length),\n        note: 'DemandGen rows expand to Asset Groups (no ad groups). Live metrics from /campaigns/metrics.'\n      };\n    }\n\n    // ═══ SQUR Ads panes ═══\n    const chanChip = (c) => ({ label: c, color: this.SQUR_CHANNELS[c].color });\n    const tierBadge = (t) => t === 'day0' ? { label: 'DAY-0 · READY', color: 'var(--app-success)' } : { label: 'PHASE-2 · STAGED', color: 'var(--app-ink-faint)' };\n\n    // §5 Campaign Builder\n    let builder = null;\n    if (kind === 'builder') {\n      const sel = this.SQUR_SPECS.find(s => s.file === this.state.specSel) || this.SQUR_SPECS[0];\n      const violations = this.SQUR_VALIDATION[sel.file] || [];\n      const clean = violations.length === 0;\n      // operations template by channel\n      let ops;\n      if (sel.channel === 'DISPLAY') ops = [\n        { op: 'CampaignBudgetOperation', act: 'CREATE' }, { op: 'CampaignOperation', act: 'CREATE', paused: true },\n        { op: 'AdGroupOperation', act: 'CREATE' }, { op: 'AdGroupAdOperation', act: 'CREATE', mult: (sel.banners || []).length + ' banner creatives' },\n        { op: 'UserListOperation', act: 'REFERENCE', mult: 'attach ' + sel.audience } ];\n      else if (sel.channel === 'DEMAND_GEN') ops = [\n        { op: 'CampaignBudgetOperation', act: 'CREATE' }, { op: 'CampaignOperation', act: 'CREATE', paused: true, meta: 'channel_type: DEMAND_GEN' },\n        { op: 'AssetGroupOperation', act: 'CREATE', mult: '× 3 (Variant A/B/C)' }, { op: 'AssetGroupAssetOperation', act: 'CREATE', mult: 'creative assets per variant' } ];\n      else ops = [\n        { op: 'CampaignBudgetOperation', act: 'CREATE' }, { op: 'CampaignOperation', act: 'CREATE', paused: true },\n        { op: 'AdGroupOperation', act: 'CREATE', mult: '× ' + sel.adGroups + ' ad groups' }, { op: 'AdGroupAdOperation', act: 'CREATE', mult: '× RSAs' },\n        { op: 'AdGroupCriterionOperation', act: 'CREATE', mult: '× ' + sel.keywords + ' keywords' } ];\n      const groupBy = (t) => this.SQUR_SPECS.filter(s => s.tier === t).map(s => { const on = s.file === this.state.specSel; return { file: s.file, name: s.name, channel: s.channel, chanColor: this.SQUR_CHANNELS[s.channel].color, budget: '€' + s.budget, active: on,\n        // D186: `border`/`bg` were never set → the template's holes resolved to undefined and the button fell back to the UA's light-grey face.\n        border: on ? 'rgb(var(--lead, var(--circuit-glow)) / 0.5)' : 'var(--app-rule, var(--border))', bg: on ? 'rgb(var(--lead, var(--circuit-glow)) / 0.08)' : 'var(--app-surface, rgb(var(--basalt-surface) / 0.35))',\n        onClick: () => this.setState({ specSel: s.file }) }; });\n      builder = {\n        day0: groupBy('day0'), phase2: groupBy('phase2'),\n        sel: { name: sel.name, file: sel.file, channel: sel.channel, chanColor: this.SQUR_CHANNELS[sel.channel].color,\n          budget: '€' + sel.budget + '/day', bid: sel.bid, goal: sel.goal, fallback: sel.fallback || null,\n          isSearch: sel.channel === 'SEARCH', isDisplay: sel.channel === 'DISPLAY', isDemandGen: sel.channel === 'DEMAND_GEN',\n          adGroups: sel.adGroups, keywords: sel.keywords, rsaH: sel.rsaH, rsaD: sel.rsaD,\n          banners: (sel.banners || []).join(' · '), audience: sel.audience, message: sel.message,\n          variants: (sel.variants || []).join(' / '), note: sel.note || null, tierBadge: tierBadge(sel.tier) },\n        ops: ops.map(o => ({ op: o.op, act: o.act, paused: !!o.paused, mult: o.mult || null, meta: o.meta || null })),\n        clean, violations, vCount: violations.length,\n        vChipLabel: clean ? 'Passed — no errors' : violations.length + ' violation' + (violations.length > 1 ? 's' : ''),\n        vChipColor: clean ? 'var(--app-success)' : 'var(--app-warn)',\n        rsaRulesApply: sel.channel !== 'DEMAND_GEN',\n        createBlockedPolicy: !clean,\n        createStubHint: 'python scripts/upload_campaign.py --spec ' + sel.file + ' --customer-id 8357078449 --live'\n      };\n    }\n\n    // §7 Conversions\n    let conv = null;\n    if (kind === 'conv') {\n      conv = { actions: this.SQUR_CONV.map(a => ({ action: a.action, value: a.value, count: a.count, primary: a.primary, isNew: !!a.isNew, estimate: !!a.estimate, note: a.note || null,\n        valColor: a.value === '€0' ? 'var(--app-ink-soft)' : 'var(--app-ink)', primaryChip: a.primary ? { label: a.primary, color: 'var(--app-success)' } : null })) };\n    }\n\n    // §6 Assets & Brand Safety\n    let assets = null;\n    if (kind === 'assets') {\n      const stChip = (s) => ({ applied: ['applied', 'var(--app-success)'], review: ['needs-review', 'var(--app-warn)'], notapplied: ['not-applied', 'var(--app-ink-faint)'], disabled: ['disabled', 'var(--app-ink-faint)'] }[s] || [s, 'var(--app-ink-faint)']);\n      const withCheck = (arr) => arr.map(g => { const cur = this.state.assetChecks[g.id] || g.state; const c = stChip(cur); return Object.assign({}, g, { curState: cur, chipLabel: c[0], chipColor: c[1],\n        cycle: () => this.setState(s => { const nx = { applied: 'review', review: 'notapplied', notapplied: 'applied', disabled: 'disabled' }[cur]; return { assetChecks: Object.assign({}, s.assetChecks, { [g.id]: nx }) }; }) }); });\n      assets = { extensions: withCheck(this.SQUR_ASSETS), safety: withCheck(this.SQUR_SAFETY).map(g => Object.assign({}, g, {\n        toneColor: g.kind === 'allow' ? 'var(--app-success)' : g.kind === 'value' ? 'var(--app-accent)' : 'var(--app-fail)',\n        sampleStr: (g.sample || []).join(' · ') + (g.count && g.sample && g.count > g.sample.length ? ' +' + (g.count - g.sample.length) + ' more' : '') })) };\n    }\n\n    // §8 Budget Pacing & Kill-Switch\n    let pacing = null;\n    if (kind === 'pacing') {\n      const lvlColor = { green: 'var(--app-success)', amber: 'var(--app-warn)', red: 'var(--app-fail)' };\n      const stageColor = { pinned: 'var(--app-ink-faint)', cold: 'var(--app-accent)', lock: 'var(--app-warn)', stable: 'rgb(var(--pastel-lavender,147 154 255))', revenue: 'var(--app-success)' };\n      const glMet = this.SQUR_GREENLIGHT.filter(g => g.met).length;\n      pacing = {\n        phases: this.SQUR_PHASES.map(p => ({ phase: p.phase, window: p.window, budget: '€' + p.budget + '/day',\n          spent: p.ceiling ? '€' + p.spent.toLocaleString() + ' / €' + p.ceiling.toLocaleString() : '—',\n          pct: p.ceiling ? Math.round(p.spent / p.ceiling * 100) : 0, pctW: p.ceiling ? Math.round(p.spent / p.ceiling * 100) + '%' : '0%',\n          target: p.target, running: p.state === 'running', locked: p.state === 'locked',\n          barColor: p.state === 'running' ? 'var(--app-success)' : 'var(--app-rule)',\n          badge: p.state === 'running' ? { label: 'LIVE', color: 'var(--app-success)' } : { label: 'LOCKED', color: 'var(--app-ink-faint)' } })),\n        greenlight: this.SQUR_GREENLIGHT.map(g => ({ crit: g.crit, target: g.target, met: g.met,\n          valueLabel: (g.eur ? '€' : '') + g.value + (g.pct || g.unit ? '%' : ''),\n          color: g.met ? 'var(--app-success)' : 'var(--app-fail)', icon: this.ic(g.met ? 'check' : 'x', 13) })),\n        glMet, glTotal: this.SQUR_GREENLIGHT.length, glAllMet: glMet === this.SQUR_GREENLIGHT.length,\n        glColor: glMet === this.SQUR_GREENLIGHT.length ? 'var(--app-success)' : 'var(--app-warn)',\n        bidprog: this.SQUR_BIDPROG.map(b => ({ campaign: b.campaign, stage: b.stage, label: b.label, color: stageColor[b.stage] })),\n        kill: this.SQUR_KILL.map(kk => ({ scope: kk.scope, rule: kk.rule, hint: kk.hint, level: kk.level, color: lvlColor[kk.level],\n          scopeColor: kk.scope === 'Account' ? 'var(--app-fail)' : kk.scope === 'Campaign' ? 'var(--app-warn)' : 'var(--app-ink-soft)' })),\n        killGreen: this.SQUR_KILL.filter(k => k.level === 'green').length, killAmber: this.SQUR_KILL.filter(k => k.level === 'amber').length, killRed: this.SQUR_KILL.filter(k => k.level === 'red').length,\n        powerIcon: this.ic('power', 16),\n        openPauseAll: () => this.setState({ pauseAll: { typed: '' } })\n      };\n    }\n\n    // Emergency Pause ALL modal (type-to-confirm THEN hold-to-confirm)\n    const pa = this.state.pauseAll;\n    const pauseAllData = pa ? {\n      typed: pa.typed, armed: pa.typed === 'PAUSE ALL', disarmed: pa.typed !== 'PAUSE ALL',\n      onType: (e) => this.setState({ pauseAll: { typed: e.target.value } }),\n      cancel: () => this.setState({ pauseAll: null, holdActive: false }),\n      holdActive: this.state.holdActive, fillWidth: this.state.holdActive ? '100%' : '0%',\n      holdStart: () => { if (this.state.pauseAll && this.state.pauseAll.typed === 'PAUSE ALL') this.holdStartPause(); },\n      holdCancel: () => this.holdCancel(),\n      armColor: pa.typed === 'PAUSE ALL' ? 'var(--app-fail)' : 'var(--app-rule)'\n    } : null;\n\n    // D2 permission model: DENIED overlay for an inbox-routed/⌘K/mega-menu attempt at a stream\n    // the current role can't reach (docs/CANVAS-IMPROVEMENT-PLAN.md item 15).\n    const accessDeniedData = this.state.accessDenied ? {\n      label: this.state.accessDenied.label, role: this.state.accessDenied.role,\n      close: () => this.setState({ accessDenied: null })\n    } : null;\n\n    // W2 · Submit-a-brief wizard (Projects List source :349). Brief → Agent → Confirm; copy verbatim.\n    const bw = this.state.briefWiz;\n    let briefWizData = null;\n    if (bw) {\n      const setBw = (patch) => this.setState(x => ({ briefWiz: Object.assign({}, x.briefWiz, patch) }));\n      const agent = this.BRIEF_AGENTS.find(a => a.id === bw.agent) || null;\n      const canNext = bw.step === 0 ? bw.text.trim().length > 0 : bw.step === 1 ? !!agent : true;\n      briefWizData = {\n        steps: ['Brief', 'Agent', 'Confirm'].map((label, i) => { const done = i < bw.step, cur = i === bw.step;\n          return { n: i + 1, label, bg: cur || done ? 'var(--app-accent)' : 'transparent', fg: cur || done ? 'rgb(var(--basalt-deep, 14 14 16))' : 'var(--app-ink-faint)',\n            border: cur || done ? 'var(--app-accent)' : 'var(--app-rule)', labelColor: cur ? 'var(--app-ink)' : 'var(--app-ink-faint)' }; }),\n        isStep0: bw.step === 0, isStep1: bw.step === 1, isStep2: bw.step === 2, notLastStep: bw.step < 2,\n        text: bw.text, count: bw.text.length, onText: (e) => setBw({ text: e.target.value }),\n        agents: this.BRIEF_AGENTS.map(a => ({ id: a.id, label: a.label, desc: a.desc, skills: a.skills,\n          border: a.id === bw.agent ? 'var(--app-accent)' : 'var(--app-rule)', bg: a.id === bw.agent ? 'var(--app-accent-dim)' : 'transparent',\n          pick: () => setBw({ agent: a.id }) })),\n        preview: bw.text.trim(), agentLabel: agent ? agent.label : '—', estCost: agent ? agent.est.toLocaleString('de-DE') : '—',\n        backOpacity: bw.step === 0 ? '0.35' : '1', nextOpacity: canNext ? '1' : '0.4',\n        back: () => setBw({ step: Math.max(0, bw.step - 1) }),\n        next: () => { if (canNext) setBw({ step: Math.min(2, bw.step + 1) }); },\n        close: () => this.setState({ briefWiz: null }),\n        submit: () => this.setState(x => ({ briefWiz: null,\n          briefsSubmitted: [{ title: bw.text.trim().split('\\n')[0].slice(0, 60), agent: agent ? agent.label : '—' }].concat(x.briefsSubmitted || []) }))\n      };\n    }\n\n    // HoldToConfirm overlay — press-and-hold ~1.2s to fire the mutating op\n    const confirmData = this.state.confirm ? { title: this.state.confirm.title, verb: this.state.confirm.verb,\n      holdActive: this.state.holdActive, fillWidth: this.state.holdActive ? '100%' : '0%',\n      holdStart: () => this.holdStart(), holdCancel: () => this.holdCancel(),\n      cancel: () => this.setState({ confirm: null, holdActive: false }) } : null;\n\n    const roster = this.ROSTER.map(a => ({ name: a.name, task: a.task, color: this.dotCol(a.status) }));\n\n    const taskList = this.state.tasks.map(id => this.TASKS.find(t => t.id === id)).filter(Boolean).map(t => ({\n      label: t.label, meta: t.meta, color: this.dotCol(t.tone), onDismiss: () => this.setState(s => ({ tasks: s.tasks.filter(x => x !== t.id) }))\n    }));\n\n    // Command palette\n    const cmds = [];\n    Object.values(this.STREAMS).filter(s => this.canAccess(this.state.role, s.id)).forEach(s => cmds.push({ group: 'Navigate', label: 'Go to → ' + s.label, iconEl: this.ic(s.icon, 16), run: () => this.selectStream(s.id) }));\n    // Leaf items — deals and clients are reachable by name, not just their stream (Batch 6).\n    const LEAVES = [\n      { group: 'Deals', label: 'Helsing GmbH — Autonomous Finance Ops', to: 'proposalPipeline' },\n      { group: 'Deals', label: 'Munich Re — Claims triage agents', to: 'proposalPipeline' },\n      { group: 'Deals', label: 'N26 — KYC automation', to: 'proposalPipeline' },\n      { group: 'Deals', label: 'Personio — HR data pipeline', to: 'proposalPipeline' },\n      { group: 'Clients', label: 'Helsing GmbH · everything they see', to: 'clientIndex' },\n      { group: 'Clients', label: 'N26 · everything they see', to: 'clientIndex' },\n      { group: 'Clients', label: 'Personio · everything they see', to: 'clientIndex' },\n      { group: 'Packages', label: 'Autonomous Finance Ops · package', to: 'packages' },\n      { group: 'Packages', label: 'Content Engine · package', to: 'packages' }\n    ];\n    LEAVES.filter(l => this.canAccess(this.state.role, l.to)).forEach(l => cmds.push({\n      group: l.group, label: l.label, iconEl: this.ic('content', 16),\n      run: () => this.selectStream(l.to)\n    }));\n    // O1.2 · every toolbox template is a palette leaf. Opens the Toolbox on that template's phase tab\n    // and opens the document editor, so ⌘K \"Cap Table\" lands on the editable document, not a list.\n    const TSP = window.__dcToolset;\n    if (TSP && TSP.items && this.canAccess(this.state.role, 'toolbox')) {\n      TSP.items.forEach(it => cmds.push({\n        group: 'Toolbox', label: it.title + ' · ' + it.meta, iconEl: this.ic('apps', 16),\n        run: () => {\n          const want = it.phase + ' ' + (it.meta.split(' · ')[1] || '');\n          const tab = (this.STREAMS.toolbox.subs || []).findIndex(s => s.label === want);\n          this.setState({ active: 'toolbox', sub: tab > 0 ? tab : 0, pane: 'default', palette: false },\n            () => this.openDetail({ doc: this.toolboxDoc(it), kind: 'Toolbox · ' + it.meta, id: it.title, title: it.title,\n              chip: 'SNAPSHOT', chipColor: 'var(--app-ink-soft)',\n              sections: [{ label: 'Template', value: it.title }, { label: 'Phase', value: it.meta }, { label: 'What it does', value: it.desc || '' }] }));\n        }\n      }));\n    }\n    cmds.push({ group: 'System', label: 'Toggle theme · ' + (isDark ? 'light' : 'dark'), iconEl: this.ic(isDark ? 'sun' : 'moon', 16), run: () => this.toggleTheme() });\n    cmds.push({ group: 'System', label: 'Open app switcher', iconEl: this.ic('apps', 16), run: () => this.setState({ mega: true, palette: false }) });\n    // P4 · \"Gaps for this stream\" — a query against canvas.index.json, rendered as a pane brief in the\n    // WP-20 drawer. Only offered when the index actually loaded; a dead command that explains itself is\n    // worse than an absent one.\n    if (window.__dcIndex) {\n      const gapStream = this.STREAMS[this.state.active];\n      cmds.push({ group: 'System', label: 'Gaps for this stream · ' + (gapStream ? gapStream.label : this.state.active),\n        iconEl: this.ic('search', 16), run: () => this.openGapsBrief(this.state.active) });\n    }\n    cmds.push({ group: 'System', label: 'Run now', iconEl: this.ic('play', 16), run: () => this.setState({ palette: false }) });\n    const q = (this.state.q || '').toLowerCase();\n    const AR_ = window.AgentRegistry, IT_ = window.InboxThreads; let taskHits = [], opMode = false;\n    if (q && AR_ && AR_.searchTasks && IT_ && typeof IT_.rawThreads === 'function') { const r = AR_.searchTasks(AR_.tasks(IT_.projectThreads(IT_.rawThreads(this.state.ctxThreads)), this.state.brandKey.slice(0, 3).toUpperCase()), this.state.q); taskHits = r.hits; opMode = r.operators; }\n    const filt = opMode ? [] : (q ? cmds.filter(c => c.label.toLowerCase().indexOf(q) >= 0) : cmds);\n    taskHits.slice(0, 8).forEach(t => { filt.push({ group: 'Tasks', label: t.key + ' \\u00b7 ' + t.title + ' \\u00b7 ' + t.status, iconEl: this.ic('check', 16), run: () => this.goTo('inbox', 'Inbox', { inboxAgent: t.agent }) }); });\n    const grouped = {};\n    filt.forEach(c => { (grouped[c.group] = grouped[c.group] || []).push(c); });\n    const paletteGroups = Object.keys(grouped).map(gk => ({ group: gk, items: grouped[gk].slice(0, 8).map(c => ({ label: c.label, iconEl: c.iconEl, onClick: c.run })) }));\n\n    const megaGroups = this.MEGA.map(g => ({\n      label: g.label,\n      tiles: g.ids.filter(id => this.STREAMS[id] && this.canAccess(this.state.role, id)).map(id => {\n        const s = this.STREAMS[id];\n        return { label: s.label, tag: s.tag, surfaces: (s.subs.filter(x => !x.bucket).length) + ' surfaces', iconEl: this.ic(s.icon, 22), onClick: () => this.selectStream(id) };\n      })\n    }));\n\n    return {\n      // FQ-9 (D158): business = tenant, data only. Rail (role template) and brand preview are untouched.\n      businesses: (this.state.businessIds || ['aaas']).map(id => {\n        const on = (this.state.business || 'aaas') === id; const B = (window.AAAS_BRANDS || {})[id];\n        return { id, label: (B && B.name) || id.toUpperCase(), active: on,\n          border: on ? 'rgb(var(--lead) / 0.5)' : 'var(--border)', bg: on ? 'rgb(var(--lead) / 0.1)' : 'transparent',\n          color: on ? 'rgb(var(--lead))' : 'rgb(var(--text))',\n          onClick: () => this.setState({ business: id, ops: true }, () => this.loadToolset()) };\n      }),\n      isFlow: flowSteps.length > 0, flowSteps, flowLabel: flowId ? this.FLOWS[flowId].label : '',\n      linkStale: !!this.state.linkStale, reloadPage: () => location.reload(),\n      rootStyle, theme: this.state.theme, wordmark, tagline, n8: 8,\n      // Brand lockup · ADR-036 §3. `brandLabelFull` is the untouched label (aria); `brandWordmark`\n      // is that label's brand segment with every \"aaas\" stripped, then CSS-uppercased.\n      // ADR-028 pins this surface's canonical host: hostnameFor('canvas') === 'canvas.aaas.love'.\n      // Not an invented \"aaas.canvas\" — the registry allows exactly canvas./toolbox./vault./store\n      // under aaas.love, and ADR-028 exists because a wrong canvas host caused a redirect loop.\n      ...(() => {\n        const full = (mvp || brandKey !== 'aaas')\n          ? (brand.site || String(brand.name || '').toLowerCase())\n          : 'canvas.aaas.love';\n        // D165: the logo switch changes the mark; the surface name \"canvas\" stays with every brand.\n        return { brandLabelFull: full, brandWordmark: this.domainWordmark('canvas.aaas.love') };\n      })(),\n      beats, lastRun: '06:30Z · ok',\n      hbStatic: !mob, hbTicker: !!mob,\n      tickerBeats: (() => { const one = beats.concat([{ label: 'Last run', value: '06:30Z · ok', color: 'rgb(var(--text) / 0.85)' }]); return one.concat(one); })(),\n      ...(() => {\n        const TB = (window.AAAS_BRANDS || {})[brandKey] || {};\n        const raw = (isDark ? TB.logoDark : TB.logoLight) || TB.logoDark;\n        const isA = brandKey === 'aaas';\n        // D164: resolve through the bundle's resource map (JS-built src is invisible to the bundler), and fall\n        // back to the text wordmark if the image fails — the logo is the brand switch, so it may never render empty.\n        const R = window.__resources || {};\n        const rid = raw ? 'logo-' + String(raw).split('/').pop().replace(/\\.[a-z]+$/i, '') : null;\n        const src = raw && !((this.state.logoFail || {})[brandKey]) ? (R[rid] || String(raw).replace(/^(\\.\\.\\/){3}/, '../../')) : null;\n        return { logoIsAaas: isA, logoIsImg: !isA && !!src, logoIsText: !isA && !src,\n          logoImgEl: src ? React.createElement('img', { src, onError: () => this.setState(s => ({ logoFail: Object.assign({}, s.logoFail, { [brandKey]: true }) })), alt: '', style: { display: 'block', height: 22, width: 'auto', maxWidth: 120, flexShrink: 0 } }) : null, logoText: brand.name };\n      })(),\n      isOps: ops,\n      opsBorder: ops ? 'rgb(var(--lead) / 0.4)' : 'var(--border)',\n      opsBg: ops ? 'rgb(var(--lead) / 0.1)' : 'transparent',\n      opsTrack: 'rgb(var(--basalt-deep))',\n      opsTrackBorder: ops ? 'rgb(var(--lead) / 0.5)' : 'rgb(255 255 255 / 0.14)',\n      opsKnob: ops ? 'rgb(var(--lead))' : 'rgb(255 255 255 / 0.45)',\n      opsKnobLeft: ops ? '16px' : '2px',\n      opsLabelColor: ops ? 'rgb(var(--lead))' : 'var(--app-ink-soft, rgb(var(--text) / 0.7))',\n      modeLabel: ops ? 'OPS' : 'USER',\n      themeIcon: this.ic(isDark ? 'moon' : 'sun', 18),\n      collapseIcon: this.ic(collapsed ? 'chevronRight' : 'chevronLeft', 18),\n      railWidth: collapsed ? '56px' : '192px',\n      isMobile: mob,\n      isWide: !mob,\n      // 4.14: the rail drawer is the primary nav on touch — 44px floor there, untouched on desktop.\n      touchMin: mob ? '44px' : 'auto',\n      barFull: !this.state.narrow,\n      // The lockup ships as two colourways, not as a tintable vector: white artwork on basalt,\n      // black on the warm off-white light theme. Both <img> srcs stay LITERAL so the correct one\n      // paints on the first frame — a {{ }} hole in src makes the browser fetch the unresolved\n      // string and 404 on every load. Pick the colourway, never recolour it.\n      themeIsDark: this.state.theme !== 'light',\n      themeIsLight: this.state.theme === 'light',\n      searchFlex: this.state.narrow ? '0 0 44px' : '1 1 180px',\n      searchMin: this.state.narrow ? '44px' : '180px',\n      railShow: mob ? this.state.railOpen : (!this.state.railHidden || railPeek),\n      // D178 · hover-reveal. A collapsed or hidden rail peeks as an overlay (no layout shift) and folds back on leave.\n      railEdge: !mob && this.state.railHidden && !railPeek,\n      railPeekOn: () => { if (!this.state.mob && (this.state.collapsed || this.state.railHidden) && !this.state.railPeek) this.setState({ railPeek: true }); },\n      railPeekOff: () => { if (this.state.railPeek) { this._railWasPeek = true; this.setState({ railPeek: false }, () => setTimeout(() => { this._railWasPeek = false; }, 50)); } },\n      railScrim: mob && this.state.railOpen,\n      railStyle: mob\n        ? (this.state.railOpen\n            ? 'position:fixed;top:0;left:0;bottom:0;z-index:59;width:min(300px,84vw);flex-shrink:0;border-right:1px solid var(--border);background:rgb(var(--basalt-deep));display:flex;flex-direction:column;overflow:hidden;box-shadow:var(--shadow-menu)'\n            : 'width:52px;flex-shrink:0;border-right:1px solid var(--border);background:rgb(var(--basalt-deep) / 0.5);display:flex;flex-direction:column;overflow:hidden')\n        : (railPeek\n            ? (this.state.railHidden\n                ? 'position:absolute;top:0;left:0;bottom:0;z-index:46;width:192px;border-right:1px solid var(--border);background:rgb(var(--basalt-deep));display:flex;flex-direction:column;overflow:hidden;box-shadow:var(--shadow-menu)'\n                : 'position:relative;z-index:46;width:192px;margin-right:-136px;flex-shrink:0;border-right:1px solid var(--border);background:rgb(var(--basalt-deep));display:flex;flex-direction:column;overflow:hidden;box-shadow:var(--shadow-menu);transition:none')\n            : 'width:' + (collapsed ? '56px' : '192px') + ';flex-shrink:0;border-right:1px solid var(--border);background:rgb(var(--basalt-deep) / 0.5);display:flex;flex-direction:column;overflow:hidden;transition:' + (this._railWasPeek ? 'none' : 'width .3s var(--liquid-ease)')),\n      toggleRail: () => this.setState(s2 => (s2.mob ? { railOpen: !s2.railOpen, settings: false } : { railHidden: !s2.railHidden })),\n      railOpenState: this.state.railOpen,\n      streamCaret: (mob ? this.state.railOpen : !this.state.railHidden) ? 'rotate(180deg)' : 'rotate(0deg)',\n      streamChipColor: (mob ? this.state.railOpen : !this.state.railHidden) ? 'rgb(var(--lead))' : 'var(--app-ink-soft, rgb(var(--text) / 0.7))',\n      closeRail: () => this.setState({ railOpen: false }),\n      settingsOpen: this.state.settings,\n      toggleSettings: () => this.setState(s2 => ({ settings: !s2.settings, railOpen: false })),\n      closeSettings: () => this.setState({ settings: false }),\n      devTools: this.state.devTools,\n      devToolsLabel: this.state.devTools ? 'on' : 'off',\n      devToolsColor: this.state.devTools ? 'rgb(var(--lead))' : 'var(--app-ink-soft, rgb(var(--text) / 0.7))',\n      toggleDevTools: () => this.setState(s2 => {\n        const on = !s2.devTools;\n        try { localStorage.setItem('canvas.devTools', on ? '1' : '0'); } catch (e) {}\n        // Leaving dev tools returns every plane to its live state — a forced state must never\n        // survive into a normal session.\n        return on ? { devTools: true } : { devTools: false, pane: 'default' };\n      }),\n      themeLabel: isDark ? 'dark' : 'light',\n      openMegaFromSettings: () => this.setState({ settings: false, mega: true }),\n      openRosterFromSettings: () => this.setState({ settings: false, rosterOpen: true }),\n      openHelpFromSettings: () => { this.setState({ settings: false }); openTour(); },\n      // ? menu (2026-09-03). \"Step plan\" = the intro tour that used to open directly from ?; it can now be\n      // dismissed (sets canvas.introSeen, same as its own close) and re-activated any time. The eight\n      // walkthrough tracks = CX-01…07 + the step plan itself as track 08, so the list is complete in one place.\n      helpMenuOpen: this.state.helpMenu,\n      toggleHelpMenu: () => this.setState(s => ({ helpMenu: !s.helpMenu, brandMenu: false, roleMenu: false })),\n      helpStepPlanLabel: ts >= 0 ? 'Dismiss the step plan' : 'Step plan · intro tour',\n      helpStepPlanMeta: ts >= 0 ? 'showing' : ((() => { try { return localStorage.getItem('canvas.introSeen') ? 'seen' : 'new'; } catch (e) { return ''; } })()),\n      helpStepPlanDot: ts >= 0 ? 'var(--app-warn)' : 'var(--app-success)',\n      helpStepPlanToggle: () => { this.setState({ helpMenu: false }); if (ts >= 0) closeTour(); else openTour(); },\n      helpOnbMeta: Object.keys(this.state.onbDone || {}).length + ' / ' + this.ONB_STEPS.length,\n      helpOpenOnboarding: () => this.setState({ helpMenu: false, ops: true, active: 'onboarding', sub: 0, pane: 'default' }),\n      // Walkthroughs, by name only — no leading ids (operator, 2026-09-03). Same-tab navigation; the\n      // in-canvas step plan is the last entry so the list is complete without a second surface.\n      helpTracks: [\n        ['Admit the scale', '0:26', './CanvasTour.dc.html#CX-01'],\n        ['Four fixed regions', '0:25', this.rootPrefix + 'assets/videos/CX-02 Four Regions.dc.html'],\n        ['Roles are filters', '1:08', './CanvasTour.dc.html#CX-03'],\n        ['A stream is one job', '0:45', './CanvasTour.dc.html#CX-04'],\n        ['Two ways in', '0:34', './CanvasTour.dc.html#CX-05'],\n        ['Why you can trust it', '0:44', './CanvasTour.dc.html#CX-06'],\n        ['One product, for anyone', '0:30', './CanvasTour.dc.html#CX-07'],\n        ['Step plan · intro tour', '6 steps', null]\n      ].map(([label, len, href]) => ({ label, len, dot: href ? 'rgb(var(--lead))' : 'var(--app-success)',\n        onClick: () => { this.setState({ helpMenu: false }); if (href) { try { window.location.assign(href); } catch (e) {} } else openTour(); } })),\n      railLabel: mvp ? 'NEW USER · YOUR PROJECT' : ops ? String(this.ROLE_LABEL[this.state.role] || this.state.role).toUpperCase() : 'USE',\n      showLabels, railGroups,\n      streamIcon: this.ic(st.icon, 14), activeLabel: st.label,\n      archivedBanner: !!st.archived, archivedReasonText: st.archivedReason || '',\n      subItems, hero,\n      // F-7a fix (4.17a): a forced state REPLACES plane content on the lens and slot paths too —\n      // the `kind` path already gates its bodies on `isDefault`.\n      isPitches, isVox, isVid,\n      isFeeds, feedContracts: this._feedContracts,\n      pitchPlannedIcon: this.ic('clock', 30),\n      isSlots: !v2Arch && (!kind && !devPane && pane === 'default'),\n      devPane: pane === 'default' ? devPane : null,\n      crumb: sh.crumb || st.crumb || null,\n      contractBadge,\n      kind, isZui: kind === 'zui', isFoundry: kind === 'foundry', isCro: kind === 'cro', isOnboarding: kind === 'onboarding' && !isOnbWizard, isAccount: kind === 'account', isIcp: kind === 'icp', isLeads: kind === 'leads', isScout: kind === 'scout', isComposer: kind === 'composer', isConvert: kind === 'convert', isKb: kind === 'kb', isGrowthRun: kind === 'growthrun',\n      isMk: kind === 'mk', isPd: kind === 'pd', pd, isDs: kind === 'ds', isCs: kind === 'cs', isQl: kind === 'ql', isPdt: kind === 'pdt', isPl: kind === 'pl',\n      // CM-H: Flows is the one context tab the shell renders itself (ArchetypeHost `process`), so it is\n      // excluded here rather than handed to PaneContext, which has no branch for it.\n      isCtx: kind === 'ctx' && !isCtxFlows, isCtxFlows, gtmTemplate, gtmInstance, gtmValid: gtmValidation.ok,\n      gtmErrorTitle: 'Strategic GTM flow failed validation — ' + gtmValidation.errors.length + ' error(s)',\n      gtmErrorDetail: gtmValidation.errors.map(e => e.message).join(' · '),\n      gtmNote, gtmNoteTone: 'ok',\n      isInb: kind === 'inb', // NIGHT-PLAN P1: 'Awaiting approval' is no longer diverted to PaneApprovals\n      subLabel: subLabel || '',\n      mkSuite: this.MK_SUITE,\n      mkConfirm: (title, verb, onConfirm) => this.setState({ confirm: { title: title, verb: verb || 'CONFIRM', onConfirm: onConfirm } }),\n      // CM-F · the personal-grant writers, exposed so the dc-import's on-grant/on-revoke holes resolve.\n      // A constructor method is NOT reachable from a template hole — omitting these made the hold\n      // complete and mutate nothing, the exact silent no-op receipt #15 catalogued.\n      ctxGrant: this.ctxGrant, ctxRevoke: this.ctxRevoke, ctxDerive: this.ctxDerive,\n      // Inbox → dashboard routing: the console triages; when a thread needs deeper management\n      // UI than mail gestures, it hands off to the owning stream.\n      inboxRoute: (id, sub) => {\n        const st2 = this.STREAMS[id];\n        if (!st2) return;\n        if (!this.canAccess(this.state.role, id)) { this.setState({ accessDenied: { label: st2.label, role: this.state.role } }); return; }\n        this.setState({ active: id, sub: sub || 0 });\n      },\n      icp, leads, leadsDisco: leads && leads.disco ? leads.disco : null,\n      isBuilder: kind === 'builder', isAssets: kind === 'assets', isConv: kind === 'conv', isPacing: kind === 'pacing',\n      isMail: kind === 'mail', mail, outSuite: this.OUT_SUITE,\n      isMailLadder: kind === 'mail' && st.id === 'outbound2' && rawSubLabel === 'Send',\n      isClient: kind === 'client', client, chat, tour, openTour,\n      isRuntrace: kind === 'runtrace', dt, sel,\n      isRev: kind === 'rev', rev,\n      isFin: kind === 'fin', fin,\n      isSvc: kind === 'svc', svc,\n      isWk: kind === 'wk', wk,\n      isAsk: kind === 'ask', ua,\n      isLanding: kind === 'landing', landing, showHero: kind !== 'landing' && kind !== 'delivery',\n      isHub: kind === 'domainsHub', hub, isDomain: kind === 'domain', dom, isCnt: kind === 'cnt', cnt,\n      isLens: kind === 'lens', lens,\n      // WP-20: record-detail plane, built fresh each render from state.detailRecord (docs/VIEW-SYSTEM.md).\n      // Selection is deliberately NOT cleared on stream/tab navigation — it survives view changes\n      // per the WP's acceptance criterion; only closeDetail()/Esc/backdrop clear it.\n      detailOpen: !!this.state.detailRecord,\n      detailSplit: this.state.detailSplit,\n      detailWidth: this.state.detailSplit ? 'min(880px,96vw)' : 'min(400px,100%)',\n      readableMaxWidth: this.state.detailSplit ? '68ch' : '100%',\n      viewerIsReadable: this.state.viewerMode !== 'rich',\n      viewerIsRich: this.state.viewerMode === 'rich',\n      viewerReadableBg: this.state.viewerMode !== 'rich' ? 'rgb(var(--lead) / 0.14)' : 'transparent',\n      viewerReadableInk: this.state.viewerMode !== 'rich' ? 'rgb(var(--lead))' : 'var(--app-ink-soft)',\n      viewerRichBg: this.state.viewerMode === 'rich' ? 'rgb(var(--lead) / 0.14)' : 'transparent',\n      viewerRichInk: this.state.viewerMode === 'rich' ? 'rgb(var(--lead))' : 'var(--app-ink-soft)',\n      setViewerReadable: () => this.setViewerMode('readable'),\n      setViewerRich: () => this.setViewerMode('rich'),\n      detail: this.state.detailRecord ? {\n        kind: this.state.detailRecord.kind, id: this.state.detailRecord.id, title: this.state.detailRecord.title,\n        chip: this.state.detailRecord.chip, chipColor: this.state.detailRecord.chipColor,\n        sections: this.state.detailRecord.sections || [],\n        // Toolbox document (O1b first cut): the template's own schema declares which context fields matter;\n        // the tenant context pack supplies the values; the user edits them here. Field state is honest —\n        // 'context' = auto-filled from the pack, 'ai' = written by Optimise, 'manual' = you typed it,\n        // 'empty' = nothing yet. Edits live in state.docEdits keyed <template>·<field>.\n        isDoc: !!this.state.detailRecord.doc,\n      docDiff: this.state.docDiff ? Object.assign({}, this.state.docDiff,\n        { accept: () => this.docAcceptDiff(), discard: () => this.setState({ docDiff: null }),\n          hasRows: (this.state.docDiff.rows || []).length > 0 }) : null,\n        doc: this.state.detailRecord.doc ? (() => {\n          const d = this.state.detailRecord.doc, ed = this.state.docEdits || {};\n          const fields = d.fields.map(f => {\n            const key = d.id + '·' + f.id, edited = ed[key], gen = (this.state.docAi || {})[key];\n            const val = edited !== undefined ? edited : (gen !== undefined ? gen : (f.value || ''));\n            const state = edited !== undefined ? 'manual' : (gen !== undefined ? 'ai' : (val ? f.origin : 'empty'));\n            const dot = { context: 'var(--app-success)', ai: 'var(--app-accent)', manual: 'var(--app-ink-soft)', empty: 'var(--app-fail)' }[state] || 'var(--app-ink-faint)';\n            return { label: f.label, state, dot, source: f.source, value: val, rows: f.long ? 4 : 2,\n              placeholder: f.placeholder || '', hint: f.hint || '',\n              onChange: (e) => { const v = e && e.target ? e.target.value : ''; this.setState(s => { const n = Object.assign({}, s.docEdits); n[key] = v; return { docEdits: n }; }); } };\n          });\n          const fin = (this.state.docFinal || {})[d.id];\n          const filled = fields.filter(f => f.value).length;\n          return { count: d.fields.length, fields, filled,\n            summary: filled + ' of ' + d.fields.length + ' filled' + (fin ? ' · finalised v' + fin.v + ' at ' + fin.at : ''),\n            busy: this.state.docBusy,\n            optimiseLabel: this.state.docBusy ? 'Optimising…' : 'Optimise document',\n            canFinalise: filled === d.fields.length,\n            finaliseLabel: filled === d.fields.length ? 'Finalise' : 'Finalise · ' + (d.fields.length - filled) + ' empty',\n            optimise: () => this.docOptimise(Object.assign({}, d, { rawFields: d.fields.map(f => ({ id: f.id, label: f.label, value: f.value, aiPrompt: f.aiPrompt, hint: f.hint })) })),\n            finalise: () => this.docFinalise(Object.assign({}, d, { rawFields: d.fields.map(f => ({ id: f.id, label: f.label, value: f.value })) })) };\n        })() : null,\n        openFullPage: () => this.openFullPage(),\n        close: () => this.closeDetail()\n      } : null,\n      isLaunch: kind === 'launch', launch, isAdsAccount: kind === 'adsAccount', adsAcct,\n      brandKey, brandLabel: brand.name,\n      brandMenuOpen: this.state.brandMenu,\n      brandCaret: this.state.brandMenu ? 'rotate(180deg)' : 'rotate(0deg)',\n      brandSwatch: brand.swatch,\n      cycleBrand: () => { const ks = Object.keys(this.BRANDS); const nx = ks[(ks.indexOf(brandKey) + 1) % ks.length];\n        this.setState({ brandKey: nx, brandMenu: false }, () => { setTimeout(() => this.broadcastBrandToFrames(), 30); this.loadToolset(); }); },\n      brandCycleTitle: (() => { const ks = Object.keys(this.BRANDS); const nx = this.BRANDS[ks[(ks.indexOf(brandKey) + 1) % ks.length]]; return 'Brand: ' + brand.name + ' — click to switch to ' + (nx && nx.name); })(),\n      toggleBrandMenu: () => this.setState(s => ({ brandMenu: !s.brandMenu, roleMenu: false })),\n      brands: Object.keys(this.BRANDS).map(kk => {\n        const b = this.BRANDS[kk];\n        return { key: kk, label: b.name, tag: b.tag, swatch: b.swatch, active: kk === brandKey,\n          bg: kk === brandKey ? 'rgb(var(--lead) / 0.1)' : 'transparent',\n          color: kk === brandKey ? 'rgb(var(--lead))' : 'rgb(var(--text))',\n          onClick: () => this.setState({ brandKey: kk, brandMenu: false }, () => { setTimeout(() => this.broadcastBrandToFrames(), 30); this.loadToolset(); }) };\n      }),\n      builder, conv, assets, pacing, pauseAllData,\n      builderAlertIcon: this.ic('alert', 15), builderClockIcon: this.ic('warnState', 15), assetsInfoIcon: this.ic('shield', 16),\n      hasStates,\n      // F-7a (D27): the control is dev-only; the state BLOCKS stay bound to hasStates, so a plane's\n      // loading/empty/error rendering is a code contract independent of who can flip it.\n      stateCtl: hasStates && this.state.devTools,\n      onb, acc,\n      coachOpen: this.state.coach, closeCoach: () => this.setState({ coach: false }),\n      helpIconEl: this.ic('help', 18),\n      openHelp: () => this.setState({ ops: !!this.state.mvp ? false : true, active: 'onboarding', sub: 0, pane: 'default' }),\n      pane, isDefault: pane === 'default', isLoading: pane === 'loading', isEmpty: pane === 'empty', isError: pane === 'error', isModeled,\n      paneStates, skeletons, stateBox, modeledBox, retry,\n      emptyCtaClick: kind === 'leads' ? () => this.setState({ pane: 'default', disco: { step: 0, icp: this.DISCO_ICPS[0], maxLeads: 250, sources: ['apollo', 'clay'], status: null } }) : () => this.setState({ pane: 'default' }),\n      errIconEl: this.ic('warnState', 30), escIconEl: this.ic('alert', 15),\n      zui, foundry, cro, confirmData, briefWizData, accessDeniedData, isProto, isGrowth, growthSub, isHome, homeTab, homeGoTab, homeGoStream, isProposals, proposalKind, isDelivery, isPackages, isTourSteps, tourStepsItems,\n      isClientIndex, showDealChip, dealChip,\n      isClientProcess, processTemplate, processInstance,\n      startEngagement: storeHasInstance ? null : startEngagement,\n      undoEngagement: (window.ProcessInstantiate && window.ProcessInstantiate.canUndo(startInstanceId)) ? undoEngagement : null,\n      procNote: this.state.procNote || '', procNoteTone: this.state.procNoteTone || 'ok',\n      isOnbWizard, wizTemplate, wizInstance, wizStep, wizValid: wizValidation.ok && !!wizTemplate,\n      wizErrorDetail: !wizTemplate ? 'process-spec.js has no services-activation template.' : (wizValidation.errors[0] ? wizValidation.errors[0].code + ' · ' + wizValidation.errors[0].message : ''),\n      processValid: processValidation.ok,\n      processErrorTitle: !processValidation.ok ? (processValidation.errors[0] ? processValidation.errors[0].code.replace(/_/g, ' ').toLowerCase() : 'invalid process plane') : '',\n      processErrorDetail: !processValidation.ok ? (processValidation.errors[0] ? processValidation.errors[0].message : 'This process plane failed validation.') : '',\n      isClientGate, gateProposal, gateApprove, gateReject, gateRowsReal, gateBlockedReason,\n      isPkgDiff, diffSpec, isClientBrief, briefSpec, isClientLedger, ledgerSpec,\n      archetypeValidation,\n      archetypeValid: !archetypeValidation || archetypeValidation.ok,\n      archetypeErrorTitle: (archetypeValidation && !archetypeValidation.ok && this._planeValidator) ? this._planeValidator.describeFailure(archetypeValidation).title : '',\n      archetypeErrorDetail: (archetypeValidation && !archetypeValidation.ok && this._planeValidator) ? this._planeValidator.describeFailure(archetypeValidation).detail : '',\n      isGrowthCalConfig, growthCalItems,\n      growthCalValid: !growthCalValidation || growthCalValidation.ok,\n      growthCalErrorTitle: (growthCalValidation && !growthCalValidation.ok && this._planeValidator) ? this._planeValidator.describeFailure(growthCalValidation).title : '',\n      growthCalErrorDetail: (growthCalValidation && !growthCalValidation.ok && this._planeValidator) ? this._planeValidator.describeFailure(growthCalValidation).detail : '',\n      viewStrip, hasViewStrip: viewStrip.length > 1, activeView, viewStripLabel: (subLabel || '').toUpperCase(),\n      calloutLabel: st.label + ' · workspace',\n      calloutBody: st.desc,\n      slots,\n      rightPanel, roster, rosterCount: this.agentCounts().total || this.ROSTER.length,\n      inboxAgent: this.state.inboxAgent || '', clearInboxAgent: () => this.setState({ inboxAgent: null }),\n      ctxThreads: this.state.ctxThreads || [],\n      // Mega-menu footer — derived with the KPI strip, never typed (see the DOMAINS derivation).\n      domFooter: this._domFooter || '',\n      rosterOpen: rosterEff,\n      // D178: the pin switch lives at the right end of the tab strip, directly left of the roster, on desktop always.\n      rosterFab: clientChrome ? !this.state.rosterClient : (mob ? !this.state.rosterOpen : (P.rightPanel !== false && !mvp)),\n      rosterPinned: !!this.state.rosterOpen, rosterPeeking: rosterPeek,\n      rosterSlim: !clientChrome && mob && !mvp && P.rightPanel !== false && !this.state.rosterOpen,\n      // D221/D222 (operator ask): per-state count pills replace the single bare 'needs' pill — one pill\n      // per attention state with n > 0. Running/idle stay plain dots; they aren't attention counts.\n      slimCounts: (() => { const c = this.agentCounts(), BG = { needs: 0.18, stuck: 0.16, done: 0.16 }, BD = { needs: 0.6, stuck: 0.5, done: 0.5 };\n        return ['needs', 'stuck', 'done'].filter(k => c[k] > 0).map(k => ({ key: k, n: c[k], isNeeds: k === 'needs', isStuck: k === 'stuck', isDone: k === 'done',\n          chipBg: 'rgb(var(--status-' + k + ') / ' + BG[k] + ')', chipBorder: 'rgb(var(--status-' + k + ') / ' + BD[k] + ')' })); })(),\n      slimShapes: ['running', 'idle'].filter(k => this.agentCounts()[k] > 0).map(k => ({ key: k, isRunning: k === 'running', isIdle: k === 'idle' })),\n      rosterEdge: !clientChrome && !mob && !mvp && P.rightPanel !== false && !this.state.rosterOpen && !rosterPeek,\n      rosterPeekOn: () => { if (!this.state.rosterOpen && !this.state.rosterPeek) this.setState({ rosterPeek: true }); },\n      rosterPeekOff: () => { if (this.state.rosterPeek) this.setState({ rosterPeek: false }); },\n      askGrow: this.state.askOn === false ? '0 0 auto' : '1 0 auto',\n      rosterWidth: rosterEff ? '288px' : '64px',\n      // Closed = just the toggle, but on an opaque ground (D173): it sits over the tail of the Tier-2 tab\n      // strip, so a transparent box let tab labels read through the switch. The left edge fades so the\n      // strip is covered, not cut.\n      rosterStyle: (!rosterEff\n        ? 'position:absolute;top:10px;right:0;z-index:40;width:auto;padding:4px 14px 0 32px;background:linear-gradient(90deg, rgb(var(--basalt-deep) / 0) 0, rgb(var(--basalt-deep)) 24px);border:none;overflow:visible'\n        : (mob\n            ? 'position:fixed;top:0;right:0;bottom:0;z-index:59;width:min(320px,86vw);border-left:1px solid var(--border);background:rgb(var(--basalt-deep));overflow-y:auto;padding:16px;box-shadow:var(--shadow-menu);display:flex;flex-direction:column'\n            : (rosterPeek\n                ? 'position:absolute;top:0;right:0;bottom:0;z-index:46;width:288px;border-left:1px solid var(--border);background:rgb(var(--basalt-deep));overflow-y:auto;padding:16px;box-shadow:var(--shadow-menu);display:flex;flex-direction:column'\n                : 'width:288px;flex-shrink:0;border-left:1px solid var(--border);background:rgb(var(--basalt-surface) / 0.3);overflow-y:auto;padding:16px;display:flex;flex-direction:column'))),\n      blockers: this.BLOCKERS.filter(b => this.state.blockersDone.indexOf(b.id) < 0).map(b => ({\n        label: b.label, meta: b.meta, cta: b.cta,\n        color: b.sev === 'red' ? 'rgb(var(--accent-red))' : 'rgb(var(--pastel-gold))',\n        onResolve: () => this.setState(st2 => ({ blockersDone: st2.blockersDone.concat([b.id]) }))\n      })),\n      blockerCount: this.BLOCKERS.filter(b => this.state.blockersDone.indexOf(b.id) < 0).length,\n      agOpen: sec('ag'), agCaret: secCaret('ag'), agToggle: secToggle('ag'),\n      pjOpen: sec('pj'), pjCaret: secCaret('pj'), pjToggle: secToggle('pj'),\n      tkOpen: sec('tk'), tkCaret: secCaret('tk'), tkToggle: secToggle('tk'),\n      blOpen: sec('bl'), blCaret: secCaret('bl'), blToggle: secToggle('bl'),\n      projects: this.PROJECTS.map(pr => ({ label: pr.label, meta: pr.meta, stage: pr.stage, color: this.dotCol(pr.state), onOpen: () => {} })),\n      projectCount: this.PROJECTS.length,\n      roles: this.ROLES.map(r2 => { const on = r2 === 'New user' ? mvp : (!mvp && ops && this.state.role === r2); return {\n        label: this.ROLE_LABEL[r2] || r2, active: on,\n        color: on ? 'rgb(var(--lead))' : 'rgb(var(--text))',\n        bg: on ? 'rgb(var(--lead) / 0.1)' : 'transparent',\n        onClick: () => this.selectRole(r2) }; }),\n      roleLabel: (this.state.founder ? 'FOUNDER · ' : '') + (mvp ? 'DELIVERY' : ops ? (this.ROLE_LABEL[this.state.role] || this.state.role).toUpperCase() : 'USE'),\n      // D171 · Founder: one switch re-skins the whole canvas (and every framed page) in the sports tenant and opens Websites.\n      founderOn: !!this.state.founder,\n      founderMeta: this.state.founder ? 'On · sports tokens, Websites first' : 'Off · switch the canvas to sports',\n      founderBg: this.state.founder ? 'rgb(var(--lead) / 0.1)' : 'transparent',\n      founderKnobLeft: this.state.founder ? '16px' : '2px',\n      founderKnob: this.state.founder ? 'rgb(var(--lead))' : 'rgb(255 255 255 / 0.45)',\n      founderTrackBorder: this.state.founder ? 'rgb(var(--lead) / 0.6)' : 'rgb(255 255 255 / 0.14)',\n      toggleFounder: () => this.setState(sf => sf.founder\n        ? { founder: false, brandKey: sf.founderPrevBrand || 'aaas', roleMenu: false, settings: false }\n        : { founder: true, founderPrevBrand: (sf.brandKey === 'sports' || sf.brandKey === 'nike') ? 'aaas' : sf.brandKey, brandKey: this.BRANDS.sports ? 'sports' : (this.BRANDS.nike ? 'nike' : sf.brandKey), roleMenu: false, settings: false },   // D209: Founder = the sports tenant   // D177: Founder keeps the current stream\n        () => { setTimeout(() => this.broadcastBrandToFrames(), 30); this.loadToolset(); }),\n      userViewActive: !ops,\n      userViewBg: !ops ? 'rgb(var(--lead) / 0.1)' : 'transparent',\n      userViewColor: !ops ? 'rgb(var(--lead))' : 'rgb(var(--text))',\n      selectUserView: () => this.setState({ ops: false, active: 'home', sub: 0, pane: 'default', roleMenu: false, settings: false, railOpen: false }),\n      roleMenuOpen: this.state.roleMenu,\n      roleCaret: this.state.roleMenu ? 'rotate(180deg)' : 'rotate(0deg)',\n      toggleRoleMenu: () => this.setState(s4 => ({ roleMenu: !s4.roleMenu, settings: false })),\n      noBlockers: this.BLOCKERS.filter(b => this.state.blockersDone.indexOf(b.id) < 0).length === 0,\n      noTasks: taskList.length === 0,\n      // D178: the switch shows PINNED state (a hover-peek is not \"on\").\n      rosterTrack: (clientChrome ? rosterEff : this.state.rosterOpen) ? 'rgb(var(--lead))' : 'rgb(var(--lead) / 0.4)',\n      rosterKnob: 'rgb(var(--basalt-deep))',\n      rosterTrackBorder: (clientChrome ? rosterEff : this.state.rosterOpen) ? 'rgb(var(--lead))' : 'rgb(var(--lead) / 0.55)',\n      rosterKnobLeft: (clientChrome ? rosterEff : this.state.rosterOpen) ? '15px' : '1px',\n      toggleRoster: () => this.setState(s => (clientChrome ? { rosterClient: !s.rosterClient } : { rosterOpen: !s.rosterOpen, rosterPeek: false })),\n      ...this.askVals(),\n      ...this.rosterVals(),\n      paneBooting: !!this.state.paneBooting, paneBootingLabel: String(subLabel || st.label || '').toUpperCase(),\n      movedCards, hasMoved: movedCards.length > 0,\n      // D201: ArchetypeHost only renders `note` on process; every other archetype's note surfaces here.\n      v2ArchNote: v2Arch && v2Arch.type !== 'process' && v2Arch.note ? v2Arch.note : '',\n      v2ArchNoteColor: v2Arch && v2Arch.noteTone === 'fail' ? 'var(--app-signal, var(--app-fail))' : (v2Arch && v2Arch.noteTone === 'ok' ? 'var(--app-success)' : 'var(--app-ink-soft, rgb(var(--text) / 0.7))'),\n      v2Arch, v2ArchLabel: v2Arch ? (rawSubLabel + ' · ' + v2Arch.type + ' archetype · ' + (v2Arch.source || 'fixture')).toUpperCase() : '',\n      showTasks: taskList.length > 0, taskCount: taskList.length, taskList,\n      paletteOpen: this.state.palette, q, paletteGroups,\n      onQ: (e) => this.setState({ q: e.target.value }),\n      setPaletteRef: (el) => { this._pin = el; },\n      closePalette: () => this.setState({ palette: false }),\n      megaOpen: this.state.mega, megaGroups,\n      closeMega: () => this.setState({ mega: false, megaTile: null, megaTileCss: '' }),\n      toggleMega: () => this.setState(s => ({ mega: !s.mega, megaTile: null, megaTileCss: '' })),\n      domainSiteTiles: this.DOMAIN_SITES.map(d => ({\n        label: d.label, tld: d.tld, iconEl: this.ic(d.icon, 22),\n        pageCount: d.pages.length + (d.pages.length === 1 ? ' page' : ' pages'),\n        onClick: () => this.setState({ domainSite: { id: d.id, page: 0 }, mega: false, megaTile: null, megaTileCss: '' }),\n        /* The page menu hangs off the TILE because the domain-site view has no chrome to put\n           a tab strip in. Without it every page after the first is dead weight in\n           DOMAIN_SITES — which is what happened when the apex gained Home (the planet door)\n           and Replay: both were in the data, neither was reachable.\n           Hover-revealed, not always-on: the grid is a map of the estate first, a link list\n           second. Single-page domains get no caret and no menu — their header row IS the page,\n           so `open` stays false for them however long the pointer rests there. */\n        hasPages: d.pages.length > 1,\n        open: this.state.megaTile === d.id && d.pages.length > 1,\n        menuStyle: this.state.megaTile === d.id ? (this.state.megaTileCss || '') : '',\n        caret: this.state.megaTile === d.id && d.pages.length > 1 ? 'rotate(180deg)' : 'none',\n        border: this.state.megaTile === d.id ? 'rgb(var(--lead) / 0.4)' : 'rgb(var(--lead) / 0.25)',\n        bg: this.state.megaTile === d.id ? 'rgb(var(--lead) / 0.1)' : 'rgb(var(--lead) / 0.05)',\n        /* onFocus is wired to the same opener so the menu is reachable by keyboard, and the\n           header click still opens page 0 — which is what a touch device gets, since it has\n           no hover at all. The menu is an accelerator, never the only route in. */\n        onEnter: (e) => this.openTileMenu(d, e),\n        onLeave: () => this.setState(s => (s.megaTile === d.id ? { megaTile: null, megaTileCss: '' } : null)),\n        pages: d.pages.map((p, i) => ({\n          label: p.label,\n          idx: (i + 1) < 10 ? '0' + (i + 1) : String(i + 1),\n          /* A page grouped under another domain keeps its OWN host — Select carries Pics,\n             TripleA and Qualify, served from aaas.pics, aaas.love/tripleA and\n             canvas.aaas.love, not aaas.select. That host rode in the row itself until\n             2026-09-22; at 200px it overlapped the label, so it now lives only in the\n             title attribute below. The accuracy point still holds — the grouping may not\n             silently reattribute four hosts to one — it is just one hover away instead of\n             fighting the label for the same line. */\n          title: (p.pageTld || d.tld) + ' · ' + p.label,\n          onClick: () => this.setState({ domainSite: { id: d.id, page: i }, mega: false, megaTile: null, megaTileCss: '' })\n        }))\n      })),\n      domainSiteActive: !!this.state.domainSite,\n      domainSiteView: (() => {\n        if (!this.state.domainSite) return null;\n        const d = this.DOMAIN_SITES.find(x => x.id === this.state.domainSite.id);\n        if (!d) return null;\n        const pageIdx = this.state.domainSite.page;\n        const page = d.pages[pageIdx] || d.pages[0];\n        return {\n          label: d.label, tld: d.tld,\n          wordmark: this.domainWordmark(d.tld),\n          /* `hasPages` and `tabs` USED to be here for a slim shell bar over the frame.\n             That bar was removed (the framed domain's own ecosystem bar carries the way\n             back, and stacking ours on top duplicated its link set), which left both\n             bindings with no consumer — dead code that read as if page switching worked.\n             Page switching now happens in the switcher tile above; removed rather than\n             left dangling, so the next reader is not misled the way this one was. */\n          iframeSrc: this.rootPrefix + page.file,\n          // Operator call 2026-09-10: closing a domain site returns to the app switcher it was\n          // opened from, not to the canvas underneath — the switcher is the place you pick another door.\n          close: () => this.setState({ domainSite: null, mega: true })\n        };\n      })(),\n      toggleOps: () => this.toggleOps(),\n      toggleTheme: () => this.toggleTheme(),\n      openPalette: () => this.openPalette(),\n      // MVP · customer-facing canvas toggle\n      isMvp: mvp, showOps: true,\n      mvpLabel: mvp ? 'CLIENT' : 'MVP',\n      mvpBorder: mvp ? 'rgb(var(--accent-red) / 0.5)' : 'var(--border)',\n      mvpBg: mvp ? 'rgb(var(--accent-red) / 0.1)' : 'transparent',\n      mvpTrack: 'rgb(var(--basalt-deep))',\n      mvpTrackBorder: mvp ? 'rgb(var(--accent-red) / 0.5)' : 'rgb(255 255 255 / 0.14)',\n      mvpKnob: mvp ? 'rgb(var(--accent-red))' : 'rgb(255 255 255 / 0.45)',\n      mvpKnobLeft: mvp ? '16px' : '2px',\n      mvpLabelColor: mvp ? 'rgb(var(--accent-red))' : 'var(--app-ink-soft, rgb(var(--text) / 0.7))',\n      firstContactLabel: mvp ? 'Leave first contact' : 'Preview first contact',\n      firstContactDot: mvp ? 'rgb(var(--accent-red))' : 'var(--app-accent)',\n      toggleMvp: () => (this.state.mvp ? this.selectRole(this.state.role === 'New user' ? 'Admin' : this.state.role) : this.enterMvp()),\n      collapseLabel: mob ? 'Close menu' : (collapsed ? 'Expand' : 'Collapse'),\n      // D198d · rail pin (desktop). Pinned = docked; unpinning hides it behind the 2cm hover zone.\n      railPinned: !this.state.railHidden,\n      railTrack: !this.state.railHidden ? 'rgb(var(--lead))' : 'rgb(var(--lead) / 0.4)',\n      railTrackBorder: !this.state.railHidden ? 'rgb(var(--lead))' : 'rgb(var(--lead) / 0.55)',\n      railKnobLeft: !this.state.railHidden ? '15px' : '1px',\n      toggleRailPin: () => this.setState(s => ({ railHidden: !s.railHidden, railPeek: false })),\n      collapseAction: mob ? () => this.setState(s => ({ railOpen: !s.railOpen })) : () => this.setState(s => ({ collapsed: !s.collapsed })),\n      toggleCollapse: () => this.setState(s => ({ collapsed: !s.collapsed })),\n      stop: (e) => e.stopPropagation(),\n      // brand stays the LAST key of this literal, after every ...spread above: an explicit key that\n      // follows the spreads cannot be clobbered by what they return, which is also what lets\n      // tools/second-brand-proof.mjs decide statically that this return supplies brand\n      // (it reported UNEVALUABLE while brand preceded the spreads).\n      brand: footerBrand\n    };\n  }\n}\n<\u002Fscript>\n<\u002Fbody>\n<\u002Fhtml>\n"