{"record":{"id":"ca4cdfa4b753d4ca","repo":"paperclipai/paperclip","slug":"could-not-read-viewer-styles-from-templatepath","errorCode":null,"errorMessage":"Could not read viewer styles from ${templatePath}","messagePattern":"Could not read viewer styles from (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"doc/plans/chat-adapters/generate-wireframes-v2.mjs","lineNumber":294,"sourceCode":"function taskMobile(screen) {\n  return baseSvg(375,812,`${mobileHeader(\"PAP-1842\")}${multiline(16,96,wrap(screen.title,32,2),20,\"#000\",24,'font-weight=\"700\"')}${tx(16,150,\"Created from Slack\",12,\"#666\")}${rc(16,174,343,96,'fill=\"#e6e6e6\"')}${tx(36,206,\"#customer-support · Refund\",14,\"#000\",'font-weight=\"700\"')}${tx(36,234,\"Assigned to Maya · Open Slack\",12,\"#666\")}${rc(16,286,343,104)}${tx(36,316,\"Ari S. · External · Linked\",12,\"#666\",'font-weight=\"600\"')}${tx(36,352,\"The refund step is timing out again.\",14,\"#000\")}${rc(16,406,343,120,'fill=\"#e6e6e6\"')}${tx(36,436,\"Maya · Agent\",12,\"#666\",'font-weight=\"600\"')}${multiline(36,470,[\"I found the failing retry boundary\", \"and prepared a patch.\"],14,\"#000\",22)}${tx(36,514,\"Delivered to Slack\",12,\"#666\")}${rc(16,542,343,168)}${tx(36,574,\"Internal note\",12,\"#666\")}${ln(36,598,339,598,'stroke=\"#666\"')}${tx(36,638,\"□  Send to channel\",14,\"#000\",'font-weight=\"600\"')}${button(207,650,132,\"Comment\",true)}${annotations([{x:8,y:166,w:359,h:112},{x:8,y:278,w:359,h:120},{x:8,y:398,w:359,h:136},{x:8,y:534,w:359,h:184}],true)}`);\n}\n\nfunction agentChannelsMobile(screen) {\n  return baseSvg(375,812,`${mobileHeader(\"Maya\")}${tx(16,96,\"Channels\",20,\"#000\",'font-weight=\"700\"')}${tx(16,124,\"Every place people can reach Maya.\",12,\"#666\")}${rc(16,158,343,48)}${tx(32,188,\"Channels\",14,\"#000\",'font-weight=\"600\"')}${tx(335,188,\"⌄\",14,\"#666\",'text-anchor=\"end\"')}${button(16,222,343,\"Connect a channel\",true)}${[[\"Slack · @maya\",\"Acme · 2 channels · Healthy\"],[\"Telegram · @maya_helper_bot\",\"Support + DMs · Needs attention\"]].map((r,i)=>{const y=294+i*112;return `${rc(16,y,343,96,i===0?'fill=\"#e6e6e6\"':'fill=\"#fff\"')}${tx(36,y+32,r[0],14,\"#000\",'font-weight=\"700\"')}${tx(36,y+62,r[1],12,\"#666\")}${tx(335,y+48,\"›\",14,\"#000\",'text-anchor=\"end\"')}`;}).join(\"\\n\")}${tx(16,546,\"Recent channel tasks\",14,\"#000\",'font-weight=\"700\"')}${rc(16,566,343,120)}${tx(36,600,\"PAP-1842 · Refund workflow\",14,\"#000\",'font-weight=\"600\"')}${tx(335,600,\"Slack\",12,\"#666\",'text-anchor=\"end\"')}${ln(32,622,343,622,'stroke=\"#e6e6e6\"')}${tx(36,656,\"PAP-1839 · Customer question\",14,\"#000\",'font-weight=\"600\"')}${annotations([{x:8,y:150,w:359,h:64},{x:8,y:214,w:359,h:64},{x:8,y:286,w:359,h:224},{x:8,y:538,w:359,h:156}],true)}`);\n}\n\nfunction flowSvg() {\n  const node = (x,y,w,title,sub,fill=false) => `${rc(x,y,w,88,fill?'fill=\"#e6e6e6\"':'fill=\"#fff\"')}${tx(x+16,y+32,title,14,\"#000\",'font-weight=\"700\"')}${tx(x+16,y+58,sub,12,\"#666\")}`;\n  const arrow = (x1,y1,x2,y2,dashed=false) => `${ln(x1,y1,x2,y2,dashed?'stroke-dasharray=\"6 4\"':'')}<polygon points=\"${x2},${y2} ${x2-9},${y2-6} ${x2-9},${y2+6}\" fill=\"#000\" stroke=\"none\"/>`;\n  return baseSvg(1280,800,`${tx(48,48,\"Chat connectors · simplified Paperclip product flow\",28,\"#000\",'font-weight=\"700\"')}${tx(48,76,\"The provider handoff completes setup; all policy editing happens later in the existing connector-detail shell.\",14,\"#666\")}${node(48,128,176,\"/apps\",\"Choose connector\",true)}${arrow(224,172,264,172)}${node(264,128,192,\"Purpose?\",\"Only when ambiguous\")}${arrow(456,172,496,172)}${node(496,128,184,\"Choose agent\",\"Exactly one\")}${arrow(680,172,720,172)}${node(720,128,184,\"Invite bot\",\"Provider handoff\")}${arrow(904,172,944,172)}${node(944,128,240,\"Connector overview\",\"Connected with defaults\",true)}${arrow(360,216,360,290,true)}${node(264,290,192,\"Existing tool flow\",\"Credential + access\")}${tx(48,258,\"CHAT PATH\",12,\"#666\",'font-weight=\"700\"')}${tx(264,274,\"TOOL BRANCH\",12,\"#666\",'font-weight=\"700\"')}${node(48,402,168,\"Channels\",\"Reach + threads\")}${node(232,402,168,\"Access\",\"People + authority\")}${node(416,402,168,\"Behavior\",\"Output + overlap\")}${node(600,402,184,\"Conversations\",\"Thread ↔ issue\",true)}${node(800,402,168,\"Activity\",\"Delivery ledger\")}${node(984,402,200,\"Agent · Channels\",\"Endpoint summary\")}${`<path d=\"M 1064 216 C 1064 336, 692 320, 692 394\" fill=\"none\" stroke=\"#000\" stroke-dasharray=\"6 4\"/><polygon points=\"692,394 686,385 698,385\" fill=\"#000\" stroke=\"none\"/>`}${arrow(692,490,692,558)}${node(600,558,184,\"Bound task\",\"Normal task UI\",true)}${arrow(784,602,896,602)}${node(896,558,224,\"Provider thread\",\"Safe publication\")}${node(48,650,168,\"Identity link\",\"From Access\")}${node(232,650,168,\"Relay drawer\",\"When private\")}${node(416,650,168,\"Detach/rebind\",\"From conversation\")}${arrow(416,694,592,626,true)}${annotations([{x:40,y:120,w:1152,h:184},{x:256,y:282,w:208,h:104},{x:40,y:394,w:1152,h:104},{x:40,y:542,w:1096,h:204}])}`);\n}\n\nfunction viewerHtml() {\n  const templatePath = join(root, \"../../../packages/skills-catalog/catalog/bundled/product/wireframe/assets/site-template.html\");\n  const template = readFileSync(templatePath, \"utf8\");\n  const style = template.match(/<style>[\\s\\S]*?<\\/style>/)?.[0];\n  if (!style) throw new Error(`Could not read viewer styles from ${templatePath}`);\n  const viewerScreens = [...screens, ...providerScreens];\n  const toc = viewerScreens.map((screen) => `<a href=\"#s${screen.id}\"><span class=\"num\">${Number(screen.id)}</span>${esc(screen.title)}</a>`).join(\"\\n\");\n  const sections = viewerScreens.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\">${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>1280×800 · desktop</span></div><img src=\"wireframes-v2/${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>375×812</span></div><img src=\"wireframes-v2/${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\");\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 — provider planning</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}code{font-size:.92em}</style></head><body><div class=\"shell\"><details class=\"toc\"><summary class=\"toc-summary\"><span><span class=\"crumb\">Chat adapters · v2</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\">Shared flow + provider deep dives</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-v2.md\"><span class=\"num\">U</span>Shared UI specification</a><a href=\"2026-09-04-chat-adapters-platform-surfaces.md\"><span class=\"num\">P</span>Platform specification</a><a href=\"2026-09-03-chat-adapters-research-notes.md\"><span class=\"num\">R</span>Research notes</a><h2>Flow</h2><a href=\"#flow\"><span class=\"num\">⤳</span>Product flow</a><h2>Screens</h2>${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 · Provider planning</div><h1>Connect simply. Configure precisely.</h1><p>Chat connectors live in the existing <code>/apps</code> catalog. The shared path asks for an agent and provider invite; detailed Slack, GitHub, Teams, and Telegram setup and behavior stay provider-specific.</p><div class=\"decision\"><b>Minimum chat path</b><span>Connectors → purpose only if ambiguous → agent → provider handoff → connected.</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-v2.md\">Shared UI specification</a><a href=\"2026-09-04-chat-adapters-platform-surfaces.md\">Platform-specific specification</a><a href=\"2026-09-03-chat-adapters-ui-surfaces.md\">v1 requirements inventory</a></div><div class=\"pills\"><span class=\"pill\">24 focused surfaces</span><span class=\"pill\">4 provider deep dives</span><span class=\"pill\">Setup + settings + interactions</span><span class=\"pill\">Desktop + mobile</span></div></header><div class=\"notice\" role=\"note\"><p><b>Review convention:</b> red dashed marks and numbered circles are annotations only. They are not proposed Paperclip interface elements.</p></div><section id=\"flow\" class=\"flow-section\"><div class=\"lede\">Navigation and product flow</div><h2>Setup ends at the provider handoff</h2><p class=\"desc\">The tool branch returns to the existing tool-connection flow. The chat branch creates a default endpoint, then uses the ordinary connector-detail shell. Screens 13–24 detail the external setup, settings, and native interaction model for each launch provider.</p><div class=\"wire\" data-zoom data-caption=\"Simplified chat connector product flow\"><div class=\"label\"><span>flow.svg</span><span>1280×800</span></div><img src=\"wireframes-v2/flow.svg\" alt=\"Simplified chat connector navigation and product flow\"/></div><div class=\"notes\"><h3>Annotations</h3><ul><li><b>1</b> — Primary chat path from the existing catalog through provider invite.</li><li><b>2</b> — Conditional tool-purpose branch into the existing setup flow.</li><li><b>3</b> — Connector detail navigation and the agent-summary route.</li><li><b>4</b> — Bound task, publication, identity, relay, and detach branches.</li></ul></div></section>${sections}<section id=\"coverage\"><div class=\"lede\">Decisions and coverage</div><h2>What this version resolves</h2><div class=\"notes\"><ul><li><b>One catalog:</b> Connectors remains the only discovery, setup, and management entry point.</li><li><b>Minimal shared wizard:</b> chat-only providers ask for agent and provider handoff; dual-purpose providers add one direction choice.</li><li><b>Real external handoffs:</b> each provider setup separates Paperclip-owned fields from provider app registration, policy, consent, installation, and verification.</li><li><b>Provider-native task boundaries:</b> Slack thread, GitHub object/review thread, Teams post/conversation, and Telegram active chat/topic behavior are explicit.</li><li><b>Least privilege:</b> GitHub code/tool access, Teams RSC/Graph access, Slack optional rich scopes, and Telegram topic administration are never implied by basic chat setup.</li><li><b>Exact fallbacks:</b> unsupported streaming, files, buttons, modals, ephemeral replies, history, or ambient visibility degrade to a named behavior.</li><li><b>Research pins:</b> Paperclip <code>b84964e5a2fa8b1e6498a1ccb471f6adba97d470</code>; Vercel Chat SDK <code>51322dde8f4aafd8a7fc7a20cbfd7ae45cafaa5c</code>; OpenTag <code>6a770d862349f8e996c23c145aef6d6275914a23</code>.</li></ul></div></section><div class=\"footer\">Generated from Paperclip's bundled <code>wireframe</code> skill viewer template. Wires use black 1.5 strokes, white surfaces, grayscale placeholders, an 8px rhythm, and 12/14/20/28 type sizes. Red is reserved for review annotations.</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.tagName==='IMG'?el: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\nfor (const screen of screens) {\n  const desktop = screen.kind === \"catalog\" ? catalogDesktop(screen)\n    : screen.kind === \"purpose\" ? purposeDesktop(screen)\n      : screen.kind === \"agent\" ? chooseAgentDesktop(screen)\n        : screen.kind === \"invite\" ? inviteDesktop(screen)\n          : screen.kind === \"detail\" ? detailDesktop(screen)\n            : screen.kind === \"task\" ? taskDesktop(screen)\n              : agentChannelsDesktop(screen);\n  const mobile = screen.kind === \"catalog\" ? catalogMobile(screen)","sourceCodeStart":276,"sourceCodeEnd":312,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/doc/plans/chat-adapters/generate-wireframes-v2.mjs#L276-L312","documentation":"The v2 wireframe generator builds the HTML viewer by reading the shared site template and extracting its `<style>...</style>` block with the regex `/<style>[\\s\\S]*?<\\/style>/`. If the template file is readable but contains no such block, `style` is undefined and the script throws this error naming the template path rather than emitting an HTML viewer with no styles.","triggerScenarios":"Running the generator when `packages/skills-catalog/catalog/bundled/product/wireframe/assets/site-template.html` no longer contains a literal `<style>` tag — e.g. the template was refactored to use an external `.css` file, `<link>`, or `<style >` with attributes, or the file was replaced.","commonSituations":"The site-template was updated by a design-system change that moved styles out of an inline tag; working on an older generator (v2) against a newer template; a bad merge dropped the style block; regex expects unadorned `<style>` and the template now has `<style media=...>`.","solutions":["Inspect the template at the path in the message and confirm whether a `<style>` block exists; restore it if it was removed by accident.","If styles moved to an external CSS file, update the generator to read the CSS file instead of regex-matching `<style>`.","Relax the regex to allow attributes, e.g. `/<style[^>]*>[\\s\\S]*?<\\/style>/`, if the tag now carries attributes.","Check out the template version contemporaneous with the v2 generator (git log on the asset) and re-run."],"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 (!template.includes(\"<style\")) {\n  throw new Error(`Template ${templatePath} has no inline <style> block; regenerate or point at the CSS file`);\n}","typeGuard":"const templateHasStyles = (html) => /<style[^>]*>[\\s\\S]*?<\\/style>/.test(html);","tryCatchPattern":"const style = template.match(/<style[^>]*>[\\s\\S]*?<\\/style>/)?.[0];\nif (!style) {\n  console.error(`No <style> in ${templatePath}; check for external-CSS refactor`);\n}","preventionTips":["When refactoring the site template, grep doc/plans/chat-adapters/generate-wireframes-*.mjs for `<style>` regex dependencies and update all versions.","Keep the inline style block as part of the template's documented contract.","Test template changes by running the oldest generator version too."],"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"}