Skip to content

chore(core): swipe actions' UIKit values and release rule - #8069

Draft
vjeux wants to merge 39 commits into
meta:mainfrom
vjeux:uikit/c12-swipe-values
Draft

vjeux wants to merge 39 commits into
meta:mainfrom
vjeux:uikit/c12-swipe-values

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Sits on main (#8054 merged). Review only this PR's change: vjeux/astryx@6aea9bd...e805797

Why

On iOS, a list row with swipe actions (Mail's archive, Settings' delete) waits for a short drag, follows the finger on a spring, and on release snaps open, snaps closed or fires its action. Astryx's Item imitates it with numbers nobody measured (an 8 px start against UIKit's 25 pt, a 200 ms ease-out against UIKit's 0.92 s spring), and a quick flick fires the row's outermost action, often Delete.

What led to it

This is part of a series bringing Astryx's touch interactions to parity with native iOS (UIKit). We recorded real UIKit rows on iOS 26.2 in a small test app: 51 releases at scripted distances and speeds, plus rows released while moving back. This PR turns the recordings into a table of values and a release rule. Nothing uses them yet; #8097 wires them into the row.

Options and tradeoffs

  1. Keep today's values: no work, but unsourced, and a flick still fires.
  2. Decide by position only, as scroll views do: misses 8 of the 44 releases we can check, and opens rows UIKit closes when they move back.
  3. Speed first, then position: a release faster than about 48 pt/s goes the way it moves; a slower one opens past half the open width.

Two tradeoffs are owner decisions this PR proposes:

  • The row claims the touch at 8 px, because the browser commits a touch to scrolling at about 10 px; it starts moving at UIKit's 25 pt. In between it holds the touch without moving.
  • The open fraction (measured 0.32–0.56, set to 0.5) and the release speed (40.7–56.1 pt/s, set to 48) stay estimates until the captures listed in MEASUREMENTS.md.

Why this choice

Option 3 reproduces all 44 checkable releases, and only a drag past the commit point fires, never a flick.

How to check it

  1. Run pnpm exec vitest run packages/core/src/Item/swipeRelease.test.ts packages/core/src/motion.
  2. Expect every case to pass; on this head all 611 do. They replay the 44 recorded releases and check that each ends where UIKit's row did, that a row moving back from past halfway closes, that a flick short of the commit point never fires, and that the spring settles in UIKit's 0.92 s. All runs are local; no CI run is expected.

There is nothing to see in a story yet: the values are internal ([chore] changeset). Authority: spec:AST-094 FR1–FR3 and FR17; spec:AST-057 is unchanged.

Evidence

  • The recorded releases and their outcomes: swipeRelease.test.ts.
  • Size: 6 files, 455 lines added, 19 removed.

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.

@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 27, 2026 10:34am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 24, 2026
@astracat-bot

astracat-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Oct 30, 2026, 1:44 PM UTC

@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 waits on an engineering owner's decision: this pull request rests on new draft requirements in spec:AST-094 (it cites FR1–FR3, FR17 and DEC-1), and resolveRelease lets a flick open a swipe row where current spec:AST-057 FR5 requires a fling, or any release past the commit point, to fire the outermost entry. An engineering owner records approval of the draft requirements by commenting /approve-spec <full head SHA> on this pull request (the author may do so as an engineering owner), and either amends AST-057 FR5 or asks for the rule to conform to it. Independently of that decision:

  • Land #8052, #8053 and #8054 first, so this pull request is reviewed as its own 408-line commit instead of 4,462 lines.
  • MEASUREMENTS.md and motion/uikit/gestures.ts point at uikit/switch.ts, uikit/contextMenu.ts and src/gestures/, which do not exist at this head.

[Automated review]

Comment thread packages/core/src/Item/swipeRelease.ts Outdated
Comment on lines +52 to +58
if (behavior === 'commit') {
return projected >= commitAt ? 'perform' : 'closed';
}
if (travel >= commitAt) {
return 'perform';
}
return projected >= swipeActions.openFraction * openWidth ? 'open' : 'closed';

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 rule departs from current spec:AST-057 FR5 ("a drag past the commit point or a fling MUST fire the outermost entry"; under commit, "a release past the commit point or a fling MUST slide the row out and fire the outermost entry"). Under reveal a fling opens instead of firing and a release past the panel's width moving back closes; under commit a release past the commit point moving back closes (swipeRelease.test.ts lines 74, 102-103 and 115-117). Nothing calls it yet, but before useSwipeAction does, FR5 needs an owner's amendment or this branch needs to conform.

Comment on lines +123 to +126
Done (iOS 26.2 simulator, Oct 23 2026; in the switch's own table,
`uikit/switch.ts`): M-SW02, M-SW03, M-SW04, M-SW05, M-SW06, M-SW09 (UISwitch
makes no haptic call), M-SW11 (Reduce Motion: the lift stays, the stretch
goes), M-SW16 (iOS 26.2). Partly: M-SW07 (a single tap only). Still open:

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 does not exist at this head, so these "Done" captures have no values in this tree, and springs.switchThumbMove / switchThumbStretch still carry @measure M-SW02 / M-SW03 as webkit values. The same holds for uikit/contextMenu.ts at line 254. Point at files in this tree, or keep these lines out until those tables land.

Comment on lines +6 to +7
* @output Exports UIKIT_GESTURES, the gesture thresholds the gesture system
* (../../gestures) reads, each with where the number comes from, and 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 (the gesture system named here and in the SYNC note at lines 30-31) is not in this tree; it arrives with the recognizer pull requests, which are not ancestors of this head.

@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

Modified Components

Item (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 284B 284B 0B
Lines of Code 1372 1413 -
Complexity Very High Very High (121) -

Component Size

Component Import before Import after Delta CSS before CSS after Delta
core/Item 11.4KB 11.4KB 0B 4.7KB 4.7KB 0B

Sizes are gzip. Import counts the JavaScript the import loads eagerly; CSS counts the package stylesheet rules referenced by the modules it can load from its own package, lazily loaded ones included.

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 34.6KB 34.6KB 0B

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

55 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 558,840
RichTextEditor Markdown parity: side by side probe light 540,756
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 35 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 waits on an engineering owner's decision: this pull request rests on new draft requirements in spec:AST-094 (FR1–FR3, FR17 and DEC-1), and resolveRelease lets a fling open or close a swipe row where current spec:AST-057 FR5 requires a fling to fire the outermost entry and a release past the panel's width to rest open. An engineering owner records approval of the draft requirements by commenting /approve-spec <full head SHA> on this pull request (the author may do so as an engineering owner), and either amends AST-057 FR5, with AST-094 FR3 reworded since it still asks for projection, or asks for the rule to conform to FR5. Independently of that decision:

  • Land #8052, #8053 and #8054 first, so this pull request is reviewed as its own 470-line commit instead of 4,524 lines.
  • The changeset says the rule is checked against 50 UIKit releases, but swipeRelease.test.ts checks 44 of batch 10a's 51; add the missing six or correct the count.
  • MEASUREMENTS.md and motion/uikit/gestures.ts point at uikit/switch.ts, uikit/contextMenu.ts and src/gestures/, which do not exist at this head.

[Automated review]

Comment on lines +54 to +62
if (behavior === 'commit') {
return 'closed';
}
if (velocity >= swipeActions.releaseVelocity) {
return 'open';
}
if (velocity <= -swipeActions.releaseVelocity) {
return 'closed';
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Under reveal a fling short of the commit point opens here, and a release past the panel's width that is moving back closes; under commit a fling short of the commit point closes. Current spec:AST-057 FR5 requires a fling to fire the outermost entry and a release past the panel's width to rest open, so before useSwipeAction calls this, FR5 needs an owner's amendment or this branch needs to conform.

'@astryxdesign/core': patch
---

[chore] Internal: the UIKit values of a swipe-action row (the swipe threshold, the spring the row follows the finger and settles with, its Reduce Motion behavior, the touch claim distance the web needs) and the release rule that picks closed, open or fire (a fast release goes the way it moves, a slow one by where it is, and only a release past the commit point fires), checked against 50 releases read from UIKit. Not exported from the package entry point; no component uses it yet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

swipeRelease.test.ts checks 44 of batch 10a's 51 releases (the table at lines 32-80, with a 45th commented out), not 50: add the six missing releases, or say 44.

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 24, 2026
# Conflicts:
#	packages/core/src/motion/README.md
Touch presentations get one native-feeling behavior by default on every
touch device, iOS and Android alike: measured values, compositor motion a
finger can catch, recognizer-tracked gestures with native arbitration, two
press profiles, and a Reduce Motion cross-fade. Touch motion is themeable,
with the measured values as theme defaults; gesture thresholds stay
internal. Size keys on the spec:AST-012 FR9 compact definition, a moved
default is restored by the caller's presentation choice (INV16, DEC-23),
and the record names the current claims it amends. Platform look and feel
(OQ3) and module-level experimental subpaths (OQ4) stay open.

Co-authored-by: vjeux <197597+vjeux@users.noreply.github.com>
@cixzhang

Copy link
Copy Markdown
Contributor

Posted by Cindy's agent on her behalf.

Parts of this PR re-implement behavior that already exists, or is being specified, as a shared part of core. Converging keeps one implementation per behavior:

  • Reduced motion. core/src/motion/uikit/swipeActions.ts:81 offers reducedMotion: 'keep'. Per design:reduced-motion DR4, core has no reduced-motion opt-out: the animator should be the one place that resolves reduced motion (touch cross-fades in 200 ms or less with no travel, a fine pointer changes at once), so the 'keep' option should be removed.

This is about where the behavior lives, not about the PR's intent.

vjeux added 4 commits October 24, 2026 16:10
Springs, scroll physics, timing curves, navigation values and gesture
thresholds for the touch presentations of core components, each with its
source (Apple documentation, a WWDC session, WebKit's UIKit-matched source,
or a measurement of UIKit on the iOS 26.2 simulator) or labelled a guess
with its measurement task. A test enforces the source rules on every domain
file, in their JSDoc and typed forms, and checks the list of simulator
measurements still owed.
Pure functions for the touch presentations: a closed-form damped spring
with every Apple parameterization and Core Animation's settling duration,
a spring chasing a moving target, UIScrollView's coast, rubber band and
release projection. Tests hold them to an RK4 integration, WebKit's spring
solver, the WWDC conversion equations and values read from UIKit, and play
the constants table through them.
…ery measurement

Review fixes: the table headers point at uikit.test.ts and MEASUREMENTS.md
(not a sources.test.ts or the README); the springs header says how many
springs are guesses (two) instead of most; gestures.ts no longer names the
gesture system that arrives later; MEASUREMENTS.md keeps the switch and
context-menu captures open until their tables land; the switch springs say
they are WebKit's iOS 18 values, not UIKit's frames. Every measured source
now carries its date, and uikit.test.ts refuses one without.
selectSnapPoint built its search window from fromIndex unclamped, so a stale
current page past the end gave an empty window and value undefined. The
index is clamped to the points first.
@vjeux

vjeux commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Agreed: one implementation.

  • Reduced motion: the 'keep' option is removed from swipeActions. The table keeps UIKit's measurement only as a note, and the animator is the one place that resolves Reduce Motion.

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
# Conflicts:
#	packages/core/src/motion/README.md
@vjeux
vjeux force-pushed the uikit/c12-swipe-values branch from 48767e4 to 50ee84f Compare October 25, 2026 00:43
@vjeux

vjeux commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Pushed in 50ee84f: swipeActions no longer has a Reduce Motion field, so the animator alone applies design:reduced-motion.

@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 waits on an engineering owner's decision on the swipe release rule: resolveRelease never fires on a fling and closes a release past the panel's width that is moving back, while current spec:AST-057 FR5, which current spec:AST-094 FR36 keeps for row swipes, requires a fling to fire the outermost entry and such a release to rest open. Either an engineering owner amends FR5 to this rule and records it by commenting /approve-spec <full head SHA> on the pull request that carries the amendment (the author may do so as an engineering owner), or the rule and its tests conform to FR5 as written. Independently of that decision:

  • The changeset says the rule is checked against 50 UIKit releases; swipeRelease.test.ts checks 44 of batch 10a's 51.
  • swipeActions.ts describes releaseVelocity against UIKit's undecayed pan velocity; describe it against the spec:AST-100 velocity, which is 0 after a still pause.

[Automated review]

@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 waits on an engineering owner's decision on the swipe release rule: resolveRelease never fires on a fling and closes a release past the panel's width that is moving back, while current spec:AST-057 FR5, which current spec:AST-094 FR36 keeps for row swipes, requires a fling to fire the outermost entry and such a release to rest open. Either an engineering owner amends FR5 to this rule and records it by commenting /approve-spec <full head SHA> on the pull request that carries the amendment (the author may do so as an engineering owner), or the rule and its tests conform to FR5 as written. Independently of that decision:

  • The changeset says the rule is checked against 50 UIKit releases; swipeRelease.test.ts checks 44 of batch 10a's 51.
  • swipeActions.ts describes releaseVelocity against UIKit's undecayed pan velocity; describe it against the spec:AST-100 velocity, which is 0 after a still pause.

[Automated review]

# Conflicts:
#	packages/core/src/motion/README.md

@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 waits on an engineering owner's decision on the swipe release rule: resolveRelease never fires on a fling and closes a release past the panel's width that is moving back, while current spec:AST-057 FR5, which current spec:AST-094 FR36 keeps for row swipes, requires a fling to fire the outermost entry and such a release to rest open. Either an engineering owner amends FR5 to this rule and records it by commenting /approve-spec <full head SHA> on the pull request that carries the amendment (the author may do so as an engineering owner), or the rule and its tests conform to FR5 as written. Independently of that decision:

  • The changeset says the rule is checked against 50 UIKit releases; swipeRelease.test.ts checks 44 of batch 10a's 51.
  • swipeActions.ts describes releaseVelocity against UIKit's undecayed pan velocity; describe it against the spec:AST-100 velocity, which is 0 after a still pause.

[Automated review]

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
# Conflicts:
#	packages/core/src/motion/README.md
# Conflicts:
#	packages/core/src/motion/README.md

@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 waits on an engineering owner's decision on the swipe release rule: resolveRelease never fires on a fling and closes a release past the panel's width that is moving back, while current spec:AST-057 FR5, which current spec:AST-094 FR36 keeps for row swipes, requires a fling to fire the outermost entry and such a release to rest open. Either an engineering owner amends FR5 to this rule and records it by commenting /approve-spec <full head SHA> on the pull request that carries the amendment (the author may do so as an engineering owner), or the rule and its tests conform to FR5 as written. Independently of that decision:

  • The changeset says the rule is checked against 50 UIKit releases; swipeRelease.test.ts checks 44 of batch 10a's 51.
  • swipeActions.ts describes releaseVelocity against UIKit's undecayed pan velocity; describe it against the spec:AST-100 velocity, which is 0 after a still pause.

[Automated review]

… check, and releaseVelocity names the spec:AST-100 release velocity it compares
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
# Conflicts:
#	packages/core/src/motion/README.md

@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 waits on an engineering owner's decision on the swipe release rule: resolveRelease never fires on a fling and closes a release past the panel's width that is moving back, while current spec:AST-057 FR5, which current spec:AST-094 FR36 keeps for row swipes, requires a fling to fire the outermost entry and such a release to rest open. Either an engineering owner amends FR5 to this rule and records it by commenting /approve-spec <full head SHA> on the pull request that carries the amendment (the author may do so as an engineering owner), or the rule and its tests conform to FR5 as written. The changeset's release count and the releaseVelocity note now match the tests and spec:AST-100, so nothing else is outstanding here.

[Automated review]

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
# Conflicts:
#	packages/core/src/motion/README.md
# Conflicts:
#	packages/core/src/motion/README.md

@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 waits on an engineering owner's decision on the swipe release rule: resolveRelease never fires on a fling and closes a release past the panel's width that is moving back, while current spec:AST-057 FR5, which current spec:AST-094 FR36 keeps for row swipes, requires a fling to fire the outermost entry and such a release to rest open. Either an engineering owner amends FR5 to this rule and records it by commenting /approve-spec <full head SHA> on the pull request that carries the amendment (the author may do so as an engineering owner), or the rule and its tests conform to FR5 as written. Nothing else is outstanding here.

[Automated review]

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
# Conflicts:
#	packages/core/src/motion/README.md
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
@vjeux
vjeux marked this pull request as draft October 29, 2026 22:30
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 29, 2026

This branch was successfully deployed

1 active deployment
Preview — e8057971 Deployed Oct 27, 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