Skip to content

feat(core): under a finger a selection mark changes in one frame - #8226

Closed
vjeux wants to merge 1 commit into
meta:mainfrom
vjeux:uikit/c06-instant-marks
Closed

vjeux wants to merge 1 commit into
meta:mainfrom
vjeux:uikit/c06-instant-marks

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

User need

On a phone, a tapped checkbox or radio fades between its colours over 125 ms, so the first frames show a pale box or two half-filled radios. UIKit changes a selection mark in the frame the selection lands. Under a finger, on every touch device, Astryx's marks now do too.

Evidence

iOS 26.2 Mobile Safari (the magenta ring is the finger) and Android 15 Chromium 158 (the dot is the finger), recorded on this PR's earlier code; the engine runs in Testing prove this head. Before is main:

Path Before After
iOS checkbox tap, ¼ speed before after
iOS radio tap, ¼ speed before after
iOS, main thread blocked 700 ms after the tap, ½ speed before after
Android checkbox tap, ¼ speed before after
Android radio tap, ¼ speed before after

Problem and solution fit

  • Under a coarse primary pointer, the colour transition of CheckboxIndicator and RadioIndicator takes the UIKit table's selection.mark.changeMs (0), from a new selection entry.
  • A mouse keeps the theme's fade. Under Reduce Motion a mouse's change is immediate, and a finger's already is.
  • CSS keys, not script: the right rule paints before hydration and costs nothing at runtime.

Current specification

  • spec:AST-094 FR1, FR2, FR13, FR24: measured values, the same on every touch platform, keyed on a coarse pointer, nothing changed under a mouse. The one-frame value stays an internal constant, because FR4 names no motion role for it.
  • design:reduced-motion DR2, DR3.

Public delta and caller burden

  • Under a finger only, a mark changes in one frame. No API, prop or theme value changes; colours, sizes, hover and the mouse's fade are unchanged.
  • One internal table entry, selection in motion/uikit, not exported from the package entry point.

Design and alternatives

  • Rejected: a shorter fade (it still blends a frame) and a per-platform gate (one touch model).
  • Not here: the checked row fill, the press release, list edit mode's circle.

Its predecessors #8331 and #8076 are merged, so this branch is now one commit on main. #8246 is stacked on it.

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

Local gates at #8246's head 1d2f527c7f, which holds this tree (4707533, on main ce667be) plus only SelectableCard files:

  • pnpm build, pnpm api:report --check, pnpm typecheck and pnpm review:pre (at this head and at feat(SelectableCard): under a finger the selection ring and border change in one frame #8246's): clean.
  • Strict ESLint on the changed files: no errors. Prettier, check:repo, check:changesets, check:knowledge and check:i18n-runtime: clean.
  • Units: Indicator, motion and SelectableCard, 879 passed. The four indicator cases in selectionMotion.test.ts fail on main's indicators.
  • Chromium, pinned to one physical core with no other test process on it: SelectionMarks and SelectableCardMark ×10 at 4 workers, 120 of 120; SelectionMarks ×10 at 1 worker, 70 of 70 (its busy-main-thread proof runs at 1 worker).
  • WebKit in the CI container profile (4 CPUs, 1 worker): both specs ×10, 120 of 120.
  • Still running: the Checkbox, CheckboxList and RadioGroup accessibility contracts, the full unit suite and repo-wide strict lint.

@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 Nov 3, 2026 6:47am 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
Contributor

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Nov 3, 2026, 6:59 AM UTC

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

Current run: The current CI run concluded skipped. Current analysis is unavailable.


Generated by PR Enrichment workflow | View current CI run

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

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: the 0 ms touch arm for the checkbox and radio marks rests only on the draft spec:AST-094 added here, whose FR4 intentionally narrows architecture:theme-tokens to pointer presentations. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request (the author may do so if they are an engineering owner), or amends the record; what has to be settled is whether touch presentations may leave the theme's duration and easing tokens, and how FR13's rule against component-owned media queries applies to the inline (pointer: coarse) arms. Independently of that decision:

  • Please land the stacked predecessors first and rebase, so this pull request is its own 687-line commit rather than 13,623 authored lines.
  • Please take the new pr-interaction CI job out with the harness and either fold it into an existing surface owner or admit it through an AST-030 amendment, since FR11 forbids a separately routed lane for new coverage.

[Automated review]

// Under a finger the mark changes in the frame the selection lands, as
// iOS's selection marks do (motion/uikit/selection.ts); a mouse keeps
// the theme's duration.
'@media (pointer: coarse)': MARK_CHANGE,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This arm is the primary change, and its only authority is the draft spec:AST-094 added in this pull request (FR4 narrows architecture:theme-tokens, pending owner review). Note too that the proposed FR13 says a component must not write its own media query for its touch presentation, which this inline (pointer: coarse) arm (here and in RadioIndicator.tsx) would contradict once AST-094 is approved as written.

Comment thread .github/workflows/ci.yml Outdated
if git fetch --quiet --no-tags --depth=1 origin "$BASE_SHA" \
&& git show "$BASE_SHA:.github/scripts/pr-scope-touches.sh" > "$helper"; then
scope=0
bash "$helper" "$BASE_SHA" '^(internal/interaction-harness/|playwright\.interaction\.config\.ts$|packages/[^/]+/src/|apps/storybook/)' || scope=$?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This job routes itself with its own path pattern over packages/[^/]+/src/ and apps/storybook/. spec:AST-030 FR11 says new coverage inside an existing surface must join that surface's owners and must not create a separately routed lane, and FR10 says a new test type is not a new surface. Fold these proofs into an existing owner, or admit the lane with an AST-030 amendment first.

where a switch outside any scroll view is needed.

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

Copy link
Copy Markdown
Contributor

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 further down) do not exist at this head, and scroll.ts, springs.ts and timing.ts cite a sources.test.ts that is really uikit/uikit.test.ts. Point these notes at files that exist, or drop them until those tables land.

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

  • Touch gating. core/src/Indicator/CheckboxIndicator.tsx:44 writes its own (pointer: coarse) check. This conflicts with AST-012 FR2 (touch is resolved once, through the closed adaptation vocabulary) and public-component-api INV16. Resolve through the shared touch resolver key so behavior matches the spec. If this behavior genuinely can't be expressed through the resolver, please raise it here as a spec question instead of shipping a separate check.

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

