Skip to content

spec(AST-100): one velocity tracker and one release filter for every gesture - #8312

Merged
cixzhang merged 14 commits into
mainfrom
spec/release-velocity
Oct 25, 2026
Merged

cixzhang merged 14 commits into
mainfrom
spec/release-velocity

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Exact decision needed

Which code decides how fast a finger was moving when it lifted, for every Astryx gesture, and how components use that speed.

Existing authority searched

Decision

New system spec spec:AST-100:

  • FR1: one Core tracker owns a gesture's samples and reports velocity in px/s; every gesture component uses it.
  • FR2: the one release filter reads a moving finger as iOS does (a pan's velocity; a scroll view's release velocity for scrolling surfaces) and reads 0 when the finger stayed still for the stillness interval before lifting, the only case that differs from iOS; no selectable alternative filters. The blend itself is recorded in architecture:interaction-modality INV10.
  • FR3: flick, fling, and commit thresholds are per-component internal tuning on top of that one velocity, never a second estimator.
  • FR4: event-clock timestamps; the same for touch, pen, and mouse.
  • FR5: physical x/y; callers map onto their logical axis after the shared right-to-left check.

Rejected alternatives

  • A fixed time window with a stillness reset: reads quick flicks up to 2.5× faster than iOS.
  • iOS's blend with no stillness reset: flings after a silent rest.
  • Several selectable estimators or per-component release filters.

Claim scope and non-goals

Release velocity only. What each component does with it (thresholds, commit points, projection, settle) stays with the component; pinch velocity and browser scroll physics are outside.

Evidence and implementation relationship

The tracker is not on main yet: #8060 adds it, so this record is the contract its first landing must meet. At #8060's head the tracker offers four selectable strategies; its default is iOS's pan blend and never decays after a pause. FR2 keeps that blend, adds the scroll-view release blend and a stillness reset, and drops the other strategies. On main, BottomSheet (two-point window, event timestamps), Item's row swipe (px/ms on the handler clock), and Toast (whole-drag average on the wall clock) each estimate velocity their own way; Current-state impact moves them onto the tracker. The rebuild set above is separate implementation work. No implementation is included.

iOS measurements behind FR2 (iOS 26.2 simulator, UIKit app, contributed in review): UIPanGestureRecognizer's velocity(in:) is 0.2·v(latest interval) + 0.8·v(previous interval), updated only on moved samples, and fits every value UIKit reported on six recorded streams to 0.1 pt/s. A scroll view's release blends its pan's last two values once more, 0.25·pan(n) + 0.75·pan(n−1), reproducing four recorded UIPickerView releases to 0.03%. Neither decays through a silent rest. Replayed through a 100 ms window, the same four releases read 2,285.7 vs 933.4, 2,285.9 vs 2,114, 979.1 vs 1,016.2 and 1,160.9 vs 543.6 px/s. FR2 follows the blend and adds the stillness reset, so it differs from iOS only when a finger rests before lifting.

Writing rubric results

  • R1 narration: none.
  • R2 research in the record: none; estimator comparisons stay out of the record.
  • R3 evidence naming a change: none; verification names sample streams and states.
  • R4 rejected alternatives over one line or outside a decision: one, inside DEC-1.
  • R5 pull request or issue references remaining: none.
  • R6 ruled open questions still listing options: none.

Scope

  • This PR contains specification records only.
  • It records observable behavior and preserves equivalent internal implementations.
  • Any named internal mechanism is intentionally public, names its dependent caller/system, and explains why an equivalent implementation would not satisfy the contract.
  • Verification states the evidence layer and failure signal without prescribing CI job/workflow topology.
  • It records one intentional durable decision, not a rescue for a separable tagalong.
  • The canonical knowledge owner, affected current claims, and overlapping open work are named; any ownership collision is resolved or called out for human decision.
  • Adjacent API, visual, accessibility, composition, and implementation facts remain outside scope unless this exact decision depends on them.
  • Public text and artifacts contain no internal Meta context.

Validation

  • pnpm check:knowledge (CI lint)
  • Prettier
  • git diff --check

…s stay per component

New system spec: one Core tracker owns pointer samples and reports px/s; one
release filter (window plus a stillness reset) is the only lift velocity a
component reads; flick thresholds stay internal per component; event-clock
timestamps for touch, pen, and mouse; direction from the caller's axis after
the right-to-left check.
@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 5:14am 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, 5:48 AM UTC

