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

  1. Check the logged `e` object to see which of the two Promise.all calls rejected (notesNotebooksList vs notesNotebooksActiveGet) and its backend message.
  2. Verify the study:notes plugin is registered and its commands are permitted in the Tauri capabilities config.
  3. Call the failing bridge function directly in the devtools console to confirm it reproduces outside the store.
  4. If the backend reports a database error, back up and repair/recreate the notes DB, then retry hydration.
  5. 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

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)