@vjeux

vjeux commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Agreed: one implementation.

@vjeux
vjeux force-pushed the uikit/c06-instant-marks branch from ecdce12 to 33c5a7d Compare October 24, 2026 23:41
@vjeux vjeux changed the title fix(core): under a finger a selection mark changes in one frame feat(core): under a finger a selection mark changes in one frame Oct 24, 2026
@vjeux

vjeux commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Pushed in 33c5a7d: the marks key on TOUCH_QUERY alone, the same on every touch device, and the text-selection commit is dropped in favor of #8270.

@vjeux

vjeux commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Pushed in 9c8c5e6: the marks' touch and Reduce Motion arms now import the shared keys (touchKeys.touch, reducedMotionKeys.instant) instead of spelling them.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approval needs an engineering owner to settle the governing spec for the one-frame checkbox and radio change under a coarse pointer: spec:AST-094 leaves which components get a touch presentation to each component's own record, which neither indicator has as current authority, and its FR4 requires touch durations to be theme motion roles rather than the build-time 0ms the marks play. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request (the author may do so as an engineering owner), or asks for the marks to keep --duration-fast under touch until the change is a motion role. Whatever is decided, these still need changes:

  • Land the predecessor pull requests and rebase onto main, which this branch conflicts with, so this diff is the 767-line selection-mark change; it carries 31,025 authored lines today.
  • The changeset says a mouse's change under Reduce Motion is "now immediate", but it already was.
  • packages/build/README.md still documents a production-build warning that no code emits.
  • The gestures README names a useMotionPresentation() hook that does not exist.
  • AST-094 asks for real-device evidence of the iOS Safari and Android Chrome behaviour, and the recordings come from the iOS simulator and a Chromium 158 setup.

[Automated review]

// immediate (design:reduced-motion DR3); a finger's already is.
// The resolver's and the policy's keys, spelled: an imported at-rule
// key lands in a different StyleX priority layer than a literal one.
'@media (pointer: coarse)': MARK_CHANGE,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This arm plays 0ms from a build-time mirror rather than a theme motion role. spec:AST-094 FR4 says a component "MUST NOT play a literal", and its Non-goals leave which components get a touch presentation to each component's own record (component:CheckboxIndicator is draft; RadioIndicator has none). It needs an engineering owner's /approve-spec on the exact head, or a motion role for the mark's change; the same applies to RadioIndicator.tsx:52.