@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 24, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 24, 2026
@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

Previews ready: Storybook and Sandbox were built with this PR's docsite. CI evidence will be added when available.

View Storybook for this PR · View Sandbox for this PR

@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 record needs an engineering owner's decision before it can be approved: it adds new release-velocity requirements (FR1–FR5, DEC-1), and the open draft spec:AST-094 in #8060, which the stacked gesture pull requests also carry, specifies the opposite reading in its FR17 (UIKit's estimate, never decayed through a rest). An engineering owner, including the author as one, records the decision by commenting /approve-spec <full head SHA> on this pull request, and that decision should also settle which record owns release velocity and the event-clock rule. Separately from that decision, a few fixes are needed:

  • Restate FR1, FR2 and FR5 as observable outcomes and move the single-tracker, sample-buffer and window wording to an architecture record, as architecture:knowledge-contracts INV18 requires for product specs.
  • In FR5, cite design:bidirectionality DR13 and architecture:internationalization INV11 instead of restating them and naming an unowned "shared right-to-left check".
  • In Platform support, cite spec:AST-013 FR1 together with FR3, and align the evidence line with the Verification table.

[Automated review]

Comment on lines +57 to +69
- **FR1 — One tracker owns the samples.** Core has one velocity tracker. A
gesture feeds it the contact's positions with their event timestamps, and it
reports velocity in CSS pixels per second on each physical axis. Every Core
and Lab component that reads the velocity of a pointer gesture uses it,
including a sheet's drag, a toast's swipe, a row swipe, paging, a wheel
picker, a navigation pop, and interactive dismiss. No component keeps its own
sample buffer or velocity math.
- **FR2 — One release filter.** The velocity a component reads when the
contact lifts comes only from the tracker's release filter: it uses the
samples within a fixed window before the newest sample, and reports 0 when
the contact stayed still for longer than a fixed stillness interval before
lifting. A component reads no other estimate and selects no alternative
filter. The window and the stillness interval are internal tuning values.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FR1 and FR2 name an internal module ("Core has one velocity tracker"), a data structure ("No component keeps its own sample buffer") and the filter algorithm ("the samples within a fixed window before the newest sample"), while Non-goals keep the tracker internal. architecture:knowledge-contracts INV18 keeps such mechanisms out of product specs unless they are an intentional public contract. Suggest stating the observable outcomes here (the same pointer stream gives the same release velocity in every gesture component; a pause longer than the stillness interval before the lift reads 0; the reading does not change with handler delay or pointer type) and moving single-tracker ownership to an architecture record listed in affects_architecture.

Comment on lines +78 to +82
- **FR5 — Direction comes from the caller's axis.** The tracker reports
physical x and y. A caller that acts along a logical axis maps the velocity
onto that axis after resolving the element's direction with the shared
right-to-left check, so a flick toward the inline end means the same in both
directions.

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 outcome is design:bidirectionality DR13 ("A gesture toward logical next/end MUST move toward logical next/end in both directions"), and direction resolution belongs to architecture:internationalization INV11 ("a lazy DOM read"). Citing both, rather than restating them and naming an unowned "shared right-to-left check", keeps one owner per fact (architecture:knowledge-contracts INV10).

Comment on lines +86 to +87
- Supported feature/engine floor: the package's supported browsers
(`spec:AST-013` FR3); pointer events with event timestamps.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

spec:AST-013 FR3 lists only the two explicit browser rows (latest desktop Chrome and iOS Safari); the supported tiers are FR1 (Baseline full support and Baseline minus two years). Suggest citing FR1 and FR3 here, and aligning the evidence line below (unit tests "prove FR1–FR5") with the Verification table, which proves FR1 with a source boundary check and FR3 and FR5 with component tests.

…tion-modality INV10; cite bidirectionality DR13 and internationalization INV11

