{"record":{"id":"50c037889ca73797","repo":"paperclipai/paperclip","slug":"could-not-replace-provider-tab-navigation","errorCode":null,"errorMessage":"Could not replace ${provider} ${tab} navigation","messagePattern":"Could not replace (.+?) (.+?) navigation","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"doc/plans/chat-adapters/generate-wireframes-v7.mjs","lineNumber":184,"sourceCode":"  for (const section of screen.sections) {\n    const rendered = sectionMobile(section, top);\n    body.push(rendered.body);\n    regions.push(rendered.region);\n    top += rendered.height + 16;\n  }\n  const actionY = top + 16;\n  body.push(button(16, actionY, 343, \"Save changes\", true));\n  regions.push({ x:8, y:actionY - 8, w:359, h:64 });\n  const height = Math.max(812, actionY + 88);\n  body.push(annotations(regions, true));\n  return { width:375, height, svg:baseSvg(375, height, body.join(\"\\n\")) };\n}\n\nfunction patchDesktopNavigation(svg, provider, tab) {\n  const start = svg.indexOf('<g><text x=\"264\" y=\"94\"');\n  const marker = '</g><text x=\"504\"';\n  const end = svg.indexOf(marker, start);\n  if (start < 0 || end < 0) throw new Error(`Could not replace ${provider} ${tab} navigation`);\n  const height = Number(svg.match(/<svg[^>]+height=\"(\\d+)\"/)?.[1]);\n  return `${svg.slice(0, start)}${endpointNav(provider, tab, height)}${svg.slice(end + 4)}`;\n}\n\nconst conversationBoundaryCopy = {\n  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.\",\n  GitHub: \"An issue, pull-request conversation, or inline review thread binds once to one Paperclip task.\",\n  \"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.\",\n  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.\"\n};\n\nfunction patchConversationBoundary(svg, provider, mobile) {\n  const startToken = mobile ? '<text x=\"16\" y=\"260\"' : '<text x=\"504\" y=\"234\"';\n  const endToken = mobile ? '<line x1=\"16\" y1=\"348\"' : '<line x1=\"504\" y1=\"278\"';\n  const start = svg.indexOf(startToken);\n  const end = svg.indexOf(endToken, start);\n  if (start < 0 || end < 0) throw new Error(`Could not replace ${provider} conversation boundary`);\n  const copy = conversationBoundaryCopy[provider];","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/doc/plans/chat-adapters/generate-wireframes-v7.mjs#L166-L202","documentation":"`patchDesktopNavigation` in the v7 generator post-processes base SVGs by locating a fixed navigation group (`<g><text x=\"264\" y=\"94\"` through `</g><text x=\"504\"`) and swapping in a provider/tab-specific endpoint nav. The hardcoded geometric anchors assume exact base-SVG layout; if either anchor is absent, the script throws rather than emitting a screen with the wrong navigation.","triggerScenarios":"Running the v7 generator after the base SVG template changed its left-nav markup or coordinates (moved text x/y, restructured groups, different tag ordering), so `svg.indexOf` returns -1 for the start anchor or the `</g><text x=\"504\"` end marker.","commonSituations":"Editing the shared SVG drawing helpers (spacing, font, nav labels) and shifting coordinates; adding screens whose base layout differs; refactoring SVG builders to emit `<text>` before `<g>` or different attribute order.","solutions":["Update the start/end anchor strings in `patchDesktopNavigation` to match the current base-SVG markup (check a generated/base SVG for the actual text coordinates).","If coordinates are now dynamic, compute the anchors from the same constants used to draw the nav instead of hardcoding x=264/y=94.","Ensure the base SVG drawing function still emits the nav group the patcher expects (e.g. after a layout refactor, restore or adapt the marker).","Re-run the generator for all providers/tabs to confirm no screen trips the patch."],"exampleFix":"// before\nconst start = svg.indexOf('<g><text x=\"264\" y=\"94\"');\nconst marker = '</g><text x=\"504\"';\n// after (match new base layout)\nconst start = svg.indexOf('<g><text x=\"280\" y=\"94\"');\nconst marker = '</g><text x=\"520\"';","handlingStrategy":"validation","validationCode":"const anchorsOk = (svg) =>\n  svg.includes('<g><text x=\"264\" y=\"94\"') && svg.includes('</g><text x=\"504\"');\nif (!anchorsOk(baseSvg)) throw new Error(\"base SVG nav anchors drifted; update patchDesktopNavigation\");","typeGuard":"const patchable = (svg, startToken, endToken) => svg.indexOf(startToken) >= 0 && svg.indexOf(endToken, svg.indexOf(startToken)) >= 0;","tryCatchPattern":"try {\n  svg = patchDesktopNavigation(svg, provider, tab);\n} catch (err) {\n  if (String(err.message).startsWith(\"Could not replace\")) {\n    console.error(`Base SVG layout for ${provider}/${tab} changed; update the anchors in patchDesktopNavigation.`);\n  } else throw err;\n}","preventionTips":["Derive nav coordinates in the SVG emitter and the patcher from shared constants.","Regenerate and inspect one base SVG after any layout/typography change.","Fail fast in the emitter if the expected nav group is not produced."],"tags":["build-script","svg","string-parsing"],"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"}