paperclipai/paperclip · error
Could not load wireframe viewer styles
Error message
Could not load wireframe viewer styles
What it means
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.
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.
Example fix
// before const style = template.match(/<style>[\s\S]*?<\/style>/)?.[0]; // after const style = template.match(/<style[^>]*>[\s\S]*?<\/style>/)?.[0];
Defensive patterns
Strategy: validation
Validate before calling
const html = readFileSync(templatePath, "utf8");
if (!html.match(/<style[^>]*>[\s\S]*?<\/style>/)) throw new Error("template style block missing"); Type guard
const hasStyleBlock = (html) => typeof html === "string" && /<style[^>]*>[\s\S]*?<\/style>/.test(html);
Try / catch
try {
render();
} catch (err) {
if (String(err.message).includes("wireframe viewer styles")) {
console.error("Check site-template.html for the inline <style> block.");
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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>`.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Could not load wireframe viewer styles
- Could not load wireframe viewer styles
- Could not load wireframe viewer styles
- Could not load wireframe viewer styles
- Could not read viewer styles from
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/15fa11d425d944e1.
Report an issue: GitHub.
Appendix: source
Thrown at doc/plans/chat-adapters/generate-wireframes-v5.mjs:341
["Start", sharedScreens.filter((screen) => screen.group === "Start")],
...providers.map((provider) => [provider.name, [
...setupScreens.filter((screen) => screen.provider === provider.name),
...detailScreens.filter((screen) => screen.provider === provider.name)
]]),
["Paperclip", sharedScreens.filter((screen) => screen.group === "Paperclip")]
];
const orderedScreens = groups.flatMap(([, screens]) => screens);
function uiDoc() {
const inventory = orderedScreens.map((screen) => `| ${screen.id} | ${screen.group} | ${screen.tab} | ${screen.title} | ${screen.desktopSize} | ${screen.mobileSize} |`).join("\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");
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`;
}
function viewerHtml() {
const template = readFileSync(join(root, "../../../packages/skills-catalog/catalog/bundled/product/wireframe/assets/site-template.html"), "utf8");
const style = template.match(/<style>[\s\S]*?<\/style>/)?.[0];
if (!style) throw new Error("Could not load wireframe viewer styles");
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");
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) => {
const notes = screen.annotations.map((note, index) => `<li><b>${index + 1}</b> — ${esc(note).replaceAll("**", "")}</li>`).join("\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>` : "";
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>`;
}).join("\n")}`).join("\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>`;
}
writeFileSync(
join(root, "2026-09-04-chat-adapters-ui-surfaces-v5.md"),
`${uiDoc().replace(
"Provider detail tabs and maximal-safe capability behavior from v4 remain unchanged.",
"Provider detail tabs remain complete. Capability inventories live on Overview and the native walkthroughs; feature toggles are absent from Settings."
)}\n`
);
writeFileSync(join(root, "index.html"), `${viewerHtml()}\n`);
console.log(`Generated ${orderedScreens.length * 2 + 1} v5 SVGs, UI specification, and index.html`);View on GitHub (pinned to 3f1d897a7c)