feat(mobile): native reader v1 — pure-RN selection + highlights (behind a toggle) - #26
Open
conoremclaughlin wants to merge 5 commits into
Open
feat(mobile): native reader v1 — pure-RN selection + highlights (behind a toggle)#26conoremclaughlin wants to merge 5 commits into
conoremclaughlin wants to merge 5 commits into
Conversation
…d a toggle
First real step of the WebView→native migration (spec: ink://inkread/specs/native-reader).
Text flow was never the blocker — the gap was gesture→character-offsets, which
RN core's <Text> can't give. Adopt @bsky.app/react-native-uitextview: native iOS
UITextView selection that returns exact {start,end} offsets, New-Arch/RN 0.86,
validated on-device (offsets exact; highlight backgroundColor paints on nested runs).
- NativeReaderView.tsx: renders each paragraph as a UITextView, highlights via the
shared core `segmentChapterRuns` (same offset model as the WebView), reports
selection as chapter-relative offsets (paragraph base + local offset). Serif to
match the classic reader.
- ReaderScreen: `readerEngine` toggle (Aa › Reader · beta, iOS-only). In native
mode the shared selection bar / color picker / tap-a-highlight all work; Extend
stays WebView-only for now.
- preferences: `readerEngine: 'webview' | 'native'` (default webview).
Scope: scroll mode + native selection → create/tap highlight, verified end-to-end
on the iPhone 17 sim (select "principles" → yellow highlight, persists across
reload). Still on the WebView until parity: paged mode, cross-page extend, TTS
sentence marks, precise position persistence. WebView remains the default.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…chapter nav Ports more WebView reader behaviour to the native (scroll-mode) reader: - Reading position: report the top-visible paragraph's chapter offset on scroll (shared `recordOffset` with the WebView path — same furthest-mark + persist logic), and restore to the saved offset on open (keyed per chapter so each chapter remounts and restores fresh, mirroring the WebView's per-chapter reload). - Chrome: shown on open and when scrolling up, hidden when scrolling down into the text — a scroll-direction model, since a tap toggle would fight the native selection gesture (and RNGH isn't a dep). Verified chrome shows on open. - Chapter nav: Prev/Next move between chapters in native mode (paged page-turns are WebView-only for now), with correct boundary disabling. Still to port: paged mode, native cross-page extend. TTS marks deferred. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Paged reading in the pure-RN reader. Instead of clipping + translating a selectable view (which breaks native selection), the chapter sits in a non-scrollable ScrollView paged by programmatic scrollTo — so selection behaves exactly as in scroll mode while turns move a screenful. Body is one UITextView with a uniform lineHeight so a whole-line page step snaps cleanly; edge taps (left/right) turn pages and flow into the neighbouring chapter at the ends. Verified on the sim: paged render, native selection + highlight bar, edge-tap page turn flowing into the next chapter. Known v1 compromises (follow-up): paragraphs join on '\n' (tight spacing, offset model intact) so no blank-line gap yet; chapter title not shown on page 1 (it's in the bottom bar); no center-tap immersive chrome-hide in paged (chrome shows on open). Scroll mode keeps proper paragraph spacing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ports the last interactive WebView feature (bar the deferred TTS marks): extend.
The WebView flow is anchor → flip pages → tap the end word; the native flow is
anchor → make a *second* native selection at the far end, and the highlight spans
from the anchor to it (handleNativeSelection folds the second selection into the
extend range — no offset probing, works in scroll and paged). The Extend button
now shows in native mode; the extend bar drops the WebView-only page arrows and
shows a native hint ("Select where the highlight ends, then pick a colour").
Verified on the sim: select "some" → Extend → select "Machine" → green highlight
spans "some … Time Machine" across lines, overlapping an existing highlight.
Native reader now has functional parity with the WebView except TTS sentence
marks (deferred). Scroll + paged, selection, create/render/tap/note highlights,
position persistence, chrome, chapter nav, and extend all run natively.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Paged mode rendered paragraphs on a single '\n' (no blank-line gap — walls of text for real prose). Now it joins them with '\n\n' for a proper gap, and maps rendered selection offsets back to the model: paragraph i sits i chars later in the rendered text, so model = rendered - (its paragraph index). Extracted the mapping to `lib/pagedOffsets.ts` with unit tests (mobile 31 → 36). Verified on the sim: paged mode shows paragraph gaps and every highlight (including a cross-line native-extend one) renders at the correct words. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Owner
Author
Full port to native — functional parity reached (device-verified)Per Conor's direction (don't merge yet — port everything first, extend last), the native reader now matches the WebView except TTS sentence marks (deferred). All verified on the iPhone 17 sim. Added since the v1 commit:
Tests: core 90, mobile 36 (+5 for the paged offset mapping), type-check clean. Known follow-ups (not blockers)
Spec (source of truth) updated to v3: |
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.
First real step of the WebView → native migration. Spec (source of truth):
ink://inkread/specs/native-reader.The insight
Text flow was never the blocker — RN
<Text>+ core's testedpaginate()handle wrapping/justification/pagination. The only gap was turning a selection gesture into[start,end)character offsets, which RN core's<Text selectable>can't give (react/react-native#23147).The fix (researched + validated)
Adopt
@bsky.app/react-native-uitextview(Bluesky) — native iOSUITextViewselection returning exact{start,end}offsets; New-Architecture-native, tested against RN 0.86, in production in Bluesky. Research ruled out the Astrocodersselectable-textfamily (archived/Paper-only) and the read-only-TextInputroute (can't render sub-range highlights).Both open questions validated on the iPhone 17 sim before wiring anything in:
start=83,end=92,slice(83,92)==="recondite".backgroundColorpaints on a nested run → highlights render natively, no overlay hack.What's in this PR
NativeReaderView.tsx— each paragraph is a nativeUITextView; highlights render via the shared@inkread/coresegmentChapterRuns(same offset model as the WebView, so a highlight lands on identical characters); selection reported as chapter-relative offsets (paragraph base + local). Serif to match the classic reader.ReaderScreen—readerEnginetoggle at Aa › Reader · beta (iOS-only). In native mode the shared selection bar / color picker / tap-a-highlight all work; Extend stays WebView-only for now.preferences—readerEngine: 'webview' | 'native', defaultwebview.@bsky.app/react-native-uitextview@2.5.0added (autolinks viapod install;ios/is gitignored/regenerated, so the dep inpackage.jsonis the source of truth).Verified end-to-end on the sim
Toggle → native reader renders (serif, justified, highlights paint) → long-press selects → color bar → created a yellow highlight on "principles", which persisted across an app reload. Screenshots in the thread.
Scope / still on the WebView (default) until parity
Paged mode, cross-page extend, TTS sentence marks, precise position persistence. The WebView remains the default engine — this is opt-in and can't regress the shipped reader.
Tests
core 90, mobile 31, type-check clean (core/mobile/web). The pure offset model (
segmentChapterRuns) is unit-tested; the native view is device-verified (components aren't node-testable in this setup).🤖 Generated with Claude Code