From an app built on core-v2:
Scrolling (#3, #4, #14)
- Each recognizer locks onto a touch within its first 6px: along its axis
and direction it holds the touch, otherwise it lets go. A new
onTouchmove binding cancels native scrolling only while a recognizer
holds the touch, so hosts use touch-action: manipulation and one
vertical gesture no longer disables vertical scrolling. Several
gestures on one host share touches through the same rule.
- .from.scrollEdge(scroller, edge) admits drag-to-dismiss only while a
scroller is at the edge (#5).
Feel
- recognition.distance sets what counts as full progress: pixels, a CSS
length, or a measuring function (#6).
- Choreographies receive overshoot, the drag past either end of the
clamped progress, and the settling spring starts from it (#7).
- Velocity is the least-squares trend of the last 100ms (#8).
- Defaults retuned to commitThreshold 0.25 and commitVelocity 0.2; the
slide presets use them (#9).
- Choreographies may set spring stiffness and damping (#10).
Hooks and helpers
- .onArm()/.onDisarm() run during a drag when releasing would start or
stop committing (#12).
- startedIn, nearEdge, atScrollEdge, anyOf, allOf start predicates (#15).
- Development builds warn when an operation's first frame moves a node
that was visibly at rest, the one-frame "pop" of mismatched open and
close choreographies (#16).
Docs: scrolling, ignored elements, animation performance, haptics, and
operating-system edge gestures including CloseWatcher/popstate for PWAs
(#11, #17, #18).
Verification: a Chromium harness with emulated touch (verify/browser)
checks swipes over scrollers, native scrolling beside gestures,
scroll-edge dismissal, onCommit inside pointerup, and the continuity
warning. Reverting the earlier capture fix makes it fail exactly as
reported ("none of the handles work").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Faarh2Ts6StJPcz2mRiHiR
- Only the host's own lostpointercapture cancels a gesture. Taking
capture makes the touched child lose its implicit touch capture, and
that bubbling event was cancelling every touch gesture immediately.
- A flick at least commitVelocity fast now decides by its direction:
backward cancels even past the distance threshold, forward commits.
Slower releases still decide by distance.
- Pointer-up finishes an already-begun operation without awaiting, so
onCommit runs inside the pointer-up event, where iOS still allows
haptics, audio, and clipboard writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Faarh2Ts6StJPcz2mRiHiR
discardOutgoing skipped finished resting edges, so a persistAtRest drawer
opened by a node revealed during a back operation stayed attached after
that back was cancelled and the node returned to history. Found by the
property tests (1000-seed run).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Faarh2Ts6StJPcz2mRiHiR
Gestures
- Cancel a captured gesture on lostpointercapture, on a new primary
pointer-down, and when its component unmounts, so operations are
never left stuck mid-transition. Bindings gain onLostpointercapture.
- Warn (console.warn) when navigate() fails or the scene rejects an
operation; stay quiet when two recognizers race for one origin.
- OriginGesture and OriginGestureSurface props are reactive. Changes
apply from the next pointer-down; recognizers are kept per list
position so re-renders never interrupt a drag.
- Remove the legacy direction/edge/threshold/action gesture form
(OriginGestureOptions, legacy OriginGesture props, MaybeOriginAction).
Scene
- Honour choreography commitVelocity (default 0.3, as before).
- Scene errors are OriginSceneError with a stable code.
- update() during settling now really catches an operation: the spring
stops, and only the latest finish() decides the outcome.
- Fix graph corruption found by the new property tests, all present on
main: replacements escaping a cancelled push, edges left dangling when
their current target differs from the original one, revealed targets
keeping operations after their back is cancelled, history pointing at
removed entries after replace, visual cycles from back through an
undecided replace, exposed nodes reactivated as active, and a cancel
cascade that could empty the whole scene.
Tests
- Add property tests checking structural invariants after every step of
random operation sequences and pointer streams (PROPERTY_SEEDS).
- Add recognizer tests for stuck gestures, warnings, reactive props,
and commitVelocity. Drop the main-comparison suites; keep benchmarks.
Also fix the origins demo's two pre-existing type errors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Faarh2Ts6StJPcz2mRiHiR
- Memoize each operation's choreography effect set in a computed, so
nodes sharing an edge stop re-evaluating it (linear, not quadratic,
in chain depth).
- Cache the scene viewport and refresh it from a ResizeObserver and at
each begin(), instead of measuring the container per node per frame.
- Index operations by origin key so outgoing lookups no longer scan
every operation; all map changes go through attach()/detach().
- Resolve px and % gesture edge distances arithmetically; only other
CSS lengths use the off-screen probe. Verified in Chromium to match
the probe within its 1/64px layout rounding.
The differential suites against main still match exactly. Benchmarks
vs main: styleForNode 2.0x (3 live ops) and 2.4x (10 live ops),
rendered frames 1.14x.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Faarh2Ts6StJPcz2mRiHiR
- Merge the small modules into 12 files: shared, choreography,
navigation, scene/{types,graph,scene}, gesture/{definition,recognizer}.
- SceneGraph now owns the begin/update/commit/cancel history rules as
methods, with the step helpers as private methods.
- createOriginScene returns a markRaw'd Scene class; operation handles
are an OperationHandle class instead of inline object literals.
- GestureRecognizer gains static create()/policyFor() factories, absorbing
the separate policy module.
- Pure helpers used by classes (matchesStart, pointOf, stepSpring,
composeNodeStyle, nodeEffectLayers, shouldCommit, ...) are exported
consts so they can be tested directly.
- Add haptics tests showing where navigator.vibrate can be triggered.
Public exports, types, and behavior are unchanged; the differential
suites against main still pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Faarh2Ts6StJPcz2mRiHiR
Rewrite the core-v2 internals without changing the public API or runtime
behavior. The four large files (types, scene, motion, gesture) become
layered modules with one responsibility each:
- shared/: geometry and environment helpers
- view/: originView recipes
- choreography/: effect types, presets, and composeEffects compositing
- actions/: forward/back/replace/above/under via one overload resolver
- scene/: SceneGraph storage, transitions (history rules), compositor
(styles and interactivity), spring (pure stepper + rAF driver), and
createOriginScene, which only wires them and owns async timing
- composables/: useOrigin and the node-scope injection key
- gesture/: builder, definition->policy normalization, start admission,
DirectionalTracker, and a GestureRecognizer session state machine
replacing fifteen loose closure variables
- components/: renderers and gesture hosts, plus their prop types
Verification (packages/core-v2/verify): differential fuzzing drives
main and this branch with identical random operation sequences and
pointer streams and requires identical scene state, styles, contexts,
callbacks, and promise results at every step. It caught every planted
mutation that applied. Benchmarks are at parity.
Also fix a pre-existing test that called the defineProps macro inside a
runtime defineComponent, and correct API.md's description of the flick
commit threshold to match the runtime.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Faarh2Ts6StJPcz2mRiHiR