Commit Graph

78 Commits

Author SHA1 Message Date
root
a0e705beec feat: redesign notifications settings with per-category in-app/push table
All checks were successful
Release / Test backend (push) Successful in 53s
Release / Check ui (push) Successful in 1m49s
Release / Docker (push) Successful in 5m49s
Release / Gitea Release (push) Successful in 21s
- Add notify_new_chapters_push field to AppUser, PATCH /api/profile, and profile loader
- Fix bell panel to reload notifications on every open (not just once on mount)
- Replace flat in-app + push toggles with structured category table (Category | In-app | Push)
- Add browser push master subscribe/unsubscribe row above the table
- Push column toggle disabled until browser is subscribed; shows — when unsupported/denied
- Update Notifications row hint to summarise active channels (In-app · Push / Off)
2026-04-12 17:56:53 +05:00
root
53edb6fdef fix: seek bars work on iOS (onchange+oninput), minimal bar is range input, float drag direction corrected
All checks were successful
Release / Test backend (push) Successful in 54s
Release / Check ui (push) Successful in 1m43s
Release / Docker (push) Successful in 6m0s
Release / Gitea Release (push) Successful in 20s
2026-04-12 08:28:59 +05:00
root
0c6c3b8c43 feat: show search button on chapter reader pages
All checks were successful
Release / Test backend (push) Successful in 1m3s
Release / Check ui (push) Successful in 1m51s
Release / Docker (push) Successful in 6m16s
Release / Gitea Release (push) Successful in 25s
2026-04-11 23:37:12 +05:00
root
ac3d6e1784 fix: move hamburger backdrop outside <header> so drawer items are not blurred
All checks were successful
Release / Test backend (push) Successful in 47s
Release / Check ui (push) Successful in 1m49s
Release / Docker (push) Successful in 6m21s
Release / Gitea Release (push) Successful in 28s
2026-04-11 23:22:32 +05:00
root
ea58dab71c fix: hamburger backdrop starts below header so menu items are not blurred
All checks were successful
Release / Test backend (push) Successful in 48s
Release / Check ui (push) Successful in 1m58s
Release / Docker (push) Successful in 6m31s
Release / Gitea Release (push) Successful in 37s
2026-04-11 18:39:32 +05:00
root
cf3a3ad910 feat: add backdrop blur overlay when mobile hamburger menu is open
All checks were successful
Release / Test backend (push) Successful in 47s
Release / Check ui (push) Successful in 1m46s
Release / Docker (push) Successful in 6m5s
Release / Gitea Release (push) Successful in 34s
2026-04-11 17:30:55 +05:00
root
8660c675b6 fix: suppress mini-bar for float/minimal player styles; persist float position
All checks were successful
Release / Test backend (push) Successful in 1m0s
Release / Check ui (push) Successful in 1m42s
Release / Docker (push) Successful in 5m55s
Release / Gitea Release (push) Successful in 39s
2026-04-11 17:20:09 +05:00
root
1e886a705d feat: notifications modal, admin dedup, and in-app notification preferences
All checks were successful
Release / Test backend (push) Successful in 48s
Release / Check ui (push) Successful in 1m53s
Release / Docker (push) Successful in 6m22s
Release / Gitea Release (push) Successful in 35s
- Replace bell dropdown with full-screen NotificationsModal (mirrors SearchModal pattern)
- Notifications visible to all logged-in users (not just admin)
- Admin users excluded from new-chapter fan-out (dedup vs Scrape Complete notification)
- Users with notify_new_chapters=false opted out of new-chapter in-app notifications
- Toggle in profile page to enable/disable in-app new-chapter notifications
- PATCH /api/profile endpoint to save notification preferences
- User-facing /notifications page (admin redirects to /admin/notifications)
2026-04-11 15:31:37 +05:00
root
f8c66fcf63 feat: stream/generate audio mode toggle
All checks were successful
Release / Test backend (push) Successful in 51s
Release / Check ui (push) Successful in 2m6s
Release / Docker (push) Successful in 6m15s
Release / Gitea Release (push) Successful in 29s
Add a user-selectable playback mode stored in user_settings:
- 'stream' (default): /api/audio-stream starts playing within seconds,
  saves to MinIO concurrently — low latency
- 'generate': queue runner task, poll until full audio is ready in
  MinIO, then play via presigned URL — legacy behaviour

UI toggles in two places:
- AudioPlayer idle pill: compact '· Stream / · Generate' inline next
  to voice name and estimated duration
- ListeningMode controls row: pill alongside Auto, Announce, Sleep;
  disabled and grayed out for CF AI voices (batch-only, no streaming)

startPlayback() now branches on audioStore.audioMode for non-CF AI
voices; generate mode uses the same runner task + progress bar flow
as CF AI but without the preview clip.

PocketBase: audio_mode text field added to user_settings on
pb.libnovel.cc (live) and in pb-init-v3.sh (create block +
add_field migration line).
2026-04-10 20:06:56 +05:00
root
2ca1ab2250 v2.6.50: notifications overhaul, fix blank page, fix chapter review loading
All checks were successful
Release / Test backend (push) Successful in 3m15s
Release / Check ui (push) Successful in 1m49s
Release / Docker (push) Successful in 5m53s
Release / Gitea Release (push) Successful in 40s
- svelte.config.js: paths.relative=false so CSS uses absolute /_app/ paths (fixes blank home page after redirect)
- ai-jobs: fix openReview() mutating stale alias r instead of $state review — was causing 'Loading results...' to never resolve for chapter-names/image-gen/description
- notifications bell: redesign with All/Unread tabs, per-item dismiss (×), mark-all-read, clear-all, 'View all' footer link
- /admin/notifications: new dedicated full-page notifications view
- api/notifications proxy: add PATCH (mark-all-read) and DELETE (clear-all, dismiss) handlers
- runner: add CreateNotification calls on success/failure in runScrapeTask, runAudioTask, runTranslationTask
- storage/import.go: real PDF (dslipak/pdf) and EPUB (archive/zip + x/net/html) parsing replacing stubs
- translation admin page: stream jobs Promise instead of blocking navigation
- store.go: DeleteNotification, ClearAllNotifications, MarkAllNotificationsRead methods
- handlers_notifications.go + server.go: PATCH /api/notifications, DELETE /api/notifications, DELETE /api/notifications/{id}
2026-04-09 15:14:00 +05:00
root
ab92bf84bb feat: import review step + admin notifications
Some checks failed
Release / Test backend (push) Failing after 16s
Release / Check ui (push) Failing after 33s
Release / Docker (push) Has been skipped
Release / Gitea Release (push) Has been skipped
- Import page: add file upload with review step before committing
- Backend: add analyze endpoint to preview chapters before import
- Add notifications collection + API for admin alerts
- Add bell icon in header with notification dropdown (admin only)
- Runner creates notification on import completion
- Notifications link to /admin/import for easy review
2026-04-09 10:30:36 +05:00
root
320f9fc76b feat: add page fade transition and CSS performance improvements
All checks were successful
Release / Test backend (push) Successful in 41s
Release / Check ui (push) Successful in 1m35s
Release / Docker (push) Successful in 5m53s
Release / Gitea Release (push) Successful in 29s
- Wrap {#key} children in fade transition (out 100ms, in 180ms+60ms delay)
  for a smooth cross-fade between pages with no added dependencies
- Halve navigation progress bar animation duration (8s → 4s) for
  more realistic feedback on typical navigations
- Add prefers-reduced-motion media query to collapse all animation/transition
  durations for users with accessibility needs
- Add content-visibility: auto on footer to skip browser paint of
  off-screen content and improve rendering performance
2026-04-08 16:54:51 +05:00
root
85492fae73 fix: replace speechSynthesis announce with real audio clip via /api/tts-announce
All checks were successful
Release / Test backend (push) Successful in 41s
Release / Check ui (push) Successful in 1m48s
Release / Docker (push) Successful in 13m20s
Release / Gitea Release (push) Successful in 40s
speechSynthesis is silently muted on iOS Safari and Chrome Android after
the audio session ends (onended), so chapter announcements never played.

Fix:
- Add GET /api/tts-announce backend endpoint: streams a short TTS clip
  for arbitrary text without MinIO caching (backend/internal/backend/)
- Add GET /api/announce SvelteKit proxy route (no paywall)
- Add announceNavigatePending/announcePendingSlug/announcePendingChapter
  to AudioStore
- Rewrite onended announce branch: sets audioStore.audioUrl to the
  announcement clip URL so the persistent <audio> element plays it;
  the next onended detects announceNavigatePending and navigates
- 10s safety timeout in case the clip fails to load/end
2026-04-08 11:57:04 +05:00
root
17fa913ba9 feat: add Forest, Mono, and Cyberpunk color themes
Some checks failed
Release / Test backend (push) Successful in 38s
Release / Check ui (push) Failing after 31s
Release / Upload source maps (push) Has been skipped
Release / Docker / ui (push) Has been skipped
Release / Docker / caddy (push) Successful in 41s
Release / Docker / backend (push) Successful in 2m29s
Release / Docker / runner (push) Successful in 2m39s
Release / Gitea Release (push) Has been skipped
Forest — deep green surfaces (#0a130d base) with green-400 accent.
  Inspired by terminal emulators and dark mode IDE forest schemes.

Mono — near-black zinc-950 surface with pure white/zinc-100 accent.
  Minimal high-contrast monochromatic palette for distraction-free reading.

Cyber — near-black blue surface with neon cyan-400 accent and dracula-palette
  success/danger colors. Cyberpunk aesthetic with neon glow feel.

Changes:
- app.css: three new [data-theme] blocks with full token sets
- +layout.svelte: added forest/mono/cyber to THEMES array (before light themes)
- profile/+page.svelte: same additions with i18n label functions
- messages/*.json: translated names in all 5 locales (en/fr/ru/pt/id)
2026-04-07 11:27:30 +05:00
root
2ed37f78c7 fix: announce chapter reliability — timeout fallback + eager chapters sync
All checks were successful
Release / Test backend (push) Successful in 44s
Release / Check ui (push) Successful in 1m53s
Release / Docker / caddy (push) Successful in 44s
Release / Docker / backend (push) Successful in 2m37s
Release / Docker / runner (push) Successful in 2m33s
Release / Upload source maps (push) Successful in 1m29s
Release / Docker / ui (push) Successful in 2m30s
Release / Gitea Release (push) Successful in 32s
Two issues causing announce to silently fail or permanently block navigation:

1. No hard timeout fallback on speechSynthesis.speak():
   Chrome Android (and some desktop) silently drops utterances not triggered
   within a user-gesture window. If both onend and onerror fail to fire (a
   known browser bug), doNavigate() was never called and the chapter
   transition was permanently lost. Added an 8-second setTimeout fallback
   (safeNavigate) that forces navigation if the speech engine never resolves.
   safeNavigate is idempotent — guarded by a 'navigated' flag so it only
   fires once even if onend, onerror, and the timeout all fire.

2. audioStore.chapters only written inside startPlayback():
   The onended handler reads audioStore.chapters to build the utterance text
   (Chapter N — Title). If auto-next navigated to this chapter and the user
   never manually pressed play (startPlayback was never called), chapters
   held whatever the previous AudioPlayer had written — potentially stale or
   empty on a book switch. Added a reactive $effect that keeps chapters in
   sync whenever the prop changes, same pattern as nextChapter.
2026-04-06 22:35:54 +05:00
root
963ecdd89b fix: auto-next transition deadlock and resume-at-end bug
All checks were successful
Release / Test backend (push) Successful in 47s
Release / Check ui (push) Successful in 1m38s
Release / Docker / caddy (push) Successful in 42s
Release / Docker / backend (push) Successful in 2m38s
Release / Docker / runner (push) Successful in 2m38s
Release / Upload source maps (push) Successful in 1m23s
Release / Docker / ui (push) Successful in 2m26s
Release / Gitea Release (push) Successful in 31s
Bug 1 — Auto-next not transitioning:
audioExpanded defaulted to false on the new chapter page because
audioStore.chapter still held the old chapter number when the page script
initialized. The $effect only opened the panel when isPlaying was already
true — a circular dependency (can't play without the panel, panel only opens
when playing). Fix: also set audioExpanded=true when autoStartChapter targets
this chapter, both in the initial $state and in the reactive $effect.

Bug 2 — Resume starts at the end:
onended called saveAudioTime() which captured currentTime≈duration and fired a
PATCH 2 seconds later (after navigation had already completed). Next visit to
that chapter restored the end-of-file position. Fix: in onended, cancel the
debounced timer (clearTimeout) and immediately PATCH audioTime=0 for the
finished chapter, so it always resumes from the beginning on re-visit.
2026-04-06 21:51:47 +05:00
root
1f83a7c05f feat: add chapter announcing setting to audio player
All checks were successful
Release / Test backend (push) Successful in 43s
Release / Check ui (push) Successful in 1m51s
Release / Docker / caddy (push) Successful in 41s
Release / Docker / backend (push) Successful in 2m24s
Release / Docker / runner (push) Successful in 2m49s
Release / Upload source maps (push) Successful in 1m25s
Release / Docker / ui (push) Successful in 2m12s
Release / Gitea Release (push) Successful in 30s
When enabled, the Web Speech API speaks the upcoming chapter number and
title (e.g. 'Chapter 12 — The Final Battle') between auto-next chapters,
giving an audible cue before the next narration begins.

- AudioStore.announceChapter ( boolean, default false)
- PBUserSettings.announce_chapter persisted to PocketBase
- GET/PUT /api/settings includes announceChapter field
- +layout.server.ts loads + defaults the field
- +layout.svelte applies on load, saves in debounced PUT, and fires
  SpeechSynthesisUtterance in onended before navigating (falls back to
  immediate navigation if speechSynthesis is unavailable)
- ListeningMode: 'Announce' pill added to the Speed · Auto · Sleep row
2026-04-06 21:12:10 +05:00
root
5098acea20 feat: universal search modal
All checks were successful
Release / Test backend (push) Successful in 41s
Release / Check ui (push) Successful in 1m42s
Release / Docker / caddy (push) Successful in 56s
Release / Docker / backend (push) Successful in 2m35s
Release / Docker / runner (push) Successful in 3m37s
Release / Upload source maps (push) Successful in 1m29s
Release / Docker / ui (push) Successful in 2m33s
Release / Gitea Release (push) Successful in 37s
- New SearchModal.svelte: full-screen modal with blurred backdrop
  - Live results as you type (300ms debounce, min 2 chars)
  - Local vs Novelfire badge on each result card (cover + title + author +
    genres + chapter count)
  - Local/remote counts shown in result header
  - 'See all in catalogue' shortcut button + footer repeat link
  - Recent searches (localStorage, max 8, per-item remove + clear all)
  - Genre suggestion chips shown when query is empty or no results found
  - Keyboard navigation: ArrowUp/Down to select, Enter to open, Escape to close
  - Body scroll lock while open
- +layout.svelte:
  - Imports SearchModal, adds searchOpen state
  - Search icon button in nav header (hidden on chapter reader pages)
  - Global keyboard shortcut: '/' or Cmd/Ctrl+K opens modal
  - Shortcut ignored when focused in input/textarea or on chapter pages
  - Modal not shown while ListeningMode is open
  - Auto-closes on route change
2026-04-06 18:41:32 +05:00
root
1cdc7275f8 fix: homepage overlay blocker and carousel auto-advance
Some checks failed
Release / Test backend (push) Successful in 41s
Release / Check ui (push) Successful in 1m43s
Release / Docker / caddy (push) Successful in 42s
Release / Docker / backend (push) Successful in 2m31s
Release / Docker / runner (push) Successful in 2m36s
Release / Docker / ui (push) Has been cancelled
Release / Gitea Release (push) Has been cancelled
Release / Upload source maps (push) Has been cancelled
CI / Backend (push) Successful in 43s
CI / UI (push) Successful in 49s
- Add pointer-events:none to ListeningMode fly-transition wrapper div in
  +layout.svelte so the exiting animation div never blocks page interaction
- Add pointer-events:auto to ListeningMode root div so it still captures
  all touch/click events correctly despite the parent being pointer-events:none
- Rewrite carousel auto-advance using $effect + autoAdvanceSeed pattern:
  replaces the stale-closure setInterval in resetAutoAdvance() with a
  reactive $effect that owns the interval and re-starts cleanly on manual
  navigation by bumping a seed counter
2026-04-06 16:58:23 +05:00
root
718929e9cd feat(player): pull-down-to-dismiss gesture on ListeningMode
Some checks failed
Release / Test backend (push) Successful in 45s
Release / Check ui (push) Failing after 31s
Release / Upload source maps (push) Has been skipped
Release / Docker / ui (push) Has been skipped
Release / Docker / caddy (push) Successful in 44s
Release / Docker / backend (push) Successful in 2m33s
Release / Docker / runner (push) Successful in 2m30s
Release / Gitea Release (push) Has been skipped
- Slide-up transition on open (fly from bottom, 320ms)
- Drag-down on the overlay follows the finger in real time with no
  transition; on release springs back (0.32s cubic-bezier) if drag
  < 130px and velocity < 0.4px/ms, otherwise slides off-screen and
  calls onclose after 220ms
- Opacity fades as the overlay is pulled down (fully transparent at 500px)
- Touch guard: gesture does not activate if touch starts inside an
  .overflow-y-auto element (chapter/voice lists) or while a modal is open
2026-04-06 16:16:09 +05:00
root
b70fed5cd7 fix(reader): clean up focus mode footer
All checks were successful
Release / Test backend (push) Successful in 39s
Release / Check ui (push) Successful in 1m35s
Release / Docker / caddy (push) Successful in 41s
Release / Docker / backend (push) Successful in 2m45s
Release / Docker / runner (push) Successful in 2m32s
Release / Upload source maps (push) Successful in 1m31s
Release / Docker / ui (push) Successful in 2m48s
Release / Gitea Release (push) Successful in 45s
- Hide the global site footer on chapter pages (not useful mid-reading)
- Merge the three separate floating nav pills into a single unified pill
  with dividers, removing the visual clutter of multiple bordered bubbles
- Float the pill lower (bottom-6) when the mini-player is not active
2026-04-06 15:42:45 +05:00
root
5dd9dd2ebb feat(nav): make book title in chapter header a link back to the book page
Some checks failed
Release / Check ui (push) Has been cancelled
Release / Docker / backend (push) Has been cancelled
Release / Docker / runner (push) Has been cancelled
Release / Upload source maps (push) Has been cancelled
Release / Docker / ui (push) Has been cancelled
Release / Docker / caddy (push) Has been cancelled
Release / Gitea Release (push) Has been cancelled
Release / Test backend (push) Has been cancelled
2026-04-06 15:38:18 +05:00
root
836c9855af fix(player): use untrack() in toggleRequest effect to prevent play/pause loop
All checks were successful
Release / Test backend (push) Successful in 37s
Release / Check ui (push) Successful in 1m40s
Release / Docker / caddy (push) Successful in 46s
Release / Docker / backend (push) Successful in 2m29s
Release / Docker / runner (push) Successful in 2m43s
Release / Upload source maps (push) Successful in 1m31s
Release / Docker / ui (push) Successful in 2m21s
Release / Gitea Release (push) Successful in 30s
Reading audioStore.isPlaying inside the toggleRequest $effect caused Svelte 5
to subscribe to it, so the effect re-ran on every isPlaying change. When
resuming from ListeningMode, play() would fire onplay → isPlaying=true →
effect re-ran → called pause() → onpause → isPlaying=false → effect re-ran
→ called play() → infinite loop. Wrapping the isPlaying read in untrack()
limits the effect's subscription to toggleRequest only.
2026-04-06 14:57:27 +05:00
Admin
79b3de3e8d fix(player): fill ListeningMode empty space + global audio context + chapter picker from mini-bar
Some checks failed
Release / Test backend (push) Successful in 49s
Release / Docker / backend (push) Has been cancelled
Release / Docker / runner (push) Has been cancelled
Release / Upload source maps (push) Has been cancelled
Release / Docker / ui (push) Has been cancelled
Release / Docker / caddy (push) Has been cancelled
Release / Gitea Release (push) Has been cancelled
Release / Check ui (push) Has been cancelled
- justify-between on scrollable body so cover art sits top, controls
  sit bottom — no more half-empty screen on tall phones
- Move ListeningMode outside {#if audioStore.active} so pausing never
  tears down the overlay and loses resume context
- Mini-bar time/track click now opens ListeningMode with chapter picker
  pre-shown (same view as the Chapters button inside ListeningMode)
- Remove the old chapterDrawerOpen mini-bar drawer (replaced by above)
- Add openChapters prop to ListeningMode for pre-opening chapter modal
2026-04-06 00:15:12 +05:00
Admin
cc1f6b87e4 fix(player): show '--:--' for unknown duration instead of '0:00'
Prevents the silly "2:01 / 0:00" display when audio src is being swapped
from preview to full audio and duration hasn't loaded yet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 23:26:33 +05:00
Admin
1abb4cd714 feat(player): CF AI preview/swap + fix PB token expiry + local build time
All checks were successful
Release / Test backend (push) Successful in 42s
Release / Check ui (push) Successful in 1m44s
Release / Docker / caddy (push) Successful in 41s
Release / Docker / backend (push) Successful in 2m42s
Release / Docker / runner (push) Successful in 2m56s
Release / Upload source maps (push) Successful in 1m30s
Release / Docker / ui (push) Successful in 2m42s
Release / Gitea Release (push) Successful in 1m14s
- Backend: add GET /api/audio-preview/{slug}/{n} — generates first ~1800-char
  chunk via CF AI so playback starts immediately; full chapter cached in MinIO
- Frontend: replace CF AI spinner with preview blob URL + background swap to
  full presigned URL when runner finishes, preserving currentTime
- AudioPlayer: isPreview state + 'preview' badge in mini-bar during swap
- pocketbase.ts: fix 403 on stale token — reduce TTL to 50 min + retry once
  on 401/403 with forced re-auth (was cached 12 h, PB tokens expire in 1 h)
- Footer build time now rendered in user's local timezone via toLocaleString()
2026-04-05 22:12:22 +05:00
Admin
5d7c3b42fa fix(player): move speed and auto-next controls out of mini-player bar
Speed and auto-next are already available in the full listening mode
overlay — no need to clutter the compact bottom bar with them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:58:09 +05:00
Admin
55df88c3e5 fix(player): remove duplicate inline player and declutter bottom bar
Some checks failed
Release / Test backend (push) Successful in 37s
Release / Check ui (push) Successful in 1m47s
Release / Docker / caddy (push) Successful in 50s
Release / Docker / backend (push) Successful in 3m11s
Release / Docker / runner (push) Successful in 2m42s
Release / Upload source maps (push) Successful in 1m30s
Release / Docker / ui (push) Failing after 1m35s
Release / Gitea Release (push) Has been skipped
When the mini-player is active on the current chapter, the collapsible
'Listen to this chapter' panel now shows a brief note instead of rendering
a full second AudioPlayer.

Bottom bar track info column: removed chapter title and book title lines
so the time display fits on one line without crowding the controls.
2026-04-05 20:31:01 +05:00
Admin
a3ad54db70 feat: add Listening Mode overlay with voice picker, speed, sleep timer, and chapter list
Some checks failed
Release / Test backend (push) Has been cancelled
Release / Check ui (push) Has been cancelled
Release / Docker / backend (push) Has been cancelled
Release / Docker / runner (push) Has been cancelled
Release / Upload source maps (push) Has been cancelled
Release / Docker / ui (push) Has been cancelled
Release / Docker / caddy (push) Has been cancelled
Release / Gitea Release (push) Has been cancelled
Adds a full-screen listening mode accessible via the headphones button in the
mini-player bar. Moves voice selector, speed, auto-next, and sleep timer out of
the reader settings panel into the new overlay. Voices are stored in AudioStore
so ListeningMode can read them without prop drilling.
2026-04-05 17:06:56 +05:00
Admin
15a31a5c64 fix: chapter menu drawer — constrain width on desktop, fix scroll
All checks were successful
Release / Test backend (push) Successful in 39s
Release / Check ui (push) Successful in 46s
Release / Docker / caddy (push) Successful in 39s
Release / Docker / backend (push) Successful in 3m12s
Release / Docker / runner (push) Successful in 3m41s
Release / Upload source maps (push) Successful in 2m13s
Release / Docker / ui (push) Successful in 2m16s
Release / Gitea Release (push) Successful in 36s
On md+ screens the drawer is now right-aligned and 320px wide (w-80)
instead of full-viewport-width. The sticky header is pulled out of the
scroll container so it never scrolls away, and overflow-y-auto is
applied only to the chapter list itself so both mobile and desktop can
scroll through long chapter lists.
2026-04-05 14:35:03 +05:00
Admin
37deac1eb3 fix: resolve all svelte-check warnings and errors (0 errors, 0 warnings)
All checks were successful
Release / Test backend (push) Successful in 45s
Release / Check ui (push) Successful in 44s
Release / Docker / caddy (push) Successful in 40s
Release / Docker / backend (push) Successful in 4m54s
Release / Docker / runner (push) Successful in 56s
Release / Docker / ui (push) Successful in 2m9s
Release / Gitea Release (push) Successful in 48s
2026-04-05 11:03:23 +05:00
Admin
0c54c59586 fix: guard against font_size=0 collapsing chapter text
All checks were successful
Release / Test backend (push) Successful in 51s
Release / Check ui (push) Successful in 47s
Release / Docker / caddy (push) Successful in 38s
Release / Docker / backend (push) Successful in 2m30s
Release / Docker / runner (push) Successful in 2m38s
Release / Docker / ui (push) Successful in 2m1s
Release / Gitea Release (push) Successful in 41s
- Replace ?? with || when reading font_size so 0 falls back to 1.0
  (affects GET /api/settings, layout.server.ts, +layout.svelte)
- Remove the explicit 'body.fontSize !== 0' exception in PUT /api/settings
  validation so 0 is now correctly rejected as an invalid font size
- Add add_index helper + idx_chapters_idx_slug_number declaration to
  scripts/pb-init-v3.sh (idempotent UNIQUE INDEX on chapters_idx)
2026-04-04 23:26:54 +05:00
Admin
c4a0256f6e feat: /subscribe pricing page + Pro nav link + fix checkout token scope
All checks were successful
Release / Test backend (push) Successful in 40s
Release / Check ui (push) Successful in 46s
Release / Docker / caddy (push) Successful in 42s
Release / Docker / backend (push) Successful in 2m35s
Release / Docker / runner (push) Successful in 2m38s
Release / Docker / ui (push) Successful in 2m12s
Release / Gitea Release (push) Successful in 41s
- Add /subscribe route with hero, benefits list, and pricing cards
  (annual featured with Save 33% badge, monthly secondary)
- Add 'Pro' link in nav for non-Pro users
- Add 'See plans' link in profile subscription section
- i18n keys across en/fr/id/pt/ru for all subscribe strings

Note: checkout still requires POLAR_API_TOKEN with checkouts:write scope.
Regenerate the token at polar.sh to fix the 502 error on subscribe buttons.
2026-04-04 20:12:24 +05:00
Admin
bdbe48ce1a fix: register MediaSession action handlers for iOS lock screen resume
All checks were successful
Release / Test backend (push) Successful in 37s
Release / Check ui (push) Successful in 56s
Release / Docker / caddy (push) Successful in 41s
Release / Docker / backend (push) Successful in 2m53s
Release / Docker / runner (push) Successful in 3m13s
Release / Docker / ui (push) Successful in 2m25s
Release / Gitea Release (push) Successful in 50s
Without explicit setActionHandler('play'/'pause'/...) the browser uses
default handling which on iOS Safari stops working after ~1 min of
pause in background/locked state (AudioSession gets suspended and the
lock screen button can't resume it without a direct user gesture).

Registering handlers in the layout (where audioEl lives) ensures:
- play/pause call audioEl directly — iOS treats this as a trusted
  gesture and allows .play() even from the lock screen
- seekbackward/seekforward map to ±15s / ±30s
- playbackState stays in sync so the lock screen shows the right icon

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 18:22:09 +05:00
Admin
8588475d03 feat: in-reader settings, more like this, reading history, audio filter, feed page
Some checks failed
Release / Test backend (push) Successful in 38s
Release / Check ui (push) Failing after 33s
Release / Docker / ui (push) Has been skipped
Release / Docker / caddy (push) Successful in 51s
Release / Docker / backend (push) Successful in 2m20s
Release / Docker / runner (push) Successful in 2m28s
Release / Gitea Release (push) Has been skipped
- Add floating gear button + theme/font/size drawer in chapter reader
- Add 'More like this' horizontal scroll row on book detail page
- Add reading history tab on profile page (chronological progress timeline)
- Add audio-available badge + filter toggle on catalogue (GET /api/audio/slugs)
- Fix discover card entry animation pop (split entry vs snap-back cubic-bezier)
- Add dedicated /feed page showing books followed users are reading
- Add Feed nav link (desktop + mobile) in all 5 locales
2026-04-03 22:32:37 +05:00
Admin
ac7b686fba fix: don't save settings immediately after login
All checks were successful
Release / Test backend (push) Successful in 41s
Release / Check ui (push) Successful in 45s
Release / Docker / caddy (push) Successful in 45s
Release / Docker / backend (push) Successful in 2m53s
Release / Docker / runner (push) Successful in 2m34s
Release / Docker / ui (push) Successful in 1m56s
Release / Gitea Release (push) Successful in 20s
The save-settings $effect was firing on the initial data load because
settingsApplied was set to true synchronously in the apply effect, then
currentTheme/fontFamily/fontSize were written in the same tick — causing
the save effect to immediately fire with uninitialized default values
(theme: "", fontFamily: "", fontSize: 0), producing a 400 error.

- Add settingsDirty flag, set via setTimeout(0) after initial apply so
  the save effect is blocked for the first load and only runs on real
  user-driven changes
- Also accept empty string / 0 as 'not provided' in PUT /api/settings
  validation as a defensive backstop
2026-04-03 21:07:01 +05:00
Admin
06d4a7bfd4 feat: profile stats, discover history, end-of-chapter sleep, rating-ranked deck
All checks were successful
Release / Test backend (push) Successful in 43s
Release / Check ui (push) Successful in 46s
Release / Docker / caddy (push) Successful in 52s
Release / Docker / backend (push) Successful in 2m32s
Release / Docker / ui (push) Successful in 2m15s
Release / Docker / runner (push) Successful in 2m50s
Release / Gitea Release (push) Successful in 22s
**Profile stats tab**
- New Stats tab on /profile page (Profile / Stats switcher)
- Reading overview: chapters read, completed, reading, plan-to-read counts
- Activity cards: day streak + avg rating given
- Favourite genres (top 3 by frequency across library/progress)
- getUserStats() in pocketbase.ts — computes streak, shelf counts, genre freq

**Discover history tab**
- New History tab on /discover with full voted-book list
- Per-entry: cover thumbnail, title link, author, action label (Liked/Skipped/etc.)
- Undo button: optimistic update + DELETE /api/discover/vote?slug=...
- Clear all history button; tab shows vote count badge
- getVotedBooks(), undoDiscoveryVote() in pocketbase.ts

**Rating-ranked discovery deck**
- getBooksForDiscovery now sorts by community avg rating before returning
- Tier-based shuffle: books within the same ±0.5 star bucket are still randomised
- Higher-rated books surface earlier without making the deck fully deterministic

**End-of-chapter sleep timer**
- New cycle option: Off → End of Chapter → 15m → 30m → 45m → 60m → Off
- sleepAfterChapter flag in AudioStore; layout handles it in onended (skips auto-next)
- Button shows "End Ch." label when active in this mode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 07:26:54 +05:00
Admin
08361172c6 feat: ratings, shelves, sleep timer, EPUB export + fix TS errors
All checks were successful
Release / Docker / caddy (push) Successful in 38s
Release / Check ui (push) Successful in 39s
Release / Test backend (push) Successful in 57s
Release / Docker / ui (push) Successful in 1m49s
Release / Docker / runner (push) Successful in 3m12s
Release / Docker / backend (push) Successful in 3m46s
Release / Gitea Release (push) Successful in 13s
**Ratings (1–5 stars)**
- New `book_ratings` PB collection (session_id, user_id, slug, rating)
- `getBookRating`, `getBookAvgRating`, `setBookRating` in pocketbase.ts
- GET/POST /api/ratings/[slug] API route
- StarRating.svelte component with hover, animated stars, avg display
- Star rating shown on book detail page (desktop + mobile)

**Plan-to-Read shelf**
- `shelf` field added to `user_library` (reading/plan_to_read/completed/dropped)
- `updateBookShelf`, `getShelfMap` in pocketbase.ts
- PATCH /api/library/[slug] for shelf updates
- Shelf selector dropdown on book detail page (only when saved)
- Shelf tabs on library page to filter by category

**Sleep timer**
- `sleepUntil` state added to AudioStore
- Layout handles timer lifecycle (survives chapter navigation)
- Cycles Off → 15m → 30m → 45m → 60m → Off
- Shows live countdown in AudioPlayer when active

**EPUB export**
- Go backend: GET /api/export/{slug}?from=N&to=N
- Generates valid EPUB2 zip (mimetype uncompressed, OPF, NCX, XHTML chapters)
- Markdown → HTML via goldmark
- SvelteKit proxy at /api/export/[slug]
- Download button on book detail page (only when in library)

**Fix TS errors**
- discover/+page.svelte: currentBook possibly undefined (use {@const book})
- cardEl now $state for reactive binding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 21:50:04 +05:00
Admin
e9c3426fbe feat: scroll active chapter into view when chapter drawer opens
All checks were successful
Release / Check ui (push) Successful in 40s
Release / Test backend (push) Successful in 43s
Release / Docker / caddy (push) Successful in 51s
Release / Docker / ui (push) Successful in 2m30s
Release / Docker / runner (push) Successful in 3m26s
Release / Docker / backend (push) Successful in 4m3s
Release / Gitea Release (push) Successful in 12s
When the mini-player chapter drawer is opened, the current chapter is
now immediately scrolled into the center of the list instead of always
starting from the top. Uses a Svelte action (setIfActive) to track the
active chapter element and a $effect to call scrollIntoView on open.
2026-04-02 20:44:12 +05:00
Admin
7196f8e930 feat(discover): Tinder-style book discovery + fix duplicate books
Some checks failed
CI / UI (push) Failing after 22s
CI / Backend (push) Successful in 53s
Release / Test backend (push) Successful in 24s
Release / Check ui (push) Failing after 24s
Release / Docker / ui (push) Has been skipped
CI / Backend (pull_request) Successful in 25s
CI / UI (pull_request) Failing after 22s
Release / Docker / caddy (push) Successful in 56s
Release / Docker / backend (push) Successful in 1m38s
Release / Docker / runner (push) Successful in 3m14s
Release / Gitea Release (push) Has been skipped
- New /discover page with swipe UI: left=skip, right=like, up=read now, down=nope
- Onboarding modal to collect genre/status preferences (persisted in localStorage)
- 3-card stack with pointer-event drag, CSS fly-out animation, 5 action buttons
- Tap card for preview modal; empty state with deck reset
- Like/read-now auto-saves book to user library
- POST /api/discover/vote + DELETE for deck reset
- Discovery vote persistence via PocketBase discovery_votes collection
- Fix duplicate books: dedup by slug in getBooksBySlugs
- Fix WriteMetadata TOCTOU race: conflict-retry on concurrent insert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 17:33:27 +05:00
Admin
aca649039c feat(ui): replace theme dots with dropdown, remove chevrons from lang and profile buttons
Some checks failed
CI / Backend (pull_request) Successful in 56s
CI / UI (pull_request) Successful in 29s
Release / Test backend (push) Successful in 27s
CI / Backend (push) Successful in 45s
CI / UI (push) Successful in 52s
Release / Check ui (push) Successful in 29s
Release / Docker / caddy (push) Successful in 58s
Release / Docker / backend (push) Successful in 3m33s
Release / Docker / ui (push) Successful in 1m52s
Release / Docker / runner (push) Failing after 11s
Release / Gitea Release (push) Has been skipped
2026-03-31 00:20:06 +05:00
Admin
e8d7108753 feat(themes): add light, light-slate, and light-rose themes
Some checks failed
CI / Backend (push) Successful in 30s
Release / Test backend (push) Failing after 11s
Release / Docker / backend (push) Has been skipped
Release / Docker / runner (push) Has been skipped
CI / UI (push) Successful in 35s
Release / Docker / caddy (push) Successful in 58s
Release / Check ui (push) Successful in 1m7s
CI / Backend (pull_request) Successful in 31s
CI / UI (pull_request) Successful in 1m1s
Release / Docker / ui (push) Failing after 2m43s
Release / Gitea Release (push) Has been skipped
Three light variants mirroring the existing dark set. All use CSS custom
properties so no component changes are needed. Theme dots in the header
and mobile drawer show a separator between dark/light groups; light-theme
swatches get a subtle ring so they're visible on light backgrounds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:13:25 +05:00
Admin
61e0d98057 feat(ui): declutter header — user menu + lang dropdown, mobile theme/lang
Some checks failed
CI / Backend (pull_request) Successful in 55s
CI / UI (pull_request) Successful in 49s
CI / UI (push) Failing after 35s
Release / Test backend (push) Successful in 42s
CI / Backend (push) Successful in 57s
Release / Check ui (push) Successful in 36s
Release / Docker / caddy (push) Successful in 1m11s
Release / Docker / runner (push) Successful in 2m5s
Release / Docker / ui (push) Successful in 2m2s
Release / Docker / backend (push) Failing after 2m32s
Release / Gitea Release (push) Has been skipped
Desktop header before: logo · Library · Catalogue · Feedback · ●●● · EN RU ID PT-BR FR · Admin · username · Sign out
Desktop header after:  logo · Library · Catalogue · Feedback · ●●● · [🌐 EN ▾] · [avatar ▾]

Changes:
- Theme dots kept but made slightly smaller (3.5 → 3.5, less gap)
- 5 bare language codes replaced with a compact globe + current locale
  dropdown (click to expand all 5 options)
- Admin link, username, and Sign out collapsed into a single user-initial
  avatar dropdown (Profile / Admin panel / Sign out)
- Click-outside overlay closes any open dropdown
- Mobile drawer: added Theme and Language rows so users can switch both
  without opening the desktop header
- Footer locale switcher removed (redundant with header and mobile drawer)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 14:54:46 +05:00
Admin
601c26d436 fix(ui): theme token consistency, seek bar a11y, colour polish
All checks were successful
CI / Backend (push) Successful in 36s
CI / Backend (pull_request) Successful in 46s
CI / UI (push) Successful in 59s
CI / UI (pull_request) Successful in 37s
- Add --color-success variable to all three themes
- Replace hard-coded amber/green Tailwind colours with CSS variables:
    Pro badge + discount badge: bg-amber-400/15 → bg-(--color-brand)/15
    Saved confirmation:         text-green-400 → text-(--color-success)
    Catalogue flash messages:   emerald/yellow → success/brand tokens
    Login hover border:         border-zinc-600 → border-(--color-brand)/50
- Seek bar in mini-player now has aria-label (player_seek_label)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 14:24:34 +05:00
Admin
4b8104f087 feat(ui): language persistence, theme fix, font/size settings, header quick-access
Some checks failed
CI / UI (push) Failing after 54s
CI / Backend (push) Successful in 1m56s
Release / Test backend (push) Successful in 41s
Release / Check ui (push) Successful in 50s
Release / Docker / caddy (push) Successful in 54s
CI / Backend (pull_request) Successful in 45s
CI / UI (pull_request) Successful in 50s
Release / Docker / ui (push) Successful in 2m23s
Release / Docker / runner (push) Successful in 3m15s
Release / Docker / backend (push) Successful in 2m3s
Release / Gitea Release (push) Failing after 2s
- Fix language not persisting after refresh: save locale in user_settings,
  set PARAGLIDE_LOCALE cookie from DB preference on server load
- Fix theme change not applying: context setter was mismatched (setTheme vs current)
- Add font family (system/serif/mono) and text size (sm/md/lg/xl) user settings
  stored in DB and applied via CSS custom properties
- Add theme color dots and language picker to desktop header for quick access
- Footer locale switcher now saves preference to DB before switching
- Remove change password section (OAuth-only, no password login)
- Fix active sessions piling up: reuse existing session on re-login via OAuth
- Extend speed step cycle to include 2.5× and 3.0× (matching profile slider)
- Replace plain checkbox with modern toggle switch for auto-next setting
- Fix catalogue status labels (ongoing/completed) to use translation keys
- Add font family and text size translation keys to all 5 locale files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 21:43:00 +05:00
Admin
0a3a61a3ef feat(i18n): add Paraglide i18n with 5 locales (v2.3.22)
Some checks failed
CI / Backend (pull_request) Successful in 41s
CI / UI (pull_request) Failing after 27s
CI / UI (push) Failing after 27s
CI / Backend (push) Successful in 48s
Release / Test backend (push) Successful in 23s
Release / Check ui (push) Failing after 33s
Release / Docker / ui (push) Has been skipped
Release / Docker / caddy (push) Failing after 52s
Release / Docker / backend (push) Failing after 11s
Release / Docker / runner (push) Successful in 1m51s
Release / Gitea Release (push) Has been skipped
- Install @inlang/paraglide-js v2.15.1; configure project.inlang settings
- Add en/ru/id/pt-BR/fr message catalogues (~140 keys each)
- Wire paraglideVitePlugin in vite.config.ts, reroute hook in hooks.ts,
  and paraglideHandle middleware in hooks.server.ts
- Migrate all routes and shared components to use m.*() message calls
- Fix duplicate onMount body in chapters/[n]/+page.svelte
- Build passes; svelte-check: 0 errors, 3 pre-existing warnings
2026-03-29 10:43:53 +05:00
Admin
7a2a4fc755 feat(ui): theme system — amber/slate/rose, profile picker, full token migration
Some checks failed
CI / Backend (pull_request) Successful in 44s
CI / UI (pull_request) Successful in 25s
CI / UI (push) Successful in 27s
Release / Test backend (push) Successful in 42s
CI / Backend (push) Successful in 44s
Release / Check ui (push) Successful in 24s
Release / Docker / caddy (push) Failing after 1m4s
Release / Docker / backend (push) Failing after 44s
Release / Docker / ui (push) Failing after 29s
Release / Docker / runner (push) Failing after 54s
Release / Gitea Release (push) Has been skipped
- Add CSS custom property token system in app.css (@theme + [data-theme] overrides)
- Three themes: amber (default), slate (indigo/dark), rose (dark pink)
- Flash prevention via inline <script> in <svelte:head> sets data-theme before paint
- Theme context (setContext/getContext) in +layout.svelte for live preview
- Theme persisted via PocketBase user_settings (PBUserSettings.theme field)
- /api/settings GET/PUT updated to handle theme field alongside existing settings
- Profile page: new Appearance section with 3 colour-swatch theme picker
- Full token migration across all 36 route/component files:
  zinc/amber hardcoded Tailwind classes → CSS var utilities (bg-(--color-surface), etc.)
- UI primitives (Badge, Button, Card, Dialog, Separator, Textarea) migrated
- accent-amber-400 replaced with inline style accent-color: var(--color-brand)
2026-03-28 23:57:16 +05:00
Admin
b783dae5f4 refactor(admin): replace tab bar with sidebar layout
Some checks failed
CI / Backend (push) Successful in 38s
CI / UI (push) Successful in 42s
Release / Test backend (push) Successful in 40s
Release / Check ui (push) Successful in 33s
CI / Backend (pull_request) Successful in 28s
Release / Docker / caddy (push) Successful in 1m11s
CI / UI (pull_request) Successful in 29s
Release / Docker / ui (push) Successful in 1m54s
Release / Docker / runner (push) Successful in 4m27s
Release / Docker / backend (push) Failing after 5m11s
Release / Gitea Release (push) Has been skipped
Move admin navigation from a two-row tab strip into a persistent left
sidebar with grouped sections (Pages / Tools). Consolidate Scrape and
Audio entries in the global top nav into a single Admin link.
2026-03-28 21:15:49 +05:00
Admin
dcf40197d4 fix(ui): brighten footer link text for readability on dark background
Some checks failed
CI / Backend (push) Successful in 51s
CI / UI (push) Successful in 28s
Release / Test backend (push) Successful in 50s
Release / Docker / caddy (push) Successful in 1m0s
Release / Check ui (push) Successful in 1m15s
CI / Backend (pull_request) Successful in 41s
CI / UI (pull_request) Successful in 32s
Release / Docker / ui (push) Successful in 2m2s
Release / Docker / backend (push) Successful in 4m9s
Release / Docker / runner (push) Successful in 5m46s
Release / Gitea Release (push) Failing after 1s
2026-03-28 21:12:46 +05:00
Admin
2cf0528730 fix(ui): show version+SHA+build time in footer; fix env not reaching runtime image
Some checks failed
CI / Backend (push) Successful in 51s
CI / UI (push) Successful in 27s
Release / Docker / caddy (push) Failing after 23s
Release / Test backend (push) Successful in 38s
Release / Check ui (push) Successful in 39s
CI / Backend (pull_request) Successful in 28s
CI / UI (pull_request) Successful in 36s
Release / Docker / backend (push) Failing after 1m25s
Release / Docker / runner (push) Successful in 2m25s
Release / Docker / ui (push) Successful in 1m51s
Release / Gitea Release (push) Has been skipped
PUBLIC_BUILD_VERSION and PUBLIC_BUILD_COMMIT were set only in the builder
stage ENV — they were never re-declared in the runtime stage, so the Node
server started with them undefined and the badge always showed 'dev'.

Fix: re-declare all three ARGs after the second FROM and set runtime ENVs.
Add PUBLIC_BUILD_TIME (ISO timestamp from gitea.event.head_commit.timestamp)
injected via build-arg in release.yaml. Badge now shows e.g.:
  v2.3.9+abc1234 · 28 Mar 2026 14:30 UTC
2026-03-28 19:33:49 +05:00