@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 pull request needs an engineering owner’s decision before it can be approved: it adds new release-velocity requirements in spec:AST-100 (FR1–FR5, DEC-1) and a new architecture:interaction-modality INV10, and the open draft spec:AST-094 in #8060 specifies the opposite release reading in FR17 and one shared threshold table in FR1. An engineering owner, including the author as one, records the decision by commenting /approve-spec <full head SHA> on this pull request, and that decision also settles which record owns release velocity. Separately from that decision, three fixes are needed:

  • FR1 covers Lab gestures, but INV10 keeps the only velocity source unexported in @astryxdesign/core and forbids local velocity math, so a Lab gesture cannot conform; scope both to Core or say how Lab reaches the source.
  • architecture:interaction-modality still says it owns only modality-path and stale-outcome requirements and names no velocity source; update its Purpose and Owning code.
  • AST-100’s FR1 verification row, and the FR2 and FR3 failure cells, still test INV10’s mechanism; prove FR1 with one stream read in two components and leave the source-boundary check to INV10.

[Automated review]

Comment on lines +57 to +62
- **FR1 — One velocity for every gesture.** Every Core and Lab component that
reads the velocity of a pointer gesture, including a sheet's drag, a toast's
swipe, a row swipe, paging, a wheel picker, a navigation pop, and interactive
dismiss, reports the same velocity, in CSS pixels per second on each physical
axis, for the same contact motion. `architecture:interaction-modality` INV10
owns the single source of that velocity.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FR1 covers every Lab gesture (including a navigation pop) and names INV10 as the single source, but INV10 keeps that source unexported in @astryxdesign/core and forbids a component its own velocity math. @astryxdesign/lab reaches Core only through published subpaths, so a Lab gesture cannot conform either way. Scope FR1 and INV10 to Core, or say in INV10 how a Lab component reaches the source.

Comment on lines +149 to +155
- **INV10 — Gesture velocity has one owner.** In `@astryxdesign/core`, one
internal velocity tracker owns a gesture's pointer samples and reports
velocity, and its one release filter, a fixed sample window with a reset to
0 after a still contact, is the only lift velocity a component reads
(`spec:AST-100`). It is not exported. No component keeps its own sample
buffer, velocity math, or alternative release filter; a component's flick
threshold compares the tracker's release velocity.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Two things to reconcile with this invariant: “It is not exported” leaves no path for the Lab gestures AST-100 FR1 covers, and the record’s Owning code still says it “owns only the cross-cutting modality-path and no-stale-interaction-outcome requirements” (with a Purpose to match), so it never names the velocity source it now owns.


| Contract | Verification | Representative states | Mutation or failure expectation |
| -------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| FR1 | Source boundary check over Core and Lab plus tracker unit tests | each gesture component; a new gesture component; steady motion in x and y | A component with its own sample buffer or velocity math; a unit other than pixels per second |

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 row restates INV10’s source-boundary check: it fails “A component with its own sample buffer or velocity math”, which an implementation that gives the same reading would pass under FR1 and the Non-goals line on equivalent implementations. Suggest proving FR1 with one recorded stream read in two components and leaving the boundary check to INV10’s 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 pull request needs an engineering owner’s decision before it can be approved: it adds new release-velocity requirements in spec:AST-100 (FR1–FR5, DEC-1) and architecture:interaction-modality INV10, now including the rule that each package outside Core keeps a matching local tracker, and the open draft spec:AST-094 in #8060 specifies the opposite release reading in FR17 and one shared threshold table in FR1. An engineering owner, including the author as one, records the decision by commenting /approve-spec <full head SHA> on this pull request, and that decision also settles which record owns release velocity. Separately from that decision, two fixes are needed:

  • AST-100’s FR1 (“INV10 owns the single source”), Intent, DEC-1 and Current-state impact still describe one Core source, while INV10 now gives each package outside Core its own tracker; align them with INV10, or drop the Lab rule and say how Lab reaches Core’s tracker.
  • Platform support says “source-boundary” tests in Verification prove FR1–FR5, but AST-100’s Verification no longer lists one; drop that word.

[Automated review]

Comment on lines +61 to +62
axis, for the same contact motion. `architecture:interaction-modality` INV10
owns the single source of that velocity.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

INV10 now says packages outside Core “cannot import the tracker” and keep “one local tracker with the same release filter, window, and stillness interval”, so a Lab gesture’s velocity does not come from a single source. Suggest saying here that the velocity is the INV10 release filter’s reading (Core’s tracker, or the one local tracker a package outside Core keeps), and aligning the Intent (“One velocity source in Core owns the samples”), DEC-1 (“One tracker with one release filter”) and the Current-state impact line on later gestures.

