{"record":{"id":"be8970adb03fb3b0","repo":"Stirling-Tools/Stirling-PDF","slug":"no-history-chain-found-for-file","errorCode":null,"errorMessage":"No history chain found for file.","messagePattern":"No history chain found for file\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"frontend/editor/src/core/services/serverStorageBundle.ts","lineNumber":53,"sourceCode":"export async function buildHistoryBundle(\n  originalFileIds: FileId[] | FileId,\n): Promise<{\n  bundleFile: File;\n  manifest: ShareBundleManifest;\n}> {\n  const roots = Array.isArray(originalFileIds)\n    ? originalFileIds\n    : [originalFileIds];\n  const uniqueRoots = Array.from(new Set(roots));\n  const allStubs: Array<{\n    rootId: FileId;\n    stubs: Awaited<ReturnType<typeof fileStorage.getHistoryChainStubs>>;\n  }> = [];\n\n  for (const rootId of uniqueRoots) {\n    const stubs = await fileStorage.getHistoryChainStubs(rootId);\n    if (stubs.length === 0) {\n      throw new Error(\"No history chain found for file.\");\n    }\n    allStubs.push({ rootId, stubs });\n  }\n\n  const zip = new JSZip();\n  const entries: ShareBundleEntry[] = [];\n\n  for (const chain of allStubs) {\n    for (const stub of chain.stubs) {\n      const file = await fileStorage.getStirlingFile(stub.id);\n      if (!file) {\n        throw new Error(`Missing file data for ${stub.name || stub.id}`);\n      }\n\n      const logicalId = stub.id;\n      const filePath = `files/${logicalId}/${sanitizeFilename(stub.name || \"file\")}`;\n      const buffer = await file.arrayBuffer();\n      zip.file(filePath, buffer);","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/core/services/serverStorageBundle.ts#L35-L71","documentation":"`buildHistoryBundle` calls `fileStorage.getHistoryChainStubs(rootId)`, which returns all stubs whose `originalFileId` (or `id`) equals `rootId`. An empty result throws. So this fires when no stored file traces its lineage back to the supplied root ID — the file isn't in IndexedDB, or it was never versioned into a chain.","triggerScenarios":"Sharing a `rootId` that was evicted by the LRU cache, cleared by the user, or never persisted (e.g. file still only in memory); passing the wrong ID type (string vs the internal FileId); the file was created in a prior session whose IndexedDB was wiped; calling `buildHistoryBundle` on a freshly-imported file before any tool operation created a history entry.","commonSituations":"User clears browsing data; Safari/WebKit evicted the blob; ID mismatch after a storage migration; sharing before any edit (no chain exists).","solutions":["Before sharing, verify the root exists: `const stub = await fileStorage.getStirlingFileStub(rootId); if (!stub) return notify('File not found')`.","If a file genuinely has no chain, offer to share the single current file via `buildSharePackage` instead of `buildHistoryBundle`.","Validate that `rootId` is the original/root ID, not a derived leaf ID.","Handle the case gracefully rather than throwing — return null and let the UI explain."],"exampleFix":"// before\nconst stubs = await fileStorage.getHistoryChainStubs(rootId);\nif (stubs.length === 0) {\n  throw new Error(\"No history chain found for file.\");\n}\n\n// after\nconst stubs = await fileStorage.getHistoryChainStubs(rootId);\nif (stubs.length === 0) {\n  const single = await fileStorage.getStirlingFileStub(rootId);\n  if (!single) throw new Error(`File ${rootId} not found in storage`);\n  return buildSharePackage([single]);\n}","handlingStrategy":"validation","validationCode":"// Verify the root exists before building a history bundle\nconst rootStub = await fileStorage.getStirlingFileStub(rootId);\nif (!rootStub) {\n  notifyUser(\"This file is no longer available in local storage.\");\n  return;\n}","typeGuard":"function isKnownRoot(stub: StirlingFileStub | null): stub is StirlingFileStub {\n  return stub !== null;\n}","tryCatchPattern":"try {\n  await buildHistoryBundle(rootId);\n} catch (e) {\n  if (e instanceof Error && e.message === \"No history chain found for file.\") {\n    notifyUser(\"No edit history for this file; sharing the current version instead.\");\n    const single = await fileStorage.getStirlingFileStub(rootId);\n    if (single) await buildSharePackage([single]);\n  } else throw e;\n}","preventionTips":["Confirm the root exists via getStirlingFileStub before sharing.","Offer buildSharePackage (single file) when no history chain exists.","Ensure rootId is the original/root ID, not a derived leaf ID.","Handle the empty-chain case gracefully instead of throwing."],"tags":["storage","sharing","history","indexeddb","validation"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}