Skip to content

refactor(core): consolidate mirrored page navigation resolvers - #256

Open
mbret wants to merge 2 commits into
masterfrom
chore/consolidation-2026-07-25
Open

refactor(core): consolidate mirrored page navigation resolvers#256
mbret wants to merge 2 commits into
masterfrom
chore/consolidation-2026-07-25

Conversation

@mbret

@mbret mbret commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Summary

The page-turn navigation resolvers in packages/core/src/enhancers/navigation/resolvers/ were three pairs of mirror-image implementations — one for leftOrTop, one for rightOrBottom — that differed only by the sign of their position deltas. Every branch, guard, and call was structurally identical; only +/- on page-size arithmetic changed. This PR unifies each pair into a single direction-parametrized function.

The direction is a genuine axis of one concept (turn a page one way vs. the other), not a flag papering over divergent behavior: it collapses to a +1 / -1 sign applied to the deltas. The control-flow graph is identical for both directions, and the change adds no new conditionals — the one pre-existing RTL ternary got simpler (two object literals became one).

Consolidation

Introduced pageNavigationDirection.ts exporting PageNavigationDirection ("leftOrTop" | "rightOrBottom") and getPageNavigationDirectionSign (-1 for leftOrTop, +1 for rightOrBottom).

Duplicated pair (deleted) Unified into
getSpineItemPositionForLeftPage.ts + getSpineItemPositionForRightPage.ts getSpineItemPositionForPage.ts
getNavigationForLeftSinglePage.ts + getNavigationForRightOrBottomSinglePage.ts getNavigationForSinglePage.ts
getNavigationForLeftOrTopPage.ts + getNavigationForRightOrBottomPage.ts getNavigationForPage.ts

Why they are the same concept: the per-pair diffs (normalized for names/comments) were only sign flips on position deltas:

  • spine-item position: x ± pageWidth, vertical-writing y ∓ pageHeight
  • single page: out-of-item fallback x/y ± pageSize
  • page (spread): scrollable-vertical y ± pageHeight, and the RTL spread branch (which swaps the sign for RTL — preserved as (isRTL ? -sign : sign))

Each sign is reproduced exactly via getPageNavigationDirectionSign(direction).

Call sites

  • manualNavigator.tsturnWith now takes a PageNavigationDirection and passes it to getNavigationForPage, instead of receiving one of two functions.
  • getNavigationForRightOrBottomPage.test.ts → renamed to getNavigationForPage.test.ts; its two cases now pass direction. Assertions unchanged.

These functions are internal to packages/core (not re-exported from any index.ts), so there is no public-API or documentation change (gitbook/ has no references — checked).

LOC

insertions deletions net
Production code 86 266 −180
New test (see below) 277 0 +277

Verification

Because a sign inversion here would silently turn pages the wrong way, equivalence was proven rather than assumed:

  1. Old-vs-new equivalence harness (temporary, not committed) — ran the original six functions side by side with the unified ones across the full matrix: 2 reading directions × 2 page-turn modes × 2 turn directions × 2 spread × 2 vertical-writing × 10 positions × SpinePosition/UnboundSpinePosition, for both directions at all three layers. Compared with Object.is so a +0/−0 divergence from sign * delta would fail rather than compare equal. All identical.
  2. Mutation testing to prove the harness wasn't vacuously passing — deliberately inverted each of the 6 sign sites; all 6 were caught.

Test coverage gap found and fixed

The pre-existing tests covered only the scrollable-vertical early return — 21% statement / 16% branch of these resolvers. The RTL spread branch, both deeper layers, and every other sign site had no unit coverage, so an inverted delta would have shipped with a green suite.

getNavigationForPage.directions.test.ts adds a table-driven test over every branch the resolver can take (horizontal controlled, out-of-spine turns, vertical-writing spread in both reading directions, vertical turns in spread, scrollable vertical). Coverage of the resolvers is now 94.7% statement / 93.75% branch / 100% functions, and all 7 sign mutations (including inverting getPageNavigationDirectionSign itself) are caught by it.

The rtl cases matter most: the extra spread page width is the only place readingDirection changes the outcome, and it was previously untested.

Also reworded the getNavigationForSinglePage @important note, which described the right/bottom page only and no longer read correctly on a function serving both directions.

Gates

Toolchain pinned via .nvmrc (Node 25). Baseline recorded on a clean checkout before editing; green before and after:

  • npm run format / npm run lint — clean
  • npm run build — 17 projects
  • npm run tsc — 6 projects
  • packages/core suite — 194 tests pass

Other opportunities noted (not in this PR)

Left for future runs to keep this PR one coherent story:

  • Repeated test-setup/context-builder blocks in playback/AudioController.test.ts, enhancers/pagination/trackPaginationInfo.test.ts, and generators/manifest/characterization.test.ts (intra-file duplication).

🤖 Generated with Claude Code

https://claude.ai/code/session_01WhdPtBDGt3oDdZchEw8xEv

The left/top and right/bottom page navigation resolvers were three pairs
of mirror-image implementations differing only by the sign of their
position deltas. Unify each pair into a single direction-parametrized
function:

- getSpineItemPositionForLeftPage + ...ForRightPage
    -> getSpineItemPositionForPage
- getNavigationForLeftSinglePage + getNavigationForRightOrBottomSinglePage
    -> getNavigationForSinglePage
- getNavigationForLeftOrTopPage + getNavigationForRightOrBottomPage
    -> getNavigationForPage

A shared PageNavigationDirection ("leftOrTop" | "rightOrBottom") maps to a
+1/-1 sign applied to the page-size deltas, so behavior is identical to the
previous per-direction code. ManualNavigator now passes the direction
instead of selecting a function.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhdPtBDGt3oDdZchEw8xEv
@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
prose-reader-demo Ready Ready Preview, Comment Jul 26, 2026 10:01pm
prose-reader-front Ready Ready Preview, Comment Jul 26, 2026 10:01pm

The consolidated resolvers derive both turn directions from a single
implementation by flipping the sign of their page-size deltas, but the
existing tests only reached the scrollable vertical early return (21%
statement / 16% branch coverage of the resolvers). An inverted delta would
have turned pages the wrong way with a green suite.

Add a table driven test covering every branch the resolver can take:
horizontal controlled, out of spine turns, vertical writing in spread for
both reading directions, vertical turns in spread and scrollable vertical.
The rtl cases are the important ones, since the extra spread page width is
the only place readingDirection changes the outcome.

Coverage of the resolvers goes to 95% statement / 94% branch, and each
individual sign in the implementation is now load bearing for a test.

Also reword the getNavigationForSinglePage note, which described the
right/bottom page only and no longer matched a function serving both
directions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhdPtBDGt3oDdZchEw8xEv
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants