paperclipai/paperclip · error
Could not load wireframe viewer styles
Error message
Could not load wireframe viewer styles
What it means
The v3 wireframe generator's `viewerHtml()` extracts the shared `<style>` block from the bundled site template via regex. When the match returns undefined — the template exists but has no inline `<style>...</style>` section — the function throws this fixed message instead of generating a styleless viewer.
Solutions
- Check the template file for a `<style>` block; restore it if removed unintentionally.
- Update the regex to `/<style[^>]*>[\s\S]*?<\/style>/` if the tag now has attributes.
- If the template now uses an external CSS file, change the generator to read that CSS file and inline it.
- Regenerate and verify index.html renders with styles.
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 template = readFileSync(templatePath, "utf8");
if (!/<style[^>]*>[\s\S]*?<\/style>/.test(template)) {
throw new Error(`site-template.html lost its inline <style>; cannot build v3 viewer`);
} Type guard
const hasInlineStyle = (html) => /<style[^>]*>[\s\S]*?<\/style>/.test(html);
Try / catch
try {
viewerHtml();
} catch (err) {
if (err.message === "Could not load wireframe viewer styles") {
console.error("site-template.html has no <style> block; restore it or update the generator to read CSS.");
} else throw err;
} Prevention
- Treat the inline <style> block as a stable contract of site-template.html.
- When changing the template, run every generate-wireframes-v*.mjs script in CI.
- Prefer widening the regex (<style[^>]*>) in new generators to tolerate attributes.
When it happens
Trigger: Running `node doc/plans/chat-adapters/generate-wireframes-v3.mjs` when `packages/skills-catalog/catalog/bundled/product/wireframe/assets/site-template.html` has no literal `<style>` opening tag: styles moved to a linked stylesheet, tag gained attributes, or the style section was lost in a merge. The file WAS read; only the pattern missed.
Common situations: Template upgraded to external CSS while old generator versions (v3–v7) still expect inline styles; merge conflict resolution dropped the style block; someone reformatted `<style>` to `<style type="text/css">`.
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/7959fc2d27994b8a.
Report an issue: GitHub.
Appendix: source
Thrown at doc/plans/chat-adapters/generate-wireframes-v3.mjs:384
screen.mobileSize = "375×1320";
}
}
writeFileSync(join(out, "flow.svg"), `${flowSvg()}\n`);
const groups = [
["Start", sharedScreens.filter((screen) => screen.group === "Start")],
["Slack", providerScreens.filter((screen) => screen.provider === "Slack")],
["GitHub", providerScreens.filter((screen) => screen.provider === "GitHub")],
["Microsoft Teams", providerScreens.filter((screen) => screen.provider === "Microsoft Teams")],
["Telegram", providerScreens.filter((screen) => screen.provider === "Telegram")],
["Paperclip", sharedScreens.filter((screen) => screen.group === "Paperclip")]
];
const orderedScreens = groups.flatMap(([, screens]) => screens);
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 operations" : `${esc(label)} planning`}</h2></div>${screens.map((screen) => {
const notes = screen.annotations.map((note,index)=>`<li><b>${index+1}</b> — ${esc(note).replaceAll("**","")}</li>`).join("\n");
return `<section id="s${screen.id}"><div class="lede">${esc(screen.group)}</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-v3/${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-v3/${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><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 — consolidated provider 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 · v3</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">Consolidated provider 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-ui-surfaces-v3.md"><span class="num">U</span>Consolidated UI specification</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 · Consolidated v3</div><h1>One start. Four provider paths.</h1><p>The generic connection screens appear once. Slack, GitHub, Microsoft Teams, and Telegram then own their setup, vertical settings, and plain-language conversation walkthroughs.</p><div class="decision"><b>No duplicate setup</b><span>“Invite Maya to Slack” appears only in the Slack group. Generic Channels and Behavior screens are replaced by each provider’s real settings.</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-ui-surfaces-v3.md">Consolidated UI specification</a><a href="2026-09-04-chat-adapters-platform-surfaces.md">Platform-specific research</a></div><div class="pills"><span class="pill">21 consolidated surfaces</span><span class="pill">4 provider groups</span><span class="pill">Vertical settings</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. Conversation walkthroughs explain what people see and what Paperclip creates; they are not standalone product pages.</p></div><section id="flow" class="flow-section"><div class="lede">Navigation and product flow</div><h2>Provider-specific after the shared start</h2><p class="desc">Connectors, conditional purpose, and agent selection are shared. The provider then owns its external handoff, settings, and conversation semantics. Paperclip operations appear once.</p><div class="wire" data-zoom data-caption="Consolidated chat connector product flow"><div class="label"><span>flow.svg</span><span>1280×900</span></div><img src="wireframes-v3/flow.svg" alt="Consolidated chat connector product flow"/></div></section>${sections}<section id="coverage"><div class="lede">Review</div><h2>What changed in v3</h2><div class="notes"><ul><li><b>Grouped navigation:</b> Slack, GitHub, Microsoft Teams, and Telegram each have their own left-navigation header and three screens.</li><li><b>No duplicate Slack handoff:</b> the earlier generic Invite screen is removed from the viewer; Slack Setup is the only version.</li><li><b>No generic settings duplicates:</b> the earlier generic Channels and Behavior screens are removed; provider settings own those decisions.</li><li><b>Normal settings layout:</b> every provider uses full-width vertical sections and ordinary setting rows, with tall canvases where needed.</li><li><b>Understandable interactions:</b> internal ingress/binding pipeline labels are replaced by the provider conversation people see beside the single Paperclip issue it creates.</li><li><b>Stable review links:</b> provider IDs 13–24 are retained, so existing anchors such as <code>#s15</code> still point to the same provider topic.</li><li><b>Paperclip base:</b> <code>7b094724e65c04949706df638d497afb02c84b62</code>, matching <code>origin/master</code> when generated.</li></ul></div></section><div class="footer">Generated with Paperclip’s wireframe contract. Provider settings intentionally use long canvases so detail remains legible in a conventional vertical settings hierarchy.</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, "index.html"), `${viewerHtml()}\n`);
console.log(`Generated ${orderedScreens.length * 2 + 1} consolidated SVGs and index.html`);
View on GitHub (pinned to 3f1d897a7c)