tonhowtf/omniget · warning
notebooksStore.hydrate failed
Error message
notebooksStore.hydrate failed
What it means
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.
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.
Example fix
// before
const [rows, active] = await Promise.all([
notesNotebooksList({ includeClosed: true }),
notesNotebooksActiveGet(),
]);
// after
const [rowsRes, activeRes] = await Promise.allSettled([
notesNotebooksList({ includeClosed: true }),
notesNotebooksActiveGet(),
]);
if (rowsRes.status === 'fulfilled') this.list = rowsRes.value;
if (activeRes.status === 'fulfilled') this.activeId = activeRes.value.notebook_id;
else throw new Error('active notebook get failed: ' + String(activeRes.reason)); Defensive patterns
Strategy: try-catch
Validate before calling
// before hydrate
if (!window.__TAURI_INTERNALS__) console.warn('Not running inside Tauri; hydration will fail');
await notebooksStore.hydrate();
if (notebooksStore.hydrated && notebooksStore.list.length === 0) {
console.warn('Hydrated to empty list — possible backend failure, offer retry');
} Type guard
function isTauriError(e: unknown): e is { message: string } {
return typeof e === 'object' && e !== null && 'message' in e && typeof (e as { message: unknown }).message === 'string';
} Try / catch
try {
await notebooksStore.hydrate();
} catch (e) {
console.warn('hydrate rejected:', e instanceof Error ? e.message : String(e));
showRetryBanner();
}
if (notebooksStore.list.length === 0) showRetryBanner(); Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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).
Understand the failure class
Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.
Related errors
AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12).
Data as JSON: /api/errors/11b93cc20523dbbe.
Report an issue: GitHub.
Appendix: source
Thrown at src/lib/study-notes/notebooks-store.svelte.ts:67
hydrated = $state(false);
loading = $state(false);
visible = $derived(this.list.filter((n) => !n.closed));
closed = $derived(this.list.filter((n) => n.closed));
active = $derived(this.list.find((n) => n.id === this.activeId) ?? null);
async hydrate() {
if (this.loading) return;
this.loading = true;
try {
const [rows, active] = await Promise.all([
notesNotebooksList({ includeClosed: true }),
notesNotebooksActiveGet(),
]);
this.list = rows;
this.activeId = active.notebook_id;
} catch (e) {
console.warn("notebooksStore.hydrate failed", e);
} finally {
this.hydrated = true;
this.loading = false;
}
}
async refresh() {
try {
this.list = await notesNotebooksList({ includeClosed: true });
} catch (e) {
console.warn("notebooksStore.refresh failed", e);
}
}
pageCountOf(id: number): number {
return this.list.find((n) => n.id === id)?.page_count ?? 0;
}
View on GitHub (pinned to 8600b91f42)