Repository navigation
chore(core): the UIKit constants table for touch presentations - #8053
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
PR Analysis Report📚 Storybook Preview🧪 Sandbox PreviewNo new or modified components detected. Bundle Size SummaryGzip figures measure each package's entry file, not the code an import pulls in.
Accessibility AuditStatus: No accessibility violations detected. Visual Regression48 of 502 shot(s) changed. A change here is a question, not a failure: check whether the after is the
and 28 more. Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
There was a problem hiding this comment.
Approval is on hold for a spec decision: the new constants table implements draft spec:AST-094 FR1–FR2, and that record (FR1–FR23 and DEC-1/DEC-2, carried here from #8052) has no engineering-owner approval yet. An engineering owner records the decision by commenting /approve-spec 28343b4215c178ba43c8f3fa9eb0478da1db995c on this pull request (the author may do so as an engineering owner), or asks for changes to the record. Independently of that decision, these need fixing:
- The diff is 2,039 authored lines because it carries the #8052 record; landing #8052 first and rebasing brings it to 1,746.
MEASUREMENTS.mdreports switch and context-menu captures as done inuikit/switch.tsanduikit/contextMenu.ts, which this branch does not contain, whilesprings.switchThumbMoveandswitchThumbStretchstill wait on M-SW02 and M-SW03.- The shared gesture sources (
UIKIT_LAB,PRESS_MEASUREMENT,SAFARI_SCROLL_CLAIM) carry no date, and themeasured:pattern inuikit.test.tsaccepts that. - Several headers name files that do not exist (
sources.test.ts,packages/core/src/gestures/README.md), andsprings.tssays most springs are guesses when 2 of 31 are.
[Automated review]
| * Every leaf carries one or more `@source` (apple-docs | wwdc | webkit | | ||
| * measured | GUESS); webkit and GUESS values also carry the `@measure` task | ||
| * that will confirm or replace them (motion/README.md lists them all). | ||
| * sources.test.ts enforces both. |
There was a problem hiding this comment.
The check is uikit.test.ts; there is no sources.test.ts (the same name appears in scroll.ts:14 and timing.ts:14), and the measurement tasks are listed in MEASUREMENTS.md, not motion/README.md.
| * that will confirm or replace them (motion/README.md lists them all). | ||
| * sources.test.ts enforces both. | ||
| * | ||
| * Most interaction springs below are GUESSES today: UIKit does not document |
There was a problem hiding this comment.
At this head 2 of the 31 springs entries are GUESS (segmentedThumb, banner); 20 cite a measurement, 7 Apple documentation and 2 WebKit. This paragraph, and the guesses wording at line 31, misstates the provenance the table exists to record.
| * chosen for consistency with UIKit" (= response 0.30 s, ratio 0.75); | ||
| * its settling duration is WebKit's iOS switch duration 0.48801 s | ||
| * (RenderThemeIOS.h) | ||
| * @measure M-SW02 |
There was a problem hiding this comment.
MEASUREMENTS.md:123-124 reports M-SW02 and M-SW03 as done, with the values in uikit/switch.ts, which this branch does not contain. Either move that note to the pull request that adds the switch table, or record the measured springs here and drop the @measure tags (README step 4).
| Text Size (Bold Text row); a minimal host app holds a UISwitch on a plain view | ||
| where a switch outside any scroll view is needed. | ||
|
|
||
| Done (iOS 26.2 simulator, Oct 23 2026; in the switch's own table, |
There was a problem hiding this comment.
uikit/switch.ts (and uikit/contextMenu.ts at line 254) does not exist at this head, so this note points readers at captures they cannot find while springs.switchThumbMove and switchThumbStretch still read as unconfirmed.
| * tap-to-click, long press, double tap and the start of a page scroll, so | ||
| * many guesses can be settled by measuring Safari itself. | ||
| * | ||
| * SYNC: When modified, update packages/core/src/gestures/README.md (the |
There was a problem hiding this comment.
packages/core/src/gestures/README.md does not exist at this head (nor does ../../gestures, named at line 7), so this SYNC: line asks maintainers to update a file they cannot find.
| * recognizer state log), iPhone 17 Pro simulator, iOS 26.2, Oct 2026. | ||
| */ | ||
| const UIKIT_LAB = | ||
| 'measured: UIKit lab, iOS 26.2 simulator, ivar reads confirmed with HID touches'; |
There was a problem hiding this comment.
This shared source has no capture date, unlike the measured: <app>, iOS <version>, <method>, <date> rule in motion/README.md:44; PRESS_MEASUREMENT and SAFARI_SCROLL_CLAIM lack one too, and 18 of the 20 sourced gesture entries use these three strings.
| ) | ||
| .sort(); | ||
| const SOURCE_KINDS = | ||
| /^@source (apple-docs https:\/\/developer\.apple\.com\/|wwdc \d{4}-\d+ |webkit Source\/|measured: .*iOS \d|GUESS — )/; |
There was a problem hiding this comment.
The measured: branch only requires iOS <digit>, so measured: iOS 2 passes. Requiring the date the README rule names would catch the dateless gesture sources.
… release curve, with main) into the theme motion roles The tables take meta#8053's keys, which already carry spec:AST-094 FR4's use names, beside this pull request's rename map; the role ids are the same either way. The menu reduced-motion spring takes meta#8053's reference-reading note. The role tests reach the table entries by their use names and keep checking that the old preset and curve-shape names are no role ids.
There was a problem hiding this comment.
This looks ready to approve: spec:AST-094 is current on main and covers the table, every value carries a source that the tests check, and nothing imports or exports the table yet. Three optional fixes would make the provenance say exactly what it claims: README.md says uikit.test.ts fails any unsourced value, though it reads only export const X = { objects; the note at scroll.ts:42 gives 0.4343 for 400 ms of decay where 0.998 per millisecond leaves 0.449; and both screenEdgePan sources say their ivar reads were confirmed with HID touches, which the simulator cannot do for an edge pan. The description could also be refreshed, since it still calls AST-094 a draft and quotes 131 tests and 1,684 lines.
[Automated review]
| * Travel before an edge pan begins. | ||
| * @source measured: UIKit lab, iOS 26.2 simulator, ivar reads confirmed | ||
| * with HID touches, Oct 23 2026: the screen-edge pan hysteresis ivar 15 | ||
| */ |
There was a problem hiding this comment.
This source says the ivar read was "confirmed with HID touches", but edgeRegion's note above and M-G11 in MEASUREMENTS.md say the simulator cannot begin an edge pan, so the 15 px hysteresis is an unconfirmed ivar read with no capture row. Saying "ivar read, not confirmed: simulator HID cannot begin an edge pan" here (and on edgeRegion) keeps the source accurate under spec:AST-094 FR2; listing it under M-G11 would keep its confirmation owed.
…rve sampling # Conflicts: # internal/interaction-harness/src/index.ts
meta#8053's constants table is on main now. Its four files (MEASUREMENTS.md, README.md, uikit/gestures.ts, uikit/timing.ts) were add/add conflicts: this branch's copy is main's plus the math's own additions (README's math section, the press release curve and M-PR1, the AST-100 velocity note), so it is kept whole.
… into meta#8320 - Pan: meta#8060's FR14 removal of the scroll-edge handoff, with this line's opt-in early claim ('early', from the table) and its committed claim that survives a second finger; meta#8076's default-on copy of that test goes. - The UIKit table takes meta#8053's form (value and unit, sources in JSDoc) and keeps earlyClaimDistance in it, dated. - usePressTracker stays in its own module here. - The README's pan row keeps scrollVelocity() and loses scrollHandoff.
… into meta#8060 The motion table, its tests and MEASUREMENTS take meta#8053's: every measured source dated, sources in JSDoc. The pan's velocity tracker still reads the scroll release weight and the stillness reset, so the table keeps those two entries (and M-G14, M-G16) in the same form, as meta#8076 has them.
…p and long-press recognizers
…a#8053) into the role-reading animator The motion README takes meta#8053's opening with the animator added and keeps this branch's file table; the harness takes main's fail-closed compareCurve beside meta#8045's modules. The animator and its tests take the use names with the next merge (meta#8067).
…n recognizer # Conflicts: # packages/core/src/motion/MEASUREMENTS.md # packages/core/src/motion/uikit/gestures.ts
meta#8053's constants table is on main. Its nine motion files were add/add conflicts against this branch's older copy: they take main's version, plus this branch's own two velocity entries, which its tracker reads: scrollReleaseWeight (0.25, interaction-modality INV10) and stillness (40 ms, spec:AST-100 FR2), with their M-G14 and M-G16 rows. The tracker reads no horizon, maxSamples or assumeStopped entry.
Brings the touch foundation's current head, which folds the new main (meta#8053, meta#8462), meta#8067, meta#8257 and F2's meta#8060: the canonical ci.yml and harness, F2's FR14 removal and the AST-094 FR4 preset renames. The generated motion role table and role kinds are regenerated (`pnpm generate:motion-roles`) with this branch's switch table in them; the motion README keeps the foundation's gesture row and adds the switch row.
meta#8076's head folds main 99ad3f6 (meta#8053, meta#8462) into the motion foundation, so the keyboard module no longer conflicts with main. One conflict: motion.doc.mjs takes meta#8076's text and keeps this branch's sentence that ChatLayout's Reduce Motion cross-fade reads `timing.curves.move`. timing.ts keeps this branch's `keyboardNotification` group, which meta#8076's next head adopts verbatim. The motion role table regenerates unchanged (45 roles), as do the CLI goldens.
meta#8270's newest head: cixzhang's 425922b and meta#8076 746871a (the new main, meta#8053, meta#8060, the role naming). Resolutions: - pressFeedback.ts keeps C01's press slots (meta#8118) and takes meta#8270's FR41 narrowing: the press controller no longer locks text selection; a held finger suppresses it only where the hold does something, through the gesture host. - The generated motion role table and role kinds are regenerated with the button, list and switch tables in them; the motion README keeps the foundation's rows and this branch's button, list and switch rows; the docs golden takes the motion topic's new skeleton hash.
…rena (#8058) Depends on #8053 (and its predecessors); review the last commit. <!-- Template: other.md (internal infrastructure: nothing is exported, so new-feature.md does not fit). --> ## Primary intent Add the core of an internal gesture-recognizer system for touch presentations: UIKit's recognizer state machine and the arena that settles which recognizer owns a touch sequence. Recognizers, the DOM host and component migrations follow in their own pull requests; nothing is exported from the package. ## User or maintainer impact Touch components each carry their own pointer handling (sheet drags, swipe actions, toast dismissal, long press, press feedback), with their own answer to "who owns this touch". Two can act on one touch, and a second finger can break a drag. With one arena every component gets UIKit's answers: exclusive recognition by default, `requireToFail`, simultaneous recognition only on request, a deterministic resolution order, and the view's touches cancelled (no click, no pressed state) once a gesture takes them. ## Problem and solution fit - Why does it happen? Ownership of a touch is decided separately in each component. - Why does that hurt? Ghost clicks, double actions, and rules that differ from component to component. - Why does it matter? Touch presentations aim at UIKit parity, and UIKit settles this in one place. Deltas: - `GestureRecognizer`: - UIKit's states (possible, began, changed, ended, cancelled, failed) and allowed transitions; - discrete and continuous recognizers; - `requireToFail`, `recognizeSimultaneouslyWith`, `shouldBegin`, `shouldReceiveTouch`; - `cancelsTouchesInView`, `delaysTouchesBegan` and `delaysTouchesEnded`, `allowedPointerTypes`. - `GestureArena`, with no DOM: - participants per touch sequence, resolution and claims; - delivery to the view; - click dispositions: allow, suppress, or defer and replay if the recognizer fails; - timers as deadlines on the event clock. ## Authority `spec:AST-094` FR15 (arbitration) and FR16 (the event clock), proposed; pending owner review. ## Change and evidence The arena's rules are tested with scripted recognizers, test doubles that begin, fail or wait on command. The rules hold independently of any real recognizer: - exclusivity and simultaneity; - require-to-fail: waiting, failing, delegate requirements, idle recognizers; - delegate hooks and innermost resolution; - independent touches; - delivery to the view, deferred and replayed clicks, the browser taking a touch; - cancellation; - a deadline that runs at its own time before a late input. Size: 2,198 lines, about 400 of them tests. This is one indivisible intent: the recognizer base and the arena call each other, so neither builds or tests alone, and the doubles-only tests are the smallest proof of the rules. ## Scope - [x] This PR has one primary intent. - [x] Independently removable deltas were removed or split. - [x] Public text and artifacts contain no internal Meta context. ## Testing - `pnpm exec vitest run --project ui packages/core/src/gestures packages/core/src/motion`: 159 tests passed. - `tsc --project packages/core/tsconfig.json --noEmit`: clean. - `eslint packages/core/src/gestures`: clean. - `pnpm check:changesets`, `node scripts/release/changeset-coverage.mjs pr --base origin/main`: clean. - Size: 6 files, 2,205 lines added. Stack (merge in this order): #8052 spec → #8053 constants table → #8058 gesture core → #8059 tap and long press → #8060 pan → #8061 other recognizers → #8054 math → #8055 animator → #8056 animator curves; the gesture DOM host, press tracking and browser proof follow the animator.
Review the last four commits; the last removes the earlier AST-094 draft commit's file, which is revised elsewhere.
User need
Phone presentations of core components should feel like iOS. Today each component carries its own unsourced numbers, so nothing can be checked against UIKit; touch-presentation work needs one place to read UIKit's values, each with proof of its origin.
Problem and solution fit
packages/core/src/motion/uikit/is that place (spec:AST-094FR1, FR2):springs.ts: every interaction spring (navigation, sheets, alerts, menus, context menus, keyboard, banner, and the platform presets, named for their use asspec:AST-094FR4 states:settle,quickChange,emphasis,fingerTracking…).scroll.ts,timing.ts,navigation.ts: UIScrollView physics, press clocks and named curves, navigation push and interactive pop.gestures.ts: recognizer thresholds, UIKit's velocity filter and the haptic stand-ins, as{value, unit}entries the gesture system reads.uikit.test.ts: fails on an unsourced value, an unknown source kind, a guess without a listed measurement task, or a source shipped in a value, and keeps the press clocks equal to the press model.MEASUREMENTS.md: the simulator captures still owed (app, gesture, value, method), with rows that component pull requests cite.Current specification
spec:AST-094FR1–FR3 (draft, not yet on main).Public delta and caller burden
None: internal, not exported, and no component reads it yet. The changeset is
[chore].Design and alternatives
{value, unit}ships, so no page pays for provenance strings.Evidence
@source apple-docs …,wwdc …,webkit Source/…(confirmed by a measurement),measured: <app>, iOS <version>, <method>, <date>, orGUESS — …with@measure M-…. Most are UIKit read on the iOS 26.2 simulator or Apple documentation.Scope
Testing
pnpm exec vitest run --project ui packages/core/src/motion: 2 files, 131 tests passed; a value with a runtimesource, an entry without@source, or the haptic block without its@measurefails it.{value, unit}and haptic pattern, evaluated before and after: identical.pnpm -F @astryxdesign/core typecheck: clean.ASTRYX_STRICT_LINT=1 pnpm exec eslint packages/core/src/motion: clean.pnpm check:knowledge,pnpm check:changesets,pnpm check:sync,pnpm check:repo(pre-commit): clean.Stack, in merge order: #8053, #8058, #8059, #8060, #8061, #8054, #8257, #8055, #8056, #8067, #8258, #8071, #8072, #8073, #8074, #8076, #8340; #8067 also follows #8043, #8044, #8045, and #8062 merges any time.