Comment on lines +90 to +91
- Browser evidence: none required by this record; the unit, source-boundary,
and component tests in Verification prove FR1–FR5, and real-device feel for

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AST-100’s Verification no longer lists a source-boundary check (FR1 is now proved with one recorded stream read through two components); the only one is INV10’s row in architecture:interaction-modality. Suggest dropping “source-boundary,” here.

@vjeux

vjeux commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

One question on FR2, with UIKit measurements (iOS 26.2 simulator, UIKit app). Edited: added UIKit replay.

Should the one filter be UIKit's measured blend rather than the window filter FR2 names (a fixed window plus a reset after a fixed stillness interval)?

What UIKit does:

  • UIPanGestureRecognizer's velocity(in:) is 0.2·v(latest interval) + 0.8·v(previous interval), updated only on moved samples. It fits every value UIKit reported on six recorded streams to 0.1 pt/s.
  • A scroll view's release (scrollViewWillEndDragging) blends its pan recognizer's last two values once more: 0.25·pan(n) + 0.75·pan(n−1). It reproduces four recorded UIPickerView releases to 0.03%.
  • Neither has a timer. Moved samples at the same point bring the pan to 0 on the second (1000 → 800 → 0 pt/s). A perfectly silent rest keeps the last value: 1000 pt/s after 500 ms (synthetic touches), 915–1120 pt/s up to 1.5 s through HID. UIScrollView coasts after that silent rest exactly like a fling: 478–514 pt at 0.998/ms, against 502 pt with no rest.

How far the window filter is from it, on the four UIPickerView swipes replayed through each filter (px/s):

Swipe UIKit 100 ms window UIKit's scroll-view blend
200 pt in 50 ms 933.4 2,285.7 (2.45×) 933.4
200 pt in 100 ms 2,114 2,285.9 (1.08×) 2,113.9
200 pt in 200 ms 1,016.2 979.1 (0.96×) 1,015.9
a 90 pt flick 543.6 1,160.9 (2.14×) 543.6

The window filter more than doubles a quick flick's release. A flick's projected distance scales with velocity, so paging and dismiss choices near their thresholds would come out differently.

Until this is answered, the gesture recognizers ship UIKit's blend, with its constant fixed by recognizer kind: a pan reports velocity(in:), and a scroll view's pan reports the scroll view's release.

@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 pull request needs an engineering owner's decision before it can be approved: it adds new release-velocity requirements in spec:AST-100 (FR1–FR5, DEC-1) and architecture:interaction-modality INV10, and the open draft spec:AST-094 in #8316 reads release velocity the opposite way in FR17 and keeps every gesture threshold in one shared table in FR1, so that decision also settles which record owns release velocity. An engineering owner, including the author as one, records the decision by commenting /approve-spec <full head SHA> on this pull request. Separately from that decision, one fix is needed:

  • INV10's last sentence ("Packages outside Core … keeps one local tracker with the same release filter"), with AST-100's Intent and DEC-1, also reaches the native package, where spec:AST-082 FR11 has native components use the platform's own control or convention; scope it to Lab (or the web packages) as FR1 and INV10's Verification row already do, or reconcile it with AST-082 if native platforms are meant.

[Automated review]

Comment on lines +156 to +159
threshold compares the tracker's release velocity. Packages outside Core
cannot import the tracker; a gesture there keeps one local tracker with the
same release filter, window, and stillness interval, so it reports the same
velocity for the same motion.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

