Appearance
Frontend
The web app lives at apps/web/ and is built with Nuxt 4, Vue 3, Nuxt UI v4, Pinia, and Tailwind CSS v4. It runs on port 3100 in development.
Pages
| Route | File | Purpose |
|---|---|---|
/ | pages/index.vue | Dashboard: stats cards, currently reading, recent additions, pipeline status |
/inbox | pages/inbox/index.vue | Paginated inbox list with search, sort, upload button, processing status |
/inbox/:id | pages/inbox/[id].vue | Metadata review: candidate picker, duplicate warnings, approve/delete/rescan |
/library | pages/library/index.vue | Library grid/list view with author/genre filters, full-text search, pagination |
/library/:id | pages/library/[id].vue | Book detail: metadata, files, downloads, collection picker, edit/delete |
/reading | pages/reading/index.vue | Reading status views — shows books filtered by status |
/reading/:status | pages/reading/[status].vue | Filtered reading list: reading, finished, unread, paused |
/stats | pages/stats.vue | Reading analytics: books finished, streaks, daily activity chart, genre distribution |
/settings | pages/settings.vue | Auth setup/login, OPDS/KoSync config, server health, job queues, failed jobs |
Authentication Flow
Uses a Backend-for-Frontend (BFF) pattern — the browser never sees the raw API key.
- User enters API key on
/settings(or runs first-time setup) - Nuxt BFF server route (
/auth/login) validates the key and sets an httpOnlybooks-authcookie - All subsequent API requests go through Nuxt's server routes, which read the cookie and inject
Authorization: Bearerheaders - Global middleware (
auth.global.ts) checks session on every navigation, redirects unauthenticated users to/settings - Client plugin (
auth-hydrate.client.ts) verifies SSR/client state match after hydration
Layout
Single default.vue layout with:
- Sidebar: Logo, search button, nav links (Home, Inbox with badge, Library, Stats), Reading section (Reading, Finished, Unread, Paused links with count badges), Settings link with failed jobs badge, color mode toggle
- Global search: Debounced (200ms) command palette searching books and navigation links
- Content area: NuxtPage with loading indicator
Composables
| Composable | Purpose |
|---|---|
useApi() | Thin $fetch wrapper — auto 401 handling, SSR cookie forwarding |
useDashboard() | Shared keyboard shortcuts (G+H/I/L/S for navigation) |
useInbox() | Inbox API: list, get, upload, rescan, approve, delete, candidates, processing, count |
useLibrary() | Library API: list, get, update, delete, reorganize, facets, cover/download URLs |
useReadingStatus() | Reading status counts and filtered lists |
useHardcover() | Hardcover connection status, sync trigger, sync log |
useSettings() | Settings API: health, jobs, credentials, setup |
useServerEvents() | SSE event streaming for real-time updates |
Components
| Component | Purpose |
|---|---|
MetadataFieldPicker | Multi-source metadata selector — field-by-field candidate selection with manual entry and validation |
UploadBookModal | Drag-and-drop file upload with progress tracking |
EditBookModal | Book metadata edit form with Zod validation |
ConfirmDialog | Reusable confirmation dialog |
ColorModeToggle | Dark/Light/System theme switcher |
ApiError | Error display with retry button |
RefetchMetadataModal | Modal for re-fetching and selecting metadata from external sources |
Store
auth.ts (Pinia): Manages authenticated and checked state. Actions: check(), login(apiKey), logout(), setAuthenticated(value).
Keyboard Shortcuts
GthenH— Go to HomeGthenI— Go to InboxGthenL— Go to LibraryGthenS— Go to SettingsEscape— Back to parent page (from detail views)
Styling
- Theme: Nuxt UI v4 with blue primary, zinc neutral
- Font: Public Sans (sans-serif)
- Dark mode: Full support via
useColorMode() - Icons: Lucide via Iconify
Form Validation
Zod schemas in utils/schemas.ts: ISBN-10/13 format, year range (1000-2100), page count, cover URL (HTTP/HTTPS).