{"record":{"id":"15fa11d425d944e1","repo":"paperclipai/paperclip","slug":"could-not-load-wireframe-viewer-styles-15fa11","errorCode":null,"errorMessage":"Could not load wireframe viewer styles","messagePattern":"Could not load wireframe viewer styles","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"doc/plans/chat-adapters/generate-wireframes-v5.mjs","lineNumber":341,"sourceCode":"  [\"Start\", sharedScreens.filter((screen) => screen.group === \"Start\")],\n  ...providers.map((provider) => [provider.name, [\n    ...setupScreens.filter((screen) => screen.provider === provider.name),\n    ...detailScreens.filter((screen) => screen.provider === provider.name)\n  ]]),\n  [\"Paperclip\", sharedScreens.filter((screen) => screen.group === \"Paperclip\")]\n];\nconst orderedScreens = groups.flatMap(([, screens]) => screens);\n\nfunction uiDoc() {\n  const inventory = orderedScreens.map((screen) => `| ${screen.id} | ${screen.group} | ${screen.tab} | ${screen.title} | ${screen.desktopSize} | ${screen.mobileSize} |`).join(\"\\n\");\n  const details = orderedScreens.map((screen) => `### ${screen.id} · ${screen.title}\\n\\nPurpose: ${screen.subtitle}\\n\\n${screen.annotations.map((item, index) => `${index + 1}. ${item}`).join(\"\\n\")}\\n\\n${screen.actions ? `Actions:\\n\\n${screen.actions.map(([label, behind]) => `- **${label}:** ${behind}`).join(\"\\n\")}\\n\\n` : \"\"}Rationale: ${screen.rationale}`).join(\"\\n\\n\");\n  return `# Paperclip Chat Adapters UI Surfaces — v5\\n\\nDate: 2026-09-04  \\nPaperclip base: \\`${baseSha}\\`  \\nReview viewer: [\\`index.html\\`](./index.html)  \\nWireframes: [\\`wireframes-v5/\\`](./wireframes-v5/)  \\nSetup audit: [\\`2026-09-04-chat-adapters-setup-audit-v5.md\\`](./2026-09-04-chat-adapters-setup-audit-v5.md)\\n\\n## What changed\\n\\n- The purpose choice now says **Use this connection as an agent tool** and applies to every provider that exposes both chat and tool connection surfaces.\\n- Agent selection is one-way. Setup shows the chosen agent as **Locked**; connecting another agent means creating another connection.\\n- Every provider setup is a persistent step-rail wizard. Each SVG represents one real phase, including the advanced custom/existing-app branches.\\n- Normal setup never asks for direct webhook, relay, Socket Mode, or polling. Paperclip chooses delivery from the instance deployment and reports it after the fact.\\n- Authenticated provider handoffs keep credentials invisible. Only customer-owned flows expose irreducible secrets: two for a custom Slack app, an App ID/private key/webhook secret for an existing GitHub App, Microsoft bot identity values for Teams, and the BotFather token for Telegram.\\n- Every button is documented below with the state change or external handoff behind it.\\n- Provider detail tabs and maximal-safe capability behavior from v4 remain unchanged.\\n- Red dashed marks and numbers are review annotations, not proposed UI.\\n\\n## Inventory\\n\\n| ID | Group | Surface | Title | Desktop | Mobile |\\n|---|---|---|---|---|---|\\n${inventory}\\n\\n## Annotation and action notes\\n\\n${details}\\n`;\n}\n\nfunction viewerHtml() {\n  const template = readFileSync(join(root, \"../../../packages/skills-catalog/catalog/bundled/product/wireframe/assets/site-template.html\"), \"utf8\");\n  const style = template.match(/<style>[\\s\\S]*?<\\/style>/)?.[0];\n  if (!style) throw new Error(\"Could not load wireframe viewer styles\");\n  const toc = groups.map(([label, screens]) => `<h2>${esc(label)}</h2>${screens.map((screen) => `<a href=\"#s${screen.id}\"><span class=\"num\">${Number(screen.id)}</span>${esc(screen.title)}</a>`).join(\"\\n\")}`).join(\"\\n\");\n  const sections = groups.map(([label, screens]) => `<div class=\"provider-break\"><div class=\"lede\">${esc(label)}</div><h2>${label === \"Start\" ? \"Shared connection start\" : label === \"Paperclip\" ? \"Shared Paperclip surfaces\" : `${esc(label)} connector`}</h2></div>${screens.map((screen) => {\n    const notes = screen.annotations.map((note, index) => `<li><b>${index + 1}</b> — ${esc(note).replaceAll(\"**\", \"\")}</li>`).join(\"\\n\");\n    const actionNotes = screen.actions ? `<h3>What the actions do</h3><ul>${screen.actions.map(([label, behind]) => `<li><b>${esc(label)}</b> — ${esc(behind)}</li>`).join(\"\\n\")}</ul>` : \"\";\n    return `<section id=\"s${screen.id}\"><div class=\"lede\">${esc(screen.group)} · ${esc(screen.tab)}</div><h2><span class=\"step-num\">${Number(screen.id)}.</span>${esc(screen.title)}</h2><p class=\"desc\">${esc(screen.subtitle)}</p><div class=\"grid\"><div class=\"wire\" data-zoom data-caption=\"${screen.id} · ${esc(screen.title)} (desktop)\"><div class=\"label\"><span>${screen.id}-${screen.slug}.svg</span><span>${screen.desktopSize} · desktop</span></div><img src=\"wireframes-v5/${screen.id}-${screen.slug}.svg\" alt=\"${esc(screen.title)} desktop wireframe\" /></div><div class=\"wire mobile-wire mobile-col\" data-zoom data-caption=\"${screen.id} · ${esc(screen.title)} (mobile)\"><div class=\"label\"><span>mobile</span><span>${screen.mobileSize}</span></div><img src=\"wireframes-v5/${screen.id}-${screen.slug}-mobile.svg\" alt=\"${esc(screen.title)} mobile wireframe\" /></div><div class=\"notes-col\"><div class=\"notes\"><h3>Annotations</h3><ul>${notes}</ul>${actionNotes}<div class=\"why\"><b>Rationale:</b> ${esc(screen.rationale)}</div></div></div></div></section>`;\n  }).join(\"\\n\")}`).join(\"\\n\");\n  return `<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"/><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/><title>Paperclip chat adapters — setup wizard review</title>${style}<style>.doc-links{display:flex;flex-wrap:wrap;gap:8px 16px;margin-top:16px}.doc-links a{min-height:48px;display:inline-flex;align-items:center;font-size:13px;font-weight:600}.notice{max-width:var(--maxw);margin:-32px 0 48px;padding:14px 18px;background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--accent);border-radius:4px}.notice p{margin:0}.decision{margin-top:24px;padding:18px;background:var(--panel);border:1px solid var(--line);border-radius:8px}.decision b{display:block;margin-bottom:6px}.provider-break{max-width:var(--maxw);margin:80px 0 8px;padding-top:24px;border-top:2px solid var(--ink)}.provider-break h2{font-size:28px;margin:6px 0 0}.toc-body h2{margin-top:18px}code{font-size:.92em}</style></head><body><div class=\"shell\"><details class=\"toc\"><summary class=\"toc-summary\"><span><span class=\"crumb\">Chat adapters · v5</span><br><span class=\"title\">Jump to a screen</span></span><span class=\"chevron\" aria-hidden=\"true\"></span></summary><nav class=\"toc-body\" aria-label=\"Section navigation\"><h1>Chat adapters</h1><div style=\"font-size:13px;color:var(--muted);margin-bottom:16px\">Setup wizard review</div><h2>Documents</h2><a href=\"2026-09-03-chat-adapters-architecture.md\"><span class=\"num\">A</span>Architecture</a><a href=\"2026-09-04-chat-adapters-setup-audit-v5.md\"><span class=\"num\">S</span>Setup audit</a><a href=\"2026-09-04-chat-adapters-ui-surfaces-v5.md\"><span class=\"num\">U</span>UI specification v5</a><a href=\"2026-09-04-chat-adapters-platform-surfaces.md\"><span class=\"num\">P</span>Platform research</a><h2>Flow</h2><a href=\"#flow\"><span class=\"num\">⤳</span>Product flow</a>${toc}<h2>Review</h2><a href=\"#coverage\"><span class=\"num\">✓</span>Decisions and coverage</a></nav></details><main><header class=\"hero\"><div class=\"crumb\">Paperclip · Connectors · Setup v5</div><h1>One decision per step. Everything else automatic.</h1><p>Provider setup is now a persistent step-rail wizard. It shows the immutable agent, sends provider-owned approvals to the provider, hides credentials when an authenticated handoff can return them, and documents the exact effect behind every action.</p><div class=\"decision\"><b>Setup is not infrastructure configuration</b><span>Paperclip chooses delivery and maximal safe capabilities. Users see only actions the provider requires them to perform.</span></div><div class=\"doc-links\"><a href=\"2026-09-03-chat-adapters-architecture.md\">Architecture plan</a><a href=\"2026-09-04-chat-adapters-setup-audit-v5.md\">Row-by-row setup audit</a><a href=\"2026-09-04-chat-adapters-ui-surfaces-v5.md\">UI specification v5</a></div><div class=\"pills\"><span class=\"pill\">45 product surfaces</span><span class=\"pill\">16 setup phases</span><span class=\"pill\">Every action explained</span><span class=\"pill\">Desktop + mobile</span></div></header><div class=\"notice\" role=\"note\"><p><b>Review convention:</b> red dashed marks are annotations, not proposed UI. The step-rail layout is informed by the supplied reference image; none of its text or functionality is copied.</p></div><section id=\"flow\" class=\"flow-section\"><div class=\"lede\">Navigation and product flow</div><h2>Minimal default paths with honest advanced branches</h2><p class=\"desc\">Agent selection happens once. Provider setup contains one focused phase at a time; transport mechanics are selected by the deployment.</p><div class=\"wire\" data-zoom data-caption=\"Chat connector setup flow\"><div class=\"label\"><span>flow.svg</span><span>1280×1040</span></div><img src=\"wireframes-v5/flow.svg\" alt=\"Chat connector setup flow\"/></div></section>${sections}<section id=\"coverage\"><div class=\"lede\">Review</div><h2>What changed in v5</h2><div class=\"notes\"><ul><li><b>Generic purpose copy:</b> “Use this connection as an agent tool” applies to every dual-purpose connector.</li><li><b>Immutable agent:</b> no setup screen offers Change agent; another agent means another connection.</li><li><b>Stepped setup:</b> Slack has two default phases plus a three-phase custom-app branch; GitHub has three default phases plus an existing-App branch; Teams has four phases; Telegram has three.</li><li><b>Automatic delivery:</b> webhook, relay, Socket Mode, and polling are not endpoint-wizard choices.</li><li><b>Credential minimization:</b> secrets appear only where a provider cannot return or provision them for Paperclip.</li><li><b>Complete review:</b> all prior provider endpoint tabs and conversation walkthroughs remain in the same grouped viewer.</li><li><b>Paperclip base:</b> <code>${baseSha}</code>, matching <code>origin/master</code> when generated.</li></ul></div></section><div class=\"footer\">Generated with Paperclip’s wireframe contract. Setup canvases grow with their focused phase; no wizard is forced into an 800px dashboard.</div></main></div><div class=\"lightbox\" id=\"lb\" aria-hidden=\"true\"><span class=\"close\" id=\"lbClose\" role=\"button\" aria-label=\"Close preview\">×</span><img id=\"lbImg\" alt=\"\"/><div class=\"caption\" id=\"lbCap\"></div></div><script>const lb=document.getElementById('lb'),lbImg=document.getElementById('lbImg'),lbCap=document.getElementById('lbCap');document.querySelectorAll('[data-zoom]').forEach(el=>el.addEventListener('click',()=>{const target=el.querySelector('img');if(!target)return;lbImg.src=target.src;lbImg.alt=target.alt;lbCap.textContent=el.dataset.caption||target.alt||'';lb.classList.add('open');lb.setAttribute('aria-hidden','false')}));function closeLightbox(){lb.classList.remove('open');lb.setAttribute('aria-hidden','true')}lb.addEventListener('click',closeLightbox);document.getElementById('lbClose').addEventListener('click',closeLightbox);document.addEventListener('keydown',e=>{if(e.key==='Escape')closeLightbox()});const tocElement=document.querySelector('details.toc'),media=window.matchMedia('(max-width:900px)'),setToc=()=>{tocElement.open=!media.matches};setToc();media.addEventListener('change',setToc);tocElement.querySelectorAll('.toc-body a').forEach(link=>link.addEventListener('click',()=>{if(media.matches)tocElement.open=false}));</script></body></html>`;\n}\n\nwriteFileSync(\n  join(root, \"2026-09-04-chat-adapters-ui-surfaces-v5.md\"),\n  `${uiDoc().replace(\n    \"Provider detail tabs and maximal-safe capability behavior from v4 remain unchanged.\",\n    \"Provider detail tabs remain complete. Capability inventories live on Overview and the native walkthroughs; feature toggles are absent from Settings.\"\n  )}\\n`\n);\nwriteFileSync(join(root, \"index.html\"), `${viewerHtml()}\\n`);\nconsole.log(`Generated ${orderedScreens.length * 2 + 1} v5 SVGs, UI specification, and index.html`);","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/doc/plans/chat-adapters/generate-wireframes-v5.mjs#L323-L359","documentation":"The v5 generator's `viewerHtml()` throws this when the shared site template, though readable, yields no `<style>` match. It is the same style-extraction guard used by v3–v7, keeping the generated review viewer styled.","triggerScenarios":"Running `node doc/plans/chat-adapters/generate-wireframes-v5.mjs` when `packages/skills-catalog/catalog/bundled/product/wireframe/assets/site-template.html` lacks an inline `<style>` element because it was refactored to external CSS, given tag attributes, or its style block was lost in a merge.","commonSituations":"Template asset drift after a design-system change; checking out only the generator directory without the matching asset; hand-editing the template and accidentally removing the closing `</style>`.","solutions":["Inspect the template for the inline style block and restore it if accidentally removed.","Broaden the match to `/<style[^>]*>[\\s\\S]*?<\\/style>/` if the tag carries attributes.","If styles moved to CSS, read that file in the generator and inline it into the viewer head.","Re-run the generator."],"exampleFix":"// before\nconst style = template.match(/<style>[\\s\\S]*?<\\/style>/)?.[0];\n// after\nconst style = template.match(/<style[^>]*>[\\s\\S]*?<\\/style>/)?.[0];","handlingStrategy":"validation","validationCode":"const html = readFileSync(templatePath, \"utf8\");\nif (!html.match(/<style[^>]*>[\\s\\S]*?<\\/style>/)) throw new Error(\"template style block missing\");","typeGuard":"const hasStyleBlock = (html) => typeof html === \"string\" && /<style[^>]*>[\\s\\S]*?<\\/style>/.test(html);","tryCatchPattern":"try {\n  render();\n} catch (err) {\n  if (String(err.message).includes(\"wireframe viewer styles\")) {\n    console.error(\"Check site-template.html for the inline <style> block.\");\n  } else throw err;\n}","preventionTips":["Verify the template after any merge touching packages/skills-catalog/.../wireframe/assets.","Run all generator versions when the template changes.","Keep the style block at the top of the template where refactors are less likely to touch it."],"tags":["build-script","regex","template"],"backgroundTag":"internal-invariant-violation","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}