Reader overhaul: native rendering line, design-handoff P0+P1, perf fixes#1
Merged
Conversation
The highlight sheet can now write/edit notes inline (trimmed, clearable) and jumps back to the stored UTF-16 anchor instead of the chapter top; the Mac reader top bar surfaces the same sheet. Native reader: long-chapter resume lands inside the matched text block at its stored UTF-16 progress instead of the block top, and chapter images decode on a utility-priority task (cancelled when scrolled away) instead of blocking the main thread. Work authored by the concurrent workbench session; committed after a full green EmptyTests run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reader docs still described the retired WKWebView pipeline: README claimed CSS-columns pagination and 108 tests; ARCHITECTURE said CloudKit was disabled, utf16Offset was never reported, and listed PDF reading, flashcard UI, and iOS notes as missing. Now documented as built: the native SwiftUI rendering path (block model, exact UTF-16 spans, cross-paragraph selection sheet, highlight notes + exact jump-back), CloudKit .automatic with the local-only fallback, ParagraphTranslation/StudyCardEntry in the store tables, the two cloud standards (OpenAI-compatible + Anthropic Messages), the reading agent loop, language-aware embeddings, pretranslation cache, the current iOS IA, deterministic screenshot smoke tests, and a refreshed known- limitations table. Test count updated to 144/144. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mac now honors -ScreenshotSeed like iOS (seeds the 思维之书 demo book at launch), and the four screenshot UI tests drive seeding/opening through launch arguments instead of depending on whatever book happened to be in the library — captures are reproducible on a clean machine. Work authored by the concurrent workbench session; committed after its verification run and a full green EmptyTests build covering these files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Real books rarely stick to p/h1-h6/blockquote/li, so the native parser now also understands: - bare text in div/section/article (many EPUBs paragraph with <div>s) — collected as implicit paragraphs at container boundaries, <br> splits, source-formatting newlines don't - <table> — caption + th/td rows parsed into a .table block, rendered as a bordered serif grid with horizontal scroll - footnote asides (epub:type *note) — paragraphs inside render as distinct 注-badged small-type blocks, still translatable - <pre> — line structure and indentation preserved, rendered selectable in monospace on a card background - nested ul/ol — indent per level, ordered lists get 1. 2. markers - figure/figcaption — captions attach to their image and display under the loaded picture; missing images now say 插图缺失 instead of an eternal generic placeholder; dt/dd parse as paragraphs Six new parser fixtures cover each shape; 150 tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 「译」 toggle becomes a three-mode menu (原文 / 双语对照 / 导读, persisted across launches) matching the Mac reader's modes. Visible paragraphs resolve through the same kind-aware persistent cache, and results that land after a mid-flight mode switch are cached but never painted into the wrong mode. 双语对照 now pretranslates the current and next two chapters in the background on iPhone/iPad too — same TranslationStore pipeline and Chapter.pretranslatedAt marker as the Mac reader, so the two platforms share one cache per book. 导读 stays per-viewport by design. Reader font size and line spacing persist via AppStorage on both platforms (committed for Mac in 9811950 alongside the smoke-test ids). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The coverage artifact was committed back in 37e27f6 and kept showing as modified after every local test run; *.profraw is already gitignored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`fastlane/test_output*.profraw` never matched anything, which is how default.profraw kept reappearing after test runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five canvas themes in the 朱批 voice — 纸色 (follows the shell light/dark), 月白, 米黄, 竹青 eye-care green, 玄夜 night — applied to the whole reading surface (top bar, text, sheets) via a palette override, with theme-aware ink colors in the native text views. Four body fonts: 衬线 (system serif, as shipped), 宋体 Songti SC, 楷体 Kaiti SC, 黑体 system sans, with bold-trait and unknown-family fallbacks. 阅读设置 gains font chips and theme swatches next to the existing size/spacing sliders; all choices persist via AppStorage and apply to Mac and iOS alike. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
左右翻页 is now the iOS default: the chapter lays out into real TextKit pages (one NSTextStorage, a text container per page) and the reader swipes or edge-taps between them — left quarter back, right quarter forward, center toggles the chrome; crossing the first/last page changes chapter. Selection, exact-range highlights, 双语/导读 notes, resume position and 还剩 N 页 all ride the same chapter-offset pipeline, and re-layout (notes arriving, font/theme changes, rotation) re-anchors to the page being read. 上下滚动 stays available in 阅读设置. Two jank fixes behind the complaint that reading stutters: - visible-paragraph reports fired on every scroll frame, each doing translation-cache fetches and chapter-text position math on the main thread — they now wait for a 180ms scroll settle (Mac + iOS) - whole-chapter background pretranslation saturated the device when the model runs locally — cache-ahead is now cloud-only; on-device translates just the visible paragraphs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Selection drags fired a callback per character; a 180ms SelectionChangeDebouncer coalesces them on both platforms, and inline note lookups go through an index dictionary instead of a linear scan per paragraph row. Work authored by the concurrent workbench session; committed after a full green EmptyTests run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The TextKit pager goes cross-platform: macOS reads in real pages too (arrow keys / space / edge clicks / always-on chevrons, 700pt column), default on, switchable back to scrolling in 阅读设置 — whose entry button had been lost from both reader top bars and is restored. Cached-translation gaps stop being permanent: empty/errored results retry while the paragraph is on screen (capped at 3 per visit), pretranslation re-checks stamped chapters and backfills holes, and the paged reader shows quiet ⟳ 生成中 / 暂不可用 lines instead of nothing. iOS paged pages rebuilt as independent text views (fixes the broken first cut), with taps listened on the text view itself; the demo book grows long enough to paginate and a UI smoke test swipes across the chapter boundary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…菜单, Mac immersion From the claude.ai/design handoff bundle (开发 Handoff §1/§3 + 视觉精修): - Tokens: vermilion unified to #A93E22 (light) per the spec, highlight gold to #E5C55E - Reader themes are now the spec's four — 纸白 (follows the shell) / 暖纸 EFE3CC / 夜读 2A241C / 墨黑 0D0C0A — with per-theme body ink - Highlights render as a thick underline wash (底线染色) instead of a background block, in the design trio 金/朱/青 picked straight from the selection menu's color dots; HighlightColor gains vermilion/celadon cases, legacy values still render - 划词菜单 leads with non-AI actions on both platforms — color dots, 复制, 词典 (system dictionary: dict:// on Mac, UIReferenceLibraryViewController sheet on iOS), 跨段 — and the AI verbs fold behind a trailing 朱 › group - Mac immersion: the chrome fades after the mouse rests 2.5s and wakes on movement; open overlays pin it visible Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
From the design handoff's 目录·书签·搜索 spec: - Bookmark model (synced store, cascade inverse on Book) with a BookmarkStore whose ⌘D-style toggle removes the existing bookmark when one already sits within ~a page of the position - The Mac in-reader panel grows 目录 / 书签 / 搜索 tabs: bookmarks list with jump-to-anchor and delete, and local full-text search whose unread-chapter hits stay folded behind a count until the reader explicitly opens them (防剧透 extends to non-AI features) - Reader top bar gains the bookmark toggle (⌘D), vermilion-filled when the current position is bookmarked - Tests: toggle semantics, reading-order sort, exact UTF-16 hit offsets, spoiler grouping, short-query guard Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The handoff's last P0 item: AIProviderRegistry upgrades the single mode/protocol choice to a provider list (On-Device Apple + the linked cloud entry mirroring the legacy config) with per-feature routes — 翻译·导读 / 回顾·概览 / 伴读对话 / 词汇 each pick their provider, and resolution walks routed → default → local, returning the routed provider's unavailability reason when nothing can serve (features degrade silently; reading never blocks). All 19 AI call sites now resolve through their feature route; the legacy AIProviderSettings stays as the editable cloud config and the registry re-syncs from it on every load, so existing setups migrate with zero breakage. AI 状态 gains the prototype's 按功能分配 capsules (click to cycle providers, unset = 默认). Tests: legacy migration, route persistence, config-edit reflection, dangling-default recovery. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The handoff's 统计口径: time only counts between real scroll/page activity pings (gaps cap at 90s — an open book on the desk isn't reading), accumulated into ReadingSession.activeSeconds; legacy rows fall back to wall-clock capped at two hours. ReadingStatsStore aggregates locally: zero-filled daily minutes for week/month charts, a streak that survives an unread "today", a rolling chars-per-active-minute speed from recent same-chapter sessions, and a per-book remaining-time estimate from that measured speed. ReadingStatsView (shared) draws the bars, streak/today/speed tiles and the 在读剩余 list — entries from the Mac library header (统计) and the iOS library greeting row. Also: the unit-test host now boots an ephemeral container — tests must never touch (or contend with a running app for) the real stores, which was crashing parallel test clones at 0.000s all evening. Full suite verified green serially; parallel-clone flake tracked separately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The aloud bar grows up per the handoff: chapter text splits into sentences (CJK/Latin terminators + newlines, exact UTF-16 ranges kept) and the queue speaks them one by one, publishing the current range so both readers paint a vermilion follow-along underline that walks the text. 朗读 now starts from the sentence under the reading position instead of the chapter's first 800 characters; a selection still reads just the selection. The bar gains a rate button cycling 0.75 / 1.0 / 1.25 / 1.5× and a 连读 toggle that rolls into the next chapter when the queue finishes. Language auto-detects (CJK → zh-CN voice). Tests cover range-exact splitting, whitespace fragments, position→sentence lookup, and language detection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BookExporter renders a book's highlights (with their notes) and bookmarks grouped by chapter, as Markdown (quote blocks, chapter headings, link lines) or plain text. Every entry carries an empty://book/<id>?c=<chapter>&o=<offset> reference back to its exact passage — EmptyDeepLink builds and parses them (scheme registration for click-through navigation is tracked separately). BookExportView (shared, opened from the 高亮·朱批 list): 高亮/批注/书签 toggles and a format switch re-render the preview live; one tap copies the whole document. Tests cover the deep-link round trip, foreign-URL rejection, markdown completeness, and toggle pruning. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t, 双页 繁体显示: render-layer simplified→traditional via the system ICU transliterator (Hans-Hant) — no bundled word tables, fully offline, source files untouched. Converted chapters cache per index; highlights and selections operate in the converted space so anchors stay self-consistent. Toggle lives in 阅读设置 (EPUB only). PDF per the handoff: per-book zoom memory (scale factor persisted and restored), 夜间反色 as hue-rotated inversion so colors keep their identity, and a Mac 单页⇄双页 spread switch. All three toggles join the settings panel's new 繁简·PDF row. Deferred with notes: vertical-rl 竖排 (no native UITextView support — needs a CoreText pass) and PDF auto-crop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The immersion timer lived in @State, so re-arming it on every continuous-hover event invalidated the whole reader view — and reader init re-parses the chapter XHTML and re-resolves every block's UTF-16 span. Every mouse movement re-parsed the book; everything felt slow. - Timers move into reference boxes (TaskBox): re-arming mutates a class, SwiftUI doesn't re-render. Same for the scroll-settle timer (was invalidating per scroll frame) and the 繁体 cache (which also mutated @State during body evaluation). - NativeChapterParser memoizes documents and resolved spans in NSCaches keyed by href + length + content tail, so legitimate re-renders (translations arriving, page flips, theme changes) reuse the parse instead of paying it again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
24 commits taking the reader from the WKWebView line to a fully native, design-handoff-aligned experience on Mac and iOS.
Native rendering line
Design handoff P0 (from the claude.ai/design bundle)
Design handoff P1
Reliability & performance
Known deferred
🤖 Generated with Claude Code