Skip to content

feat(reading): V2 reader page protocol for bars: tap position, direction, keys, flush (#1263) - #1287

Merged
mrviduus merged 5 commits into
mainfrom
v2/1263-page-chrome-protocol
Nov 13, 2026
Merged

mrviduus merged 5 commits into
mainfrom
v2/1263-page-chrome-protocol

Conversation

@mrviduus

@mrviduus mrviduus commented Nov 12, 2026 •

Copy link
Copy Markdown
Owner

Summary

Four additions to the V2 reader page's protocol that the reader chrome needs: a tap says where it landed, the page says which way the book pages, navigation keys pressed inside the book reach the host, and the host can ask for a pending save at once. Both hosts turn pages on the forwarded keys; bars and tap zones come in the next two PRs.

Stack: 2 of 4. Base is #1285 (selection event). Merge order: #1285 → this → Android bars → web bars. Full CI runs here only after GitHub retargets this PR to main.

Slice

Wave 14, B4/B5 (page half). Refs #1263, Refs #1264 (both are closed by the host PRs).

Protocol

page -> host  { type: 'tap', x, y }                 // page-viewport px; sections, image books, and the page's own document
              { type: 'opened', ..., dir: 'ltr'|'rtl' }
              { type: 'relocate', ..., dir: 'ltr'|'rtl', moved: boolean }
              { type: 'key', key: 'ArrowLeft'|'ArrowRight'|'PageUp'|'PageDown'|'Space'|'Escape', shift, repeat }
host -> page  window.__fol.flush()                   // writes a debounced `store` now; nothing pending = nothing sent

Changes

  • tap carries x/y, mapped like word.rect (a scaled fixed-layout frame is right). Also posted for a click on the page's own document: in paginated flow the outer columns and the margin rows are outside every section frame, which is exactly where edge zones are. A press that dismisses a selection still sends only selection-cleared.
  • dir is the direction the renderer actually pages by: a fixed-layout book's page-progression direction; a reflowable book's shown section, read the way the paginator reads it. A Hebrew EPUB with no spine attribute is rtl; a spine rtl over English text is ltr. It rides opened and every relocate.
  • key: a closed list of six keys, never with Ctrl/Alt/Meta, never an arrow with Shift (keyboard selection keeps working), only when the key's target is the document body (a focused <summary>, button, link, media control, form field keeps its key). Listened for on section documents and on the page's own document. The default is prevented for forwarded keys. Auto-repeats carry repeat: true.
  • relocate.moved: true only when the reader moved the place themselves (swipe, scroll, prev()/next()); false for the open and restore, an anchored re-layout (resize, rotation, font, flow) and a host goTo. It is the save rule's own test, computed once, so hosts stop guessing from CFI or fraction differences when to hide the bars.
  • flush(), and the page flushes itself on visibilitychange → hidden and on pagehide.
  • chrome.mjs (+ .d.mts), shared by both hosts: navKey, the readers readTap / readDir / readKey (bounded, closed lists, fresh objects; the page renders untrusted book content), and keyAction({ key, shift, dir }) → 'prev'|'next'|'bars'|null.
  • Hosts (V2ReaderScreen.tsx, V2ReaderHost.tsx): keep the latest dir, drop repeats, turn the page on prev/next. So keyboard paging works on this PR alone, including Space in scrolled flow, which the page now cancels.
  • Build: BUDGET → 264410 with ledger lines; generated page rebuilt. New fixtures tsrtl.epub, tshe.epub.

Tests

  • chrome.test.ts (readers incl. hostile payloads, navKey targets, every keyAction branch), V2ReaderHost.test.tsx (keys → turns, dir from opened and relocate, repeats dropped).
  • Format check in Chromium against the committed page: 281 ok rows. New: taps at the far edges and margin rows in paginated flow, beside a letterboxed image page, on a scaled page; an edge tap that dismisses a selection; keys forwarded / not forwarded; Space on a <summary>; Shift+Arrow extends a selection; repeats; flush(), hidden, pagehide each write once; dir for the three direction cases.
  • Each guard removed in turn against a rebuilt page or the unit tests; each removal fails a named row or test.
  • mobile tsc 0, vitest 1441; web tsc 0, vitest 1178 passed / 3 skipped; web build, check:v2-reader, -formats, -sandbox, -prod-csp ok.

Rollback plan

Revert the PR. No server, schema or stored-data change.

Docs: updated (apps/mobile/src/lib/v2reader/README.md protocol rows, flush() row)

Notes

What the review changed (/code-review high, 10 findings; 8 fixed, 1 documented, 1 did not reproduce as written)

  • Taps in the paginator's margins sent nothing; the first check passed only because it had switched to scrolled flow.
  • The page cancelled Space/PageDown while hosts only logged them.
  • dir came from the spine attribute only, which is not what the paginator lays out by. An earlier row of this PR asserted the wrong value and was corrected.
  • Shift+Arrow and keys on focused controls were taken from the book.

Known and left

  • V2 reader web: a page turn in the last 400 ms before a tab close or route change can be lost #1286 (filed from this review): on web a page turn in the last 400 ms before a tab close or a route change can be lost (the pagehide flush cannot reach the host). visibilitychange → hidden is covered.
  • Vertical writing (vertical-rl) is not mapped: dir reads direction only.
  • Auto-repeat does nothing for now (one press = one turn).
  • In scrolled flow the first scroll after an anchored re-layout sends no relocate at all (foliate's #justAnchored), so that one scroll is not seen as a move; fixing it needs a fork change.

Device checks owed by the owner

  • Taps at the screen edges under touch reach the host with sane coordinates.
  • A Bluetooth keyboard turns pages; after a chapter boundary it still does.
  • A real right-to-left book (EPUB, and a Kindle book) reports rtl.

🤖 Generated with Claude Code

mrviduus and others added 3 commits November 12, 2026 15:01
tap carries x, y (page viewport, scaled frames too); opened carries dir (foliate's
book.dir); key forwards a closed list of navigation keys from a section; flush()
plus a flush on hidden and pagehide. chrome.mjs: navKey and the hosts' readers,
used by both hosts. tsrtl.epub fixture. BUDGET 263379.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
tap and keys from the page's own document too; keys only on the document itself,
never Shift+arrow, with repeat; keyAction in chrome.mjs and both hosts turn on
prev/next (repeats dropped); dir as the renderer pages (pagedDir) on opened and
relocate; Hebrew fixture; onLoad pin restored; web pagehide gap documented.
BUDGET 264121.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
highlightSelection of the live selection; page rebuilt, BUDGET 264403.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@mrviduus mrviduus added the reading Area: reading (reading screen, reading position, offline) label Nov 12, 2026
)

true only when the reader moved the place; false for an open and its restore,
an anchored re-layout (resize, font, flow) and a host goTo. Hosts ignore it here.
BUDGET 264410.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@mrviduus
mrviduus enabled auto-merge November 13, 2026 01:39
@mrviduus
mrviduus merged commit bfea2b7 into main Nov 13, 2026
14 checks passed
@mrviduus
mrviduus deleted the v2/1263-page-chrome-protocol branch November 13, 2026 01:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

reading Area: reading (reading screen, reading position, offline)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant