{"record":{"id":"11b93cc20523dbbe","repo":"tonhowtf/omniget","slug":"notebooksstore-hydrate-failed","errorCode":null,"errorMessage":"notebooksStore.hydrate failed","messagePattern":"notebooksStore\\.hydrate failed","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/lib/study-notes/notebooks-store.svelte.ts","lineNumber":67,"sourceCode":"  hydrated = $state(false);\n  loading = $state(false);\n\n  visible = $derived(this.list.filter((n) => !n.closed));\n  closed = $derived(this.list.filter((n) => n.closed));\n  active = $derived(this.list.find((n) => n.id === this.activeId) ?? null);\n\n  async hydrate() {\n    if (this.loading) return;\n    this.loading = true;\n    try {\n      const [rows, active] = await Promise.all([\n        notesNotebooksList({ includeClosed: true }),\n        notesNotebooksActiveGet(),\n      ]);\n      this.list = rows;\n      this.activeId = active.notebook_id;\n    } catch (e) {\n      console.warn(\"notebooksStore.hydrate failed\", e);\n    } finally {\n      this.hydrated = true;\n      this.loading = false;\n    }\n  }\n\n  async refresh() {\n    try {\n      this.list = await notesNotebooksList({ includeClosed: true });\n    } catch (e) {\n      console.warn(\"notebooksStore.refresh failed\", e);\n    }\n  }\n\n  pageCountOf(id: number): number {\n    return this.list.find((n) => n.id === id)?.page_count ?? 0;\n  }\n","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src/lib/study-notes/notebooks-store.svelte.ts#L49-L85","documentation":"notebooksStore.hydrate() bootstraps the notebook store by awaiting two backend calls in one Promise.all: notesNotebooksList({includeClosed:true}) and notesNotebooksActiveGet(), both Tauri pluginInvoke commands ('study:notes:notebooks:list' and ':active_get'). If either IPC command rejects (backend error, plugin not registered, DB failure), the Promise.all rejects and the catch logs 'notebooksStore.hydrate failed' while the store still marks itself hydrated with an empty list.","triggerScenarios":"notesNotebooksList or notesNotebooksActiveGet rejects: the study-notes plugin command is unregistered/not allowed in capabilities, the notes SQLite store cannot be opened, the backend returns an error payload, or the IPC channel fails; Promise.all fails fast on the first rejection.","commonSituations":"App started before the backend plugin finished registering; corrupted or locked notes database; missing capability permission for the notes commands; a backend migration failed so notebooks:list or notebooks:active_get errors out; running the frontend in a plain browser (no Tauri invoke available).","solutions":["Check the logged `e` object to see which of the two Promise.all calls rejected (notesNotebooksList vs notesNotebooksActiveGet) and its backend message.","Verify the study:notes plugin is registered and its commands are permitted in the Tauri capabilities config.","Call the failing bridge function directly in the devtools console to confirm it reproduces outside the store.","If the backend reports a database error, back up and repair/recreate the notes DB, then retry hydration.","Add a rehydrate/retry affordance in the UI since the store swallows the error and sets hydrated=true with empty data."],"exampleFix":"// before\nconst [rows, active] = await Promise.all([\n  notesNotebooksList({ includeClosed: true }),\n  notesNotebooksActiveGet(),\n]);\n// after\nconst [rowsRes, activeRes] = await Promise.allSettled([\n  notesNotebooksList({ includeClosed: true }),\n  notesNotebooksActiveGet(),\n]);\nif (rowsRes.status === 'fulfilled') this.list = rowsRes.value;\nif (activeRes.status === 'fulfilled') this.activeId = activeRes.value.notebook_id;\nelse throw new Error('active notebook get failed: ' + String(activeRes.reason));","handlingStrategy":"try-catch","validationCode":"// before hydrate\nif (!window.__TAURI_INTERNALS__) console.warn('Not running inside Tauri; hydration will fail');\nawait notebooksStore.hydrate();\nif (notebooksStore.hydrated && notebooksStore.list.length === 0) {\n  console.warn('Hydrated to empty list — possible backend failure, offer retry');\n}","typeGuard":"function isTauriError(e: unknown): e is { message: string } {\n  return typeof e === 'object' && e !== null && 'message' in e && typeof (e as { message: unknown }).message === 'string';\n}","tryCatchPattern":"try {\n  await notebooksStore.hydrate();\n} catch (e) {\n  console.warn('hydrate rejected:', e instanceof Error ? e.message : String(e));\n  showRetryBanner();\n}\nif (notebooksStore.list.length === 0) showRetryBanner();","preventionTips":["Never assume hydrated=true means data loaded — check list.length","Expose a manual rehydrate button in the UI","Verify Tauri capabilities include all study:notes commands before release","Log which Promise.all member rejected by using Promise.allSettled during development","Add an app-startup health check against a trivial notes command"],"tags":["svelte","tauri","ipc","frontend","error-handling"],"backgroundTag":"api-request-failed","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}