|
| 1 | +# Architecture and test boundaries |
| 2 | + |
| 3 | +```text |
| 4 | +Pointer adapter (directional touch, mouse, pen) |
| 5 | +Touch adapter (directional values unsupported) |
| 6 | + ↓ |
| 7 | +start snapshot: origin, owner, adapter, coordinates, threshold |
| 8 | + ↓ |
| 9 | +shared intent: 6px slop → axis/direction arbitration |
| 10 | + ↓ |
| 11 | +recheck same owner + entire relevant top boundary chain |
| 12 | + ↓ |
| 13 | +claim → shared resistance → pulling ⇄ armed |
| 14 | + ↓ |
| 15 | +release once → application onRefresh → refreshing |
| 16 | + ↓ |
| 17 | +resolve / reject / synchronous throw → settling → idle or disabled |
| 18 | +``` |
| 19 | + |
| 20 | +Scroll ownership lives in `src/scroll-ownership.ts`. Root does not acquire |
| 21 | +scroll ownership by adding mechanical overflow. The root/ancestor owner is |
| 22 | +resolved at start; origin descendants are checked for competing scroll |
| 23 | +consumers. Invalid explicit refs remain unresolved. Browser-owned gestures are |
| 24 | +never resumed mid-stream. |
| 25 | + |
| 26 | +`src/mechanics.ts` owns intent, resistance, progress and hysteresis. Visual travel |
| 27 | +remains direct through the threshold and sublinear above it. Hysteresis is |
| 28 | +`min(6, threshold * 0.25)`; every finite positive threshold is valid. The 6px |
| 29 | +intent slop still applies even for threshold 4. Armed release holds at |
| 30 | +`min(52, session threshold, released distance)`. The internal 52px resting height |
| 31 | +is an indicator presentation convention, not a measured height or public prop. |
| 32 | +Applications should size their indicator for this hold; small thresholds imply |
| 33 | +smaller available space. Progress remains 1 during refreshing and returns to 0 |
| 34 | +when settling begins, independent of promise success/failure. |
| 35 | + |
| 36 | +Session listeners are removed before release/abandonment; cleanup releases root |
| 37 | +capture only after clearing session state so capture-loss events cannot commit |
| 38 | +again. Touch fallback uses one non-passive root touchmove listener during a |
| 39 | +candidate only. Session-scoped window end/blur/additional-pointer detection handles |
| 40 | +abandonment outside the root. No idle RAF, document touchmove listener, generic |
| 41 | +gesture dependency, observer of content mutations, or application promise |
| 42 | +cancellation is introduced. React supplies its normal delegated start handlers. |
| 43 | + |
| 44 | +A disabled change cancels an uncommitted session. Committed work finishes, then |
| 45 | +settles to disabled. Unmount removes listeners; promise completion is ignored |
| 46 | +for UI updates. Application errors are consumed for lifecycle cleanup without |
| 47 | +console logging, retries, or a second error callback. Applications retain error |
| 48 | +UI and telemetry ownership. |
| 49 | + |
| 50 | +## Verification layers |
| 51 | + |
| 52 | +- Pure mechanics tests: intent, resistance, progress, threshold hysteresis. |
| 53 | +- React tests: adapter invariants, null refs, nesting, lifecycle, StrictMode, |
| 54 | + cancellation, editing exclusions, ref/threshold rerenders and listener cleanup. |
| 55 | +- Browser tests: computed touch-action, actual overflow geometry, sticky headers, |
| 56 | + Window/element scroll surfaces, dynamic auto ancestors, nesting, large lists, |
| 57 | + console hygiene, and trusted Chromium CDP touch traces. |
| 58 | +- Synthetic DOM touch tests: exercise adapter integration in each engine. They |
| 59 | + cannot prove native touch delivery or arbitration. |
| 60 | +- Physical-device QA: native overscroll/refresh, selection handles, zoom, |
| 61 | + Safari/Firefox mobile fallback and actual browser chrome interactions. |
| 62 | + |
| 63 | +Directly invoking React handlers or `page.mouse` cannot establish mobile touch |
| 64 | +support. Beta requires meaningful physical iOS and Android evidence. |
| 65 | + |
| 66 | +Native listeners route through handlers published at React commit, so owner and |
| 67 | +refresh callback changes are observed without resubscribing active sessions. |
| 68 | +The threshold remains a gesture-start snapshot. The JS gzip budget is 3,200 B |
| 69 | +(previously 3,000 B), allowing the focused compatibility adapter and ownership |
| 70 | +checks; the measured baseline before hardening was 2,194 B. Runtime dependencies |
| 71 | +remain zero beyond React peers. |
0 commit comments