feat: continuous reader mode with persisted preference
Add a vertical-scroll continuous mode to the reader alongside the existing single-page mode. A segmented toggle in the reader top bar switches between them; in continuous mode a gap selector (None/Small/Medium/Large → 0/12/32/64px) controls the spacing between stacked pages. Settings page mirrors the same controls. Backend: new user_preferences table (one row per user, lazily inserted, ON DELETE CASCADE) and GET/PATCH /api/v1/auth/me/preferences gated by the existing CurrentUser extractor. Allowed values are enforced both by API validation and table-level CHECK constraints. Eight integration tests cover defaults, persistence, partial updates, validation errors, auth, per-user isolation, and cascade. Frontend: a new preferences store mirrors the theme-store pattern with a localStorage shadow so anonymous browsers get a consistent experience and logged-in users don't flash defaults while the server response is in flight. Server values that the frontend doesn't recognize (forward-compat) are ignored rather than poisoning the UI; non-401 PATCH errors revert the optimistic local update; logout clears the shadow so user A's settings don't follow user B on a shared browser. In continuous mode native scrolling handles Space/PageDown/arrows; Home/End remain wired and call scrollIntoView() so jumping to chapter bounds stays one keystroke. Single-page mode (chevrons, arrow-key pagination, next-page preload) is unchanged. Versions bumped 0.13.0 → 0.14.0 in lockstep. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { logout } from '$lib/api/auth';
|
||||
import { preferences } from '$lib/preferences.svelte';
|
||||
import { session } from '$lib/session.svelte';
|
||||
import { theme } from '$lib/theme.svelte';
|
||||
import Upload from '@lucide/svelte/icons/upload';
|
||||
@@ -15,9 +16,17 @@
|
||||
|
||||
onMount(() => {
|
||||
theme.init();
|
||||
preferences.init();
|
||||
if (!session.loaded) session.refresh();
|
||||
});
|
||||
|
||||
// Pull fresh server preferences whenever the user changes (login,
|
||||
// logout, account switch). The store's seq guard keeps the most recent
|
||||
// response authoritative.
|
||||
$effect(() => {
|
||||
if (session.user) preferences.refresh();
|
||||
});
|
||||
|
||||
onDestroy(() => theme.destroy());
|
||||
|
||||
async function handleLogout() {
|
||||
@@ -26,6 +35,10 @@
|
||||
await logout();
|
||||
} finally {
|
||||
session.setUser(null);
|
||||
// Don't let user A's reader preferences linger for the next
|
||||
// person who uses this browser (or as guest state for the
|
||||
// same user). Resets state + localStorage.
|
||||
preferences.clearForLogout();
|
||||
loggingOut = false;
|
||||
goto('/login');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user