'@astryxdesign/core': patch
---

[feat] Under a finger (a coarse primary pointer, on every touch device), a checkbox or radio mark now changes in the frame the selection lands, the way UIKit shows a selection mark. The box or circle used to fade between its two colors over the theme duration, so a tap first showed half-filled frames. With a mouse the marks keep the theme duration and easing, and under Reduce Motion a mouse's change is now immediate.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A mouse's change under Reduce Motion was already immediate: the base declares '@media (prefers-reduced-motion: reduce)': '0s' for every pointer, and this change only narrows that key to fine pointers. Suggest "stays immediate", or drop the clause.

Comment thread packages/build/README.md

Unsupported declarations fail the build by default with StyleX's replacement guidance. Set `propertyValidationMode` explicitly in `stylexOverrides` (modern API) or `stylexOptions` (legacy API) only when a migration needs the upstream `warn` or `silent` behavior.

A production build also checks the StyleX stylesheet for generated custom properties (`--<prefix><hash>`) that it reads but no stylesheet in the build declares, which browsers drop: a `.stylex.ts` export read in a style that is not a `defineVars`/`defineConsts` value, or one whose module never reached the build. It warns, naming the variable and the rule that reads it. Only stylesheets count as declarations, so a property the app sets at runtime (an inline style) is reported too: give it a name outside the StyleX class-name prefixes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No code at this head emits this warning: the revert "astryxStylex keeps no undeclared-variable warning" removed the check with its fixtures, tests and changeset. Drop this paragraph with it.

Comment thread packages/core/src/gestures/README.md Outdated
is right. Behaviour reads
`useTouchPresentation()` (SSR-safe), a decision taken once reads
`readTouchPresentation()`, and a component with no interaction to infer
from reads `useMotionPresentation()`: UIKit timings under a finger, theme

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

useMotionPresentation() is not defined anywhere at this head; name a hook that exists, or drop the sentence.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change needs an engineering owner's decision before it can be approved: spec:AST-094 FR4 says a component must read touch motion through motion roles and must not play a literal, and the checkbox and radio marks play a 0 ms value from the UIKit table's mirror with no motion role and no component record that owns their touch presentation. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request (the author may do so as an engineering owner); otherwise the marks keep --duration-fast under touch until their record names the presentation and the value is a motion role. These changes are needed either way:

  • Land #8331 and #8076 first and rebase, so this pull request carries only its own change instead of about 23,000 stacked lines.
  • Add recordings from a real iPhone in Safari and a real Android phone in Chrome, which AST-094's platform support section requires; simulator and emulator recordings do not cover it.
  • Fix the changeset's claim that a mouse's change under Reduce Motion is "now immediate" (it already was), and the stacked docs that name useMotionPresentation() or say a theme may retune motion roles.

[Automated review]

'@astryxdesign/core': patch
---

[feat] Under a finger (a coarse primary pointer, on every touch device), a checkbox or radio mark now changes in the frame the selection lands, the way UIKit shows a selection mark. The box or circle used to fade between its two colors over the theme duration, so a tap first showed half-filled frames. With a mouse the marks keep the theme duration and easing, and under Reduce Motion a mouse's change is now immediate.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The base indicators already set '@media (prefers-reduced-motion: reduce)': '0s' for every pointer, so a mouse's change under Reduce Motion was already immediate. Drop the clause, or say it stays immediate.

Comment thread packages/core/src/gestures/README.md Outdated
is right. Behaviour reads
`useTouchPresentation()` (SSR-safe), a decision taken once reads
`readTouchPresentation()`, and a component with no interaction to infer
from reads `useMotionPresentation()`: UIKit timings under a finger, theme

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

useMotionPresentation() exists nowhere in the tree. Point to useTouchPresentation() or drop the sentence.

@@ -71,3 +72,13 @@ export const navigation = {
*/
interactivePopFinishThreshold: 0.5,
} as const;

/**
* Makes each spring, duration and curve above a motion role a theme may

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This registers with no roles, so none of the values above becomes a role, and spec:AST-094 FR4 says a theme cannot override roles yet (DEC-11). Describe them as internal constants.

@@ -122,3 +124,16 @@ export const scroll = {
*/
scrollToTopRate: 0.99035,
} as const;

/**
* Makes each spring, duration and curve above a motion role a theme may

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same as navigation.ts: no roles are passed, so nothing here becomes a role, and no theme may retune a role yet (spec:AST-094 DEC-11).

Comment thread packages/core/src/motion/README.md Outdated
import {resolveMotion} from '../motion/resolveMotion';
import {registerSpringsTable, springs} from '../motion/uikit';

// Once, so the springs follow the theme's roles:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Themes do not set motion roles yet (spec:AST-094 FR4, DEC-11); this line and line 69 promise theme control that does not exist.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

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: the one-frame touch presentation for the checkbox and radio marks has no current component record, and its 0 ms is a build-time literal where spec:AST-094 FR4 requires a motion role. An engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request (the author may do so as an engineering owner), or asks that the marks keep --duration-fast under touch until the indicators' record names the presentation and the 0 ms is a role in the generated table. Whatever the decision, please:

  • Land #8076, #8331 and their predecessors first, so this diff holds only the selection-mark change (765 of its 22,487 changed lines).
  • Add real-device recordings in iOS Safari and Android Chrome, which spec:AST-094 requires for claims about those browsers.
  • Fix the changeset, which says a mouse's change under Reduce Motion is now immediate although it already was.

[Automated review]

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

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: no current record gives the checkbox and radio marks a 0 ms touch presentation (spec:AST-094 leaves each component's touch presentation to its own record), and the marks play a build-time constant where AST-094 FR4 requires a motion role. An engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner; the alternative is keeping --duration-fast under touch until the indicators' records name the presentation and the value is a motion role. Independently of that decision, please:

  • Land #8076 and #8331 first, or rebase once they land, so the diff is only the selection-mark change (765 of 21,162 authored lines).
  • Fix the changeset's line 5, which says a mouse's Reduce Motion change is now immediate although it already was.
  • Add recordings from a physical iPhone in Safari and a physical Android phone in Chrome, which spec:AST-094 requires for claims about those browsers.

[Automated review]

// immediate (design:reduced-motion DR3); a finger's already is.
// The resolver's and the policy's keys, spelled: an imported at-rule
// key lands in a different StyleX priority layer than a literal one.
'@media (pointer: coarse)': MARK_CHANGE,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No current record decides that this mark changes in 0 ms under a finger: spec:AST-094 leaves each component's touch presentation to the component's own record, and its FR4 asks for a motion role rather than a constant. An engineering owner decides this; RadioIndicator.tsx:52 carries the same arm.

'@astryxdesign/core': patch
---

[feat] Under a finger (a coarse primary pointer, on every touch device), a checkbox or radio mark now changes in the frame the selection lands, the way UIKit shows a selection mark. The box or circle used to fade between its two colors over the theme duration, so a tap first showed half-filled frames. With a mouse the marks keep the theme duration and easing, and under Reduce Motion a mouse's change is now immediate.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A mouse's change under Reduce Motion was already immediate: both marks set '@media (prefers-reduced-motion: reduce)': '0s' before this change. "Is now immediate" announces a change nobody gets; drop the clause or say it stays immediate.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

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: no current record gives the checkbox and radio marks a 0 ms touch presentation (spec:AST-094 leaves each component's touch presentation to its own record), and the marks play a build-time constant where AST-094 FR4 requires a motion role. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner; otherwise the marks keep --duration-fast under touch until the indicators' record names the presentation and the value is a motion role. Independently of that decision, please:

  • Land #8076 and #8331 first, or rebase once they land, so the diff holds only the selection-mark change (765 of its 20,885 authored lines).
  • Fix line 5 of the changeset, which says a mouse's change under Reduce Motion is now immediate although it already was.
  • Add recordings from a physical iPhone in Safari and a physical Android phone in Chrome, which spec:AST-094 requires for claims about those browsers.

[Automated review]

'@astryxdesign/core': patch
---

[feat] Under a finger (a coarse primary pointer, on every touch device), a checkbox or radio mark now changes in the frame the selection lands, the way UIKit shows a selection mark. The box or circle used to fade between its two colors over the theme duration, so a tap first showed half-filled frames. With a mouse the marks keep the theme duration and easing, and under Reduce Motion a mouse's change is now immediate.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A mouse's change under Reduce Motion was already immediate: both marks set '@media (prefers-reduced-motion: reduce)': '0s' for every pointer before this change, and this change only narrows that key to fine pointers. "Is now immediate" announces a change nobody gets; drop the clause or say it stays immediate.

// immediate (design:reduced-motion DR3); a finger's already is.
// The resolver's and the policy's keys, spelled: an imported at-rule
// key lands in a different StyleX priority layer than a literal one.
'@media (pointer: coarse)': MARK_CHANGE,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No current record decides that this mark changes in 0 ms under a finger: spec:AST-094 leaves each component's touch presentation to the component's own record (component:CheckboxIndicator is draft; RadioIndicator has none), and its FR4 asks for a motion role rather than a constant. An engineering owner decides this; RadioIndicator.tsx:52 carries the same arm.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approval needs a governing spec for this addition: an engineering owner has to decide whether CheckboxIndicator and RadioIndicator get this one-frame touch presentation, which no current spec grants, and whether its 0 ms may stay a table constant where spec:AST-094 FR4 asks for a named motion role. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and as an engineering owner the author may do so; either way, two changes remain:

  • Move the #8076 and #8331 work out of this branch (land those first, then rebase) so it carries only the 835-line selection-mark change instead of more than 20,000 changed lines.
  • Add a tap recording of the checkbox and radio marks from a real iPhone in Safari and a real Android phone in Chrome, which spec:AST-094 requires for claims about those browsers.

[Automated review]

// immediate (design:reduced-motion DR3); a finger's already is.
// The resolver's and the policy's keys, spelled: an imported at-rule
// key lands in a different StyleX priority layer than a literal one.
'@media (pointer: coarse)': MARK_CHANGE,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This one-frame touch presentation needs an engineering owner's decision: spec:AST-094 leaves which components get a touch presentation to each component's own record (neither indicator has a current one), and its FR4 asks for a named motion role where this arm plays the selectionMotionConsts constant. The same applies to RadioIndicator.tsx:52.

Comment on lines +327 to +328
// touch page here is the engine's own touch emulation; real iOS Safari and
// Android Chrome are the simulator and emulator recordings.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

spec:AST-094 Platform support asks for real-device evidence for every claim about iOS Safari and Android Chrome behaviour, so simulator and emulator recordings do not cover the “every touch device” claim; a tap recording from a real iPhone in Safari and a real Android phone in Chrome is still needed.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approval needs a governing spec for this addition: an engineering owner has to decide whether CheckboxIndicator and RadioIndicator get this one-frame touch presentation, which no current spec grants, and whether its 0 ms may stay a table constant where spec:AST-094 FR4 asks for a named motion role. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and as an engineering owner the author may do so; either way, two changes remain:

  • Move the #8076 and #8331 work out of this branch (land those first, then rebase) so it carries only the 848-line selection-mark change instead of more than 17,000 changed lines.
  • Add a tap recording of the checkbox and radio marks from a real iPhone in Safari and a real Android phone in Chrome, which spec:AST-094 requires for claims about those browsers.

[Automated review]

// immediate (design:reduced-motion DR3); a finger's already is.
// The resolver's and the policy's keys, spelled: an imported at-rule
// key lands in a different StyleX priority layer than a literal one.
'@media (pointer: coarse)': MARK_CHANGE,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This 0 ms arm is the decision the review is waiting on: spec:AST-094 leaves each component's touch presentation to its own record, and its FR4 asks that a touch duration be a motion role rather than a constant. An engineering owner records the decision with /approve-spec <full head SHA>; RadioIndicator.tsx line 52 is the same arm.

'@astryxdesign/core': patch
---

[feat] Under a finger (a coarse primary pointer, on every touch device), a checkbox or radio mark now changes in the frame the selection lands, the way UIKit shows a selection mark. The box or circle used to fade between its two colors over the theme duration, so a tap first showed half-filled frames. With a mouse the marks keep the theme duration and easing, and under Reduce Motion they still change at once.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

“on every touch device” is a claim about iOS Safari and Android Chrome behaviour, and spec:AST-094 Platform support asks for real-device evidence for those (spec:AST-013 FR10). The description's recordings come from the iOS simulator and an Android emulator; a tap recording from a physical iPhone in Safari and a physical Android phone in Chrome would cover it.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approval needs a governing spec for this addition: an engineering owner has to decide whether CheckboxIndicator and RadioIndicator get this one-frame touch presentation, which no current spec grants (spec:AST-094 leaves that to each component's own record), and whether its 0 ms may stay a table constant where spec:AST-094 FR4 asks for a named motion role. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Whatever the decision, two changes remain:

  • Land #8076 and #8331 first, or take them out of this branch, and rebase, so this pull request carries only the 848-line selection-mark change instead of 16,758 changed lines.
  • Add tap recordings of both marks from a physical iPhone in Safari and a physical Android phone in Chrome, which spec:AST-094 Platform support requires for the "every touch device" claim, or narrow that claim.

[Automated review]

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for this. Under a finger, the checkbox and radio marks now change in the same frame as the selection, and in Chromium phone emulation the difference is clear: 40 ms after a tap the current mark is still mid-fade, while this branch already shows the final colours. The mouse fade and both Reduce Motion paths are unchanged.

Waiting on an owner decision. No current spec gives these indicators a touch presentation. The touch-presentations spec (AST-094) leaves that to each component's own record, the CheckboxIndicator record is still a draft that does not mention touch, and RadioIndicator has no record. AST-094 FR4 also asks that touch durations be named motion roles rather than literals, and the 0 ms here is a table constant; the description marks FR4 as pending owner review. An engineering owner can provide or identify the governing spec, or record the decision by commenting /approve-spec <full head SHA> on this pull request. The author may do so, as an engineering owner. The alternative is to keep --duration-fast under touch until the indicators' record names the presentation and the 0 ms is a role.

Needed either way:

  • Scope: this branch also carries #8076 and #8331, and those two make up almost all of its 16,930 authored lines; the selection-mark change itself is 848. Please land them first, or drop them from this branch, and rebase so this pull request contains only the selection-mark change.
  • Device evidence: the changeset says the marks change in one frame "on every touch device", and AST-094 asks for real-device evidence for claims about iOS Safari and Android Chrome. The recordings so far come from an iOS simulator and from an Android emulator running Chromium. Please attach tap recordings from a physical iPhone in Safari and a physical Android phone in Chrome, or narrow the wording to what the evidence shows.

[Automated review]

// immediate (design:reduced-motion DR3); a finger's already is.
// The resolver's and the policy's keys, spelled: an imported at-rule
// key lands in a different StyleX priority layer than a literal one.
'@media (pointer: coarse)': MARK_CHANGE,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Under a coarse pointer this arm plays 0ms from a defineConsts mirror of selection.mark.changeMs. AST-094 FR4 asks that every touch duration be a motion role ("MUST NOT play a literal"), and no current record gives CheckboxIndicator or RadioIndicator a touch presentation (AST-094 Non-goals). This waits on an engineering owner: comment /approve-spec <full head SHA>, or keep --duration-fast under touch until the indicators' record names the presentation. The same applies to RadioIndicator.tsx:52.

'@astryxdesign/core': patch
---

[feat] Under a finger (a coarse primary pointer, on every touch device), a checkbox or radio mark now changes in the frame the selection lands, the way UIKit shows a selection mark. The box or circle used to fade between its two colors over the theme duration, so a tap first showed half-filled frames. With a mouse the marks keep the theme duration and easing, and under Reduce Motion they still change at once.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"on every touch device" is a claim about iOS Safari and Android Chrome. AST-094's Platform support asks for real-device evidence for those claims (spec:AST-013 FR10), and the recordings so far come from an iOS simulator and from an Android emulator running Chromium. Please attach physical-device tap recordings, or narrow this sentence to a coarse primary pointer.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

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 providing or identifying the governing spec for the one-frame checkbox and radio change under a coarse pointer: spec:AST-094 leaves which components get a touch presentation to each component's own record, which neither indicator has as current authority, and its FR4 asks for a motion role where the marks play a 0 ms table constant. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request (the author may do so, as an engineering owner), or asks for the marks to keep --duration-fast under touch until their record names the presentation and the value is a motion role. Whatever is decided, please land #8076, #8331 and the #8360 stack first, or drop them from this branch, and rebase: they are 27,521 of the 28,369 changed lines, beside the 848-line selection-mark change.

[Automated review]

// immediate (design:reduced-motion DR3); a finger's already is.
// The resolver's and the policy's keys, spelled: an imported at-rule
// key lands in a different StyleX priority layer than a literal one.
'@media (pointer: coarse)': MARK_CHANGE,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This 0 ms arm is the decision the review waits on: spec:AST-094 leaves which components get a touch presentation to each component's own record (component:CheckboxIndicator is draft and silent on touch; RadioIndicator has none), and its FR4 asks for a motion role where this plays the selectionMotionConsts mirror of selection.mark.changeMs. An engineering owner records it with /approve-spec <full head SHA>; RadioIndicator.tsx:52 carries the same arm.

@vjeux
vjeux marked this pull request as draft October 29, 2026 22:34
vjeux added a commit to vjeux/astryx that referenced this pull request Nov 2, 2026
…n prelude

StyleX 0.19.0's dev runtime keys a conditional rule whose prelude has more than one parenthesized condition by the prelude alone, so jsdom got only the first rule written under, say, @media (prefers-reduced-motion: reduce) and (pointer: coarse), and a test that read a later one read nothing. vitest.config.ts now names internal/test-utils/src/stylexInject.ts as the babel plugin's runtimeInjection: StyleX's own inject, plus each rule it drops, inserted once into a sheet of its own. Test-only: no StyleX code is patched, and the build keeps every rule as before. It comes out at the StyleX bump to a release that carries meta/stylex#1991, which fixes the key upstream.

Red first: the new stylexMultiConditionMedia test (two styles, and one style with two declarations, under one two-condition query) failed with StyleX's own injection; on meta#8226 + meta#8682 + main, Indicator/selectionMotion.test.ts failed 2 of 6. Both pass with it, and the full ui suite on main has the same results (15,951 passing, plus the new test).
iOS has no checkbox or radio control; a choice is marked on its row, and on
iOS 26.2 the mark changes in the frame the selection lands with no animation
of its own: the checkmark accessory of a single-choice list in Settings, the
completion circle in Reminders, a collection-view list's multiselect
accessory, with and without Reduce Motion. CheckboxIndicator and
RadioIndicator faded their box or circle between its two colors over the
theme's fast duration under every pointer, so a tap showed half-filled
frames first. Under a coarse primary pointer the fade now takes the UIKit
table's value, one frame (motion/uikit/selection.ts, mirrored for the
stylesheet in Indicator/selectionMotion.stylex.ts and held to it by
selectionMotion.test.ts). A mouse keeps the theme's duration and easing
tokens; under Reduce Motion a mouse's change is immediate, in the instant
key of the reduced-motion policy (design:reduced-motion DR3), and a
finger's already is.

The browser spec proves both in Chromium and WebKit: every frame of a tap
shows the mark wholly in its old or its new colour, a busy main thread
cannot stretch the change, and a mouse keeps the fade.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

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 deciding the governing spec for the one-frame checkbox and radio change under a coarse pointer; the review found no blocking code defect.

What has to be settled:

  • spec:AST-094 leaves which components get a touch presentation to each component's own record, and component:CheckboxIndicator and component:Indicator (which covers RadioIndicator) do not name one yet.
  • AST-094 FR4 asks that a touch duration be a registered motion role, while both indicators play 0ms from a defineConsts mirror of selection.mark.changeMs.

An engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner; the alternatives are for the indicators' records to name the presentation (as component:ProgressBar FR4 does) with its value as a registered role, or for the marks to keep --duration-fast under touch. In Chromium phone emulation a tap no longer starts the five 125 ms colour transitions, and the mouse fade and Reduce Motion behaviour are unchanged. Three new comments could be trimmed to their reason (non-blocking, noted inline).

[Automated review]

// mouse's change is immediate too.
// The keys are spelled out: an imported at-rule key lands in another
// StyleX priority layer than a literal one.
'@media (pointer: coarse)': MARK_CHANGE,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This arm is the decision the review waits on. spec:AST-094 leaves which components get a touch presentation to each component's own record, and component:CheckboxIndicator does not name one; AST-094 FR4 also asks that a touch duration be a registered motion role, while this reads a defineConsts mirror of selection.mark.changeMs. An engineering owner can approve it as written with /approve-spec <full head SHA>, or the record can name the presentation and the table can register the value as a role first.

Comment on lines +43 to +47
// Under a finger the mark changes in the frame the selection lands, as
// UIKit's marks do (motion/uikit/selection.ts). Under Reduce Motion a
// mouse's change is immediate too.
// The keys are spelled out: an imported at-rule key lands in another
// StyleX priority layer than a literal one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: "Under Reduce Motion a mouse's change is immediate too." restates the narrowed Reduce Motion key on lines 49–50. A shorter comment that keeps only the reason and the quirk:

// Under a finger the mark changes in the frame the selection lands, as
// UIKit's marks do. The keys are literal: an imported at-rule key lands in
// another StyleX priority layer.

import {selectionMotionConsts} from './selectionMotion.stylex';
import type {IndicatorProps} from './types';

/** How long the mark takes to change under a finger: one frame. */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: this comment restates the constant. Naming it TOUCH_MARK_CHANGE says the same thing without a comment (the same applies in RadioIndicator.tsx).

Comment on lines +47 to +51
// Under a finger the mark changes in the frame the selection lands, as
// UIKit's marks do (motion/uikit/selection.ts). Under Reduce Motion a
// mouse's change is immediate too.
// The keys are spelled out: an imported at-rule key lands in another
// StyleX priority layer than a literal one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: same as in CheckboxIndicator.tsx; the Reduce Motion sentence restates the narrowed key on lines 53–54.

@vjeux

vjeux commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Superseded: under the owner's ruling, main's indicators already play the theme duration on every pointer and change at once under Reduce Motion; SelectableCard's Reduce Motion arm lands in #8326.

@vjeux vjeux closed this Nov 3, 2026
cixzhang added a commit that referenced this pull request Nov 4, 2026
…y an at-rule prelude (#8695)

* test: the test runtime keeps every StyleX rule under a multi-condition prelude

StyleX 0.19.0's dev runtime keys a conditional rule whose prelude has more than one parenthesized condition by the prelude alone, so jsdom got only the first rule written under, say, @media (prefers-reduced-motion: reduce) and (pointer: coarse), and a test that read a later one read nothing. vitest.config.ts now names internal/test-utils/src/stylexInject.ts as the babel plugin's runtimeInjection: StyleX's own inject, plus each rule it drops, inserted once into a sheet of its own. Test-only: no StyleX code is patched, and the build keeps every rule as before. It comes out at the StyleX bump to a release that carries meta/stylex#1991, which fixes the key upstream.

Red first: the new stylexMultiConditionMedia test (two styles, and one style with two declarations, under one two-condition query) failed with StyleX's own injection; on #8226 + #8682 + main, Indicator/selectionMotion.test.ts failed 2 of 6. Both pass with it, and the full ui suite on main has the same results (15,951 passing, plus the new test).

* test: the StyleX test injection lets an insertRule error throw

The setup caught and dropped any error from inserting a rule StyleX's dev runtime had dropped, so a rule jsdom refused would have gone missing without a trace. It never fired: the full ui suite passes with the catch removed (15,952 tests). A refused rule now throws with the rule's text.

* test: the StyleX test injection restores every rule its dev runtime keys by a prelude alone

Review: StyleX 0.19.0's dev runtime keys a rule by an at-rule's prelude
alone not only under a top-level multi-condition query, but also under one
nested in a one-condition query (`@supports (…) { @media (…) and (…) {…} }`),
under a named container query, and under `@starting-style`; the injection
restored only the first case, so a test that read a later rule under the
others still read nothing.

The injection now keys rules with a copy of StyleX's own getSeenRuleKey,
which StyleX does not export, and restores every later rule whose key is an
at-rule's prelude rather than a selector, once each.

It also skips the group last-member rule, which the test setup refuses on
purpose (its marker now lives in jsdomRefusedRules.ts, shared with
setup.ts): a refusal of that rule threw out of the component's import. Other
refusals still throw. And it records the first rule under a key as present,
so the same rule compiled in two modules is not copied into the test sheet.

* Merge remote-tracking branch 'origin/main' into vjeux/stylex-multi-condition

* test: the StyleX test injection keeps its record across vi.resetModules()

CodeBunny on #8695: the record of restored rules lived in module state,
which vi.resetModules() resets while StyleX's injector keeps its keys, so
restored rules were inserted again and a later rule imported first after a
reset was lost. The record lives on globalThis; a guard case resets between
fixtures under one prelude.

Co-authored-by: Cindy Zhang <cindyxz@meta.com>

This branch was successfully deployed

1 active deployment
Preview — 4707533d Deployed Nov 3, 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. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants