Commit Graph
316 Commits
Author SHA1 Message Date
hectorqinandClaude Opus 5.5 c8dba91c6d fix(web): sources search picker styles leaked from the card form rules
The source filter's searchable select is rendered inside .sources-card,
whose generic label/input/button rules stacked the search icon above a
second bordered 14px input (iOS zooms on focus) and centred the trigger
text. Its base styles also used media-only aliases (--border, --surface,
--media-selection), so outside media the trigger had no border and the
selected option no highlight. Fall back to the global tokens, scope the
picker's own controls, use a 16px input and hide the native clear button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 02:22:07 +00:00
hectorqinandClaude Opus 5.5 4931516eec fix(web): keep every source failure across streamed search chunks
mergePages replaced the accumulated page with the latest chunk, so only
the most recent chunk's errors were shown. Merge errors by source and
code, as the reader's alternatives panel already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 01:57:46 +00:00
hectorqin 6c274a75e2 Merge branch 'fix/pwa-orientation-swipe' 2026-10-28 09:05:18 +00:00
hectorqinandClaude Opus 5.5 6514387f9e fix(web): PWA orientation, media swipes and follow-finger page turns
- Manifest: drop orientation "any" (Chrome maps it to the full sensor, so the
  installed app ignored the phone's rotation lock); the service worker serves
  the manifest network-first so installed apps can pick up manifest changes.
- Media: derive the channel from the path for static routes (演播者 showed 影视);
  keep the tab bar on audiobook/narrators; a channel with no library swipes a
  whole channel at a time.
- Reader (paged mode): the column strip follows the finger; release turns on a
  flick or past 30% of the width, otherwise springs back; chapter edges fall
  through to the normal turn.
- PWA smoke test stubs the current window.__readerFlush hook.
- Docs: ui.md §3.8 drag-follow and media swipe rules; pwa.zh-CN.md orientation
  and manifest caching.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:05:18 +00:00
hectorqin fe313d043a Merge branch 'fix/mobile-pwa-styles' 2026-10-28 07:35:51 +00:00
hectorqinandClaude Opus 5.5 2f784d1d4e fix(web): mobile PWA style review fixes
- Reader (scroll paging): mask the line the page's bottom edge cuts through
  (clip-path, --reader-page-mask) so no page shows a half-sliced last line;
  re-measured after page-turn animations.
- Landscape sidebar layout: the sidebar owns the left safe-area inset, the page
  column the right one; fix .shelf-head's swapped left/right padding; reader
  top/bottom bars respect left/right insets.
- Guard all :hover rules with (hover: hover) and (pointer: fine), enforced by
  test/design-tokens.test.ts.
- PWA update banner sits above the phone tab bar and reader toolbar.
- Top safe-area on sources header and phone media pages; 16px sidebar search
  on touch (iOS focus zoom); larger hit areas for chips and cover controls.
- Manifest: unified launch colour #FDF9F6, maskable icon, drop
  window-controls-overlay; regenerate branding assets.
- Docs: ui.md safe-area/touch/hover/reader rules, pwa.zh-CN.md icons and
  window notes, CLAUDE.md stale bundle notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 07:35:51 +00:00
hectorqin 88c264e5ca Merge branch 'ci/github-actions' v0.1.0 2026-10-27 10:25:40 +00:00
hectorqinandClaude Opus 5.5 e426ac8b82 fix(android): replace the broken Gradle wrapper; CI UI review installs server deps
The committed gradle-wrapper.jar was the real 8.11.1 wrapper jar nested
inside another jar, without the org/gradle/cli classes, so ./gradlew failed
everywhere with NoClassDefFoundError: org/gradle/cli/CommandLineParser
(CI was the first place it ran from a fresh checkout). gradlew was a
hand-written launcher and gradlew.bat, which the dev guide tells Windows users
to run, did not exist.

Regenerated with `gradle wrapper --gradle-version 8.11.1` from the official
distribution (sha256 f397b287…, matching services.gradle.org); the jar matches
the published gradle-8.11.1-wrapper.jar.sha256 (2db75c40…). The distribution
URL is unchanged. .gitattributes pins gradlew to LF and *.bat to CRLF.

The UI review job starts its media fixture through server/node_modules/tsx,
so it installs the server's dependencies too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:25:40 +00:00
hectorqin cccc06ba8d Merge branch 'ci/github-actions' 2026-10-27 10:19:56 +00:00
hectorqinandClaude Opus 5.5 4e149c73d7 ci: release only for v* tags or manual runs; CI on main; Android SDK packages
- main is the long-lived development branch: release.yml no longer runs on
  a push to it, only on v* tags and workflow_dispatch. ci.yml now runs on
  every branch push, main included.
- android-actions/setup-android's default package list includes `tools`,
  which current command-line tools no longer carry, so sdkmanager failed;
  install exactly platform-tools, android-35 and build-tools 35.0.1.
- reader-chrome.test.ts left its React roots mounted; a scheduled commit
  could land after jsdom teardown and fail the run with an unhandled
  "Right-hand side of 'instanceof' is not an object" (all tests passing).
  Unmount in afterEach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:19:56 +00:00
hectorqin 5ce7b2f682 Merge branch 'ci/github-actions' 2026-10-27 10:15:49 +00:00
hectorqinandClaude Opus 5.5 57b73abb45 ci: use the repository's DOCKER_USERNAME/DOCKER_PASSWORD secrets
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:15:49 +00:00
hectorqin cd158f6785 Merge branch 'ci/github-actions' 2026-10-27 10:09:32 +00:00
hectorqinandClaude Opus 5.5 d7c557b066 ci: GitHub Actions build/test and multi-arch Docker Hub release
ci.yml (PRs, non-main pushes, and called by release.yml):
- server: typecheck, test, build
- web: typecheck, test (with server deps, for search parity), build
- UI review: production bundle in Playwright's Chromium, screenshots kept
- Android: shell contract check, web assets staged and verified, debug APK

release.yml (main, v* tags, manual):
- runs CI first; nothing is published from a red build
- builds hectorqin/reader-pro for linux/amd64 and linux/arm64 on native
  runners (no QEMU), pushes by digest, then one job creates the multi-arch
  tags: main + sha-<short> for main; x.y.z, x.y, latest + sha for tags
- needs DOCKERHUB_USERNAME and DOCKERHUB_TOKEN repository secrets

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:09:32 +00:00
hectorqinandClaude Opus 5.5 392148c8da fix(android): shell contract check counts only page-callable methods
check-shell.sh failed on main in three places, none of them real defects:

- Every `fun` at method indentation counted as a bridge method, so the
  Kotlin-only SpeechBridge.interruptForMedia (called by the media bridge,
  not the page) was reported as undeclared. Only methods directly under
  @JavascriptInterface are the page's contract now.
- configuredServerUrl was declared as an optional *property*
  (`configuredServerUrl?: () => string`), which the declaration reader does
  not see. Written as the equivalent optional method, `configuredServerUrl?(): string`.
- The package/directory check stripped only src/main/java/, so every unit
  test under src/test/java/ was reported as misplaced. Any source set's java/
  root is stripped now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:09:32 +00:00
hectorqin da587e18f6 Merge branch 'feat/touch-swipe' 2026-10-27 10:01:46 +00:00
hectorqinandClaude Opus 5.5 4296145721 feat(web): swipe turns pages on phones; swipe between media and shelf tabs
Reader
- The reading surface declared no touch-action, so the browser claimed every
  horizontal drag (pointercancel): a swipe never turned a page on a phone, and
  a rightward one triggered the browser's back gesture and left the book.
  Paged and fixed layouts now leave every move to the gesture layer
  (touch-action: none, on the host and on the paged column in the shadow
  root); scroll mode keeps vertical panning only (pan-y), so it still scrolls
  and no longer navigates away. Horizontal swipes in scroll mode stay inert.

Tabs
- useSwipeTabs (shared/ui): a clearly horizontal touch swipe moves one tab.
  Mouse, short or diagonal moves, scrolls the browser took over, swipes that
  start on a control or a sideways-scrolling row, and active selections are
  ignored; the click the finger lifted over is suppressed. The entering body
  slides in from the swipe side (header and tab row stay put; reduced motion
  disables it).
- Media: the eight catalog tabs are one line across channels — 影视(首页 电影
  剧集)→ 音乐(专辑 歌手 曲目)→ 有声书(有声书 演播者). Library and sort carry
  over within a channel and reset across one.
- Shelf: 全部 · 在读 · 想读 · 已读.

Tests: swipe-tabs (gesture rules, catalog order and params) and the reader's
touch-action rules in reader-layout-css.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:01:46 +00:00
hectorqin bdd0c4553b Merge branch 'fix/reader-paged-highlights-toolbar' 2026-10-27 09:37:57 +00:00
hectorqinandClaude Opus 5.5 1137b0d90d fix(web): paged mode lands on page boundaries; highlights draw and follow reflow; bottom bar stays visible
Paged mode
- Restoring the position after the viewport height changes (address bar,
  keyboard, rotation) wrote max * fraction, which is almost never a column
  boundary: the screen showed half of one page and half of the next. Every
  paged scrollLeft write (restore, TTS follow, revealBlock, search) now goes
  through one page-snapping helper, and a scroll the browser makes on its own
  (focus, selection autoscroll) is snapped back.

Highlights
- localSectionId prefixed `xhtml:` from the format alone, so a windowed book
  whose sections have no prefix saved highlights that matched no chapter and
  were never drawn (and its progress failed to restore). The loaded document's
  own section ids are now the authority.
- Highlight boxes are measured once; font size, margins, mode switch, resize
  or a late image moved the text under them. They are repainted after each.

Mobile bottom bar
- `.reader-route-host` had min-height: 100vh, which on a phone browser is
  taller than the visible viewport while the address bar shows, pushing the
  reader's bottom toolbar under it. Height is 100dvh with a 100vh fallback.
- Notices are bottom-centre, exactly over 朗读/外观; in the reader they now
  stack above the bottom bar instead of swallowing the next tap.

Tap to turn hides the toolbar
- A tap on the outer thirds turns the page and puts a showing toolbar away;
  the middle third still toggles it.

UI review scripts: page-turn-regression waits for login to finish rather than
for the shelf (the app now opens on media); reader-regression shows the
toolbar before pressing 下一章 after tap turns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 09:24:49 +00:00
hectorqinandClaude Opus 5.5 6abe98a07e docs: add CLAUDE.md for Claude Code
Commands (per-package install/dev/test/build, single tests across the two web
runners, UI review) and the cross-file architecture: book identity, metadata
precedence, ShelfService as the only DTO builder, sync semantics, additive
schema, isolated media worker, hash routing and single-bundle constraints,
imperative reader stage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 09:24:49 +00:00
hectorqin 3cae4d193f Merge pull request #726 from hectorqin/ui-review-polish
UI review polish
2026-10-27 15:05:36 +08:00
hectorqin 7dad491c17 fix(web): keep PWA sheets visible 2026-10-26 15:20:06 +08:00
hectorqin ccba6e3d49 fix(web): handle standalone PWA sheet layout 2026-10-26 15:09:30 +08:00
hectorqin a1c8c61102 fix(web): use WebView sheet layout fallback 2026-10-26 14:02:09 +08:00
hectorqin cf14bf3dc5 fix(web): size reader sheets before scrolling 2026-10-26 13:49:46 +08:00
hectorqin 92855d9199 fix(web): make mobile sheet body a bounded scroller 2026-10-26 13:42:45 +08:00
hectorqin cd8f1337c2 fix(web): keep mobile sheets scrollable 2026-10-26 10:00:19 +08:00
hectorqinandClaude Opus 5.5 7fbf9dbbf0 feat(web): shelf and library paint a cover-grid skeleton while loading
Both screens showed an empty page with a floating "正在加载…" until the
first response. The .book-grid.is-skeleton styles (same grid and density as
the real one, a shimmer that goes flat under reduced motion) were already in
reader.css from the legacy shelf but nothing rendered them; BookGridSkeleton
does, so the first paint fills in instead of jumping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 13:22:17 +08:00
hectorqinandClaude Opus 5.5 a634e9e955 fix(web): every search field says its scope
The side-nav global search and the shelf's own field sat side by side on
desktop and both read "搜索书名、…"; the shelf one was even labelled
搜索书库 while it only filters the shelf.

- Global (side nav, /search): 搜索书籍和影音 — also no longer truncated in
  the 216px rail.
- Shelf: 在书架中筛选书名、作者、系列, labelled 筛选书架; results are 筛选结果.
- Library: 在书库中搜索书名、作者.
- ui.md §4 records the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 13:15:20 +08:00
hectorqinandClaude Opus 5.5 81ec4ba6bc refactor(web): seven role-named type sizes replace ~50 literal font sizes
ui.md's four type roles only governed the reading side; the media sheets
used every integer from 8 to 24px plus dozens of rem values. Every font size
up to 24px now reads one of --text-display/title/body/ui/label/meta/micro.

- --text-ui (14px) is the media list/settings body; --text-display (22px) the
  page hero; --text-micro (10px) only for cover/icon badges.
- --text-meta moves 0.72rem -> 0.75rem, so the 200+ media 12px lines land on
  it unchanged and the reading side's meta grows by half a pixel.
- Inputs that were 16px land on --text-title (16.8px), keeping the iOS
  focus-zoom floor; ui.md says so explicitly.
- Sizes above 24px (player initials, hero numerals) and the decorative serif
  shorthands stay literal; the token test rejects literals <= 24px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 13:10:31 +08:00
hectorqinandClaude Opus 5.5 fbb7298bf0 perf(web): split the reader and admin screens out of the first load
client.js was one 1.48 MB bundle (413 kB gzip) because the build assumed the
Android shell loads from file://. It no longer does: WebHost serves the
bundle from https://appassets.androidplatform.net/ via WebViewAssetLoader,
the service worker precaches every emitted file, and the server serves any
file under assets/ (caching hashed names forever).

- The reader, media library/task/item admin pages and source editor pages
  load on first visit (app/router/lazy-pages.tsx); wrappers keep the page
  names, so the route tree and its tests read unchanged.
- Chunks are content-hashed; the entry stays client.js and CSS stays one
  index.css, so no host-facing name changes and no unstyled flash.
- A tab running an old client after a deploy gets "应用已更新 · 重新加载"
  instead of a dynamic-import error.
- ui:review now opens a book and asserts the reader chunk loads and renders.

client.js: 1478 kB -> 777 kB (gzip 413 -> 231 kB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 13:04:18 +08:00
hectorqinandClaude Opus 5.5 597369392e fix(web): one back glyph for second-level pages; file-manager header aligns
- 书库 / 文件管理 / 书源 headers used arrow-left while every media page uses
  chevron-left; they now share chevron-left.
- The trailing "›" in collection links shared the label's flex: 1 and sat in
  the middle of the button; it now ends the button in muted ink.
- On phones the 上传书籍 / 新建文件夹 actions split the row under the title
  instead of hanging off the right edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 12:14:04 +08:00
hectorqinandClaude Opus 5.5 7e8b0ea278 feat(web): placeholder covers take a stable per-title tint
A shelf of TXT books (or a media library before scraping) was a wall of
identical panels, so finding one meant reading every placeholder. Titles now
hash to one of twelve hues 30° apart (ui/cover-tint.ts), mixed 10-20% into
the theme surface, so the tint follows light/dark and the text on it keeps
its contrast. Shelf, library and media covers share it. ui.md §2.7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 12:10:46 +08:00
hectorqinandClaude Opus 5.5 c5125792d7 refactor(web): six-step radius tokens replace ~40 literal corner sizes
The stylesheets used 2/3/4/5/6/7/8/9/10/11/12/13/14/15/18/19/20/22/24px plus
a dozen rem values for corners, so the same kind of control differed by a
pixel or two between neighbouring screens. --radius-xs/sm/md/lg/xl/pill
(4/8/10/14/20/999px) now carry every corner; --reader-radius is --radius-md.
Controls whose radius was half their height (switches, the role tag, the
home hero action, the nav icon pill) use --radius-pill so they stay capsules
if their height changes. ui.md §2.6 documents the scale; a test fails on any
new literal border-radius.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 12:06:19 +08:00
hectorqinandClaude Opus 5.5 92cfabe740 feat(web): primary buttons fill with brand-text so they outweigh secondaries
The light themes' brand fill (#F2B79F etc.) is softer than the white
secondary button beside it, so 保存/播放 read as the quieter choice. Primary
buttons now use brand-text with bg on it, the same recipe as danger, and a
pair the token spec already holds to 4.5:1 in every theme. brand stays for
state fills (selected, progress, switches). Spec updated (rule 6).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:54:48 +08:00
hectorqinandClaude Opus 5.5 90e01fa518 fix(web): UI review round 1 — duplicate headers, overlays, empty states
- Library editor / permissions: hide the frame header while the form (which
  guards unsaved changes) draws its own; back labels say 返回媒体库管理.
- Media catalog: empty state renders below the library toolbar it describes.
- Video player: keep "elapsed / total" on phones instead of a dangling "/".
- Library browse: the count line is a static footer, not a sticky overlay.
- History: unplayable rows dim their play button and title.
- Shelf: drop the stale .shelf-status footer rule that indented the filter
  chips; the subtitle no longer repeats the toolbar's count.
- Media browse settings: switches instead of checkboxes; labels keep one line.
- Sources: empty source picker links to 书源管理.
- Side nav: hover only on fine pointers, lighter than the selected state.
- Media tile titles match the shelf's weight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:38:45 +08:00
hectorqin dca100425a Merge pull request #724 from hectorqin/v3
feat: 修改插件管理
2026-10-21 18:45:52 +08:00
hectorqin 415bb8228c feat: 修改插件管理 2026-10-21 18:45:00 +08:00
hectorqin b7ed226478 Merge pull request #723 from hectorqin/v3
V3
2026-10-21 18:43:40 +08:00
hectorqin 92f26ba122 feat(web): ⋯ menus drop down; drop 影音设置; rebuild 插件管理
- ActionMenu is an anchored dropdown on every width (popover, no title bar or backdrop; closes on pick, Escape, Tab or a press outside; arrow keys). ActionSheet stays for question-style picks. Styles in styles/action-menu.css.
- Remove the media settings home and its ⋯ entry: panels live only at /me/:panel; /media/:channel/settings[/:panel] redirects to 我的. Library and task pages return to 我的 or the channel.
- 插件管理: one list with status tags and a per-row ⋯ menu (启用/停用, 卸载 with confirmation); install moves into a Sheet (npm / local file, trust switch). Old plugin styles removed from reader.css.
- 首页: drop the redundant 继续阅读 pill on the reading card.
- Docs, unit tests and ui:review updated (stub admin settings in the media review).
2026-10-21 15:26:49 +08:00
hectorqin 2d7e858ecc refactor(web): drop Mantine; one ActionMenu for every ⋯ menu
- Replace every Mantine component with the toolkit (Button, Callout, IconButton) or plain markup; remove MantineProvider, its stylesheet and the --mantine variable bridge; keep its form-font reset in reader.css. Uninstall @mantine/core and @mantine/hooks.
- ActionMenu/ActionSheet (ui/action-menu.tsx): the file manager's Mantine Menu, the shelf pick dialog and the media <details> menus (catalog, narrators, detail, libraries, library rows, folders, chapters, queue, resource panel, favorites filter) are one compact Sheet menu.
- Toolkit Callout for page-level messages; operation failures go to FloatingNotice; .page-state for whole-page loading/error.
- Shared SourcesHeader and SourceBookDetailSheet; source editor and credentials pages rebuilt without Mantine, deleting a source now asks first.
- Remove dead code: SettingsPage (unrouted), MiniPlayer, ui/dom.ts (formatters moved to lib/format.ts), the unused metadata form dialog, useAppThemeDark.
- ui:review react-app passes again: fixed stale steps (/settings, mini player, login race, missing-edition detail).
2026-10-21 11:38:12 +08:00
hectorqin fbb6dc65cf feat(ui): one Sheet for every overlay; drop 来源与刮削
- ui/sheet.tsx Sheet/ConfirmSheet replace the centred Modal, SweetAlert confirms, DialogView overlays, the reader Panel, the shelf settings floating card, the two media dialogs and Mantine Modal.
- Phones: bottom sheet. Wide windows: right-edge drawer; compact sheets (confirm, prompt, pick) open next to the tap or focused control.
- One set of exits (close, Escape, backdrop), 关闭 label, focus starts on 取消, danger confirms.
- Remove sweetalert2 and the unused @mantine/notifications.
- Remove the 来源与刮削 media settings panel and its entries in 我的 and 影音设置; TMDB and MusicBrainz stay configurable in 系统设置 > 服务配置.
- docs/ui.md 3.7 describes the rule; tests and review scripts follow the new markup.
2026-10-21 07:30:27 +08:00
hectorqin b5ca02c66c style(home, mine): lighter continue cards and balanced settings columns
- 首页: continue card on ch-read-soft with ink text, compact meter beside the action; media cards beside reading on wide windows; no repeated part title when length is unknown.
- 我的: soft profile card with role tag; groups in two top-to-bottom columns on wide windows, reading order restored on narrow ones; inset row separators.
2026-10-20 22:27:44 +08:00
hectorqin 05762208d9 docs(prototype): 我的 as the settings hub and one page column
- 8 我的(手机): 内容 / 偏好 / 管理 / 账号 as in the app; the list scrolls above the tab bar.
- New 20b 我的(桌面 · 设置中心): groups two to a row in the page column.
- Desktop 首页 / 书架 / 媒体库管理 use the shared 32px gutter (was 40px).
- 设计系统 notes the shared page column and that settings live in 我的.
2026-10-20 18:27:11 +08:00
hectorqin 1f534ee4c3 feat(shell): one page column for the four tabs, 我的 as the settings hub
- 首页/书架/影音/我的 share --page-measure and --page-gutter, so content edges stay put across tabs at every width; 我的 lays its groups two per row on wide screens.
- 我的 gathers every setting (偏好 / 管理 / 账号); its panels live at /me/:panel and return to 我的.
- 影音设置 keeps only media settings and links to 我的; 来源与刮削 is now reachable.
- 系统设置 moves from the shelf header to 我的 → 管理.
2026-10-20 14:58:00 +08: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
Kiro Agent b71a2ddd4e feat(media): 字幕与音轨 sheet and landscape full screen
Prototype 12: 字幕, 音轨 and 版本 are tabs of one sheet. The subtitle tab
lists 关闭, sidecars (外挂 · srt), extracted and browser-embedded streams
(内嵌); 添加外挂字幕 converts an SRT/VTT/ASS file from the device to WebVTT
in the browser (UTF-8, UTF-16 or GB18030) and selects it for the session;
字幕延迟 shifts sidecar cues in 0.5 s steps from their original timing; 字号
(小/标准/大) is saved per device and drives ::cue.

Prototype 13: a 全屏 button enters full screen on the player, locks the
phone to landscape where allowed, and floats 选集, 倍速, 适应屏幕 (cover),
字幕 and 锁定 over the picture; they fade with DPlayer's controller. 锁定
stops taps from reaching the player until unlocked. Speed presets are also
in the 更多 sheet. All of this is web-only; the Android player keeps its
own menus.
2026-10-20 04:33:53 +00:00
Kiro Agent 807f69b129 feat(shell): desktop search across books and media, and connection status
The desktop sidebar gets the prototype's search box (搜索书名、片名、歌曲、有声书),
opening a new /search page that shows the first books (library index) and
media results with links to each full search. The account block shows the
sync engine's view of the server: 已连接, 正在同步, 离线 or 同步出错.
2026-10-20 04:28:32 +00:00
Kiro Agent c30e07c270 feat(reader): reading papers as a reader setting
白纸 / 暖黄 / 浅绿 / 灰调 / 夜间 (prototypes 4 and 23) tint only the reading
page — paper, ink, readouts — while the chrome keeps the reading theme.
Stored per device in AppSettings (paper, paperFollowsDark), set from the
reader's 外观 sheet or 外观与主题. 夜间 makes the reader dark; a light paper
gives way to the theme's dark values while the app is dark unless
跟随系统深色模式 is off. Old sepia/green/dark page themes migrate to the
matching paper. Tokens are mirrored in TS and CSS and held to contrast by
theme-tokens.test.ts.
2026-10-20 04:26:51 +00:00