paperclipai/paperclip · error

Could not load wireframe viewer styles

Error message

Could not load wireframe viewer styles

What it means

The v6 generator's `viewerHtml()` extracts the shared template's inline `<style>` block and throws this fixed message when the extraction returns undefined, aborting generation so the review viewer never ships unstyled.

Solutions

  1. Check the template for a complete `<style>...</style>` block and restore if missing.
  2. Relax the regex to allow attributes: `/<style[^>]*>[\s\S]*?<\/style>/`.
  3. If styles were moved to a CSS file, update the generator to read and inline that CSS.
  4. Re-run the generator and confirm index.html is styled.

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

if (!/<style[^>]*>[\s\S]*?<\/style>/.test(readFileSync(templatePath, "utf8"))) {
  throw new Error("site-template.html missing inline style block required by v6 generator");
}

Type guard

const stylePresent = (html) => /<style[^>]*>[\s\S]*?<\/style>/.test(html);

Try / catch

try {
  generateViewer();
} catch (err) {
  if (err.message === "Could not load wireframe viewer styles") {
    console.error("Template lost its <style> block; restore or switch the extractor to the external CSS.");
  } else throw err;
}

Prevention

When it happens

Trigger: Running `node doc/plans/chat-adapters/generate-wireframes-v6.mjs` when `packages/skills-catalog/catalog/bundled/product/wireframe/assets/site-template.html` contains no literal `<style>...</style>` section — external stylesheet refactor, attributed style tag, or merge damage to the asset.

Common situations: Newer template format vs older generator copy; partial worktree checkout; hand-edited template missing the closing `</style>` tag so the non-greedy regex cannot match.

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


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/81ca420e9ca8f446. Report an issue: GitHub.

Appendix: source

Thrown at doc/plans/chat-adapters/generate-wireframes-v6.mjs:336

  ["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 uiDocument() {
  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, effect]) => `- **${label}:** ${effect}`).join("\n")}\n\n` : ""}Rationale: ${screen.rationale}`).join("\n\n");
  return `# Paperclip Chat Adapters UI Surfaces — v6\n\nDate: 2026-09-04  \nPaperclip base: \`${baseSha}\`  \nReview viewer: [\`index.html\`](./index.html)  \nWireframes: [\`wireframes-v6/\`](./wireframes-v6/)  \nMinimum-setup specification: [\`2026-09-04-chat-adapters-minimum-setup-v6.md\`](./2026-09-04-chat-adapters-minimum-setup-v6.md)\n\n## Relevance rule\n\nA setup screen may show only something the operator must click, copy, paste, upload, choose, or perform at the provider during that step. Do not repeat the selected agent, describe automatic Paperclip work, list capabilities, or show successful checks. Errors and unmet prerequisites appear only when they occur.\n\n## Current setup inventory\n\n- Slack: Add to Slack and a three-step customer-owned-App fallback converge on one test screen.\n- GitHub: App Manifest creation, repository installation, and test; existing App is an advanced fallback.\n- Microsoft Teams: one guided command, one install link, and test; manual Microsoft registration is an advanced fallback.\n- Telegram: BotFather token and one private-message test.\n- Capabilities and health remain on Overview and the interaction walkthroughs, never in setup.\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)}</li>`).join("\n");
    const actions = screen.actions ? `<h3>What the actions do</h3><ul>${screen.actions.map(([name, effect]) => `<li><b>${esc(name)}</b> — ${esc(effect)}</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-v6/${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-v6/${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>${actions}<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 — minimum setup 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 · v6</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">Minimum setup 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-minimum-setup-v6.md"><span class="num">M</span>Minimum setup</a><a href="2026-09-04-chat-adapters-ui-surfaces-v6.md"><span class="num">U</span>UI specification v6</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 v6</div><h1>Only show what the person must do.</h1><p>Every provider wizard now contains only required clicks, copied values, uploads, provider actions, and test instructions. Automatic Paperclip work is absent from setup.</p><div class="decision"><b>Relevance test</b><span>If the operator cannot act on it during this step, it does not appear on the screen.</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-minimum-setup-v6.md">Minimum setup specification</a><a href="2026-09-04-chat-adapters-ui-surfaces-v6.md">UI specification v6</a></div><div class="pills"><span class="pill">${orderedScreens.length} product surfaces</span><span class="pill">${setupScreens.length} setup phases</span><span class="pill">No setup status reports</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 supplied screenshot informed only the persistent step-rail layout.</p></div><section id="flow" class="flow-section"><div class="lede">Navigation and product flow</div><h2>Minimum provider setup</h2><p class="desc">Normal paths are short. Manual provider registration appears only when the simpler handoff is unavailable.</p><div class="wire" data-zoom data-caption="Minimum chat connector setup flow"><div class="label"><span>flow.svg</span><span>1280×880</span></div><img src="wireframes-v6/flow.svg" alt="Minimum chat connector setup flow"/></div></section>${sections}<section id="coverage"><div class="lede">Review</div><h2>What changed in v6</h2><div class="notes"><ul><li>Agent identity is not repeated after the agent-selection step.</li><li>Automatic credentials, delivery, capabilities, and successful checks are absent from setup.</li><li>Test screens contain instructions only.</li><li>Slack's customer-owned-App branch gives the exact manifest, installation, token, signing-secret, and channel test sequence.</li><li>GitHub uses its manifest and repository-installation handoffs.</li><li>Teams uses the install link returned by the guided CLI command; package upload is no longer the normal path.</li><li>Telegram goes directly from BotFather token to a private-message test.</li><li>Paperclip base: <code>${baseSha}</code>, matching <code>origin/master</code> when generated.</li></ul></div></section><div class="footer">Generated with Paperclip’s wireframe contract. Empty space is intentional; setup contains no non-actionable filler.</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-v6.md"), `${uiDocument()}\n`);
writeFileSync(join(root, "index.html"), `${viewerHtml()}\n`);
console.log(`Generated ${orderedScreens.length * 2 + 1} v6 SVGs across ${orderedScreens.length} product surfaces.`);

View on GitHub (pinned to 3f1d897a7c)