Commit Graph

435 Commits

Author SHA1 Message Date
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>
v2.5.35
2026-04-02 17:33:27 +05:00
Admin
a771405db8 feat(audio): WAV streaming, bulk audio generation admin endpoints, cancel/resume
Some checks failed
CI / Backend (push) Successful in 48s
CI / UI (push) Successful in 28s
Release / Check ui (push) Successful in 39s
Release / Test backend (push) Successful in 49s
Release / Docker / caddy (push) Successful in 59s
CI / Backend (pull_request) Successful in 41s
CI / UI (pull_request) Successful in 46s
Release / Docker / ui (push) Successful in 1m31s
Release / Docker / backend (push) Successful in 3m27s
Release / Docker / runner (push) Successful in 3m47s
Release / Gitea Release (push) Failing after 32s
- Add StreamAudioWAV() to pocket-tts and Kokoro clients; pocket-tts streams
  raw WAV directly (no ffmpeg), Kokoro requests response_format:wav with stream:true
- GET /api/audio-stream supports ?format=wav for lower-latency first-byte delivery;
  WAV cached separately in MinIO as {slug}/{n}/{voice}.wav
- Add GET /api/admin/audio/jobs with optional ?slug filter
- Add POST /api/admin/audio/bulk {slug, voice, from, to, skip_existing, force}
  where skip_existing=true (default) resumes interrupted bulk jobs
- Add POST /api/admin/audio/cancel-bulk {slug} to cancel all pending/running tasks
- Add CancelAudioTasksBySlug to taskqueue.Producer + asynqqueue implementation
- Add AudioObjectKeyExt to bookstore.AudioStore for format-aware MinIO keys

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v2.5.34
2026-04-02 16:19:14 +05:00
Admin
1e9a96aa0f fix(payments): fix TypeScript cast errors in polar webhook handler
Some checks failed
CI / UI (push) Successful in 25s
Release / Test backend (push) Successful in 22s
CI / Backend (push) Successful in 1m59s
Release / Check ui (push) Successful in 27s
Release / Docker / caddy (push) Successful in 44s
CI / UI (pull_request) Successful in 27s
CI / Backend (pull_request) Failing after 1m26s
Release / Docker / runner (push) Successful in 1m40s
Release / Docker / backend (push) Successful in 2m34s
Release / Docker / ui (push) Successful in 3m21s
Release / Gitea Release (push) Successful in 13s
Cast through unknown to satisfy TS strict overlap check for
PolarSubscription and PolarOrder types from Record<string, unknown>.
v2.5.33
2026-03-31 23:40:11 +05:00
Admin
23ae1ed500 feat(payments): lock checkout email via Polar server-side checkout sessions
Some checks failed
CI / UI (pull_request) Failing after 23s
CI / Backend (push) Successful in 27s
CI / Backend (pull_request) Successful in 53s
CI / UI (push) Failing after 25s
Release / Test backend (push) Successful in 28s
Release / Check ui (push) Failing after 30s
Release / Docker / ui (push) Has been skipped
Release / Docker / caddy (push) Successful in 42s
Release / Docker / backend (push) Successful in 1m50s
Release / Docker / runner (push) Successful in 3m47s
Release / Gitea Release (push) Has been skipped
Replace static Polar checkout links with a server-side POST /api/checkout
route that creates a checkout session with customer_external_id = user ID
and customer_email locked (not editable). Adds loading/error states and
a post-checkout success banner on the profile page.
v2.5.32
2026-03-31 23:36:53 +05:00
Admin
e7cb460f9b fix(payments): point manage subscription to org customer portal
Some checks failed
CI / Backend (pull_request) Successful in 32s
CI / UI (pull_request) Failing after 28s
CI / Backend (push) Successful in 26s
CI / UI (push) Failing after 25s
Release / Check ui (push) Failing after 16s
Release / Docker / ui (push) Has been skipped
Release / Test backend (push) Successful in 47s
Release / Docker / caddy (push) Successful in 50s
Release / Docker / runner (push) Successful in 2m0s
Release / Docker / backend (push) Successful in 2m59s
Release / Gitea Release (push) Has been skipped
v2.5.31
2026-03-31 23:26:57 +05:00
Admin
392248e8a6 fix(payments): update Polar checkout links to use checkout link IDs
Some checks failed
CI / Backend (pull_request) Successful in 25s
CI / UI (pull_request) Failing after 16s
CI / UI (push) Failing after 17s
CI / Backend (push) Successful in 41s
Release / Test backend (push) Successful in 40s
Release / Docker / caddy (push) Successful in 54s
Release / Docker / backend (push) Successful in 1m55s
Release / Docker / runner (push) Successful in 2m52s
Release / Docker / ui (push) Has been cancelled
Release / Gitea Release (push) Has been cancelled
Release / Check ui (push) Has been cancelled
v2.5.30
2026-03-31 23:25:26 +05:00
Admin
68ea2d2808 feat(payments): fix Polar webhook + pre-fill checkout email
Some checks failed
CI / Backend (pull_request) Successful in 26s
CI / UI (pull_request) Failing after 24s
CI / Backend (push) Successful in 26s
Release / Check ui (push) Failing after 16s
Release / Docker / ui (push) Has been skipped
CI / UI (push) Failing after 31s
Release / Test backend (push) Successful in 41s
Release / Docker / caddy (push) Successful in 33s
Release / Docker / runner (push) Successful in 2m58s
Release / Docker / backend (push) Successful in 3m43s
Release / Gitea Release (push) Has been skipped
- Fix customer email path: was data.customer_email, is actually
  data.customer.email per Polar v1 API schema
