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>
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>
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>
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>
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>
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>
IconName came from the legacy font's code-point table, so a new icon meant a
new font glyph, while 59 files imported lucide-react directly with their own
glyphs, 17 sizes and strokes from 1.4 to Lucide's default 2. icon.tsx now
defines every name (in the product's words: video, audiobook, lock,
folder-browse, …), each drawn by one Lucide glyph; IconName is its keys.
Icon gains a six-step size scale (14–32), `filled`, and `strong` for the
current navigation item; the stroke is 1.8 everywhere, 1.4 at the 32px
empty-state size. ui.md §1 describes the rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The browsing page asked /library/browse for the folder's entries but drew only
its crumbs, and the crumbs appear only once you are already in a subfolder:
a shareable #/library?path=科幻 scope existed, but nothing on screen led to it.
Subfolders are now a row of chips under the search field; one press narrows
the scope a level and the crumbs widen it again. Folders excluded by a scan
rule are not shown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
§3.10 gains the 工具 button and the 朗读中 state, the comic exception to the
page slider, the open-failure state with 重试/返回, and the keyboard map.
§3.9 no longer mentions a 快捷 row the settings panel does not have; the TXT
rows live under 更多排版.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Audit of README, docs/, android/README.md, android/contract.md and CLAUDE.md
against the current server, web and Android code: V3 navigation and settings
locations, Docker Hub release images, GitHub clone URL and CI, missing API
endpoints and error codes, architecture of lazy chunks/themes/sources, media
pages and routes, the Android bridge contract, and the new file-management and
member-upload permissions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
媒体库管理: 新建媒体库 and 扫描与刮削 are visible above the list, an empty
state offers 新建媒体库, and cards get a channel-coloured kind icon, a
指定用户 tag, a one-line directory, missing files in the warning colour,
开始扫描 for never-scanned libraries, and 扫描记录 in the ⋯ menu. Cards
take the full width when the server has no 扫描与元数据 switches.
扫描与刮削: scan and AI scan share one toolbar (library in ?library=,
refresh, a single primary action) instead of duplicated buttons; scan
records show their time, keep title/state/action on one line, put the
failure in a warning box followed by 重试, and have an empty state.
刮削 preselects the page's library, lays the form out in two columns
when wide, and keeps 已选 N and 开始批量匹配 pinned under the item list.
Styles live in features/media/styles/manager.css, loaded after v2.css.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Legado plugin now cleans by bookSourceType (text paragraphed, image
sources untouched); the reader only re-typesets text-only chapters, which
never includes a comic chapter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Novels saved from a site keep the page's <div>, <br/> and 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>
- 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>
- 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>
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>
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>
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>
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>
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>
- 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).
- 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).
- 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.
- 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 我的.
- 首页/书架/影音/我的 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 我的 → 管理.
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.
白纸 / 暖黄 / 浅绿 / 灰调 / 夜间 (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.
Implements docs/prototypes/Reader V3 主题语义令牌.md (v3.1):
- 暖陶 (default), 雾蓝, 青绿 app themes plus the reading-only 黑白灰, each
light and dark. Components read --color-* tokens from
styles/theme-tokens.css, selected by data-theme + data-mode; the
TypeScript mirror lives in shared/theme/theme.ts.
- Brand is split per the spec: brand/ch-* only fill (with on-brand/on-ch on
top), brand-text/ch-*-text for text. --reader-accent/--media-accent are now
brand text; --reader-fill/--media-fill are brand fills. Selected segmented
controls use brand-soft + brand-text.
- One preference per server+account (reader.theme.v2): app theme, mode
(system/light/dark), the follow-system light/dark pair, and the reading
theme. V3 choices migrate per spec §8 (rose falls back to 暖陶 light).
- 外观与主题 rebuilt from prototype 23: follow-system switch with the
light/dark pair, app-theme cards with light/dark previews, and the
reading theme (跟随应用 / 黑白灰).
- Reader: 黑白灰 is set on the reader container only; the appearance panel
offers 跟随应用/黑白灰 and 明暗; day/night sets the mode; the e-ink preset
uses 黑白灰 light.
- Video and music players wear the current theme's dark values (§4.7).
- Removed the legacy light/sepia/green/dark palettes and the
<html data-theme> writes from reader settings, which would now collide
with data-theme="green".
- Tests: theme-tokens.test.ts keeps CSS and TS values identical and checks
the §5 contrast pairs for all 8 palettes; theme.test.tsx covers migration,
resolution, the controller and the settings page.
The book now wears the same six app themes as every other page (plus
follow-system); the separate white/sepia/green/dark reading themes are gone.
The 外观 sheet's swatches are the app themes drawn in their own colours and
the day/night rail button switches between 暖纸 and 深夜. The bottom bar
becomes a rounded sheet. 界面 is renamed 外观 in the bar, panel, review
scripts and spec.
Shelf title is 书架 in the serif face and loses the now-redundant headphones
link. docs/ui.md gains the V3 shell section.
Appearance: font size, line height, theme (drawn as swatches in each theme's
own paper and ink) and brightness now fill the first screen. Margins,
alignment, font, TXT rows and the four readouts move into closed <details>.
Read aloud: the start button is first and primary; rate gains preset steps;
pitch and volume are folded under 更多. Capability hiding is unchanged.
Spec updated in docs/ui.md; chrome tests cover order, folds and swatches.
Unify media secondary page chrome, loading states, playback controls, favorites filtering, and source/library screens with the legacy reader experience.
Align the React media detail, player, history, queue, shelf, and resource flows with the legacy UX. Refresh the migration validation record and add focused route, resource, and shelf parity coverage.
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.