86 Commits
Author SHA1 Message Date
hectorqin dca46d909e perf: harden sync and authentication and defer route loading 2026-11-08 05:17:42 +00:00
hectorqin eeb508f880 feat(media): add global and per-user controls with reading-only mode 2026-11-07 12:09:26 +00:00
hectorqin 7e66582f84 perf(pdf): render with PDF.js and authenticated range loading 2026-11-07 11:21:27 +00:00
hectorqinandClaude Opus 5.5 84e04d84b3 perf(server): cheaper rescans, stream-hash large books
Scan IO
- The walk decides whether a path is absorbed (page of a directory book,
  stray image on a shelf) before stat'ing it, replacing the separate
  recursive listing of every claimed directory.
- Ancestor checks are set lookups along the path instead of prefix scans
  over every shelf/claimed directory (quadratic on large libraries).
- Directory handlers may provide fingerprint(); an unchanged comic folder
  is settled from its listing instead of re-parsing and re-reading its
  cover every scan.

Memory
- File handlers may provide parseFile(ctx, {size, contentHash}). The
  scanner then hashes the file as a stream and settles identical bytes
  under a touched mtime before parsing. PDF, CBZ and EPUB implement it;
  EPUB metadata/cover/spine are read entry by entry through ZipArchive
  (falling back to the buffered path for archives it cannot open).
  parse(buf) is unchanged for acquisitions.

