Three paragraph-mode problems, all fixed:
- Shift+P inside paragraph mode flashed and re-entered (and pressing it
repeatedly did nothing). A single keypress toggled twice: eventDispatcher
.dispatch() iterated the live listener Set while awaiting each listener, and
the exit's awaited dispatch('paragraph-mode-disabled') let React re-run
useParagraphMode's subscription effect mid-loop, adding a handler the same
dispatch then called. Snapshot the listeners before iterating (dispatchSync
already did), so a listener added during a dispatch can't fire for the
current event.
- Shift+P / Escape only worked when focus sat on the overlay. Handle the
overlay's keys the way a dialog/alert does: focus the dialog element on open
and handle Escape / the toggle shortcut / paragraph navigation in its own
onKeyDown (stopping propagation so the global handler can't double-fire),
instead of a global window listener. Suppress the focus ring on the
programmatically-focused, non-tab-stop container.
- Resume jumped to the chapter start, and repeated enter/exit walked further
back. Two causes: (a) entering/exiting scrolled the underlying view to the
focused paragraph's start, which rewinds a page when that paragraph began on
the previous page — don't scroll on resume/exit (the paragraph is already on
screen); navigation still scrolls. (b) resume preferred the rAF-debounced
store progress and a stored last-paragraph CFI that can come out malformed
and resolve to an empty range, shadowing the correct candidate and sending
findByRange to the first block. Resume from the view's live, foliate-
generated lastLocation CFI first (set synchronously on every relocate,
resolved against the current document so it survives iframe recreation).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(sync): kind-agnostic replica primitives
Extract dict-only sync into shared primitives (registry, pull/apply
orchestrator, persist env, schema allowlist) so other kinds can plug
in. Companion changes: per-replica Storage Manager grouping,
useReplicaPull boot-race recovery, manifest=null reconciliation on
every boot pull, copyFile takes explicit srcBase + dstBase, settled-
event helpers, lenient webDownload Content-Length (R2/S3 signed URLs
commonly omit it), and generic "File" transfer toast copy any replica
kind can share.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(sync): cross-device custom font sync
Plug the font replica adapter into the kind-agnostic primitives:
font store gains replica wiring, custom font import publishes the
replica row + queues a binary upload, and bootstrap registers the
font adapter and download-complete handler.
Includes legacy flat-path migration so pre-existing fonts sync
without re-import, full @font-face activation on auto-download
(load + mount the rule, mirroring manual import), and a fix to
createCustomFont so contentId / bundleDir / byteSize survive the
trip through addFont — otherwise import-time publish silently
no-oped on missing contentId.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add ~290 new unit tests covering utils (lru, diff, css, a11y, walk,
usage, txt-worker), services (EdgeTTSClient, transformers), and
suppress noisy console output across all test files. Rename 27
camelCase test files to dash-case for consistency.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>