{"record":{"id":"672615668404945b","repo":"garrytan/gstack","slug":"bundleerrormessage","errorCode":null,"errorMessage":"${bundleErrorMessage}","messagePattern":"\\$\\{bundleErrorMessage\\}","errorType":"exception","errorClass":"RenderCallError","httpStatus":null,"severity":"error","filePath":"make-pdf/src/diagram-prepass.ts","lineNumber":387,"sourceCode":"        `(${READY_TIMEOUT_MS}ms). Check \\`browse js \"window.__errors\"\\` on tab ${this.tabId}.`,\n      );\n    }\n  }\n\n  /**\n   * Call one of the bundle's async window functions with JSON-safe string\n   * args. Errors come back as a recognizable ERR: prefix so a render failure\n   * is data, not a thrown browse exit.\n   */\n  call(fn: string, ...args: Array<string | number>): string {\n    const argList = args.map((a) => JSON.stringify(a)).join(\",\");\n    const expression =\n      `window.${fn}(${argList})` +\n      `.then(r => \"OK:\" + r)` +\n      `.catch(e => \"ERR:\" + String((e && e.message) || e))`;\n    const result = this.js(expression);\n    if (result.startsWith(\"OK:\")) return result.slice(3);\n    if (result.startsWith(\"ERR:\")) throw new RenderCallError(result.slice(4));\n    throw new RenderCallError(`unexpected bundle result: ${result.slice(0, 200)}`);\n  }\n\n  private js(expression: string): string {\n    // Large payloads (scene JSON, SVG text, data URIs) blow past argv limits —\n    // browseClient.js shells out with the expression as an argv element. The\n    // limit is BYTES, not chars (CJK content is 3x its char count in UTF-8),\n    // and Windows caps the whole command line at 32,767 chars — so anything\n    // big ships via `browse eval <file>` instead: one spawn, any size.\n    if (Buffer.byteLength(expression, \"utf8\") <= MAX_ARGV_EXPR_BYTES) {\n      return browseClient.js({ expression, tabId: this.tabId });\n    }\n    return this.jsViaFile(expression);\n  }\n\n  /** argv-safe path for big expressions: stage to a tmp file under browse's\n   *  safe dirs and run `browse eval <file>` (one spawn regardless of size). */\n  private jsViaFile(expression: string): string {","sourceCodeStart":369,"sourceCodeEnd":405,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/make-pdf/src/diagram-prepass.ts#L369-L405","documentation":"RenderCallError thrown by RenderTab.call() when the bundle-side async function (e.g. __renderMermaid, __excalidrawToSvg) rejects, or returns a value without the expected OK:/ERR: protocol prefix. The bundle reports failures as 'ERR:<message>' so a render failure is data, not a browse exit; this error re-throws that message.","triggerScenarios":"Calling tab.call('__renderMermaid', id, source) or tab.call('__excalidrawToSvg', source) where the bundle function throws (invalid mermaid syntax, unparseable excalidraw JSON, renderer OOM), or returns an unexpected string. Also if the bundle was poisoned by a prior fence and not reloaded.","commonSituations":"A mermaid fence with a syntax error the bundle rejects; excalidraw JSON missing a required field; a CJK/huge diagram exhausting renderer memory; a prior poisoned fence corrupting global state because loadBundle reset was skipped; a bundle version whose function signatures changed.","solutions":["Read the ERR: message embedded in the thrown error — it is the bundle's own diagnostic.","Validate mermaid/excalidraw source before calling (JSON.parse for excalidraw is already done upstream; add a mermaid lint step).","Ensure the reset contract holds: after any render error, loadBundle() must run before the next fence.","Rebuild the bundle if the function name or signature changed (`bun run build:diagram-render`)."],"exampleFix":"// before\nconst result = this.js(expression);\nif (result.startsWith('OK:')) return result.slice(3);\nif (result.startsWith('ERR:')) throw new RenderCallError(result.slice(4));\nthrow new RenderCallError(`unexpected bundle result: ${result.slice(0, 200)}`);\n\n// after: include the fence id and function in the error for traceability\nconst result = this.js(expression);\nif (result.startsWith('OK:')) return result.slice(3);\nif (result.startsWith('ERR:')) throw new RenderCallError(`${fn}: ${result.slice(4)}`);\nthrow new RenderCallError(`${fn}: unexpected bundle result: ${result.slice(0, 200)}`);","handlingStrategy":"try-catch","validationCode":"// Validate fence source before sending to the bundle.\nfunction validateFence(fence: DiagramFence): string | null {\n  if (fence.lang === 'mermaid') {\n    if (/graph|sequenceDiagram|flowchart|classDiagram|stateDiagram|erDiagram/.test(fence.source)) return null;\n    return 'mermaid source does not start with a known diagram type';\n  }\n  try { JSON.parse(fence.source); return null; } catch { return 'excalidraw source is not valid JSON'; }\n}","typeGuard":"import { RenderCallError } from './diagram-prepass';\nfunction isRenderCallError(e: unknown): e is RenderCallError {\n  return e instanceof RenderCallError;\n}","tryCatchPattern":"for (const fence of fences) {\n  try {\n    render(fence);\n  } catch (e) {\n    if (e instanceof RenderCallError) {\n      slots.set(fence.token, buildDiagnosticBlock(fence, e.message));\n      tab.loadBundle(); // reset contract before next fence\n      continue;\n    }\n    throw e;\n  }\n}","preventionTips":["Always run loadBundle() after a RenderCallError before the next fence.","Lint mermaid and JSON.parse excalidraw sources before invoking the bundle.","Keep fence payloads small to avoid renderer OOM.","Pin the diagram-render bundle to a version whose function names match the caller."],"tags":["make-pdf","diagram","render","mermaid","excalidraw"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}