As written, this sentence also covers @astryxdesign/native, a package outside Core, where spec:AST-082 FR11 has a native component use "the platform's own control or convention" for sheets, navigation and scrolling, so its release velocity would be the platform's, not this filter's. AST-100 FR1, its Platform support (browsers, pointer events) and this record's INV10 Verification row ("a Lab-to-Core comparison") all stop at Core and Lab. Suggest scoping the sentence to Lab (or to the web component packages) and aligning AST-100's Intent ("every Astryx component") and DEC-1 ("wherever a package cannot reach it") with it; if native platforms are meant, say so in AST-100 FR1 and reconcile it with AST-082 FR11.

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
AST-100 settles on one release filter (meta#8312), so no velocity strategy is
offered by name; the one filter lands in meta#8320.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
AST-100 now specifies iOS's blend with a stillness reset: a contact still
for longer than the stillness interval before lifting releases at 0 (meta#8312).
Pan, scroll-view pan, pinch and rotation pass the lift's event time to the
tracker, which reads 0 when no sample came within velocity.stillness: 40
ms, Android's ASSUME_POINTER_STOPPED_TIME (AOSP VelocityTracker), since
iOS has no time cutoff and keeps the last motion. A lift while moving keeps
the blend's value, so the recorded iOS releases are unchanged. The
constants table accepts an AOSP source.

@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 pull request needs an engineering owner's decision before it can be approved: spec:AST-100 (FR1–FR5, DEC-1) and architecture:interaction-modality INV10 add new release-velocity requirements, FR2's stillness reset departs from the UIKit reading measured in this pull request's comments, and draft spec:AST-094 still reads velocity (FR17) and thresholds (FR1) the other way, so the decision also settles which record owns them. An engineering owner, including the author as one, records it by commenting /approve-spec <full head SHA> on this pull request. Separately from that decision, two wording fixes are needed:

  • Scope the sameness to one kind of surface in the architecture record's Purpose ("every Core and Lab gesture reads the same release velocity") and its Deciding specs line, and in AST-100's FR1 and FR3 failure cells, which a correct wheel-and-sheet pair would fail.
  • The Intent says "a flick of the same speed does the same thing in each of them", but FR3 keeps thresholds per component; say that components share the reading, not the outcome, and state DEC-1's harm the same way.

[Automated review]

Comment on lines +49 to +50
intent remains authoritative over stale work, and every Core and Lab gesture
reads the same release velocity. This record defines those shared interaction invariants, 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.

This still says every Core and Lab gesture reads the same release velocity, while AST-100 FR1 and FR2 now give dragged and scrolling surfaces different readings: on moved intervals of 500, 1,000 and 1,500 px/s, INV10 gives a sheet 1,100 px/s and a wheel picker 725 px/s. Suggest "every Core and Lab gesture of a kind reads the same release velocity", and the same scope in the Deciding specs line ("decides one release velocity for every gesture").

| -------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| FR1 | One recorded sample stream read through two different components, plus a unit check | a sheet drag and a row swipe fed the same stream; a Lab gesture and a Core gesture fed the same stream; steady motion in x and y | Two components reporting different velocities for the same stream, or a unit other than pixels per second |
| FR2 | Release velocity tests through a component over recorded iOS streams | recorded pan and scroll-view releases; acceleration before lift; a pause longer than the stillness interval before lift; one sample | A release that differs from the velocity iOS recorded for the same stream, or a non-zero release after a still pause |
| FR3 | Component tests at each threshold | a flick just under and just over a component's threshold; the same velocity across two components | Two components acting on different velocities for the same flick, or a threshold reachable from public API |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

With per-kind readings, a wheel picker and a sheet fed the same flick act on 725 and 1,100 px/s, both correct under FR2, but this row fails "Two components acting on different velocities for the same flick". Suggest "two components of the same kind" here, and the same in FR1's failure cell (line 119), whose "a Lab gesture and a Core gesture" names no kind.

Comment on lines +38 to +40
their finger, every Core and Lab component of that kind reads the same speed
from that motion, as iOS does, so a flick of the same speed does the same thing
in each of them, and a finger that

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FR3 keeps flick, fling and commit thresholds per component, so one speed can still fire a row's action (0.6 px/ms today) and leave a toast in place (1.2 px/ms). Suggest saying that every component of a kind judges a flick from the same speed, and stating DEC-1's harm (lines 132-133) as a disagreement in the reading.

…re cells; components share the reading, not the outcome
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 0d6f631

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Oct 25, 2026

@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.

Changes requested on one line: the release-velocity contract in spec:AST-100 and architecture:interaction-modality INV10 is owner-approved at this head, but AST-100's Platform support says no browser evidence is needed and that unit tests prove FR4, which conflicts with the current spec:AST-013 FR10 (real-browser evidence for browser event timing). FR4's promise that a late handler and the pointer type leave the velocity unchanged depends on the timestamps browsers put on pointer events, so please require real-browser evidence for FR4 in that line and in FR4's Verification row. That edit is a new commit, so an engineering owner, the author included, then records approval again by commenting /approve-spec <full head SHA>.

[Automated review]

Comment on lines +94 to +96
- Browser evidence: none required by this record; the unit and component
tests in Verification prove FR1–FR5, and real-device feel for
each component remains that component's evidence.

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 line says unit tests prove FR4 and no browser evidence is needed, but FR4 (lines 79-81) promises that a late handler does not change the velocity and that touch, pen and mouse give the same velocity for the same motion, which depends on the timestamps browsers put on pointer events. spec:AST-013 FR10 allows unit tests only for guards and deterministic state and requires real-browser evidence for browser event timing, and a newer record cannot silently override it (architecture:knowledge-contracts INV9). Suggested wording, with the matching evidence added to the FR4 Verification row (line 122):

- Browser evidence: FR4 reads browser event timestamps, so real-browser
  evidence shows that a delayed handler and touch, pen, and mouse input leave
  the release velocity unchanged (`spec:AST-013` FR10); unit and component
  tests prove FR1–FR3 and FR5, and real-device feel for each component remains
  that component's evidence.

Comment on lines +160 to +162
threshold compares the tracker's release velocity. Lab cannot import the
tracker; a Lab gesture keeps one local tracker with the same blend and
stillness interval, so it reports the same velocity for the same motion.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Non-blocking: nothing routes this Lab rule to a Lab path. The record's interaction route (line 39) lists INV1–INV4, INV7 and INV8, applies_to covers Core only, and spec:AST-100's path-applicability names BottomSheet, Item and Toast, so pnpm specs:for packages/lab/src/Chart/ChartZoom.tsx lists neither INV10 nor AST-100. Adding INV10 to the interaction route would put it in front of the first Lab gesture review.

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 9a6e9a4

@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 spec can be approved as written: spec:AST-100 and architecture:interaction-modality INV10 are owner-approved at this head, and the browser-evidence line now asks for real-browser evidence for FR4, as spec:AST-013 FR10 requires. Nothing else needs to change before merge. Two optional follow-ups would tighten it: adding INV10 to the record's interaction route would put the Lab rule in front of the first Lab gesture review, and FR2's "the one case where it differs from iOS" could also name Platform support's fewer-than-two-moved-samples zero, unless iOS reads 0 there too.

[Automated review]

reports for the same motion: a pan's velocity for a dragged surface, and a
scroll view's release velocity for a scrolling surface. It is 0 when the
contact stayed still for longer than a fixed stillness interval before
lifting, the one case where it differs from iOS. Every component of a kind

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Non-blocking: Platform support (lines 92-93) also gives a gesture with fewer than two moved samples a release velocity of 0, and the FR2 Verification row (line 122) checks its "one sample" state against "the velocity iOS recorded". Unless iOS reads 0 there too, this sentence names only one of two departures; something like "…before lifting; this and Platform support's fewer-than-two-moved-samples rule are where it differs from iOS" would keep FR2 and its test consistent.

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec c112133

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
AST-100 now specifies iOS's blend with a stillness reset: a contact still
for longer than the stillness interval before lifting releases at 0 (meta#8312).
Pan, scroll-view pan, pinch and rotation pass the lift's event time to the
tracker, which reads 0 when no sample came within velocity.stillness: 40
ms, Android's ASSUME_POINTER_STOPPED_TIME (AOSP VelocityTracker), since
iOS has no time cutoff and keeps the last motion. A lift while moving keeps
the blend's value, so the recorded iOS releases are unchanged. The
constants table accepts an AOSP source.
@cixzhang
cixzhang enabled auto-merge (squash) October 25, 2026 05:49

@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 spec can be approved as written: spec:AST-100 and architecture:interaction-modality INV10 are owner-approved at this head, and every current record they cite agrees with them, including the real-browser evidence for FR4 that spec:AST-013 FR10 requires. Nothing needs to change before merge. Two optional follow-ups would tighten it: adding INV10 to the modality record's interaction route would put the Lab rule in front of the first Lab gesture review, and FR2's “the one case where it differs from iOS” could also name Platform support's fewer-than-two-moved-samples zero, unless iOS reads 0 there too.

[Automated review]

@vjeux

vjeux commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Measured data on FR2's stillness reset next to a UIKit scroll view, for the record. No change is proposed here.

Setup. #8320's FR4 page ran in Mobile Safari 26.2 on the iOS 26.2 simulator, fed the exact HID streams the UIKit measurements used. Each stream ran 4× in Safari and 4× in UIKit in the same session. The UIKit side was a full-screen pan view, and a full-screen two-axis UIScrollView read through scrollViewWillEndDragging(_:withVelocity:targetContentOffset:).

Stream. Eleven 150 pt drags at 250 pt/s, at 0–90°, each ending in a 200 ms still hold before the lift.

  • UIKit pan (velocity(in:)): kept the moving speed in 44 of 44 runs.
  • UIKit scroll view (willEndDragging): released at 0 in 32 of 44 runs and kept the speed in the other 12. Meanwhile its own pan recognizer's velocity(in:) kept the speed in every run, so it's the scroll view that zeroes the release after a still hold, not the recognizer.
  • This page, both kinds: 0 in all 44 runs whose hold was silent (the lift 40 ms or more after the last move).
    • In the other 44 runs, the simulator delivered the last moving step late, 4–9 ms before the lift. Those read about the moving speed (45°: (−130, −130) pt/s; 90°: −218 pt/s).
    • In those late-step runs, UIKit's scroll view read 0 in 18 of 19.

So after a still hold, FR2's reset agrees with UIKit's scroll view more often than UIKit's pan does. The scroll view isn't consistent here, though (32 of 44). In an earlier measurement, a 1000 pt/s drag kept its fling after 500–1000 ms silent rests, so speed or the last interval seems to matter to it.

Every stream that lifts at its last sample matches UIKit within the replay's run-to-run spread (FR4). Those numbers are in #8320's Testing section.

@cixzhang
cixzhang merged commit 39690bf into main Oct 25, 2026
67 checks passed
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
AST-100 settles on one release filter (meta#8312), so no velocity strategy is
offered by name; the one filter lands in meta#8320.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
AST-100 now specifies iOS's blend with a stillness reset: a contact still
for longer than the stillness interval before lifting releases at 0 (meta#8312).
Pan, scroll-view pan, pinch and rotation pass the lift's event time to the
tracker, which reads 0 when no sample came within velocity.stillness: 40
ms, Android's ASSUME_POINTER_STOPPED_TIME (AOSP VelocityTracker), since
iOS has no time cutoff and keeps the last motion. A lift while moving keeps
the blend's value, so the recorded iOS releases are unchanged. The
constants table accepts an AOSP source.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
AST-100 now specifies iOS's blend with a stillness reset: a contact still
for longer than the stillness interval before lifting releases at 0 (meta#8312).
Pan, scroll-view pan, pinch and rotation pass the lift's event time to the
tracker, which reads 0 when no sample came within velocity.stillness: 40
ms, Android's ASSUME_POINTER_STOPPED_TIME (AOSP VelocityTracker), since
iOS has no time cutoff and keeps the last motion. A lift while moving keeps
the blend's value, so the recorded iOS releases are unchanged. The
constants table accepts an AOSP source.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
AST-100 now specifies iOS's blend with a stillness reset: a contact still
for longer than the stillness interval before lifting releases at 0 (meta#8312).
Pan, scroll-view pan, pinch and rotation pass the lift's event time to the
tracker, which reads 0 when no sample came within velocity.stillness: 40
ms, Android's ASSUME_POINTER_STOPPED_TIME (AOSP VelocityTracker), since
iOS has no time cutoff and keeps the last motion. A lift while moving keeps
the blend's value, so the recorded iOS releases are unchanged. The
constants table accepts an AOSP source.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
AST-100 now specifies iOS's blend with a stillness reset: a contact still
for longer than the stillness interval before lifting releases at 0 (meta#8312).
Pan, scroll-view pan, pinch and rotation pass the lift's event time to the
tracker, which reads 0 when no sample came within velocity.stillness: 40
ms, Android's ASSUME_POINTER_STOPPED_TIME (AOSP VelocityTracker), since
iOS has no time cutoff and keeps the last motion. A lift while moving keeps
the blend's value, so the recorded iOS releases are unchanged. The
constants table accepts an AOSP source.
@github-actions
github-actions Bot deleted the spec/release-velocity branch October 26, 2026 06:31

This branch was successfully deployed

1 active deployment
Preview — c1121336 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