{"record":{"id":"a19fa00a37aaf526","repo":"garrytan/gstack","slug":"invalid-mermaid-render-id-id","errorCode":null,"errorMessage":"invalid mermaid render id: ${id}","messagePattern":"invalid mermaid render id: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/diagram-render/src/entry.ts","lineNumber":67,"sourceCode":"\n// Font stacks must match make-pdf/src/print-css.ts (sans + CJK + emoji) so\n// mermaid's text measurement in this tab matches the print document's layout.\nconst PRINT_SANS =\n  'Helvetica, \"Liberation Sans\", Arial, \"Hiragino Kaku Gothic ProN\", ' +\n  '\"Noto Sans CJK JP\", \"Microsoft YaHei\", \"Apple Color Emoji\", ' +\n  '\"Segoe UI Emoji\", \"Noto Color Emoji\", sans-serif';\n\nmermaid.initialize({\n  startOnLoad: false,\n  securityLevel: \"strict\",\n  theme: \"neutral\",\n  fontFamily: PRINT_SANS,\n  htmlLabels: false,\n  flowchart: { htmlLabels: false },\n});\n\nwindow.__renderMermaid = async (id: string, text: string): Promise<string> => {\n  if (!/^[A-Za-z][\\w-]*$/.test(id)) throw new Error(`invalid mermaid render id: ${id}`);\n  const { svg } = await mermaid.render(id, text);\n  return svg;\n};\n\nwindow.__mermaidToExcalidraw = async (text: string): Promise<string> => {\n  const { elements, files } = await parseMermaidToExcalidraw(text);\n  const converted = convertToExcalidrawElements(elements);\n  const scene = {\n    type: \"excalidraw\",\n    version: 2,\n    source: \"gstack-diagram-render\",\n    elements: converted,\n    appState: { viewBackgroundColor: \"#ffffff\" },\n    files: files ?? {},\n  };\n  return JSON.stringify(scene);\n};\n","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/lib/diagram-render/src/entry.ts#L49-L85","documentation":"Thrown by __renderMermaid() when the caller-supplied diagram id fails the regex ^[A-Za-z][\\w-]*$. Mermaid bakes the id into internal SVG element ids (gradients, markers, clip paths), so an invalid id would produce malformed SVG or id collisions between two diagrams inlined into one document. The guard enforces: starts with a letter, remaining chars are word characters or hyphens. This is a hard precondition before mermaid.render is ever called.","triggerScenarios":"Caller invokes window.__renderMermaid(id, text) with an id that starts with a digit, contains spaces/slashes/colons, is empty, or includes unicode. Typical orchestrator ids are 'mermaid-fence-<n>' which always pass.","commonSituations":"Orchestrator bug generating ids from raw fence indices without a letter prefix; user-supplied diagram name used directly as id; id derived from a file path containing slashes; an id that worked in an older mermaid but now fails stricter validation.","solutions":["Prefix the id with a letter, e.g. 'mermaid-fence-' + index.","Strip or replace any non-[A-Za-z0-9_-] characters before calling.","Ensure the id is non-empty and starts with [A-Za-z].","If auto-generating, use a fixed prefix like 'd' + counter."],"exampleFix":"// before\n__renderMermaid('1-flow', graph);\n\n// after\n__renderMermaid('mermaid-fence-1', graph);","handlingStrategy":"validation","validationCode":"function sanitizeMermaidId(id: string): string {\n  let cleaned = id.replace(/[^\\w-]/g, '');\n  if (!/^[A-Za-z]/.test(cleaned)) cleaned = 'd' + cleaned;\n  return cleaned || 'diagram';\n}","typeGuard":"function isValidMermaidId(id: string): boolean {\n  return typeof id === 'string' && /^[A-Za-z][\\w-]*$/.test(id);\n}","tryCatchPattern":null,"preventionTips":["Generate ids from a fixed prefix + counter: 'mermaid-fence-' + n.","Strip user-supplied diagram names of non-word characters before use.","Unit-test the id generator against the regex.","Document the id contract for any caller of __renderMermaid."],"tags":["diagram-render","mermaid","validation","identifier"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}