Projects BibleWeb Navigation UX Reading progress tracking
Done

Reading progress tracking

Area: Navigation UX

1. Context

Users reading through the Bible want to resume exactly where they left off without having to manually navigate back to their chapter. This feature silently tracks the last-read position and makes it available the next time the app loads — removing the friction of re-finding your place.

2. Functional

  • Stores the user's last-read bookId (number) and chapter (number) together with a Unix timestamp.
  • Persisted to localStorage under the key reading-progress as a JSON object.
  • Initialized once on client mount via initReadingProgress() — reads from localStorage and loads the stored position into reactive state.
  • Updated on every chapter navigation via updateReadingProgress(bookId, chapter) — writes the new position to both reactive state and localStorage.
  • Exposed as a read-only reactive getter readingProgress.current returning ReadingPosition | null.
  • Silent error handling: corrupted localStorage data is silently discarded; unavailable storage is also silently skipped. No toasts or error states.

3. UX & Design

  • Entirely invisible to the user — no explicit "save progress" button, no notifications.
  • On return visits the app can read readingProgress.current and route the user directly to their last chapter.
  • Because it writes to localStorage, progress survives page reloads and browser restarts on the same device.
  • No cross-device sync — progress is local-only.

4. Technical

Store: apps/web/src/lib/stores/reading-progress.svelte.ts

  • Uses Svelte 5 $state for reactivity; the module-level current variable is the single source of truth at runtime.
  • localStorage key: 'reading-progress'
  • Stored shape: { bookId: number, chapter: number, timestamp: number }
  • Guards against SSR: initReadingProgress() returns immediately if typeof window === 'undefined'.

API surface:

initReadingProgress(): void          // call once on mount
updateReadingProgress(bookId, chapter): void  // call on each chapter load
readingProgress.current              // reactive read — ReadingPosition | null

No server-side component. No database writes. No authentication required.

5. Status

Implemented. The store exists and is ready to be wired up to the reader's chapter navigation. Verify that initReadingProgress() is called in the root layout and updateReadingProgress() is called in the reader's chapter load logic.