{"record":{"id":"bf2432e7adf0ad6f","repo":"paperclipai/paperclip","slug":"could-not-load-wireframe-viewer-styles-bf2432","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-v7.mjs","lineNumber":300,"sourceCode":"  ...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 uiDocument() {\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, effect]) => `- **${label}:** ${effect}`).join(\"\\n\")}\\n\\n` : \"\"}Rationale: ${screen.rationale}`).join(\"\\n\\n\");\n  const defaults = fixedBehavior.map(([name, behavior]) => `- **${name}:** ${behavior}`).join(\"\\n\");\n  return `# Paperclip Chat Adapters UI Surfaces — v7\\n\\nDate: 2026-09-04  \\nPaperclip base: \\`${baseSha}\\`  \\nReview viewer: [\\`index.html\\`](./index.html)  \\nWireframes: [\\`wireframes-v7/\\`](./wireframes-v7/)\\n\\n## Product decision\\n\\nOverview is removed. Activated connectors open on Settings and expose only four management tabs: Settings, Access, Conversations, and Activity. Settings contains only destination reach that a user can plausibly change.\\n\\n${defaults}\\n\\n## Settings inventory\\n\\n- Slack: allowed channels and an Allow direct messages toggle.\\n- GitHub: allowed repositories only.\\n- Microsoft Teams: allowed channels, Allow direct messages, and Allow group chats.\\n- Telegram: allowed groups/topics and an Allow direct messages toggle.\\n\\n## Screen 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)}</li>`).join(\"\\n\");\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>` : \"\";\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-v7/${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-v7/${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>`;\n  }).join(\"\\n\")}`).join(\"\\n\");\n  const defaults = fixedBehavior.map(([name, behavior]) => `<li><b>${esc(name)}</b> — ${esc(behavior)}</li>`).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 — configurable reach 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 · v7</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\">Configurable reach 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-v7.md\"><span class=\"num\">U</span>UI specification v7</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-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=\"#decisions\"><span class=\"num\">✓</span>Fixed product behavior</a></nav></details><main><header class=\"hero\"><div class=\"crumb\">Paperclip · Connectors · Settings v7</div><h1>Configure reach. Default everything else.</h1><p>The read-only Overview tab is gone. Settings contains only destinations and private-conversation toggles that an operator can plausibly change.</p><div class=\"decision\"><b>Product rule</b><span>Task boundaries, delivery, credentials, installation drift, and provider capabilities are fixed behavior or conditional repair state—not 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-v7.md\">UI specification v7</a><a href=\"2026-09-04-chat-adapters-platform-surfaces.md\">Platform decisions</a></div><div class=\"pills\"><span class=\"pill\">${orderedScreens.length} product surfaces</span><span class=\"pill\">4 management tabs</span><span class=\"pill\">Reach-only 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.</p></div><section id=\"flow\" class=\"flow-section\"><div class=\"lede\">Navigation and product flow</div><h2>Setup, then reach-only management</h2><p class=\"desc\">Activated connectors open on Settings. Diagnostics and conditional repairs live in Activity.</p><div class=\"wire\" data-zoom data-caption=\"Chat connector product flow\"><div class=\"label\"><span>flow.svg</span><span>1280×880</span></div><img src=\"wireframes-v7/flow.svg\" alt=\"Chat connector product flow\"/></div></section>${sections}<section id=\"decisions\"><div class=\"lede\">Product decisions</div><h2>Behaviors removed from Settings</h2><div class=\"notes\"><ul>${defaults}</ul></div></section><div class=\"footer\">Generated with Paperclip’s wireframe contract. The current viewer contains no Overview page or non-configurable settings rows.</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, \"2026-09-04-chat-adapters-ui-surfaces-v7.md\"), `${uiDocument()}\\n`);\nwriteFileSync(join(root, \"index.html\"), `${viewerHtml()}\\n`);\nconsole.log(`Generated ${orderedScreens.length * 2 + 1} v7 SVGs across ${orderedScreens.length} product surfaces.`);\n","sourceCodeStart":282,"sourceCodeEnd":314,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/doc/plans/chat-adapters/generate-wireframes-v7.mjs#L282-L314","documentation":"The v7 generator's `viewerHtml()` extracts the inline `<style>` block from the shared site template with a regex and throws this fixed message when no match is found, preventing generation of an unstyled review viewer. Same guard family as generators v2–v6.","triggerScenarios":"Running `node doc/plans/chat-adapters/generate-wireframes-v7.mjs` when `packages/skills-catalog/catalog/bundled/product/wireframe/assets/site-template.html` no longer contains a literal `<style>...</style>` section: styles extracted to external CSS, style tag given attributes, or asset truncated/damaged in a merge.","commonSituations":"Template refactors outpacing the plan-directory generator snapshots; partial checkouts; hand edits that drop the closing `</style>` tag so the non-greedy regex fails.","solutions":["Confirm the template still has a complete inline `<style>` block; restore it if removed accidentally.","Adjust the regex to `/<style[^>]*>[\\s\\S]*?<\\/style>/` if the tag now has attributes.","If styles now live in a CSS file, read that file in `viewerHtml()` and inline it in the generated head.","Re-run the generator and inspect the built index.html."],"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 has no inline <style>; v7 viewer cannot be generated\");\n}","typeGuard":"const isStyledTemplate = (html) => /<style[^>]*>[\\s\\S]*?<\\/style>/.test(html);","tryCatchPattern":"try {\n  writeViewer();\n} catch (err) {\n  if (err.message === \"Could not load wireframe viewer styles\") {\n    console.error(\"Restore inline <style> in site-template.html or read the external CSS instead.\");\n  } else throw err;\n}","preventionTips":["Keep the inline style block as a template contract; update all seven generators together if it moves.","Add a CI step running generate-wireframes-v7.mjs after template edits.","Use attribute-tolerant regexes for style extraction."],"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"}