Skip to content

chore(core): the UIKit constants table for touch presentations - #8053

Merged
cixzhang merged 14 commits into
meta:mainfrom
vjeux:wip/uikit-pr-m1
Oct 25, 2026
Merged

cixzhang merged 14 commits into
meta:mainfrom
vjeux:wip/uikit-pr-m1

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

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-094 FR1, FR2):

  • springs.ts: every interaction spring (navigation, sheets, alerts, menus, context menus, keyboard, banner, and the platform presets, named for their use as spec:AST-094 FR4 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-094 FR1–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

  • Values stay in their source's vocabulary (response and damping ratio, duration and bounce, or mass, stiffness and damping), so each reads as published or measured; the next pull request's math normalizes them.
  • Every source is JSDoc, gesture thresholds included: only {value, unit} ships, so no page pays for provenance strings.

Evidence

  • Every entry carries @source apple-docs …, wwdc …, webkit Source/… (confirmed by a measurement), measured: <app>, iOS <version>, <method>, <date>, or GUESS — … with @measure M-…. Most are UIKit read on the iOS 26.2 simulator or Apple documentation.
  • The test checks every domain file, so a new component file is checked as soon as it is added.
  • Production bundle (minified, gzip): the gesture table alone 2,397 → 785 B; a Carousel on the gesture system 63,389 → 61,489 B.

Scope

  • One capability is added.
  • Unrelated fixes, cleanup, and policy changes were removed or split.
  • Consumer docs, focused tests, and migration evidence ship with the feature.
  • Public text and artifacts contain no internal Meta context.

Testing

  • pnpm exec vitest run --project ui packages/core/src/motion: 2 files, 131 tests passed; a value with a runtime source, an entry without @source, or the haptic block without its @measure fails it.
  • Every {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.
  • Prettier, pnpm check:knowledge, pnpm check:changesets, pnpm check:sync, pnpm check:repo (pre-commit): clean.
  • Size: 11 files, 1,684 lines added.

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.

@vercel

vercel Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 25, 2026 11:25pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Oct 25, 2026, 11:41 PM UTC

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

No new or modified components detected.

Bundle Size Summary

Gzip figures measure each package's entry file, not the code an import pulls in.

Package Entry (ESM) Entry gzip before Entry gzip after Delta
@astryxdesign/core 4.9KB 1.2KB 1.2KB 0B
Package stylesheet Gzip before Gzip after Delta
@astryxdesign/core 33.8KB 33.8KB 0B

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

48 of 502 shot(s) changed.

A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.

component story theme mode pixels
Selector Indicator Space Bottom Sheet Narrow End RTL neutral light 753,933
Selector Indicator Space Bottom Sheet Wide Start neutral light 740,072
RichTextEditor Markdown parity: side by side probe dark 556,900
RichTextEditor Markdown parity: side by side probe light 538,816
Chat Full AI Chat probe light 230,823
Chat Full AI Chat probe dark 229,805
AppShell With Mobile Nav probe light 217,010
AppShell With Mobile Nav probe dark 216,960
Collapsible Dividers probe dark 66,445
Collapsible Dividers probe light 66,445
AppShell Playground neutral dark 65,198
AppShell Playground neutral light 63,788
Chat Default neutral dark 36,304
Chat Default neutral light 35,767
Chat Empty State probe light 23,531
Chat Empty State probe dark 22,622
ChatAutoScroll Scroll Behavior Comparison neutral dark 15,378
ChatAutoScroll Scroll Behavior Comparison neutral light 14,107
Chat With Attachments probe light 13,406
Chat With Attachments probe dark 11,232

and 28 more.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.md reports switch and context-menu captures as done in uikit/switch.ts and uikit/contextMenu.ts, which this branch does not contain, while springs.switchThumbMove and switchThumbStretch still wait on M-SW02 and M-SW03.
  • The shared gesture sources (UIKIT_LAB, PRESS_MEASUREMENT, SAFARI_SCROLL_CLAIM) carry no date, and the measured: pattern in uikit.test.ts accepts that.
  • Several headers name files that do not exist (sources.test.ts, packages/core/src/gestures/README.md), and springs.ts says 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 — )/;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
… 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.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
The gesture table's sources live in JSDoc now (meta#8053): the menu press group (holdOpenSlop, handoff) keeps its values and units, its source and measurement become @source and @measure like every other entry. The motion README keeps this stack's uikit/menu.ts row beside the new gestures.ts row.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@cixzhang
cixzhang merged commit 4abbc52 into meta:main Oct 25, 2026
65 checks passed
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
…rve sampling

# Conflicts:
#	internal/interaction-harness/src/index.ts
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
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.
@vjeux
vjeux deleted the wip/uikit-pr-m1 branch October 26, 2026 00:05
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
… 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.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
The UIKit table's long-press entries take meta#8053's form (sources in JSDoc)
and keep their values from longPress.ts, which the table test lists as a
module with no table of its own.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
… 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.
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
@astracat-bot astracat-bot Bot mentioned this pull request Oct 26, 2026
4 tasks done
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
…into the animator's React hook and browser proofs

The motion table files keep this branch's copy, main's meta#8053 table plus the
chain's additions. The harness takes main's fail-closed compareCurve and its
tests beside meta#8446's modules, index and README.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
…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).
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
…n recognizer

# Conflicts:
#	packages/core/src/motion/MEASUREMENTS.md
#	packages/core/src/motion/uikit/gestures.ts
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
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.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
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.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
meta#8076 folds the new main (with meta#8053's squash and meta#8462) and the strict
lint fix. The merge is clean; meta#8076's side stands for the foundation, the
harness, ci.yml and package.json.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
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.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
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.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
… this change

Takes meta#8076's ci.yml whole: the CheckboxList row press contract no longer
needs a workflow step of its own, since the pull-request accessibility run
finds CheckboxListPress.a11y.chromium.spec.ts by its file name and runs it
for a CheckboxList change (meta#8367).
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
cixzhang pushed a commit that referenced this pull request Oct 26, 2026
…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.

This branch was successfully deployed

1 active deployment
Preview — 0032cb6a Deployed Oct 25, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants