Skip to content

chore(core): resolve a component's presentation so a caller's choice wins over touch defaults - #8298

Merged
cixzhang merged 1 commit into
meta:mainfrom
vjeux:uikit/f2-presentation
Oct 31, 2026
Merged

cixzhang merged 1 commit into
meta:mainfrom
vjeux:uikit/f2-presentation

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Small batch of the merge plan: one commit on main holding only this change. Its paragraph for the gestures README waits for that README, which comes with #8076; the resolver's file header carries the same contract.

Maintainer impact

A component that behaves differently under a finger, such as a menu that tracks the finger or becomes a sheet, must still let a caller pin the desktop behavior. Today components read presentation ?? 'popover', which cannot tell an omitted prop from an explicit 'popover', so a component that adopts a touch default would override a caller who asked for the popover. The menu, context menu and picker work that adopts touch modes builds on this resolver (#8104, #8627, #8080, #8125, #8201, #8159, #8162).

Intended invariant

A caller's explicit presentation always wins over the touch default (architecture:public-component-api INV16), and the surface equals useAdaptivePresentation's for every value on every device, so no caller sees a change:

presentation surface touch input modes
'popover' popover never
'bottom-sheet' sheet under a finger
'adaptive' sheet on a compact touch screen, else popover under a finger
omitted, whenOmitted: 'touch' popover under a finger

The modes follow the pointer, never the platform. The server and hydrating render never present touch modes, and no hint changes that (spec:AST-094 FR13). The hook listens only to the queries a request reads (architecture:react-component-runtime INV5): none for a pinned 'popover', the pointer for 'bottom-sheet' and the omitted 'touch' default, and the pointer and compact size for 'adaptive'. A touch-mode style is applied through touchModes, never nested under the iOS-only @supports gate.

Change

  • utils/presentation.ts: usePresentation and readPresentation (and the pure resolvePresentation) return the surface to render and touchModes, whether the component's touch input modes apply; usePresentation subscribes to each media query only while the request reads it. Internal: no component reads it yet.
  • hooks/useAdaptivePresentation.ts: its header points a component that also switches input modes to usePresentation, whose surface is its own.

Evidence

  • Failure before the change: an omitted presentation and an explicit 'popover' resolve the same, so a touch default cannot tell them apart; a server render given a touch hint must still answer the popover with no touch modes, a case that failed first. On an iPhone, a resolver that seeded its server snapshot from the live window fails both the server-render case and the hydrateRoot case; listening to both queries for every value fails the subscription case. A resolver that gives 'adaptive' the sheet without the coarse pointer, or reads compact by width only, fails the surface matrix; one that ignores the theme's md fails the themed case.
  • Success after the change: the unit matrix (iPhone, iPad, Android, desktop, Mac Safari, a fine-pointer narrow window, a phone in landscape, and the 767/768 and 499/500 compact edges, plus 'adaptive' under a theme whose md is 1000) covers each row above, a live pointer change and the one-moment read; on an iPhone, the server render and the hydrating render answer the popover with no touch modes, and after hydration the phone's sheet with touch modes; each request holds only the subscriptions it reads.
  • Product/runtime behavior verified unchanged: every value renders useAdaptivePresentation's surface on every device, and no component reads the resolver yet.

Scope

  • No intended public API, product behavior, visual direction, or policy change.
  • Product changes discovered during the work were removed or split.
  • Public text and artifacts contain no internal Meta context.

Testing

Local gates at 9f0595f, one commit on main d73908f holding only this change (4 files, +571/−1):

@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 31, 2026 1:26am 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
🟢 Reviewed (for maintainers only) Oct 31, 2026, 1:35 AM UTC

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

Component Size

Component Import before Import after Delta CSS before CSS after Delta
core/ContextMenu 76.2KB 76.2KB 0B 15.1KB 15.1KB 0B
core/DropdownMenu 74.1KB 74.1KB 0B 15.2KB 15.2KB 0B
core/Markdown 146.5KB 146.5KB 0B 18.0KB 18.0KB 0B
core/MoreMenu 74.4KB 74.4KB 0B 15.2KB 15.2KB 0B
core/MultiSelector 74.3KB 74.3KB 0B 14.8KB 14.8KB 0B
core/Pagination 86.7KB 86.7KB 0B 15.6KB 15.6KB 0B
core/PowerSearch 168.3KB 168.3KB 0B 20.1KB 20.1KB 0B
core/Selector 78.3KB 78.3KB 0B 15.3KB 15.3KB 0B
core/Table 85.8KB 85.8KB 0B 15.8KB 15.8KB 0B
core/TableCell 77.4KB 77.4KB 0B 15.5KB 15.5KB 0B
core/TableHeaderCell 77.0KB 77.0KB 0B 15.3KB 15.3KB 0B
core/Tokenizer 70.0KB 70.0KB 0B 16.7KB 16.7KB 0B
lab/CommentThread N/A 218.1KB N/A N/A 2.4KB N/A
lab/Comments N/A 222.7KB N/A N/A 2.5KB N/A
richtext/RichTextEditorToolbar N/A 214.3KB N/A N/A 454B N/A

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

Accessibility Audit

Status: The accessibility audit did not run on this pull request. It runs after merges to main and before each release; pull requests that touch components run it with the ci:heavy-lanes label.


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

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approval is on hold: besides the resolver commit, this pull request's compare carries the whole unmerged touch-presentation stack (28,468 authored lines), including the draft spec:AST-094 and additions no current spec covers: theme motion roles, the motion/experimental and gestures/experimental subpaths, and the new 744px/500px compact-touch rule. An engineering owner needs to approve or reject those draft requirements and identify the governing spec for the additions, or confirm that those decisions stay with #8052–#8076; 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. Independently of that decision, please:

  • merge the stack first, so this pull request contains only the resolver commit;
  • drop the platform adaptation condition or amend spec:AST-012 FR2, whose when vocabulary is closed;
  • keep the experimental hooks out of stable astryx search and astryx hook output without an opt-in (spec:AST-017 FR27) and restore the no-icon-set goldens;
  • reconcile spec:AST-094 FR4 and DEC-1 with the theme-tunable touch motion the stack ships.

[Automated review]

* is everything else. Pair `ios` with `pointer: 'coarse'` for a touch
* profile, such as touch motion tuned per platform.
*/
platform?: 'ios' | 'other';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

platform is a fifth when field, but spec:AST-012 FR2 closes that vocabulary to width, pointer, contrast and motion ("Future environment fields require explicit additions to this closed vocabulary"). Please remove it here or amend AST-012 FR2 through an owner-approved record change first.

Comment on lines +73 to +75
- **FR4 — Physics are not themable in touch presentations.** A touch
presentation MUST read its motion from the table on every theme. Themes keep
color, shape, type and spacing; the theme's duration and easing tokens keep

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FR4 says a touch presentation reads its motion from the table on every theme, while the same stack lets themes retune touch springs, durations and curves that the animator plays (packages/core/src/motion/resolveMotion.ts). One of the two needs to change before either lands.

@@ -1,4 +1,24 @@
Results for "experimental" (3)
Results for "experimental" (7)
name: useAnimator

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

These goldens now list four experimental hooks in stable astryx search output with no opt-in. The header of packages/cli/test/experimental-isolation.test.mjs calls a golden diff that names experimental the leak the test exists to catch (spec:AST-017 FR27).

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approval is on hold: besides its two resolver commits, this pull request's compare carries the whole unmerged touch-presentation stack (30,652 authored lines), including the draft spec:AST-094 and additions no current spec covers: theme motion roles, the motion/experimental and gestures/experimental subpaths and a new motion/reducedMotion.stylex export. An engineering owner needs to approve or reject those draft requirements and identify the governing spec for the additions, or confirm that those decisions stay with #8052–#8076; 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. Independently of that decision, please:

  • merge the stack first, so this pull request contains only the resolver commits;
  • build the compact-touch query from the theme's effective md point, as spec:AST-012 FR9 requires (packages/core/src/utils/touchPresentation.ts);
  • align spec:AST-094 FR8 with the 200 ms limit in design:reduced-motion DR2, reconcile FR4 and DEC-1 with the theme-tunable touch motion the stack ships, and cite spec:AST-090 only once it exists;
  • keep the experimental hooks out of astryx hook output without an opt-in (spec:AST-017 FR27), and announce or withhold the motion/reducedMotion.stylex export.

[Automated review]

Comment thread packages/core/src/utils/presentation.ts Outdated
* CSS: a style that implements a touch surface or input mode is applied with
* `touchModes` (`stylex.props(touchModes && styles.touchMode)`), never as a
* bare media key on a base style, so a pinned `'popover'` keeps none of it;
* touch-only rules inside it stay nested as IOS_TOUCH_STYLE_KEYS spell. On

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 clause predates the one-touch-model refactor: IOS_TOUCH_STYLE_KEYS adds @supports (-webkit-touch-callout: none), which touchPresentation.ts reserves for a style iOS WebKit itself requires, so an author following it would ship a touch mode's rules to iOS Safari only. Drop it, or say that touch-only rules key on TOUCH_QUERY.

Comment on lines +68 to +72
export const COMPACT_QUERY = DEFAULT_COMPACT_DEFINITION.query;

/** Both: presentations such as a sheet in place of a popover. */
export const COMPACT_TOUCH_QUERY =
DEFAULT_COMPACT_DEFINITION.coarsePointerQuery;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

These are built once from DEFAULT_COMPACT_DEFINITION, so a theme that overrides md (for example adaptations: {widthBreakpoints: {md: 700}}, which AppShell honours) never moves adaptive, the menu drill-in or this resolver. spec:AST-012 FR9 defines compact as below the effective md point; compactDefinition(points) with the nearest theme's effective breakpoints would follow it.

Comment on lines +107 to +110
place, and any other move fades through. A navigation push or pop
cross-fades over 0.5 s on the ease-in-out curve; a sheet, a full-screen
modal or an alert fades on its own presentation spring and drops its travel
and scale; menus open and close on UIKit's Reduce Motion spring. Motion 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.

A 0.5 s cross-fade and spring-timed fades exceed what design:reduced-motion DR2 allows ("MUST change opacity only and MUST finish in 200 ms or less"), and DR4 forbids a longer animation under reduced motion. That record is current and this one neither names nor amends it, while the stack's animator already uses the 200 ms fade. Please restate FR8 as DR2 and DR3, or propose the amendment to that record's owner.

follows.
- **FR7 — Exits animate as UIKit's do.** A touch presentation MUST play the
exit its UIKit counterpart plays. **Proposed; pending owner review:** this
departs from the draft `spec:AST-090` FR1 (exits off by default) and FR10

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-090 is not in the repository on main or at this head, so its owners have nothing to rule on and an approver cannot check what FR7 overrides. Cite it once it lands, or drop the amendment until then.

Comment thread scripts/sync-exports.js Outdated
Comment on lines +193 to +198
// Proposed; pending owner review (spec:AST-094 OQ4): the Reduce Motion
// media keys and fade constants, for a transition in another package.
'./motion/reducedMotion.stylex': {
source: './src/motion/reducedMotion.stylex.ts',
types: './dist/motion/reducedMotion.stylex.d.ts',
default: './dist/motion/reducedMotion.stylex.js',

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-094 has no OQ4, and no Changeset announces @astryxdesign/core/motion/reducedMotion.stylex; .changeset/touch-experimental-subpaths.md says nothing else is added to any subpath. Outside an /experimental boundary this becomes a stable import path on release. Keep it out of the exports map until an owner decides it, or announce it with that decision.

Comment on lines +36 to +40
// An experimental hook (spec:AST-017 FR27) is never a search result:
// search answers as it would without it.
if (doc?.stability === 'experimental') {
continue;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Search now skips experimental hooks, but astryx hook --list and astryx hook <name> still find them: discoverHooks scans every src/ directory except utils, __tests__ and node_modules, and the hook list reads no stability. That changes stable output without an opt-in (spec:AST-017 FR27); the same skip in hook discovery, plus a hook --list golden, would close it.

@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: the presentation resolver now keeps touch modes out of the server and hydrating render, and its CSS note no longer sends touch-mode rules to the iOS-only gate, so the resolver itself matches public-component-api INV16 and AST-094 FR13. This branch still carries #8076's gesture, motion, harness and build stack (15,626 of the 16,048 lines it changes outside the lockfile), so please land #8076 first and rebase onto main, or put the resolver's commits on main by themselves. Separately, #8076's gesture host listens only under a coarse pointer (GestureHost.ts:580-583), which contradicts react-component-runtime INV13 and AST-094 FR43 and should be fixed there before it lands.

[Automated review]

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 29, 2026
This pull request sat on meta#8076 and carried its old copies of the harness
meta#8045 and meta#8046 landed on main. meta#8360 carries meta#8076 with main's harness,
so after the merge this pull request's own diff is the presentation
resolver's commits. The one conflict, the gestures README, keeps both
sides: this pull request's paragraph on the presentation prop ends the
touch presentation section, and meta#8360's migration section follows it.

@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: the presentation resolver itself matches architecture:public-component-api INV16, but this branch now merges #8360 as well and carries 27,232 lines of the open #8076 and #8360 stack around the 422-line resolver. Please land #8360 and the pull requests it merges first and rebase onto main, or move the resolver's commits onto main by themselves. Before that stack lands, its gesture host should attach for every pointer, as architecture:react-component-runtime INV13 and spec:AST-094 FR19 and FR43 require, rather than only under a coarse primary pointer, and PressTracker should be built by a create… factory rather than a class.

[Automated review]

Comment on lines +12 to +22
* ONLY WHERE THE PRIMARY POINTER IS COARSE
*
* - Recognizers attach, and the host registers listeners, only while the
* primary pointer is coarse (`spec:AST-094` FR19, FR24; coarsePointer.ts).
* Where it is fine, an attach is recorded but nothing is installed: no
* document, window or element listener, no `touch-action` or other style,
* nothing delivered, so a finger on a touchscreen laptop gets the
* browser's defaults. The host follows the query: turning coarse installs
* it and styles every registration; turning fine cancels every touch in
* flight and removes it all. A development warning names the first attach
* under a fine pointer.

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 is the opposite of CURRENT: spec:AST-094 FR19 says "Recognizers attach for every pointer, with FR43's exceptions", FR43 says "Gestures attach for every pointer, a mouse, a pen, and a finger, not only a coarse primary pointer", DEC-21 rejects "keeping gestures and press tracking touch-only", and architecture:react-component-runtime INV13 says "The gesture host attaches for every pointer, with no touch-only query". As written, a mouse or pen on a fine primary pointer gets no pan, swipe, pinch, rotation or hover. Attach for every pointer, express FR43's exceptions as per-recognizer pointer filters, keep fine-pointer document listeners to the press (FR24), and reverse the tests at GestureHost.test.ts:3037-3158, in the pull request that owns the host.

return tap.numberOfTapsRequired === 1 && tap.numberOfTouchesRequired === 1;
}

export class PressTracker implements TouchConsumer {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

architecture:react-component-runtime INV13 keeps classes to the recognizer family ("its base, the arena, and the host") and builds "Every other module ... from create… factories"; its verification fails "a class outside the recognizer family". A createPressTracker factory, used by usePressTracker.ts:100, would meet it.

*
* Every touch motion passes through here, so the role a value plays is named
* in one place. Until the public motion-token set is decided, a role plays
* its table value: touch motion stays internal (`spec:AST-094` DEC-11), so

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-094 DEC-15 superseded this: "This supersedes DEC-11's "all internal" for exactly these three knobs". The same citation appears at :29, motion/README.md:23, :49, :73, :244 and animator.ts:149, :2244; citing DEC-15 and spec:AST-108 FR3 (roles play their table value until the knobs ship) would keep them accurate.

@vjeux

vjeux commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

The DEC-11 citations are in the motion files #8076 brings, not in this pull request's diff; #8677 corrects them to DEC-15 and spec:AST-108 FR3. Every pointer and the press tracker factory: #8665 and #8763, which merge before this.

@vjeux
vjeux marked this pull request as draft October 29, 2026 22:35
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 29, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 29, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 29, 2026
meta#8078's dependencies in one commit, so its compare link shows only its own change: meta#8627 503bc5b, meta#8360 045f666 (which carries meta#8076, meta#8073 and meta#8446) and meta#8298 8cb7c3a.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 29, 2026
… as a dependency

meta#8627 is now one commit on main; meta#8360 carries meta#8076, meta#8073 and meta#8446.

useMenuPress.ts: the trigger half lives in menuTriggerPress.ts, so it
takes meta#8360's trigger changes there. The trigger's holds run on the
long-press detector and pass FR29's 13 px drift allowance as each press
starts, under every primary pointer (FR43). The hold duration comes from
motion/uikit/longPress.ts. MENU_PRESS_TRIGGER_DRIFT_PX is re-exported
beside MENU_PRESS_LONG_PRESS_MS. The stray-click window is the shared
click guard's, from meta#8360.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 29, 2026
meta#8078 now takes meta#8627 503bc5b, meta#8360 045f666 and meta#8298 8cb7c3a.
handoffPosition's comment takes meta#8078's FR43 wording and keeps the
continued finger (FR33).
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 29, 2026
meta#8078 now takes meta#8627 503bc5b, meta#8360 045f666 and meta#8298 8cb7c3a.
useMenuPress.ts keeps meta#8078's detector type; the useMediaQuery import
has no reader left and goes.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 29, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 29, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 29, 2026
meta#8089 now takes meta#8080, which takes meta#8078 (with meta#8627, meta#8360 and meta#8298)
and meta#8186. The DropdownMenu spec's FR5 row takes meta#8080's list (FR26–FR32,
FR34 and FR35) and its state names FR35 verified by this PR; FR28's
cancel clear and FR34 stay pending on this line. Prettier re-pads the
table.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 29, 2026
meta#8089 now takes meta#8080, which takes meta#8078 (with meta#8627, meta#8360 and meta#8298)
and meta#8186. DropdownMenuTouch.ts registers UIKit's two menu springs for
the grow, from meta#8080, and keeps the springs table for the in-place
sub-menu; its header says so. The usage text takes meta#8080's wording,
meta#8089's popover sentence and this PR's in-place sub-menu sentence.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 29, 2026
meta#8080 now takes meta#8078 dcda3d0 (with meta#8627 503bc5b, meta#8360 045f666
and meta#8298 8cb7c3a) and meta#8186 c614025. The usage text keeps meta#8080's
wording and this PR's sentence on the popover beside its trigger.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 30, 2026
…wins over touch defaults

usePresentation and readPresentation resolve a component's
`presentation` prop: a caller's explicit value always wins over the
touch default (architecture:public-component-api INV16). The surface is
the popover or the sheet, and touchModes says whether the component's
touch input modes apply, the same on every touch device; a server or
hydrating render takes no touch modes. meta#8298's change, rebased onto main
as one commit; its gestures README paragraph waits for that README, and
the resolver's file header carries the same contract.
@vjeux
vjeux force-pushed the uikit/f2-presentation branch from 8cb7c3a to 24eb9bc Compare October 30, 2026 22:01
@github-actions github-actions Bot removed the needs:design-review Affects visuals — Design should review label Oct 30, 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: the resolver itself matches architecture:public-component-api INV16 and spec:AST-094 FR13, but its FR13 test renders only on the default desktop device, so it would still pass if usePresentation put touch modes into a phone's hydrating render. Please make setDevice(IPHONE) the first line of that test, and consider a hydrateRoot case as well. Separately, and not blocking, subscribing to the pointer and compact queries only for the requests that read them, as useAdaptivePresentation does, would keep a pinned 'popover' free of listeners.

[Automated review]

expect(result.current).toMatchObject({touchModes: true});
});

it('renders no touch surface or input mode on the server or while hydrating, and takes no hint that would (spec:AST-094 FR13)', () => {

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 case runs on the desktop device beforeEach sets, where a live read and the server snapshot both answer popover/false, so a usePresentation that seeded its server snapshot from the live window (touch modes in a phone's hydrating render, which spec:AST-094 FR13 rules out) would still pass. Making setDevice(IPHONE); the first line makes that variant fail while the current resolver still passes; a hydrateRoot case would cover the hydrating render directly.

Comment thread packages/core/src/utils/presentation.ts Outdated
presentation: AdaptivePresentation | undefined,
{whenOmitted = 'popover'}: UsePresentationOptions = {},
): ResolvedPresentation {
const touch = useTouchPresentation();

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: this subscribes to both the pointer and the compact query for every value, so a pinned 'popover' holds two listeners, and the omitted 'touch' default and 'bottom-sheet' hold one they never read. useAdaptivePresentation holds none for a fixed value (architecture:react-component-runtime INV5: optional behavior SHOULD acquire local resources only while enabled); a no-op subscription for the queries a request does not read would match it.

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 31, 2026
…wins over touch defaults

usePresentation and readPresentation resolve a component's
`presentation` prop: a caller's explicit value always wins over the
touch default (architecture:public-component-api INV16). The surface is
the popover or the sheet, and touchModes says whether the component's
touch input modes apply, the same on every touch device; a server or
hydrating render takes no touch modes, and the hook listens only to the
queries a request reads (architecture:react-component-runtime INV5).
meta#8298's change, rebased onto main as one commit; its gestures README
paragraph waits for that README, and the resolver's file header carries
the same contract.
@vjeux
vjeux force-pushed the uikit/f2-presentation branch from 24eb9bc to 8026021 Compare October 31, 2026 00:03
@vjeux

vjeux commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in 8026021: the FR13 test now starts with setDevice(IPHONE), and a hydrateRoot case checks that the hydrating render matches the server's popover with no touch modes before the phone's sheet follows; a resolver seeding its server snapshot from the live window fails both. usePresentation also subscribes only to the queries a request reads, so a pinned 'popover' holds no listener.

@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: the resolver matches architecture:public-component-api INV16 and spec:AST-094 FR13, and the FR13 test and scoped subscriptions now hold up, but the surface-parity matrix in presentation.test.ts would still pass a resolver that drops the coarse-pointer check for 'adaptive', reads compact by width only, or ignores the theme's md. Please add a fine-pointer narrow window (600×800), a phone in landscape (852×393) and the 767/768 and 499/500 edges to that matrix, plus a usePresentation('adaptive') case under a theme with md: 1000 like the one in touchPresentation.test.ts. Those cases catch all three variants and pass on this head.

[Automated review]

});

it("renders useAdaptivePresentation's surface for every value on every device", () => {
for (const next of [IPHONE, IPAD, ANDROID, DESKTOP, MAC_SAFARI]) {

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 matrix has no short screen, no fine-pointer compact screen and no compact edge, so a usePresentation that gives 'adaptive' the sheet without checking the coarse pointer (a desktop window under 768px wide), or that reads compact by width only (a phone in landscape), still passes every case. spec:AST-094 FR13 is verified at "coarse and fine pointer; narrow, wide and short" and spec:AST-012 FR9 at "width 767/768 at height 800; width 1024 at height 499/500; overridden md". Adding {coarse: false, width: 600, height: 800}, {coarse: true, width: 852, height: 393}, 767/768 by 800 and 1024 by 499/500 here, plus a usePresentation('adaptive') case under a theme with md: 1000 (as touchPresentation.test.ts:285-308 does), catches those variants and passes on this head.

…wins over touch defaults

usePresentation and readPresentation resolve a component's
`presentation` prop: a caller's explicit value always wins over the
touch default (architecture:public-component-api INV16). The surface is
the popover or the sheet, and touchModes says whether the component's
touch input modes apply, the same on every touch device; a server or
hydrating render takes no touch modes, and the hook listens only to the
queries a request reads (architecture:react-component-runtime INV5).
meta#8298's change, rebased onto main as one commit; its gestures README
paragraph waits for that README, and the resolver's file header carries
the same contract.
@vjeux

vjeux commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Added in 9f0595f: the surface matrix now covers a fine-pointer narrow window (600×800), a phone in landscape (852×393) and the 767/768 and 499/500 edges, with each new device's 'adaptive' answer pinned, plus a usePresentation('adaptive') case under a theme whose md is 1000. A resolver that drops the coarse check for 'adaptive' or reads compact by width only fails the matrix, and one that ignores the theme's md fails the themed case.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This looks ready to approve: the resolver matches architecture:public-component-api INV16, spec:AST-094 FR13 and spec:AST-012 FR9, and the added fine-narrow, landscape, edge and themed-md cases now fail for a resolver that drops the coarse-pointer check, reads compact by width only, or ignores the theme's md. One optional addition: the "keeps the sheet meaning of adaptive and bottom-sheet" case checks 'bottom-sheet' only on a desktop, so an iPhone assertion of {surface: 'bottom-sheet', touchModes: true} would pin that row of the description's table.

[Automated review]

Comment on lines +175 to +178
expect(resolve('bottom-sheet')).toMatchObject({
surface: 'bottom-sheet',
touchModes: false,
});

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: this is the only client-side check of 'bottom-sheet''s touchModes, and it runs on the desktop, so a resolver that never gives 'bottom-sheet' touch modes still passes every case. An iPhone assertion would pin the "with touch modes under a finger" half of this case's title and the description's table:

setDevice(IPHONE);
expect(resolve('bottom-sheet')).toEqual({surface: 'bottom-sheet', touchModes: true});

This branch was successfully deployed

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

Labels

ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) 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