feat: /profile dashboard with tabbed preferences, account, bookmarks, collections (0.18.0)

Tabbed user dashboard at `/profile` that absorbs `/settings` and
surfaces bookmarks + collections in one place.

- New `/profile` shell with tabs: Overview (counts), Preferences
  (theme + reader prefs, ported from /settings; works for guests
  via localStorage), Account (password change; auth-gated),
  Bookmarks, Collections. Guest tab list is filtered to what they
  can actually use.
- `/settings` is a 308 redirect to `/profile/preferences` so old
  bookmarks land cleanly. The "Settings" link in the top nav is
  replaced by a Profile link between Upload and Bookmarks; Bookmarks
  + Collections stay as shortcuts per the user spec.
- Extracts `lib/components/BookmarkList.svelte` and
  `lib/components/CollectionsGrid.svelte` so the top-level
  /bookmarks + /collections routes and the new profile tabs render
  the same UI without duplication. Both layers use a three-state
  load (authenticated / guest / error) to handle network hiccups
  inline.
- Deep links preserved via `?next=` on every sign-in CTA.

88 frontend unit tests + svelte-check clean; 12 of 12 e2e tests in
profile.spec.ts and reader-mode.spec.ts pass (8 other e2e failures
predate this branch and stay flagged for cleanup).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-17 17:59:29 +02:00
parent 274cc819ca
commit 7560d59616
21 changed files with 1060 additions and 613 deletions

View File

@@ -6,9 +6,9 @@
import { session } from '$lib/session.svelte';
import { theme } from '$lib/theme.svelte';
import Upload from '@lucide/svelte/icons/upload';
import UserCircle from '@lucide/svelte/icons/user-circle';
import Bookmark from '@lucide/svelte/icons/bookmark';
import FolderOpen from '@lucide/svelte/icons/folder-open';
import Settings from '@lucide/svelte/icons/settings';
import LogOut from '@lucide/svelte/icons/log-out';
import '$lib/styles/tokens.css';
@@ -53,6 +53,10 @@
<Upload size={18} aria-hidden="true" />
<span>Upload</span>
</a>
<a class="nav-link" href="/profile" data-testid="nav-profile">
<UserCircle size={18} aria-hidden="true" />
<span>Profile</span>
</a>
<a class="nav-link" href="/bookmarks">
<Bookmark size={18} aria-hidden="true" />
<span>Bookmarks</span>
@@ -67,10 +71,6 @@
<span data-testid="session-loading" aria-busy="true"></span>
{:else if session.user}
<span class="username" data-testid="session-user">{session.user.username}</span>
<a class="nav-link" href="/settings" data-testid="nav-settings">
<Settings size={18} aria-hidden="true" />
<span>Settings</span>
</a>
<button
class="icon-btn"
type="button"