paperclipai/paperclip · error
Could not replace conversation boundary
Error message
Could not replace ${provider} conversation boundary What it means
`patchConversationBoundary` rewrites the conversation-boundary explanatory copy inside each provider SVG by finding fixed coordinate anchors (`<text x="504" y="234"` desktop, `<text x="16" y="260"` mobile) and an end marker (`<line ...>`), then splicing in wrapped copy. Missing anchors mean the base SVG layout drifted from what the patcher assumes, so it throws per provider.
Solutions
- Regenerate or inspect a base SVG and update `startToken`/`endToken` in `patchConversationBoundary` to the current coordinates and attribute order.
- Keep the boundary placeholder emission and the patcher anchors in one shared constant set so they cannot drift.
- If attribute order changed, match on the minimal stable substring or use a regex instead of exact indexOf tokens.
- Re-run the generator and verify every provider/mobile combination patches cleanly.
Example fix
// before const startToken = mobile ? '<text x="16" y="260"' : '<text x="504" y="234"'; // after (anchors moved) const startToken = mobile ? '<text x="16" y="280"' : '<text x="504" y="254"';
Defensive patterns
Strategy: validation
Validate before calling
const hasBoundaryAnchors = (svg, mobile) => {
const s = mobile ? '<text x="16" y="260"' : '<text x="504" y="234"';
const e = mobile ? '<line x1="16" y1="348"' : '<line x1="504" y1="278"';
return svg.includes(s) && svg.includes(e);
}; Type guard
const canPatchBoundary = (svg, mobile) => svg.indexOf(mobile ? '<text x="16" y="260"' : '<text x="504" y="234"') >= 0;
Try / catch
try {
svg = patchConversationBoundary(svg, provider, mobile);
} catch (err) {
if (String(err.message).includes("conversation boundary")) {
console.error(`Boundary anchors missing for ${provider}${mobile ? " (mobile)" : ""}; resync patchConversationBoundary with the SVG emitter.`);
} else throw err;
} Prevention
- Keep boundary text/line coordinates in one constants module used by both the SVG emitter and the patcher.
- Avoid reordering SVG attributes if string anchors depend on them; prefer regex anchors instead.
- Run the full v7 generation across all providers and both orientations after layout changes.
When it happens
Trigger: Running the v7 generator when the base SVG for a provider no longer contains the boundary text/line at the expected coordinates — layout changes moved the text, the block was removed, attribute order changed (e.g. `<line y1="278" x1="504" ...>`), or mobile/desktop variants were restructured.
Common situations: Adjusting SVG spacing/typography in shared helpers so y=234/260 or the line at y1=278/348 moved; deleting the placeholder boundary block; reformatting attribute order in the SVG emitter.
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 replace navigation
- Could not load wireframe viewer styles
- Could not load wireframe viewer styles
- Could not load wireframe viewer styles
- Could not load wireframe viewer styles
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/751db808ba6736ca.
Report an issue: GitHub.
Appendix: source
Thrown at doc/plans/chat-adapters/generate-wireframes-v7.mjs:201
const end = svg.indexOf(marker, start);
if (start < 0 || end < 0) throw new Error(`Could not replace ${provider} ${tab} navigation`);
const height = Number(svg.match(/<svg[^>]+height="(\d+)"/)?.[1]);
return `${svg.slice(0, start)}${endpointNav(provider, tab, height)}${svg.slice(end + 4)}`;
}
const conversationBoundaryCopy = {
Slack: "A channel root and its replies are one task. The first mention in an existing thread binds it. A DM has one open task; after completion the next message starts another.",
GitHub: "An issue, pull-request conversation, or inline review thread binds once to one Paperclip task.",
"Microsoft Teams": "A channel post and its replies are one task. A personal or group chat has one open task; after completion the next message starts another.",
Telegram: "A DM or ordinary group has one open task; after completion the next addressed message starts another. A forum topic has one stable topic-to-task binding."
};
function patchConversationBoundary(svg, provider, mobile) {
const startToken = mobile ? '<text x="16" y="260"' : '<text x="504" y="234"';
const endToken = mobile ? '<line x1="16" y1="348"' : '<line x1="504" y1="278"';
const start = svg.indexOf(startToken);
const end = svg.indexOf(endToken, start);
if (start < 0 || end < 0) throw new Error(`Could not replace ${provider} conversation boundary`);
const copy = conversationBoundaryCopy[provider];
const replacement = mobile
? multiline(16, 260, wrap(copy, 46, 4), 12, "#666", 18)
: multiline(504, 234, wrap(copy, 88, 2), 14, "#666", 20);
return `${svg.slice(0, start)}${replacement}${svg.slice(end)}`.replaceAll("active task", "open task");
}
const sharedDefinitions = [
{ id:"01", slug:"connectors-catalog", title:"Connectors", subtitle:"Connect tools and places where people talk to agents.", group:"Start", tab:"Shared", annotations:["The existing Apps catalog remains the entry point.", "Filters separate chat and tool methods.", "Each connector row has one Connect action.", "Connection state remains visible in the catalog."], rationale:"The current Connectors surface remains canonical." },
{ id:"02", slug:"connection-purpose", title:"Choose how to connect", subtitle:"Shown for every connector that supports both chat and tool methods.", group:"Start", tab:"Shared", annotations:["The existing connection wizard shell and selected provider are reused.", "Chat with an agent is the incoming-conversation path.", "Use this connection as an agent tool is the outbound tool/credential path.", "Single-purpose providers skip the choice."], rationale:"The registry drives the same direction choice for every dual-surface connector." },
{ id:"03", slug:"choose-agent", title:"Which agent do you want to chat with?", subtitle:"Choose the one agent represented by this connection.", group:"Start", tab:"Shared", annotations:["The existing agent selector is reused.", "Only active agents can be selected.", "One selection is required.", "Continue begins provider setup."], rationale:"This is the only shared Paperclip-specific setup decision." },
{ id:"11", slug:"bound-task", title:"Externally bound task", subtitle:"A normal Paperclip task with explicit publication and detach controls.", group:"Paperclip", tab:"Task", annotations:["The task shows its external source.", "External actors remain attributed.", "Publishing back to the provider is explicit for human comments.", "The agent remains locked until detach."], rationale:"External work stays in the ordinary governed task experience." },
{ id:"12", slug:"agent-channels", title:"Agent Channels", subtitle:"See every provider identity representing this agent.", group:"Paperclip", tab:"Agent", annotations:["Channel identities are summarized per provider.", "Health and recent tasks remain visible.", "Connections open in Connectors.", "Connect a channel preselects this agent."], rationale:"Agent detail summarizes endpoints while Connectors manages them." }
];
const sharedScreens = sharedDefinitions.map((screen) => {
for (const suffix of ["", "-mobile"]) writeFileSync(join(out, `${screen.id}-${screen.slug}${suffix}.svg`), readFileSync(join(previous, `${screen.id}-${screen.slug}${suffix}.svg`), "utf8"));
return { ...screen, desktopSize:"1280×800", mobileSize:"375×812" };View on GitHub (pinned to 3f1d897a7c)