{"record":{"id":"bf46ef91da57d5e1","repo":"Stirling-Tools/Stirling-PDF","slug":"missing-final-file-data-for-sharing","errorCode":null,"errorMessage":"Missing final file data for sharing.","messagePattern":"Missing final file data for sharing\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/core/services/serverStorageUpload.ts","lineNumber":37,"sourceCode":"}\n\nexport async function uploadHistoryChain(\n  originalFileId: FileId,\n  existingRemoteId?: number,\n): Promise<{ remoteId: number; updatedAt: number; chain: StirlingFileStub[] }> {\n  const chain = await fileStorage.getHistoryChainStubs(originalFileId);\n  if (chain.length === 0) {\n    throw new Error(\"No history chain found.\");\n  }\n\n  const finalStub =\n    chain\n      .slice()\n      .reverse()\n      .find((stub) => stub.isLeaf !== false) || chain[chain.length - 1];\n  const finalFile = await fileStorage.getStirlingFile(finalStub.id);\n  if (!finalFile) {\n    throw new Error(\"Missing final file data for sharing.\");\n  }\n\n  const { bundleFile, manifest } = await buildHistoryBundle(originalFileId);\n  const auditLog = new File(\n    [JSON.stringify(manifest, null, 2)],\n    \"audit-log.json\",\n    {\n      type: \"application/json\",\n      lastModified: Date.now(),\n    },\n  );\n  const formData = new FormData();\n  formData.append(\"file\", finalFile, finalFile.name);\n  formData.append(\"historyBundle\", bundleFile, bundleFile.name);\n  formData.append(\"auditLog\", auditLog, auditLog.name);\n\n  if (existingRemoteId) {\n    const response = await apiClient.put(","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/core/services/serverStorageUpload.ts#L19-L55","documentation":"Thrown by uploadHistoryChain when the history chain's leaf stub exists in metadata but getStirlingFile(leaf.id) returns null — the latest version's bytes are unreadable/missing.","triggerScenarios":"The leaf stub resolves (isLeaf !== false) but its IndexedDB blob is gone or session-flagged unreadable, while earlier versions or stub metadata remain.","commonSituations":"WebKit blob eviction hitting only the newest version; partial write during a tool operation; an unreadable record flagged earlier in the session.","solutions":["Re-open the latest version in the workbench to re-store its bytes.","Check the leaf stub's dataUnavailable flag before uploading.","Fall back to the most recent readable version in the chain if the leaf is unreadable."],"exampleFix":"// before\nconst finalFile = await fileStorage.getStirlingFile(finalStub.id);\nif (!finalFile) throw new Error('Missing final file data for sharing.');\n\n// after\nlet finalFile = await fileStorage.getStirlingFile(finalStub.id);\nif (!finalFile) {\n  // walk backwards to the most recent readable version\n  for (const s of chain.slice().reverse()) {\n    finalFile = await fileStorage.getStirlingFile(s.id);\n    if (finalFile) break;\n  }\n}\nif (!finalFile) throw new Error('No readable version in chain.');","handlingStrategy":"validation","validationCode":"const leaf = await fileStorage.getStirlingFileStub(finalStub.id);\nif (!leaf || leaf.dataUnavailable) {\n  // pick an earlier readable version or abort\n}","typeGuard":"async function readableLeaf(chain: StirlingFileStub[]): Promise<StirlingFileStub | null> {\n  for (const s of chain.slice().reverse()) {\n    const meta = await fileStorage.getStirlingFileStub(s.id);\n    if (meta && !meta.dataUnavailable) return s;\n  }\n  return null;\n}","tryCatchPattern":null,"preventionTips":["Before uploading, confirm the leaf stub is readable (dataUnavailable !== true).","Keep a fallback to the prior readable version rather than aborting the share."],"tags":["indexeddb","file-storage","history-chain","blob","sharing"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}