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>
- 影音浏览 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>
外观与主题, 书架, 影音浏览 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>
- 偏好设置 › 打开播放器 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>
我的 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>
- 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>
/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>
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>
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>
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>
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>
The row was an auto-fit grid of 84px columns, so a phone fitted four and 更多
wrapped onto a line of its own (a sixth button, 选集, made it worse). The
buttons now share the row in equal columns however many there are.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- scan and AI-scan job lists refresh every 5 s only while a job is queued or
running, every 30 s otherwise; the AI list no longer adds its own 2.5 s
timer on top of the query's.
- AI batch details that fail to load say so instead of showing an empty list;
expand buttons carry aria-expanded.
- 刮削 errors show the message, not "Error: …".
- 按作品信息自动匹配 sits beside 搜索候选 instead of inside a 自动匹配 disclosure.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
Within one stylesheet and one @media context, a property that a later rule
with the same selector sets again can never apply. 74 such declarations are
removed (e.g. the second of three .media-task-tabs looks), along with the
rules they leave empty. A later value an older WebView might reject (dvh,
color-mix, clamp, …) is treated as an enhancement, so the earlier value stays
as its fallback. ui:review scenes are unchanged apart from playback timing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
- 在线匹配 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>
媒体库管理: 新建媒体库 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 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>
- 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>
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>
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>
- 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>
- 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 我的 → 管理.
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.
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.
白纸 / 暖黄 / 浅绿 / 灰调 / 夜间 (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 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).
Mantine follows the app theme (colour scheme and primary/blue variables), so
the source editor, plugin page and dialogs stop being default blue.
Desktop video player puts the episode rail beside the picture and aligns the
tool row with it. 我的 and 首页 are top-level pages and lose the back arrow;
the theme page no longer says it only applies to media.
One navigation replaces the media channel rail: a four-tab bar on phones
(首页 / 书架 / 影音 / 我的) and a sidebar from 900px up, with the three media
channels under 影音. The bar shows on top-level pages, gives way to each
page's own back arrow on pushed pages, and is absent for the book and the
player.
AuthenticatedShell now applies the chosen theme to every page except the
book, so shelf, library and sources wear the same palette as media. Channel
colours become theme tokens (--media-theme-ch-*) held to AA by test.
首页 shows where you left off: the book (continueReading), unfinished video
and audiobook (history), and the shelf. It replaces the redirect to
/media/video.
Fixed headers and the search/more actions, which were positioned against the
old rail, now clear the sidebar.
Account card (name, server, role), links to the pages that own library,
sources, media libraries, theme, playback and account settings, and sign out.
Reached from the channel navigation and the shelf header. Follows the media
theme tokens. Fixes the channel-current check so /media/* is not mistaken for /me.
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.