{"record":{"id":"7959fc2d27994b8a","repo":"paperclipai/paperclip","slug":"could-not-load-wireframe-viewer-styles","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-v3.mjs","lineNumber":384,"sourceCode":"    screen.mobileSize = \"375×1320\";\n  }\n}\nwriteFileSync(join(out, \"flow.svg\"), `${flowSvg()}\\n`);\n\nconst groups = [\n  [\"Start\", sharedScreens.filter((screen) => screen.group === \"Start\")],\n  [\"Slack\", providerScreens.filter((screen) => screen.provider === \"Slack\")],\n  [\"GitHub\", providerScreens.filter((screen) => screen.provider === \"GitHub\")],\n  [\"Microsoft Teams\", providerScreens.filter((screen) => screen.provider === \"Microsoft Teams\")],\n  [\"Telegram\", providerScreens.filter((screen) => screen.provider === \"Telegram\")],\n  [\"Paperclip\", sharedScreens.filter((screen) => screen.group === \"Paperclip\")]\n];\nconst orderedScreens = groups.flatMap(([, screens]) => screens);\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 operations\" : `${esc(label)} planning`}</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    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>`;\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 — 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>`;\n}\n\nwriteFileSync(join(root, \"index.html\"), `${viewerHtml()}\\n`);\nconsole.log(`Generated ${orderedScreens.length * 2 + 1} consolidated SVGs and index.html`);\n","sourceCodeStart":366,"sourceCodeEnd":395,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/doc/plans/chat-adapters/generate-wireframes-v3.mjs#L366-L395","documentation":"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.","triggerScenarios":"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.","commonSituations":"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\">`.","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."],"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 template = readFileSync(templatePath, \"utf8\");\nif (!/<style[^>]*>[\\s\\S]*?<\\/style>/.test(template)) {\n  throw new Error(`site-template.html lost its inline <style>; cannot build v3 viewer`);\n}","typeGuard":"const hasInlineStyle = (html) => /<style[^>]*>[\\s\\S]*?<\\/style>/.test(html);","tryCatchPattern":"try {\n  viewerHtml();\n} catch (err) {\n  if (err.message === \"Could not load wireframe viewer styles\") {\n    console.error(\"site-template.html has no <style> block; restore it or update the generator to read CSS.\");\n  } else throw err;\n}","preventionTips":["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."],"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"}