Commit Graph
162 Commits
Author SHA1 Message Date
hectorqin 7e66582f84 perf(pdf): render with PDF.js and authenticated range loading 2026-11-07 11:21:27 +00:00
hectorqinandClaude Opus 5.5 dd20dc6a87 fix(web): settings page draws shelf switches; theme saves report via Notyf
The SwitchRow styles were scoped to `.sheet`, so on 偏好设置 › 书架 the
显示作者/显示阅读进度 switches rendered as empty transparent buttons. Unscope
them, and restore the pill over the media page's generic button rule.

主题已保存 and theme save errors were inline <p> lines; they now go through
the global notice (ui.md §3.6), one notice updated in place per panel.

ui:review captures the shelf section and measures the switches and the
floating theme notice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:24:16 +00:00
hectorqinandClaude Opus 5.5 d956723a83 test(web): list-return tests use MemoryRouter; data routers fail on Node 24
The real cause of the CI failure: createMemoryRouter builds a fetch Request for
every navigation, and on Node 24 (CI) undici rejects jsdom's AbortSignal
("RequestInit: Expected signal to be an instance of AbortSignal"), so no click or
Back ever navigated. Locally Node 22 accepts it. The test now mounts a plain
MemoryRouter, like every other test, and reads location / navigation type /
navigate through a probe component.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 00:23:58 +00:00
hectorqinandClaude Opus 5.5 9eb1411017 test(web): give the list-return and catalog tests a time budget, not 50 polls
CI failed every test in media-list-return.test.tsx: each ran ~550-650ms, the whole
budget of settle()'s 50 x 10ms polls, because the first render is slower on the
runner than locally. settle() now polls for up to 4s (under vitest's 5s test
timeout); media-catalog-page.test.tsx had the same helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 00:20:11 +00:00
hectorqinandClaude Opus 5.5 4ea2ea6096 fix(web): media browsing, lists, player queue and query cache regressions
- 影音浏览 preferences finally take effect: cover density (phone 3/2 columns),
  default catalog sort, the continue card (channel and 首页) and the 来源库
  selector, read live from the per-device preferences.
- A catalog or favorites page past the end clamps to the last page instead of
  showing nothing / claiming there are no favorites.
- 演播者 shows the 有声书 category tabs (shared CatalogTabs).
- The channel ⋯ menu offers 媒体库管理 only to administrators.
- History and search restore their scroll on back (also back after forward);
  a detail opened from search returns to the search; browser Back between two
  searches shows the earlier search again.
- The player's queue re-renders after 上移 / 移除.
- Media writes invalidate the cached queries they change (editions, metadata,
  hierarchy, libraries, recognition…); item details are always rechecked, so
  刷新资源状态 sees a rescan.
- Aborts surface as ApiError('aborted') everywhere (including body reads), so a
  shared request aborted by one caller is retried for a live one and never shows
  "signal is aborted without reason".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 23:51:33 +00:00
hectorqinandClaude Opus 5.5 a0a0fb5f2f feat(web): 偏好设置 shows its four kinds as tabs, one at a time
外观与主题, 书架, 影音浏览 and 播放 were four stacked sections on one long page.
They are now tabs (role=tablist, arrow keys, one tab stop): only the selected
panel renders, ?section= picks it (外观与主题 when absent) and switching replaces
the history entry, so back still leaves for 我的.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 23:51:33 +00:00
hectorqinandClaude Opus 5.5 7044ab3ef7 fix(web): player, catalog and query regressions found by the media reviews
- 偏好设置 › 打开播放器 always opened the video player; it now opens the playing
  channel's player (playerPath, shared with the mini player's link).
- The player's panel and back navigation dropped item/part from the address, so a
  refresh could not restore playback; the address now follows the playing part, and
  a restoring link shows 正在恢复播放… instead of flashing 当前没有播放内容.
- A link that cannot be restored no longer leaves another video expanded over its
  explanation; the other playback stays in the mini player.
- The first category tab of 音乐 and 有声书 was never marked current (the bare
  channel path redirects to it).
- A navigation query shared by two callers handed the first caller's abort to the
  second (a detail opened right after login showed "request aborted").
- The metadata and match pages' fixed header covered the sidebar at >=701px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:27:29 +00:00
hectorqinandClaude Opus 5.5 7810d5943e fix(web): five regressions the browser reviews caught
- A book downloaded for offline reading did not open offline: nothing ever writes
  books into the offline mirror, so the fallback found nothing. The reader page now
  falls back to the book record the download stored in its manifest.
- 书源登录 reopened on the cached credential status after saving; saving now
  refetches that source's status.
- A newly installed/upgraded/enabled/removed plugin's source types did not appear
  in 添加书源 for up to five minutes; plugin changes now refetch the type list.
- The extension page did not scroll its selected tab into view on narrow screens.
- The shelf settings sheet's sticky headings stopped 14px below the header again
  (the padding rule was lost when .panel-body became .sheet-body).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:12:56 +00:00
hectorqinandClaude Opus 5.5 b8e5102669 feat(web): 我的 opens every row as a page; preferences on one page
我的 mixed sheets (书架显示, 系统设置, OPDS nested in the shelf sheet) with pages
(外观与主题, 影音浏览, 播放), and the personal preferences were four separate rows.

- 偏好设置 (#/me/settings) holds 外观与主题, 书架, 影音浏览 and 播放 as sections of
  one page, with a jump bar and ?section=; every control applies at once (影音浏览
  loses its save button and unsaved-changes prompt). The shelf's gear still opens
  the same fields (ShelfSettingsFields) as a sheet shortcut.
- 系统设置 becomes three admin pages of 我的 › 管理: 用户管理, 注册与邀请, 服务配置
  (#/me/admin/*, lazily loaded).
- 连接外部阅读器 (OPDS) is a page (#/me/opds) under 账号, also reached from the
  书架 section and the shelf sheet; back returns to whoever opened it.
- Old /me/theme|browse|playback and /media/:channel/settings[/:panel] redirect to
  the matching section; /me/account keeps its page.
- Drops MediaSettingsPage and the CSS only it used; docs follow the new paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:12:40 +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 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 a904bb5a83 feat(web): 点击区域设置 — a 3×3 tap grid with a per-cell action
The two-way 左退右进 / 左进右退 switch becomes nine cells, each 上一页, 下一页,
菜单, 上一章, 下一章 or 无操作, edited as a small page in 阅读设置 → 翻页 with
左右互换 and 恢复默认. The default grid is the old standard layout and a stored
`tapZone: 'reversed'` is folded into its grid. At least one 菜单 cell is kept,
right-to-left books mirror the grid, and the gesture layer now reports the row
as well as the column, from layout geometry like before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:27:51 +00:00
hectorqinandClaude Opus 5.5 21e2509c86 feat(web): 阅读记录 in 书籍信息 brings back an overwritten reading position
Tapping the title opens 书籍信息, which now ends with 阅读记录: the places this
account has been in the book on any device, newest first, each with its
chapter, percentage, device and time. The position on screen is pushed
before the list is read, so its entry is marked 当前; pressing another entry
goes back there and saves it as the position. Offline the section says the
history cannot be read; a failed read offers 重试.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 03:51:42 +00:00
hectorqinandClaude Opus 5.5 0115dbd81a feat(web): in scroll mode, pulling past a chapter's end opens the next one
Taps and keys already crossed chapters, but a reader scrolling by hand reached
the last line and had to find 下一章 (#702, #698). A touch pull of more than
72px that starts at the end of a chapter, or wheel scrolling past it, now
opens the next chapter; the same at the top goes back. Only a gesture that
begins at the edge counts, so arriving at the end does not turn by itself,
and a cooldown keeps trackpad momentum from turning a second chapter. Touch
events are used because the browser cancels pointers once it scrolls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:56:36 +00:00
hectorqinandClaude Opus 5.5 5cddc9ef1e fix(web): 首页 and the shelf show the progress just read when the reader closes
Leaving the reader left the new position to the sync debounce, and 首页's
继续阅读 and the shelf's 最近阅读 order were served from a query cache that stays
fresh for a minute, so coming back from a book showed the old chapter and
percentage (#663). Closing the reader now saves the position, pushes the
outbox, and then re-reads the home and shelf queries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:56:36 +00:00
hectorqinandClaude Opus 5.5 20a85c33fe fix(web): restore the ⋯ menu row styles removed as dead CSS
a894bdd deleted .action-menu-item/-label/-icon/-check/-description and the
.sheet-list-icon/-check/-description rules as unused, but ActionMenu builds
those names as `${base}-item` (base 'action-menu' or 'sheet-list'), which the
dead-class scan did not recognise. Every ⋯ dropdown and its bottom-sheet form
lost their row layout, hover, danger colour and check mark. Both files are
restored to their state before that commit.

The class-usage test now covers every stylesheet (not only media) and
understands built names: `${expr}-suffix` with a literal prefix, and
`prefix-${expr}`. Library markup (Notyf, DPlayer) is exempt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:47:19 +00:00
hectorqinandClaude Opus 5.5 cdd9abd813 chore(web): remove unused app and media exports
LoadingPage and isApiError; the media search's back/forward restore
(restoreSearchHistory, its history tagging and saved-entry map,
clearSearchReturn, restoreSearchReturn), which nothing ever called — the
in-memory return to search still works; SeriesPlayback and its styles;
useMediaRequest, useMediaDetail, useMediaPlayback and searchQuery; and the
short route aliases at the end of MediaItemManagementPages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 09:49:37 +00:00
hectorqinandClaude Opus 5.5 d8b3491e95 chore(web): remove the reading engine and helpers nothing runs
An import graph from main.tsx shows a previous reader still in the tree:
render/{reader,paged,pdf,text,container,theme}.ts with lib/{events,locator}.ts,
ui/settings.ts and bridge.ts — superseded by ReaderScreen/ReaderView and
never imported. Also unreachable: render/paginator.ts, render/window.ts and
ui/shelf-order.ts (only their own tests used them; the shelf sorts on the
server), formats/remote-zip.ts (re-exported, never called; chapters are
fetched per section), ui/source-capabilities.tsx and the sources pages barrel.

Inside live modules: PrefetchCache (only shouldPrefetch is used),
textToParagraphs / paragraphElement / endsAtParagraphBreak (the streaming
TXT reader's carry), txt.ts's textToHtml alias, and ChunkCursor. Their tests
go with them; the node:test list, the Vitest excludes and the docs that
named these files are updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 09:49:37 +00:00
hectorqinandClaude Opus 5.5 12fbe0cbcd chore(web): remove the icon font toolchain and unused icon names
The UI draws Lucide SVGs through ui/icon.tsx and has not loaded the icon
font for a while; the font, its code-point table and the generator only fed
their own tests. Removed: tools/icons (generator, glyph paths, stroker),
src/styles/reader-icons.ttf, src/ui/icon-names.ts, the `icons` npm script and
the font/metrics tests. icons.test.ts keeps the contracts that still apply:
no borrowed names (now checked against ICONS), only icon.tsx imports
lucide-react, the .icon rule and icon buttons. The .icon rule drops its
font-era declarations.

ICONS loses the names nothing draws (the Font Awesome spellings kept as
aliases, add-circle, film, line-height, indent, sign-out); the reader's 朗读
button is `read-aloud` rather than `volume`. Docs and CLAUDE.md describe
adding an icon in icon.tsx.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 09:48:04 +00:00
hectorqinandClaude Opus 5.5 b997e89059 refactor(web): every icon goes through Icon; one glyph per idea
All direct lucide-react imports (59 files) now render <Icon name=…>. Along
the way the set converges: settings is one gear (not Settings/Settings2),
lock one padlock (not Lock/LockKeyhole), music one note (not Music/Music2),
video one clapperboard (the library cards and 我的 used Film), clock one face;
sizes snap to the scale and the stroke is uniform; decorative icons are
aria-hidden by default (about 36 were not). Text characters used as icons
(✓ on highlight colours, + 加书签, the 目录's +/− fold) are icons too.

icons.test.ts fails if any file but ui/icon.tsx imports lucide-react.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 07:26:51 +00:00
hectorqinandClaude Opus 5.5 6ba3dfc990 style(web): media and reader edges use --safe-*, media focus rings use the token
Eleven media rules and two in the file manager read env(safe-area-inset-*)
directly, which escapes both the Android shell (it zeroes --safe-*) and any
layer that owns an edge; they now read the variables, and design-tokens.test.ts
rejects new direct reads (the sidebar column's documented exception stays).
Media focus outlines use --focus-ring/--focus-offset like the rest of the app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 04:59:43 +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 0d0e263988 feat(web): one-tap bookmark in the reader's top bar
Adding a bookmark took four steps (toolbar → 工具 → 笔记 → 加书签) and nothing
on the page said whether it already had one. The top bar now has a bookmark
toggle: pressed and filled when the page on screen carries a bookmark (the
same exact-position test 阅读工具 uses), one press adds or removes it, and the
change syncs like any other note.

The bookmark glyph joins the icon set. tools/icons/build.mjs now emits the
alias table from paths.mjs (ALIASES) instead of dropping it, so running
`npm run icons` reproduces icon-names.ts. IconButton takes `pressed` for
toggles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 04:27:44 +00:00
hectorqinandClaude Opus 5.5 5199f3d4e5 feat(web): a comic's slider moves through the whole book
A comic's chapters are its pages, so "this chapter's pages" is always 1 and
the slider was permanently disabled: reaching page 150 of 200 meant scrolling
the 目录. For fixed layout with one-page chapters the slider is now the
book's page number (第 7/200 页); the label follows the thumb and the page
opens on release, so a drag decodes one image rather than every page passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 04:19:00 +00:00
hectorqinandClaude Opus 5.5 9bd3187179 perf(web): dragging a reader slider re-measures once per frame, brightness never
A slider sends one settings change per step. Each change measured the reading
position and queued its own restore, so a drag through the font-size range
re-measured a half-reflowed page many times a frame and could lose the place.
The position is now measured once before the first change of a frame and
restored once after the last; brightness, an overlay, skips both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 04:14:15 +00:00
hectorqinandClaude Opus 5.5 73caebef5d style(web): transitions use the motion tokens; media colours come from the theme
- every transition (home, 我的, media tabs/cards, media select, action menu,
  notices) is timed with --motion-duration/--motion-ease, so reduced motion
  reaches them; design-tokens.test.ts rejects literal transition durations.
- media rules drop the old green palette fallbacks (var(--text,#30392c) …),
  which only ever applied outside the themed scope, and the green-tinted
  shadows become neutral.
- the untinted media cover placeholder uses the theme surface instead of a
  fixed green gradient, so it no longer flashes green in other themes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 04:10:03 +00:00
hectorqinandClaude Opus 5.5 cdce43f98f refactor(web): remove ~320 lines of media CSS for classes nothing renders
The media styles grew in layers and replaced screens left their rules behind
(old channel switcher, details-based menus, manager tabs/toolbar, match
confirm box, resume cover, settings groups, …). 56 class names had no
reference in the source; rules naming only them are gone, and dead members
of :is()/:where() lists are dropped while the live ones stay. Screenshots of
every ui:review scene match the previous build apart from timing noise.

media-css-classes.test.ts now fails when a media stylesheet names a class the
source never renders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 03:59:39 +00:00
hectorqinandClaude Opus 5.5 d73b987de9 fix(web): shell honours touch and safe-area rules; home and 我的 tell the truth
- every typed field is at least --text-title on touch screens, so iOS does not
  zoom on focus (guarded by design-tokens.test.ts); the search page too.
- notices float above the phone tab bar; shell and notices use --safe-* vars.
- unknown links land on 首页 instead of the video channel.
- 首页 shows loading and error states for 继续阅读 instead of 还没有在读的书.
- 我的 shows the sync engine's connection state, and 退出登录 asks first.
- SwitchRow names its button from its label and the whole row toggles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 03:48:46 +00:00
hectorqinandClaude Opus 5.5 23290305c9 fix(web): reader opens offline, explains failures, and pages from the keyboard
- a book that cannot open keeps saying why over the stage, with 重试 and 返回;
  offline, a book downloaded for offline reading opens from its mirrored record.
- 返回 from a deep link or cold start goes to the shelf instead of leaving.
- keys are heard without focusing anything first; Space/Shift+Space page,
  S toggles 朗读, and focused buttons keep Space/Enter.
- 右→左 mirrors taps, swipes, drags and arrow keys.
- the tap that closes a note card no longer also turns the page.
- 目录 opens at the current chapter; the 朗读 button shows 朗读中 while reading.
- the selection toolbar is placed by its measured height and flips above the
  selection when it does not fit below.
- staged books fetch the next section while the current one is read, and a
  section is never fetched twice at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 03:48:46 +00:00
hectorqinandClaude Opus 5.5 d33eb9971c fix(web): media admin asks before destructive matches and explains errors
- 在线匹配 no longer opens the confirm sheet on load or after a search; only
  pressing a candidate does. Candidate buttons name their candidate.
- 移除在线匹配 asks first and says a series' seasons and episodes lose their
  online data too.
- admin pages show MediaScreenError's words (服务正在准备, 不可用, 离线, 无权限)
  instead of raw error codes; the back link is named by its destination.
- 扫描全部媒体库 submits every library and reports which ones failed instead of
  failing as a whole; scan records show a percentage like the library cards.
- metadata inputs are no longer shrunk to 13px on phones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 03:48:46 +00:00
hectorqinandClaude Opus 5.5 08c0454192 fix(web): shelf and library follow the URL, report writes, and guard file deletes
书架:
- sort and filter are read from the URL on every navigation, so back/forward
  and the nav's plain 书架 link clear a filter; ?sort= is validated.
- a page past the end clamps to the last page instead of showing 书架还没有书.
- ⋯ is an ActionMenu like every other ⋯, with 从书架拿掉 in the danger colour;
  removal reports 从书架拿掉 N 本 or the error, and the confirmation uses the
  same words and says the files on disk are untouched.
- the shared Pager replaces the unstyled hand-written one; load errors offer 重试.

书库 and 文件管理:
- 加入书架 reports its result or error; page numbers clamp; the folder listing
  no longer follows the book page; the search box follows the URL.
- selection is cleared when the folder or page changes, so 删除 can no longer
  act on files that are not on screen; delete confirmations name the files.
- each file action says what it did; both upload buttons report skipped files.
- members opening a 文件管理 link land on the same folder's browsing page, and
  admins reloading it are not shown 无权访问 while /auth/me is pending.
- text glyphs (×, ›, +) are icons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 03:48:46 +00:00
hectorqinandClaude Opus 5.5 60d4b41bf1 fix(web): typeset book-source HTML chapters like TXT
Source chapters are scraped fragments (`<div>  text<br><br><br><br>`) served
as text/html, so the TXT cleanup never ran: every paragraph was followed by
three empty lines and the indent setting did nothing. Like legado-E, a
text-only chapter of a `chapters` book is read back as characters when it
is loaded (htmlToText) and typeset by the TXT path. Done at read time, so
chapters the server cached earlier are cleaned too; chapters with images
or tables keep their markup and EPUBs never take this path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 06:20:27 +00:00
hectorqinandClaude Opus 5.5 52b757bbd3 fix(web): clean web-page markup out of TXT chapters before typesetting
Novels saved from a site keep the page's <div>, <br/> and &nbsp; in the
TXT. Escaped, the tags were drawn as text and the chapter ran together
as one paragraph. cleanTextMarkup turns block tags into line breaks,
drops inline tags, decodes common entities and treats whitespace-only
lines as blank, before paragraphs and the chapter heading are inferred.
Only real tag names match; other markup is still escaped as text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:51:18 +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
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
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
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
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
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 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
hectorqin 415bb8228c feat: 修改插件管理 2026-10-21 18:45:00 +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 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