Skip to content

Page curl: implement the state machine and move the controller onto it - #21

Closed
cameronwebbable wants to merge 5 commits into
artem/page-curl-state-machine-testsfrom
cam/rw-47750-page-curl-state-machine
Closed

cameronwebbable wants to merge 5 commits into
artem/page-curl-state-machine-testsfrom
cam/rw-47750-page-curl-state-machine

Conversation

@cameronwebbable

@cameronwebbable cameronwebbable commented Oct 28, 2026 •

Copy link
Copy Markdown

Stacks on #20. The rekindled side is linked below.

Problem

#20 added the RNCPageCurlStateMachine interface and eight tests against an empty stub. RNCWebViewPageCurl.m still carried its own state across _state, _awaitingLanding, _failedBakes and _pendingSettle, and every method touched some of them. That spread is where the four review rounds on #17 kept finding ordering bugs, and no test covered any of it.

Fix

Two commits.

Implement the machine and finish its tests. The machine is Foundation only: events in, effects out. Its rules are the ones the controller already followed, written down in one place.

  • EnterState is emitted only when the state changes. A settle that arrives in the state it already matches changes nothing.
  • A landing result is ignored unless a landing was requested. A late bridge callback after a cancel cannot move the machine.
  • A settle request is acted on only in awaitingSettle, and only when its token and serial match the request still outstanding.
  • A settle that arrives during a bake is kept and baked next, so a resize burst ends on the latest layout.
  • Three failed bakes in a row stop the loop instead of retrying forever.

apple-tests/RNCPageCurlStateMachineTests.m has 46 tests in nine groups. The last one, testEveryOrderingKeepsTheInvariants, feeds the machine every sequence of the 14 events to depth 5 and checks the invariants after each step. That is 537,824 sequences in about 2.2 s on an M4 Pro.

Move the controller onto it. RNCWebViewPageCurl.m goes from 1404 to 1332 lines. The local enum, RNCPageCurlStateName and RNCPageCurlMaxFailedBakes are gone with the five ivars they served. The controller keeps what only it can do: the bridge calls, the landing watchdog, the renderer, the input blocker and the JS events. It feeds the machine and carries out the effects it returns in apply:.

Let the curl pan wait on WebKit's holds. A touch that moved less than 8pt in its first 350ms was handed to WebKit for good by a timer, so a drag that began with a short pause never curled, and the manager's own swipe rule turned the page with no animation. The timer is gone. The curl pan now requires WebKit's long holds to fail: the selection loupe (0.5s, 8pt), the link lift (0.65s, 10pt) and the long press (0.75s, 10pt). Those fail the moment the finger moves past their allowance, so a drag curls about 17ms after the finger passes the pan's own threshold, measured on the Duo simulator with the native log. A finger that stays still for 0.5s still selects. The holds shorter than 0.3s (the tap highlight at 0.12s, image analysis at 0.1s) keep yielding to the pan as before; waiting on them too blocked every drag that paused more than 120ms, which is how that threshold was chosen.

Verification

  • The test scheme is green: xcodebuild test -workspace bookwise.xcworkspace -scheme react-native-webview-Unit-Tests from reading-clients/bookwise/ios on the rekindled branch.
  • Gesture cases on the Duo simulator after the hold change: fast and medium mid-page drags curl, an edge drag curls, a tap turns, and a finger that creeps under 8pt for 0.5s gets the selection loupe and the Define sheet, not a page turn.
  • Regression pass on the iPhone Duo simulator with the native log on, reading the machine's transitions: swipe turn, tap turn, cancelled slow drag, turns on the closed display, unfold mid-book (resize rebake, spine change teardown and re-enable, kept settles collapsed to the latest), and turns across chunk edges. Every one ended in idle on the expected page.

Not in this PR

  • The CI job that runs the test scheme. It lives in rekindled.
  • The docs/curl-spam.sh stress run and the Metal texture check from the plan.

cameronwebbable and others added 2 commits October 27, 2026 16:26
The machine mirrors the controller's transitions: a settle during a bake or a turn is kept and baked after, a failed bake blanks the slots and asks for a settle up to three times, a landed turn drops the settle for the page it left until the manager moves or another page bakes. Results that arrive in the wrong state change nothing, so a bake, landing or settle request from before a teardown is inert.

Covers the nine groups of the test plan, 46 tests. The ordering sweep replays every sequence of five events from the enabled state and checks the invariants after each step.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01635Bx126DsG1CbDzDxxBJx
The controller no longer keeps its own copy of the bake loop. It reports each event to RNCPageCurlStateMachine and carries out the effects it returns, so the transitions the tests cover are the ones that run. The landing watchdog and the settle request move with their effects; results from before a teardown are dropped by the teardown count as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01635Bx126DsG1CbDzDxxBJx
@linear-code

linear-code Bot commented Oct 28, 2026

Copy link
Copy Markdown

RW-47750

cameronwebbable and others added 3 commits October 28, 2026 14:03
… timer

A touch that moved less than 8pt in its first 350ms handed itself to
WebKit for good, so a drag that began with a short pause never curled
and the manager's own swipe rule turned the page instead. Now the curl
pan requires WebKit's long holds (the selection loupe, the link lift) to
fail, which they do as soon as the finger moves, and the short tap
highlight holds keep yielding to the pan as before. A still finger still
selects; a moving one curls, however long it paused first.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01635Bx126DsG1CbDzDxxBJx
It was there to diagnose the hold arbitration and has served that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01635Bx126DsG1CbDzDxxBJx
A turn started mid-page moved the far corner, so the fold trailed well
to the right of the finger. The grab point is now the touch, held no
closer to the spine than grabFloor of the page width, and the fold sweeps
in from the outer edge to meet it at sweepGain times the finger's speed.
grabFloor 1 is the old edge grab exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01635Bx126DsG1CbDzDxxBJx
@cameronwebbable

Copy link
Copy Markdown
Author

Closed when its branch was renamed to cam/rw-47750-page-curl. Continued in #22.

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.

1 participant