Repository navigation
Page curl: run the controller on a tested state machine, grab under the finger - #22
Draft
cameronwebbable wants to merge 7 commits into
Draft
cameronwebbable wants to merge 7 commits into
cameronwebbable wants to merge 7 commits into
Conversation
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
… 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
A margin tap turned the page with no animation even with the curl on. The manager now hands the tap over as a tapTurn message; once the tap's own touchEnd has brought the controller back to idle, it runs the same turn a released flick does (tapTurnVelocity, about 450ms). With nothing baked to curl onto, or the controller busy, it turns instantly the way a tap always did. The controller tells the manager it does this when it is enabled, so the manager under an older binary keeps instant taps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01635Bx126DsG1CbDzDxxBJx
…r run A tap reused the drag release's curve, which leaves at full speed, and the drag's 30pt bend-in, through which the fold races ahead of the page edge at 3.6 times its speed. On the Duo simulator the first frame moved the fold 134pt. A tap turn now eases in and out over tapTurnDuration (0.42s) and forms its bend over tapBendInDistance (300pt). Measured frame to frame the largest change in fold speed drops from 91pt to 20pt per frame and the first frames move 0, 1 and 2pt. Drags and their release keep the old curve and bend-in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01635Bx126DsG1CbDzDxxBJx
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.
Stacks on #20. Replaces #21, which closed when its branch was renamed; it had no reviews, so nothing was lost. Rekindled side: readwiseio/rekindled#14202 (RW-47750).
Problem
#20 added the
RNCPageCurlStateMachineinterface and eight tests against an empty stub.RNCWebViewPageCurl.mstill carried its own state across_state,_awaitingLanding,_failedBakesand_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
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.
EnterStateis emitted only when the state changes. A settle that arrives in the state it already matches changes nothing.awaitingSettle, and only when its token and serial match the request still outstanding.apple-tests/RNCPageCurlStateMachineTests.mhas 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.mgoes from 1404 to 1332 lines. The local enum,RNCPageCurlStateNameandRNCPageCurlMaxFailedBakesare 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 inapply:.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.
Grab the page under the finger. A turn started mid-page moved the far corner, so the fold trailed well to the right of the finger: about 290pt behind it 45pt into a drag from the middle of the right page. The grab point is now the touch, held no closer to the spine than
grabFloorof the page width (0.3), and the fold sweeps in from the outer edge to meet the finger atsweepGain(2) times the finger's speed, so no flap appears at the grab point on the first pixel.grabFloor1 gives the old edge grab exactly. Both are tuning keys, so the default can be changed from JS.Curl margin taps. A margin tap turned the page with no animation even with the curl on. The manager now hands a margin tap over as a
tapTurnmessage. Once that tap's owntouchEndhas brought the controller back to idle, it curls the page from rest: an ease in and out overtapTurnDuration(0.42 s), with the bend formed overtapBendInDistance(300 pt) instead of the drag's 30 pt. Through the drag's short bend-in a timed animation makes the fold race ahead of the page edge at 3.6 times its speed and then fall back; with the release's ease-out on top, the first frame moved the fold 134 pt. Measured on the Duo simulator, the largest frame-to-frame change in fold speed is now 20 pt per frame, down from 91. With nothing baked to curl onto, or the controller busy, it callsinstantlyScrollPageDown/Upthe way a tap always did. The controller callsnativePageCurlAnimateTapson the manager when it is enabled, so the manager under an older binary keeps instant taps. No state machine change: a tap turn isturnBeganfrom idle, like a drag.Verification
xcodebuild test -workspace bookwise.xcworkspace -scheme react-native-webview-Unit-Testsfromreading-clients/bookwise/ioson the rekindled branch.idleon the expected page.Not in this PR
docs/curl-spam.shstress run and the Metal texture check from the plan.