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>
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>
- 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.
- 首页/书架/影音/我的 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 我的 → 管理.
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 three media channels are a segmented control in the page header on phones
(the sidebar lists them from 701px up), and every channel page now has its
one h1. ChannelSwitcher, MediaChannelEntry, renderChannelLinks and the
legacy showChannelEntry are removed. Shell breakpoints move to 700/701px.
Review script react-app.mjs follows the new DOM (app-nav selectors, summary
and option roles for the favourites filter).
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.
The now-playing title row carried an ellipsis that opened the same sheet as
the header (or options-row) ellipsis. Drop it; the header one stays.
Lyrics no longer reuse the book icon that means chapters; episodes no longer
share the layers icon with versions. The v2 review script measures the
remaining more-options button.
Retune the six media themes to the V3 semantic palette. Theme ids are
unchanged so saved choices keep working; only names change (暖砂 -> 暖纸,
深海 -> 深夜). System appearance now resolves to 暖纸 / 深夜.
Add a vitest guard that checks WCAG AA text pairs for every theme.
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.
Route login, shelf, library, file operations, reading, sources and extension feedback through one compact themed notification host. Keep native modal feedback in the top layer, escape remote text, update progress in place and let reading controls receive clicks beneath message text. Cover lifecycle, error handling and layout with unit and Chromium checks.
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.