- Add resolveUser() helper: tries polar_customer_id → email → external_id
- Add subscription.active and subscription.canceled event handling
- Handle order.created for fast-path pro upgrade on purchase
- Profile page: fetch user email + polarCustomerId from PocketBase
- Profile page: pre-fill ?customer_email= on checkout links
- Profile page: link to polar.sh/purchases for existing customers
v2.5.29
2026-03-31 23:11:34 +05:00
Admin
7b1df9b592 fix(infra): fix libretranslate healthcheck; fix scrollbar-none css
All checks were successful
CI / Backend (push) Successful in 25s
Release / Test backend (push) Successful in 24s
CI / UI (push) Successful in 51s
Release / Check ui (push) Successful in 29s
CI / UI (pull_request) Successful in 27s
CI / Backend (pull_request) Successful in 44s
Release / Docker / caddy (push) Successful in 54s
Release / Docker / ui (push) Successful in 2m4s
Release / Docker / runner (push) Successful in 2m56s
Release / Docker / backend (push) Successful in 3m23s
Release / Gitea Release (push) Successful in 12s
v2.5.28
2026-03-31 22:36:19 +05:00
Admin
f4089fe111 fix(admin): add layout guard and redirect /admin to /admin/scrape
All checks were successful
CI / Backend (push) Successful in 45s
CI / UI (push) Successful in 56s
Release / Check ui (push) Successful in 26s
Release / Test backend (push) Successful in 39s
CI / Backend (pull_request) Successful in 24s
Release / Docker / caddy (push) Successful in 48s
CI / UI (pull_request) Successful in 40s
Release / Docker / runner (push) Successful in 2m52s
Release / Docker / backend (push) Successful in 3m27s
Release / Docker / ui (push) Successful in 3m38s
Release / Gitea Release (push) Successful in 14s
- Add +layout.server.ts to enforce admin role check at layout level,
  preventing 404 on /admin and protecting all sub-routes centrally
- Add +page.server.ts to redirect /admin → /admin/scrape (was 404)
v2.5.27
2026-03-31 22:33:39 +05:00
Admin
87b5ad1460 feat(auth): add debug-login bypass endpoint secured by DEBUG_LOGIN_TOKEN
All checks were successful
CI / Backend (push) Successful in 43s
CI / UI (push) Successful in 1m10s
Release / Check ui (push) Successful in 26s
Release / Test backend (push) Successful in 41s
CI / Backend (pull_request) Successful in 25s
Release / Docker / caddy (push) Successful in 47s
CI / UI (pull_request) Successful in 41s
Release / Docker / ui (push) Successful in 2m32s
Release / Docker / backend (push) Successful in 3m57s
Release / Docker / runner (push) Successful in 4m8s
Release / Gitea Release (push) Successful in 12s
v2.5.26
2026-03-31 21:59:58 +05:00
Admin
168cb52ed0 fix(admin): use --color-surface for drawer bg (--color-bg was undefined)
Some checks failed
CI / Backend (push) Successful in 43s
CI / UI (push) Successful in 58s
Release / Check ui (push) Successful in 26s
Release / Test backend (push) Successful in 38s
Release / Docker / caddy (push) Successful in 45s
CI / Backend (pull_request) Successful in 26s
Release / Docker / ui (push) Failing after 10s
CI / UI (pull_request) Successful in 42s
Release / Docker / runner (push) Failing after 19s
Release / Docker / backend (push) Successful in 1m42s
Release / Gitea Release (push) Has been skipped
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v2.5.25
2026-03-31 18:53:58 +05:00
Admin
e1621a3ec2 fix(infra): move Redis to prod, fix LibreTranslate config loading
All checks were successful
CI / Backend (push) Successful in 46s
CI / UI (push) Successful in 52s
Release / Docker / caddy (push) Successful in 34s
Release / Test backend (push) Successful in 48s
Release / Check ui (push) Successful in 49s
CI / Backend (pull_request) Successful in 25s
CI / UI (pull_request) Successful in 56s
Release / Docker / runner (push) Successful in 1m46s
Release / Docker / ui (push) Successful in 2m30s
Release / Docker / backend (push) Successful in 2m47s
Release / Gitea Release (push) Successful in 13s
- Add Redis sidecar to prod docker-compose; backend connects locally (redis:6379)
- Caddy layer4 now proxies redis.libnovel.cc:6380 → local redis:6379 (not homelab LAN)
- Remove HOMELAB_REDIS_ADDR; homelab runner connects out to prod Redis via rediss://
- Remove local Redis from homelab runner compose; drop redis_data volume
- Fix config.Load() missing LibreTranslate section — LIBRETRANSLATE_URL was never read
v2.5.24
2026-03-31 18:26:32 +05:00
Admin
10c7a48bc6 fix(admin): move mobile nav toggle into content area to avoid z-index conflict
Some checks failed
CI / Backend (push) Successful in 43s
CI / UI (push) Successful in 59s
Release / Test backend (push) Successful in 37s
Release / Check ui (push) Successful in 27s
Release / Docker / caddy (push) Failing after 39s
CI / UI (pull_request) Successful in 25s
CI / Backend (pull_request) Successful in 44s
Release / Docker / ui (push) Successful in 2m5s
Release / Docker / runner (push) Successful in 2m27s
Release / Docker / backend (push) Successful in 3m12s
Release / Gitea Release (push) Has been skipped
The fixed top bar was hidden behind the main site navbar (z-50).
Replace with an inline 'Admin menu' button at the top of the content.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v2.5.23
2026-03-31 17:07:28 +05:00
Admin
8b597c0bd2 fix(caddy): fix logo branding on all error pages
All checks were successful
Release / Test backend (push) Successful in 23s
CI / Backend (push) Successful in 41s
CI / UI (push) Successful in 49s
Release / Check ui (push) Successful in 27s
CI / UI (pull_request) Successful in 27s
CI / Backend (pull_request) Successful in 47s
Release / Docker / caddy (push) Successful in 1m7s
Release / Docker / backend (push) Successful in 1m48s
Release / Docker / ui (push) Successful in 2m2s
Release / Docker / runner (push) Successful in 3m9s
Release / Gitea Release (push) Successful in 13s
Match main site logo style: lowercase 'libnovel' in full brand amber.
Add meta http-equiv refresh fallback for 5xx pages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v2.5.22
2026-03-31 16:39:38 +05:00
Admin
28cafe2aa8 fix(admin): make sidebar responsive on mobile with slide-out drawer
All checks were successful
CI / Backend (push) Successful in 50s
CI / UI (push) Successful in 25s
Release / Test backend (push) Successful in 39s
Release / Docker / caddy (push) Successful in 32s
Release / Check ui (push) Successful in 47s
CI / UI (pull_request) Successful in 27s
CI / Backend (pull_request) Successful in 43s
Release / Docker / runner (push) Successful in 2m1s
Release / Docker / ui (push) Successful in 2m30s
Release / Docker / backend (push) Successful in 3m3s
Release / Gitea Release (push) Successful in 13s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v2.5.21
2026-03-31 16:33:17 +05:00
Admin
65f0425b61 fix(i18n): add _inputs param to admin_nav paraglide inner functions; exclude Go binaries from git
All checks were successful
Release / Test backend (push) Successful in 43s
Release / Check ui (push) Successful in 25s
CI / Backend (push) Successful in 26s
Release / Docker / caddy (push) Successful in 47s
CI / Backend (pull_request) Successful in 28s
CI / UI (push) Successful in 54s
CI / UI (pull_request) Successful in 40s
Release / Docker / backend (push) Successful in 2m1s
Release / Docker / ui (push) Successful in 2m14s
Release / Docker / runner (push) Successful in 3m37s
Release / Gitea Release (push) Successful in 22s
The admin_nav_* message files generated by paraglide used 0-param inner
functions but called them with inputs, causing 50 svelte-check type errors.
Add _inputs = {} to each inner locale function to match the call signature.

Also adds backend/backend and backend/runner to .gitignore — binaries are
built inside Dockerfile and should never be committed.
v2.5.20
2026-03-31 10:53:44 +05:00
Admin
4e70a2981d fix(pipeline): add redis+libretranslate to homelab, make Asynq enqueue errors non-fatal
Some checks failed
CI / Backend (pull_request) Successful in 35s
CI / UI (push) Failing after 37s
CI / Backend (push) Successful in 53s
CI / UI (pull_request) Failing after 27s
- homelab/docker-compose.yml: add redis:7-alpine service (port 6379 bound to host
  so Caddy TLS proxy on prod can reach it), add libretranslate service, add
  redis_data and libretranslate_data volumes
- asynqqueue/producer.go: Asynq enqueue failures are now logged as warnings instead
  of returned as errors — PB record already exists so runner picks it up via poll
- backend/main.go: pass logger to NewProducer

Root cause: Redis was not reachable at 192.168.0.109:6379 because the redis
container had no host port binding. Caddy TLS proxy terminates TLS but could
not TCP-connect to the backend Redis.
2026-03-31 10:18:13 +05:00
Admin
004cb95e56 feat(i18n): translate admin sidebar nav labels (pages + tools)
Some checks failed
CI / Backend (pull_request) Successful in 49s
CI / UI (pull_request) Failing after 23s
Release / Test backend (push) Successful in 30s
CI / Backend (push) Successful in 1m0s
CI / UI (push) Failing after 45s
Release / Check ui (push) Failing after 19s
Release / Docker / ui (push) Has been skipped
Release / Docker / caddy (push) Successful in 1m21s
Release / Docker / runner (push) Successful in 2m40s
Release / Docker / backend (push) Successful in 3m28s
Release / Gitea Release (push) Has been skipped
v2.5.19
2026-03-31 00:42:32 +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
v2.5.18
2026-03-31 00:20:06 +05:00
Admin
8d95411139 fix(caddy): add SNI connection_policy to layer4 TLS block and anchor redis.libnovel.cc cert
Some checks failed
CI / Backend (pull_request) Successful in 30s
CI / UI (pull_request) Successful in 46s
Release / Test backend (push) Successful in 32s
CI / Backend (push) Successful in 49s
CI / UI (push) Successful in 57s
Release / Check ui (push) Successful in 31s
Release / Docker / caddy (push) Successful in 1m19s
Release / Docker / runner (push) Failing after 1m11s
Release / Docker / ui (push) Successful in 2m1s
Release / Docker / backend (push) Successful in 5m1s
Release / Gitea Release (push) Has been skipped
Without a connection_policy, Caddy resolved the TLS cert by the Docker
internal IP (172.18.0.5) instead of the hostname, causing TLS handshake
failures on :6380 (rediss:// from prod backend → homelab Redis / Asynq).

Changes:
- Caddyfile: add connection_policy { match { sni redis.libnovel.cc } } to
  the layer4 :6380 tls handler so Caddy picks the correct cert
- Caddyfile: add redis.libnovel.cc virtual-host block (respond 404) to
  force Caddy to obtain and cache a TLS cert for that hostname
- homelab/docker-compose.yml: add REDIS_ADDR, REDIS_PASSWORD,
  LIBRETRANSLATE_URL, LIBRETRANSLATE_API_KEY, and
  RUNNER_MAX_CONCURRENT_TRANSLATION to the runner service for parity with
  homelab/runner/docker-compose.yml
v2.5.17
2026-03-31 00:02:01 +05:00
Admin
f9a4a0e416 fix: remove paraglideVitePlugin from vite.config — root cause of 500 errors
Some checks failed
CI / Backend (push) Failing after 11s
CI / UI (push) Successful in 45s
Release / Test backend (push) Successful in 53s
Release / Check ui (push) Successful in 1m3s
Release / Docker / caddy (push) Successful in 55s
CI / Backend (pull_request) Successful in 48s
Release / Docker / backend (push) Failing after 11s
Release / Docker / runner (push) Failing after 11s
CI / UI (pull_request) Successful in 50s
Release / Docker / ui (push) Successful in 2m20s
Release / Gitea Release (push) Has been skipped
The paraglideVitePlugin runs at build time (buildStart hook) and fetches
the inlang plugin from cdn.jsdelivr.net to recompile messages. This:
  1. Overwrites messages.js with 'export * as m from ...' unconditionally
  2. Causes Rollup SSR tree-shaking to replace all m.*() calls with (void 0)
  3. Crashes every page server-side with 'TypeError: (void 0) is not a function'

The plugin is no longer needed: compiled paraglide output is committed to
git and updated via 'npm run paraglide' when messages change. Removing the
plugin lets Vite treat messages.js as a plain static module, keeping all
exports intact through the SSR bundle.
v2.5.16
2026-03-30 23:15:27 +05:00
Admin
a4d94f522a feat: styled error pages for all error surfaces
Some checks failed
CI / Backend (push) Failing after 11s
CI / UI (push) Successful in 51s
Release / Test backend (push) Successful in 54s
Release / Check ui (push) Successful in 1m9s
CI / Backend (pull_request) Successful in 45s
CI / UI (pull_request) Successful in 56s
Release / Docker / caddy (push) Successful in 1m22s
Release / Docker / backend (push) Failing after 1m46s
Release / Docker / ui (push) Successful in 2m32s
Release / Docker / runner (push) Successful in 3m35s
Release / Gitea Release (push) Has been skipped
- ui/src/error.html: custom SvelteKit last-resort fallback (replaces
  the bare '500 | Internal Error' shown when +error.svelte itself fails)
  — branded, auto-refreshes in 20s, book+lightning SVG illustration
- ui/src/routes/+error.svelte: improved with context-aware SVG
  illustrations (question mark book for 404, lightning bolt for 5xx),
  larger status watermark, and a Retry button on non-404 errors
- caddy/errors/500.html: new static error page matching the 502/503/504
  design — served by Caddy when a gateway-level 500 occurs
- Caddyfile: add handle_errors 500 block pointing at /srv/errors/500.html
- caddy/Dockerfile: COPY errors/ into image so static pages are baked in
v2.5.15
2026-03-30 23:00:00 +05:00
Admin
34c8fab358 fix: commit all paraglide generated output files to git
All checks were successful
CI / Backend (push) Successful in 51s
CI / UI (push) Successful in 33s
Release / Test backend (push) Successful in 39s
Release / Check ui (push) Successful in 57s
Release / Docker / caddy (push) Successful in 36s
CI / Backend (pull_request) Successful in 45s
CI / UI (pull_request) Successful in 1m8s
Release / Docker / backend (push) Successful in 2m9s
Release / Docker / runner (push) Successful in 3m5s
Release / Docker / ui (push) Successful in 2m44s
Release / Gitea Release (push) Successful in 29s
These files are needed at CI check time. paraglide fetches its plugin
from cdn.jsdelivr.net which is unavailable in the CI environment,
causing compile to produce empty output. Committing the generated
output means CI never needs to recompile them.
v2.5.14
2026-03-30 22:29:32 +05:00
Admin
d54769ab12 fix: commit paraglide output to git; remove compile from prepare script
paraglide-js fetches its plugin from cdn.jsdelivr.net at compile time.
In CI (no outbound internet access), this silently produces an empty
_index.js, causing svelte-check to fail with 'not a module' errors.

Fix:
- Commit all generated src/lib/paraglide/ files to git (removing
  the auto-generated .gitignore that was hiding them)
- Simplify prepare script to only run svelte-kit sync
- Add separate 'npm run paraglide' script for developers to
  regenerate when messages/*.json source files change
2026-03-30 22:28:54 +05:00
Admin
d2a4edba43 fix: strip paraglide's 'export * as m' after compile in prepare script
Some checks failed
CI / Backend (pull_request) Successful in 54s
CI / UI (pull_request) Successful in 35s
Release / Test backend (push) Successful in 24s
CI / Backend (push) Successful in 1m2s
CI / UI (push) Successful in 1m0s
Release / Check ui (push) Failing after 29s
Release / Docker / ui (push) Has been skipped
Release / Docker / runner (push) Failing after 1m27s
Release / Docker / caddy (push) Successful in 1m31s
Release / Docker / backend (push) Failing after 1m31s
Release / Gitea Release (push) Has been skipped
paraglide-js unconditionally emits 'export * as m from ...' in messages.js
which causes Vite/Rollup SSR to tree-shake all named message imports,
replacing every m.*() call with (void 0)() and crashing every page.
Strip the two offending lines via a Node.js one-liner in the prepare script
so the fix survives every npm ci run in CI.

Also stop tracking messages.js in git since it is always regenerated.
v2.5.13
2026-03-30 22:16:02 +05:00
Admin
4e7f8c6266 feat: streaming audio endpoint with MinIO write-through cache
Some checks failed
CI / Backend (push) Failing after 11s
Release / Check ui (push) Failing after 55s
Release / Test backend (push) Successful in 56s
Release / Docker / ui (push) Has been skipped
CI / UI (push) Failing after 1m6s
Release / Docker / caddy (push) Successful in 41s
CI / Backend (pull_request) Successful in 58s
CI / UI (pull_request) Successful in 55s
Release / Docker / runner (push) Failing after 55s
Release / Docker / backend (push) Failing after 1m23s
Release / Gitea Release (push) Has been skipped
Add GET /api/audio-stream/{slug}/{n}?voice= that streams MP3 audio to the
client as TTS generates it, while simultaneously uploading to MinIO. On
subsequent requests the endpoint redirects to the presigned MinIO URL,
skipping generation entirely.

- PocketTTS: StreamAudioMP3 pipes live WAV response body through ffmpeg
  (streaming transcode — no full-buffer wait)
- Kokoro: StreamAudioMP3 uses stream:true mode, returning MP3 frames
  directly without the two-step download-link flow
- AudioStore: PutAudioStream added for multipart MinIO upload from reader
- WriteTimeout bumped 60s → 15min to accommodate full-chapter streams
- X-Accel-Buffering: no header disables Caddy/nginx response buffering
v2.5.12
2026-03-30 22:02:36 +05:00
Admin
b0a4cb8b3d fix: remove spurious 'export * as m' from messages.js causing all pages to 500
Some checks failed
CI / Backend (push) Successful in 1m9s
CI / UI (push) Successful in 34s
Release / Test backend (push) Successful in 39s
Release / Docker / caddy (push) Successful in 46s
CI / Backend (pull_request) Successful in 43s
Release / Check ui (push) Successful in 1m21s
CI / UI (pull_request) Successful in 34s
Release / Docker / ui (push) Successful in 2m2s
Release / Docker / backend (push) Failing after 3m8s
Release / Docker / runner (push) Successful in 3m46s
Release / Gitea Release (push) Has been skipped
The paraglide messages.js had an extra 'export * as m from ...' line which
caused Rollup/Vite to tree-shake all actual message function imports in the
SSR bundle. Every m.* call compiled to (void 0)(), crashing every page
server-side with TypeError. Removed the duplicate namespace re-export.
v2.5.11
2026-03-30 21:23:37 +05:00
Admin
f136ce6a60 fix: remove distinct background from error page status code box
Some checks failed
CI / Backend (pull_request) Successful in 50s
CI / UI (pull_request) Successful in 43s
CI / UI (push) Successful in 37s
CI / Backend (push) Successful in 46s
Release / Test backend (push) Successful in 53s
Release / Check ui (push) Successful in 44s
Release / Docker / backend (push) Failing after 43s
Release / Docker / caddy (push) Failing after 55s
Release / Docker / runner (push) Successful in 2m14s
Release / Docker / ui (push) Successful in 2m53s
Release / Gitea Release (push) Has been skipped
v2.5.10
2026-03-30 20:34:14 +05:00
Admin
3bd1112a63 fix: remove default sort=-updated from listOne to prevent PocketBase 400 errors
All checks were successful
CI / Backend (push) Successful in 36s
CI / Backend (pull_request) Successful in 48s
CI / UI (push) Successful in 1m3s
CI / UI (pull_request) Successful in 37s
Collections without an 'updated' field (books, user_sessions, user_settings,
user_library) were returning 400 because listOne always sent sort=-updated.
Changed default to empty string since we only fetch 1 record (no ordering needed).
2026-03-30 20:32:38 +05:00
Admin
278e292956 fix(home): use book.summary instead of book.description in hero card
Some checks failed
CI / Backend (push) Successful in 1m3s
CI / UI (push) Successful in 40s
Release / Docker / caddy (push) Failing after 10s
Release / Test backend (push) Successful in 40s
CI / UI (pull_request) Successful in 41s
CI / Backend (pull_request) Successful in 59s
Release / Docker / runner (push) Failing after 38s
Release / Docker / backend (push) Successful in 3m35s
Release / Check ui (push) Successful in 1m1s
Release / Docker / ui (push) Successful in 2m50s
Release / Gitea Release (push) Has been skipped
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v2.5.9
2026-03-30 19:44:25 +05:00
Admin
76de5eb491 feat(reader): chapter comments + readers-this-week count
Some checks failed
CI / Backend (push) Successful in 48s
CI / UI (push) Failing after 22s
Release / Check ui (push) Failing after 33s
Release / Docker / ui (push) Has been skipped
Release / Test backend (push) Successful in 53s
Release / Docker / caddy (push) Successful in 48s
CI / Backend (pull_request) Successful in 44s
CI / UI (pull_request) Failing after 44s
Release / Docker / runner (push) Failing after 46s
Release / Docker / backend (push) Successful in 1m54s
Release / Gitea Release (push) Has been skipped
- CommentsSection now accepts a chapter prop and scopes comments to that chapter
- Chapter reader page mounts CommentsSection with current chapter number
- Book detail page shows rolling 7-day unique reader count badge
- API GET/POST pass chapter param; pocketbase listComments filters by chapter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v2.5.8
2026-03-30 19:34:12 +05:00
Admin
c6597c8d19 feat(home): hero resume card, horizontal scroll rows, genre strip, dedup
Some checks failed
CI / Backend (push) Successful in 1m0s
CI / UI (push) Failing after 26s
Release / Test backend (push) Successful in 53s
CI / Backend (pull_request) Successful in 45s
Release / Docker / caddy (push) Successful in 1m13s
CI / UI (pull_request) Failing after 33s
Release / Docker / runner (push) Failing after 1m27s
Release / Docker / backend (push) Successful in 3m35s
Release / Check ui (push) Failing after 31s
Release / Docker / ui (push) Has been skipped
Release / Gitea Release (push) Has been skipped
- First continue-reading book becomes a wide hero card with title,
  description, genre tags, and a prominent Resume ch.N CTA
- Remaining in-progress books move to a horizontal scroll shelf
- Recently Updated deduplicates by slug; books with multiple new
  chapters show a green "+N ch." badge
- Genre discovery strip (horizontal scroll) links to /catalogue?genre=X
- Stats demoted to a subtle two-number footer bar
- All rows use horizontal scroll instead of fixed grids

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v2.5.7
2026-03-30 19:20:28 +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>
v2.5.6
2026-03-30 19:13:25 +05:00
Admin
90dbecfa17 feat(profile): auto-save settings, merge sections, fix font/size preview
Some checks failed
CI / Backend (push) Successful in 30s
Release / Test backend (push) Successful in 33s
Release / Docker / caddy (push) Failing after 10s
CI / UI (push) Successful in 52s
Release / Check ui (push) Successful in 1m7s
CI / Backend (pull_request) Successful in 40s
CI / UI (pull_request) Successful in 53s
Release / Docker / runner (push) Failing after 46s
Release / Docker / backend (push) Successful in 3m16s
Release / Docker / ui (push) Successful in 5m2s
Release / Gitea Release (push) Has been skipped
- Remove both "Save settings" buttons; all settings now auto-save with
  800ms debounce and show a transient "✓ Saved" indicator
- Apply theme, font family, and font size to context immediately on
  change so the preview is live without waiting for the save
- Merge Appearance + Reading settings into a single Preferences card
  with dividers — fewer sections, less visual noise
- Pro users see a compact subscription row; free users see upgrade CTAs
- Speed label splits value and units (shows "1.5x" separately in brand
  color) for cleaner readout
- Auto-advance toggle gains a subtitle description

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v2.5.5
2026-03-30 19:07:51 +05:00
Admin
2deb306419 fix(i18n+settings): rename pt-BR→pt, fix theme/locale persistence
Some checks failed
CI / Backend (push) Successful in 56s
CI / UI (push) Successful in 38s
Release / Test backend (push) Successful in 42s
Release / Docker / caddy (push) Failing after 11s
CI / Backend (pull_request) Failing after 11s
Release / Docker / backend (push) Failing after 38s
CI / UI (pull_request) Successful in 44s
Release / Check ui (push) Successful in 1m53s
Release / Docker / runner (push) Failing after 1m26s
Release / Docker / ui (push) Successful in 3m46s
Release / Gitea Release (push) Has been skipped
Root cause: user_settings table was missing theme, locale, font_family,
font_size columns — PocketBase silently dropped them on every save.
Added the four columns via PocketBase API.

Also:
- listOne now sorts by -updated so the most-recent settings record wins
- PARAGLIDE_LOCALE cookie is now cleared when switching back to English
- pt-BR renamed to pt throughout (messages, inlang settings, validLocales)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v2.5.4
2026-03-30 19:05:14 +05:00
Admin
fd283bf6c6 fix(sessions): prune stale sessions on login to prevent accumulation
Some checks failed
CI / Backend (push) Successful in 43s
CI / UI (push) Successful in 36s
Release / Test backend (push) Successful in 56s
Release / Check ui (push) Successful in 50s
Release / Docker / caddy (push) Successful in 40s
CI / UI (pull_request) Failing after 37s
CI / Backend (pull_request) Successful in 48s
Release / Docker / runner (push) Failing after 32s
Release / Docker / backend (push) Successful in 2m39s
Release / Docker / ui (push) Successful in 1m45s
Release / Gitea Release (push) Has been skipped
Sessions not seen in 30+ days are deleted in the background each time
a new session is created. No cron job needed — self-cleaning on login.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v2.5.3
2026-03-30 18:24:16 +05:00
Admin
3154a22500 fix(errors): redesign all Caddy error pages with consistent branded layout
Some checks failed
CI / UI (push) Successful in 43s
Release / Test backend (push) Successful in 45s
CI / Backend (push) Successful in 48s
Release / Check ui (push) Successful in 36s
CI / Backend (pull_request) Successful in 47s
CI / UI (pull_request) Successful in 36s
Release / Docker / caddy (push) Failing after 1m50s
Release / Docker / runner (push) Successful in 1m57s
Release / Docker / backend (push) Successful in 3m13s
Release / Docker / ui (push) Successful in 2m42s
Release / Gitea Release (push) Has been skipped
Add header/footer, LibNovel wordmark, pulsing status indicator, faint
watermark code, and auto-refresh countdown (20s for 502/504, 30s for 503).
Fix two-tone background by setting background on html+body. 404 is static
with no auto-refresh.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v2.5.2
2026-03-30 18:22:14 +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>
v2.5.1
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
4a267d8fd8 feat: open catalogue + book pages to public (hybrid open model)
All checks were successful
CI / Backend (pull_request) Successful in 47s
CI / UI (pull_request) Successful in 44s
CI / Backend (push) Successful in 44s
Release / Test backend (push) Successful in 28s
CI / UI (push) Successful in 1m12s
Release / Check ui (push) Successful in 56s
Release / Docker / caddy (push) Successful in 1m9s
Release / Docker / ui (push) Successful in 1m57s
Release / Docker / runner (push) Successful in 3m45s
Release / Docker / backend (push) Successful in 3m50s
Release / Gitea Release (push) Successful in 14s
Reading content is now publicly accessible without login:
- /catalogue, /books/[slug], /books/[slug]/chapters, /books/[slug]/chapters/[n]
  are all public — no login redirect
- /books (personal library), /profile, /admin remain protected

Unauthenticated UX:
- Chapter page: "Audio narration available — Sign in to listen" banner
  replaces the audio player for logged-out visitors
- Book detail: bookmark icon links to /login instead of triggering
  the save action (both desktop and mobile CTAs)

SEO:
- robots.txt updated: Allow /books/ and /catalogue, Disallow /books (library)
- sitemap.xml now includes /catalogue as primary crawl entry point

i18n: added reader_signin_for_audio, reader_signin_audio_desc,
      book_detail_signin_to_save in all 5 languages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v2.5.0
2026-03-30 14:16:22 +05:00
Admin
c9478a67fb fix(ci): use full gitea.com URL for gitea-release-action
All checks were successful
CI / UI (push) Successful in 41s
CI / Backend (push) Successful in 50s
Release / Test backend (push) Successful in 56s
Release / Check ui (push) Successful in 34s
CI / Backend (pull_request) Successful in 46s
CI / UI (pull_request) Successful in 34s
Release / Docker / runner (push) Successful in 3m15s
Release / Docker / backend (push) Successful in 3m29s
Release / Docker / ui (push) Successful in 2m30s
Release / Docker / caddy (push) Successful in 1m17s
Release / Gitea Release (push) Successful in 17s
Bare `actions/` references resolve to github.com by default in act_runner.
gitea-release-action lives on gitea.com so must use the full https:// URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v2.4.1
2026-03-29 22:38:36 +05:00
Admin
1b4835daeb fix(homelab): switch Fider SMTP to port 587 + STARTTLS
All checks were successful
CI / Backend (pull_request) Successful in 50s
CI / UI (pull_request) Successful in 1m10s
Port 465 (SMTPS) is blocked on the homelab server. Port 587 with STARTTLS
works. Updated FIDER_SMTP_PORT=587 and FIDER_SMTP_ENABLE_STARTTLS=true in
Doppler prd_homelab, and made EMAIL_SMTP_ENABLE_STARTTLS dynamic so it reads
from Doppler instead of being hardcoded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 22:16:01 +05:00
Admin
c9c12fc4a8 fix(infra): correct doppler entrypoint for watchtower container
All checks were successful
CI / UI (pull_request) Successful in 39s
CI / Backend (pull_request) Successful in 45s
- Fix binary path: /usr/bin/doppler (not /usr/local/bin)
- Mount /root/.doppler config so the container can auth without DOPPLER_TOKEN env
- Set HOME=/root so doppler locates the mounted config directory
- Add explicit --project/--config flags to override directory-scope lookup
- Production: --project libnovel --config prd
- Homelab: --project libnovel --config prd_homelab

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 22:05:33 +05:00
Admin
dd35024d02 chore(infra): run watchtower via doppler for fresh secrets on restart
All checks were successful
CI / UI (pull_request) Successful in 39s
CI / Backend (pull_request) Successful in 47s
Mount the host doppler binary into the watchtower container and use it as
the entrypoint so WATCHTOWER_NOTIFICATION_URL and other secrets are fetched
from Doppler each time the container starts, rather than being baked in at
compose-up time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 21:52:32 +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>
v2.4.0
2026-03-29 21:43:00 +05:00
Admin
5da880d189 fix(runner): add heartbeat + translation polling to asynq mode
All checks were successful
CI / UI (push) Successful in 57s
CI / Backend (pull_request) Successful in 1m4s
CI / Backend (push) Successful in 1m45s
CI / UI (pull_request) Successful in 51s
Two bugs prevented asynq mode from working correctly on the homelab runner:

1. No healthcheck file: asynq mode never writes /tmp/runner.alive, so
   Docker healthcheck always fails. Added heartbeat goroutine that
   writes the file every StaleTaskThreshold (30s).

2. Translation tasks not dispatched: translation uses ClaimNextTranslationTask
   (PocketBase poll queue), not Redis/asynq. Audio + scrape use asynq mux,
   but translation sits in PocketBase forever. Added pollTranslationTasks()
   goroutine that polls PocketBase on the same PollInterval as the old
   poll() loop.

All Go tests pass (go test ./... in backend/).
2026-03-29 20:22:37 +05:00
Admin
98631df47a feat(billing): Polar.sh Pro subscription integration
Some checks failed
CI / UI (push) Successful in 1m36s
CI / Backend (push) Successful in 59s
Release / Test backend (push) Successful in 39s
Release / Check ui (push) Successful in 33s
CI / Backend (pull_request) Successful in 44s
CI / UI (pull_request) Successful in 34s
Release / Docker / runner (push) Successful in 2m44s
Release / Docker / ui (push) Successful in 2m45s
Release / Docker / backend (push) Successful in 3m35s
Release / Docker / caddy (push) Successful in 1m8s
Release / Gitea Release (push) Failing after 2s
- Webhook handler verifies HMAC-SHA256 sig and updates user role on
  subscription.created / subscription.updated / subscription.revoked
- Audio endpoint gated: free users limited to 3 chapters/day via Valkey
  counter; returns 402 {error:'pro_required'} when limit reached
- Translation proxy endpoint enforces 402 for non-pro users
- AudioPlayer.svelte surfaces 402 via onProRequired callback + upgrade banner
- Chapter page shows lock icon + upgrade prompts for gated translation langs
- Profile page: subscription section shows Pro badge + manage link (active)
  or monthly/annual checkout buttons (free); isPro resolved fresh from DB
- i18n: 13 new profile_subscription_* keys across all 5 locales
v2.3.25
2026-03-29 13:21:23 +05:00
Admin
83b3dccc41 fix(ui): run paraglide compile in prepare so CI type-check finds $lib/paraglide/server
Some checks failed
CI / Backend (pull_request) Successful in 50s
CI / UI (pull_request) Successful in 1m0s
CI / UI (push) Successful in 42s
Release / Test backend (push) Successful in 46s
CI / Backend (push) Successful in 1m1s
Release / Check ui (push) Successful in 34s
Release / Docker / caddy (push) Successful in 1m4s
Release / Docker / runner (push) Successful in 2m7s
Release / Docker / ui (push) Successful in 2m26s
Release / Docker / backend (push) Successful in 3m28s
Release / Gitea Release (push) Failing after 2s
The paraglide output dir has its own .gitignore (ignores everything), so
generated files are never committed. CI ran `npm ci` then `svelte-check`
without ever invoking vite, so $lib/paraglide/server was missing at
type-check time.

Adding `paraglide-js compile` to the prepare script ensures the output is
regenerated during `npm ci` before svelte-kit sync and svelte-check run.

Also adds translation_jobs collection to pb-init-v3.sh.
v2.3.24
2026-03-29 11:50:06 +05:00
Admin
588e455aae chore(homelab): add LibreTranslate service to runner compose
Some checks failed
CI / Backend (pull_request) Failing after 11s
CI / UI (pull_request) Failing after 11s
- libretranslate/libretranslate:latest, internal Docker network only
- LT_LOAD_ONLY=en,ru,id,pt,fr (only pairs the runner needs)
- LT_API_KEYS=true, key stored in Doppler prd_homelab
- Runner depends_on libretranslate (service_healthy)
- LIBRETRANSLATE_URL=http://libretranslate:5000 (no tunnel needed)
- RUNNER_MAX_CONCURRENT_TRANSLATION wired from Doppler
2026-03-29 11:42:52 +05:00
Admin
28ac8d8826 feat(translation): machine translation pipeline + admin bulk enqueue UI
Some checks failed
CI / Backend (push) Failing after 11s
Release / Check ui (push) Failing after 51s
Release / Docker / ui (push) Has been skipped
CI / UI (push) Failing after 55s
Release / Test backend (push) Failing after 1m9s
Release / Docker / backend (push) Has been skipped
Release / Docker / runner (push) Has been skipped
Release / Docker / caddy (push) Failing after 28s
Release / Gitea Release (push) Has been skipped
CI / UI (pull_request) Failing after 42s
CI / Backend (pull_request) Successful in 3m45s
- LibreTranslate client (chunks on blank lines, ≤4500 chars, 3-goroutine semaphore)
- Runner translation task loop (OTel, heartbeat, MinIO storage)
- PocketBase translation_jobs collection support (create/claim/finish/list)
- Per-chapter language switcher on chapter reader (EN/RU/ID/PT/FR, polls until done)
- Admin /admin/translation page: bulk enqueue form + live-polling jobs table
- New backend routes: POST /api/translation/{slug}/{n}, GET /api/translation/status,
  GET /api/translation/{slug}/{n}, GET /api/admin/translation/jobs,
  POST /api/admin/translation/bulk
- ListTranslationTasks added to taskqueue.Reader interface + store impl
- All builds and tests pass; svelte-check: 0 errors
v2.3.23
2026-03-29 11:32:42 +05:00