{"record":{"id":"e4f449abb1943aca","repo":"Stirling-Tools/Stirling-PDF","slug":"failed-to-build-payload","errorCode":null,"errorMessage":"Failed to build payload","messagePattern":"Failed to build payload","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/core/tools/pdfTextEditor/PdfTextEditor.tsx","lineNumber":1591,"sourceCode":"              },\n            );\n          }\n          console.warn(\n            \"[handleSaveToWorkbench] Incremental export failed, falling back to full export\",\n            incrementalError,\n          );\n          // Fall through to full export\n          if (isLazyMode && totalPages > 0) {\n            const allPageIndices = Array.from(\n              { length: totalPages },\n              (_, index) => index,\n            );\n            await ensureImagesForPages(allPageIndices);\n          }\n\n          const payload = buildPayload();\n          if (!payload) {\n            throw new Error(\"Failed to build payload\", {\n              cause: incrementalError,\n            });\n          }\n\n          const { document, filename } = payload;\n          const serialized = JSON.stringify(document);\n          const jsonFile = new File([serialized], filename, {\n            type: \"application/json\",\n          });\n\n          const formData = new FormData();\n          formData.append(\"fileInput\", jsonFile);\n          const response = await apiClient.post(\n            CONVERSION_ENDPOINTS[\"text-editor-pdf\"],\n            formData,\n            {\n              responseType: \"blob\",\n            },","sourceCodeStart":1573,"sourceCodeEnd":1609,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/core/tools/pdfTextEditor/PdfTextEditor.tsx#L1573-L1609","documentation":"Thrown when buildPayload() returns null during the fallback full-export path of the PDF text editor's save-to-workbench flow. buildPayload() returns null only when loadedDocument is falsy (PdfTextEditor.tsx:1214). This error fires after an incremental/partial export already failed (caught in the catch block), meaning the component tried to recover via a full rebuild but had no loaded document to rebuild from.","triggerScenarios":"An incremental export via /api/v1/convert/pdf/text-editor/partial/{cachedJobId} threw (network error, 4xx/5xx, expired cachedJobId). The catch block then attempted a full export fallback, but loadedDocument was null/undefined at that moment — e.g. the document was unloaded or cleared while the async export was in flight, or the component was unmounted mid-save.","commonSituations":"User navigates away or closes the editor while a large lazy-mode PDF is exporting. Race condition where the editor's document state is reset (file context swap, memory cleanup) before the fallback export runs. The incremental export failed because the cached job expired (server eviction) and the editor state was concurrently cleared.","solutions":["Guard against loadedDocument being null before entering the export flow; bail early with a user-facing message instead of reaching the fallback.","Capture the document snapshot synchronously before the first await (incremental export) so the fallback still has data.","Verify cachedJobId validity before the incremental call to avoid the initial failure that triggers the fallback.","If the document is genuinely gone, show a toast prompting the user to reload the PDF rather than throwing."],"exampleFix":"// before\nconst payload = buildPayload();\nif (!payload) {\n  throw new Error(\"Failed to build payload\", { cause: incrementalError });\n}\n\n// after\nconst payload = buildPayload();\nif (!payload) {\n  alert({\n    alertType: \"error\",\n    title: t(\"pdfTextEditor.export.documentMissing\", \"The document is no longer loaded. Please reopen the file and try again.\"),\n  });\n  return;\n}","handlingStrategy":"validation","validationCode":"// Before entering the export fallback, snapshot the document synchronously:\nconst docSnapshot = loadedDocumentRef.current;\nif (!docSnapshot) {\n  alert({ alertType: \"error\", title: \"Document no longer loaded. Please reopen the file.\" });\n  return;\n}\n// pass docSnapshot into buildPayload so the fallback always has data","typeGuard":"// Narrow before use\nfunction hasLoadedDocument(doc: unknown): doc is LoadedDocument {\n  return !!doc && typeof doc === \"object\" && Array.isArray((doc as LoadedDocument).pages);\n}","tryCatchPattern":"// In the catch block, validate payload presence gracefully:\n} catch (incrementalError) {\n  const payload = buildPayload();\n  if (!payload) {\n    console.error(\"Fallback export skipped: document not loaded\", incrementalError);\n    alert({ alertType: \"error\", title: \"Export failed. Please reload the PDF and retry.\" });\n    return;\n  }\n  // ... proceed with full export\n}","preventionTips":["Snapshot the loaded document synchronously before any await so async state changes cannot null it mid-flow.","Disable the Save button while no document is loaded.","Avoid relying on a fallback path that re-reads mutable state after async failures."],"tags":["react","state-management","pdf-editor","race-condition","fallback"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}