Synthetic library (1k shelves, 100 comics / 20k pages, one 300MB PDF):
unchanged rescan ~3.6s -> ~0.9s; first-scan peak RSS +352MB -> +78MB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 09:18:23 +00:00
hectorqinandClaude Opus 5.5 5c807c46d9 fix(server): nested comic chapters, directory handler dispatch, AI batch cap
- comic-directory: `!isBookCollection()` negated a Promise, so chapter
  folders inside a volume (第01卷/第001话/*.jpg) were never collected.
  Await it, count only real book files as a collection (ComicInfo.xml
  and other sidecars no longer veto a chapter), and keep the root's
  loose-page pass non-recursive so volume pages are not counted twice.
- scanner: remember the handler that claimed a directory instead of
  re-resolving it with Array.find over an async predicate; drop the
  unused directory signature; list the tree once per scan and memoise
  "holds a book file" across discovery and the shelf pass.
- ai: one splitPathBatches for jobs and direct scans; folders larger
  than batchSize are split instead of overflowing a prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 09:07:46 +00:00
hectorqinandClaude Opus 5.5 df93900236 feat: 首页 continue cards share one shape; recently added / random media rows
- Books, video and audiobooks in 「继续」 are the same card (cover, work title,
  episode/chapter, progress), most recent first, up to two books. An episode
  resolves to its series for title and poster instead of reading "第 1 集".
- New rows 看点什么 / 听本书 / 听张专辑, each switching between 最近添加 and
  随便看看 (with 换一批); hidden when the channel is empty or unavailable.
- Server: browse accepts sort=random and sort=added (newest available file
  mtime across the work and its episodes/tracks; no schema change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:24:43 +00:00
hectorqinandClaude Opus 5.5 c0b8acd469 fix(server): 时长统计 reads 500 in production — resolve the shelf on first use
main.ts registers the routes with a placeholder context and attaches the
services afterwards, so the stats route captured ctx.shelf while it was still
undefined and every summary failed with "Cannot read properties of undefined
(reading 'getMany')". Writes never touched the shelf and were unaffected. The
service is now built on first request; a new test builds the app the way
main.ts does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 12:50:51 +00:00
hectorqinandClaude Opus 5.5 005b9be99e feat: AI 扫描 follows the AI switches, like AI 总结 already did
/api/v1/ai/capabilities now also answers `scan`: 启用 AI and 启用 AI 扫描 on and
Base URL, key and model filled in. 扫描与刮削 shows the AI 扫描 tab only then; while
the answer is unknown there is no tab, a ?tab=ai link falls back to 媒体库扫描,
and a server without the endpoint keeps the tab as before. The answer is
re-asked each time the page opens, so a switch changed in 系统设置 takes effect
on the next visit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:55:19 +00:00
hectorqinandClaude Opus 5.5 3b1f8b46dc feat: 时长统计 — reading, watching and listening time by day, month, year and work
Reading time is counted by the reader: only while the page is visible and the
reader has turned, scrolled or tapped within three minutes, or while 朗读 plays.
It is kept per book per 15-minute UTC slot and per device install, saved
locally until sent, and stored as MAX(stored, reported) in the new
reading_time table, so a replayed report never counts twice.

Media time is derived on the server from the progress saves the web player and
Android Media3 already send: the smaller of the wall-clock gap and the position
advance between two saves of one session, gaps over 30s ignored. It is stored
in media.db (media_watch_time), so neither client changes.

我的 → 时长统计 shows a stacked bar per day (one month), month (one year) or
year, with a legend that filters by kind, a table view and the works ranked for
the range or the bar picked. Administrators also get every account's totals.
Chart colours are new mode-level tokens (--color-chart-1..4), validated for
colour-vision separation on both surfaces. The React UI review now covers the
page and names the overflowing elements when a screen overflows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:55:52 +00:00
hectorqinandClaude Opus 5.5 842331684a feat(server): keep a per-book reading-progress history
reading_progress keeps one row per book and last-writer-wins, which answers
"where am I" and loses "where was I": a stale device, a mistapped 目录 entry
or a clock running ahead overwrote the place for good (P0: 进度历史).

Every valid progress write now also lands in reading_progress_history,
including a write that lost the merge. A device reading on through one
chapter updates its latest entry (same chapter, within 5% and 30 minutes)
instead of adding rows; another chapter, a jump, another device or an
out-of-order write is a new entry. The newest 50 per book are kept.
GET /api/v1/sync/progress/:bookId/history lists them, newest first, for the
signed-in account only.

The table is new (CREATE TABLE IF NOT EXISTS), so the schema change is
additive and older builds sharing the database ignore it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 03:51:42 +00:00
hectorqinandClaude Opus 5.5 c1056743ad feat: the reader shows AI 总结 only when the server can answer it
The top bar always drew AI 总结, although AI is off by default, so most
readers pressed it only to be told AI_DISABLED (ui.md §4/§5: a control the
reader cannot use is not drawn). GET /api/v1/ai/capabilities tells any signed-in
account whether chapter summaries are enabled and configured, without exposing
the configuration; the reader asks once per book and leaves the button out
when the answer is no. Against an older server (404) the button stays.

The busy line while a summary is generated is a global notice like every other
one, instead of a hand-drawn .floating-notice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 04:36:01 +00:00
hectorqinandClaude Opus 5.5 32595c87a5 feat: file management is admin-only; members upload books behind a setting
- browse move/rename/mkdir/delete/metadata now require an administrator
  (403 ADMIN_REQUIRED for members).
- Members may upload, but only add books: unsupported formats are skipped,
  zip/cbz are stored as comic archives rather than unpacked, and overwrite is
  refused. The new business setting library.memberUpload (default on) turns
  member uploads off (403 UPLOAD_DISABLED); GET /library/upload reports
  `allowed`.
- The library page shows 上传书籍 to members when uploads are allowed.
- Shelf tests read user_books for the owner only, now that a second user exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 02:03:45 +00:00
Kiro Agent f15365d03c feat(shelf): 在读 / 想读 / 已读 and 分组 (prototypes 2 and 19)
Server: user_books gains reading_status. The shelf list computes each
book's readingState — an explicit 在读/已读 mark wins, otherwise progress
decides (>=99% 已读, started 在读), and 想读 gives way once reading starts —
filters by ?status=, and PUT /api/v1/books/:id/reading-status sets or
clears the mark.

Client: chips 全部/在读/想读/已读 and 分组 (a series or a tag from the
shelf's facets) above the toolbar, all kept in the URL; a state label on
each cover; the ⋯ menu marks 想读/在读/已读, clears the mark, or takes the
book off the shelf.

Docs: API, UI and media user guide for this and the other V3.1 gaps.
2026-10-20 04:47:06 +00:00
Kiro Agent 68e085482c feat(media): library management states and 扫描与元数据 (prototype 20)
Server:
- Scan jobs record the number of files listed (total), so a running scan
  can report a real percentage.
- New 扫描设置 business settings, shown as the page's 扫描与元数据 card:
  自动扫描新文件 (hourly rescan of every library, default on),
  刮削封面与简介 (after a completed scan, scrape items that have no online
  metadata and were never attempted, with the first configured provider;
  default off), 读取内嵌字幕与歌词 (default on; off hides embedded subtitle
  streams and lyrics tags), 扫描隐藏文件夹 (default off: names starting
  with '.' are skipped, e.g. ._film.mp4 resource forks).
- MediaAutomation runs the scheduled scans and post-scan scraping as the
  first active administrator; MediaScanner gains onFinished/isScanning.

Client:
- Each library card shows its latest scan: 上次扫描 今天 09:12 beside
  重新扫描; 正在扫描 · 已处理 62% with a progress bar and 取消; or the
  failure (路径不可访问…) with 重试 and 查看详情 (the tasks page).
  Starting or cancelling a scan now stays on the page; scan-jobs is polled.
- 扫描与元数据 card beside the list saves each switch with the settings
  revision, and hides itself on servers without these settings.
2026-10-20 04:41:03 +00:00
hectorqin 8584d7b4e3 fix(web): align React UI with legacy reader UX
Unify media secondary page chrome, loading states, playback controls, favorites filtering, and source/library screens with the legacy reader experience.
2026-10-05 13:23:27 +08:00
hectorqin a4586faadd feat(extensions): support file and batch actions 2026-10-02 23:30:08 +08:00
hectorqin 358d6005d1 feat(media): add batch match policies and richer recent playback 2026-10-01 13:30:28 +08:00
hectorqin 79eeb5ded6 test(android): cover asset origin login bootstrap 2026-10-01 08:09:29 +08:00
hectorqin 0166164c04 feat(media): add directory filename rules and replace folder routes 2026-09-29 15:35:07 +08:00
hectorqin 713d1eb5aa fix(media): recognize disc filenames under directory rules 2026-09-29 10:50:53 +08:00
hectorqin 2d13f11811 feat(media): support named season and edition groups 2026-09-29 09:59:56 +08:00
hectorqin 9bf885eabc fix(media): infer episode numbers for directory series 2026-09-29 09:48:38 +08:00
hectorqin dc2c96ae9a fix(media): apply inherited rules and clean release numbering 2026-09-29 09:38:33 +08:00
hectorqin 5f3a75c973 fix(media): keep folder navigation and honor directory season rules 2026-09-29 07:28:41 +08:00
hectorqin 0f06d6e9f3 fix(media): restore recognition scrolling and add bulk selection 2026-09-28 22:33:32 +08:00
hectorqin 385e68de78 feat(media): add directory recognition rules and reviewed reidentification 2026-09-28 17:53:30 +08:00
hectorqin f583b32e46 feat(media): clean missing resources by directory 2026-09-28 17:24:04 +08:00
hectorqin 501ca71f6b fix(media): prefer OpenList direct playback and open player before buffering 2026-09-28 13:39:47 +08:00
hectorqin ff976a4cf6 feat(settings): manage business configuration from admin pages 2026-09-28 12:38:54 +08:00
hectorqin 996f0c555e fix(media): accelerate remote scans and expose scan diagnostics 2026-09-28 10:58:02 +08:00
hectorqin 73f9eb6e20 feat(media): add isolated media libraries and playback
Support local and OpenList libraries, metadata, independent media navigation, themes, and audio/video playback. Preserve approved v2 prototypes and current documentation; archive superseded review artifacts and keep generated validation output outside version control.
2026-09-27 18:22:51 +08:00
hectorqin 06b3d09e22 feat(admin): manage users and invite-based registration 2026-09-24 17:19:48 +08:00
hectorqin 1d86efab34 feat(reader): add selection annotations and server-side book search 2026-09-24 16:03:35 +08:00
hectorqin 2d85c39756 feat(search): log stream termination reasons and plugin diagnostics 2026-09-24 11:48:42 +08:00
hectorqin 524d015b35 feat(plugins): load dependent forms on tab and selection changes 2026-09-24 11:26:57 +08:00
hectorqin 4cc7bff5f0 feat(plugins): add rule debugging workbench layout 2026-09-24 11:09:27 +08:00
hectorqin aa7aee051d feat(sources): organize source login and plugin management 2026-09-24 10:32:47 +08:00
hectorqin 7bff77eaf8 feat(reader): deliver P0-P2 reading tools and ecosystem integrations
Add reliable reading progress, verified backup and restore, reading tools, offline downloads, source quality checks, personal credential status, OPDS serving and verified WebDAV backup uploads.
2026-09-24 07:47:32 +08:00
hectorqin e3cabf4b1f feat(web): add PWA installation and offline app shell 2026-09-24 06:34:21 +08:00
hectorqin 95cb3134db feat(sources): support default source and refine switching UI 2026-09-23 15:16:15 +08:00
hectorqin 4896e5efc7 feat(reader): stream alternative sources and refresh current chapters
Add book information dialogs and compact toolbar actions with source provenance. Reuse Streamable HTTP for alternative discovery and preserve cached text when a forced refresh fails.
2026-09-23 13:16:54 +08:00
hectorqin 0a468992be feat(plugins): update installed packages while preserving source data 2026-09-23 12:42:14 +08:00
hectorqin cb96d48d62 fix(search): stop on stream disconnect and allow concurrent book actions 2026-09-23 11:41:24 +08:00
hectorqin a852682e0c fix(plugins): isolate all request timeouts and cancellations 2026-09-23 11:14:41 +08:00
hectorqin 0782202dae fix(plugins): isolate search stop failures and float operation feedback
A searchCancel deadline previously killed the shared plugin process and prevented subsequent acquisition. Release only the stop request and allow late cleanup to finish. Keep ordinary hard cancellation unchanged. Cover process and HTTP acquisition regressions and compact extension feedback without layout shifts.
2026-09-23 09:46:02 +08:00
hectorqin d7ebc3c898 feat(plugins): support web npm and tgz installation 2026-09-22 19:21:37 +08:00
hectorqin 72c4405435 chore: keep host repository plugin agnostic 2026-09-22 17:47:21 +08:00
hectorqin a131a8cc45 feat(plugins): add bounded diagnostic output 2026-09-22 15:56:22 +08:00
hectorqin 5f8554eadd feat(search): stream source results over HTTP 2026-09-22 14:38:36 +08:00
hectorqin 171297dc39 feat(search): add rolling multi-source sessions 2026-09-22 13:33:44 +08:00