Skip to content

feat(core): UIScrollView zoom constants and the zoom stage geometry - #8147

Draft
vjeux wants to merge 1504 commits into
meta:mainfrom
vjeux:uikit/c24-zoom-geometry
Draft

vjeux wants to merge 1504 commits into
meta:mainfrom
vjeux:uikit/c24-zoom-geometry

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #8320 (the gesture host) and #8340 (an animator fix). Review only this PR's change: vjeux/astryx@f5ae6b7...848f031

Why

Lightbox's touch zoom (#8148) has to feel like the iOS Photos viewer, which people compare it with on their own phones: a pinch past the maximum gives and eases back, and a double tap fills the screen. A hard stop at the limit, or a bouncy spring, shows at once. This PR adds the measured values and the geometry #8148 builds on; nothing renders differently yet.

What led to it

The UIKit-parity work for Astryx measures each touch behavior in a small UIKit test app and in Photos (iOS 26.2 simulator) and records every number with its source (spec:AST-094 FR1–FR4). The zoom is the first Lightbox piece.

Options and tradeoffs

  • The settle back from past a limit: a spring (common on the web) overshoots where Photos does not. UIKit runs one eased animation whose length follows the zoom ratio, 0.1 s + 0.3 s·|ln ratio|, at most 0.4 s; this PR uses that.
  • The double-tap zoom as a number (2×) or as a rule: Photos zooms a landscape photo 3.261× and a portrait one 1.449×, so it is stored as the rule "fill the screen".
  • Theme tokens or internal constants: internal, because touch motion has no theme surface (feat(core): the internal touch foundation: press tracking and the animator #8076).

Why this choice

Measured values with their source can be checked (a test enforces a source on every entry), and pure geometry is testable without a browser. One guess remains and is flagged: the zoom when the photo already fills the screen (2×).

What is in it

  • motion/uikit/zoomMedia.ts: past a maximum M the zoom reads M·(2 − M/s), s the pinch's scale; past a minimum m, m ÷ (2 − s/m); the settle timing above; a maximum of 6 times the photo's pixel size.
  • Lightbox/zoomStage.ts: where a zoomed photo rests, the transform that keeps a point under the fingers, the drag's rubber band, the double-tap and release targets, and the settle as compositor animations.

How to check it

Nothing changes on screen in this PR. With #8148 on top, in the "With zoom" story on a phone:

  1. Pinch the photo past its maximum and let go: it eases back within 0.4 s, without bouncing.
  2. Double-tap a landscape photo: it fills the screen's height.

The unit tests pin each measured number (pnpm vitest run packages/core/src/Lightbox packages/core/src/motion, all passing).

Evidence

  • Size of import {Lightbox} (gzip): JavaScript 52,420 → 52,679 bytes, CSS unchanged.
  • No public API; a [chore] changeset.

@vercel

vercel Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 27, 2026 12:57pm 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
@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Oct 24, 2026
@astracat-bot

astracat-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Oct 31, 2026, 5:43 AM UTC

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This pull request is on hold for a spec decision: the UIKit motion foundation and the zoom geometry rest only on draft spec:AST-094, so an engineering owner (the author qualifies, as an engineering owner) needs to approve or amend its DEC-1, DEC-2, FR1–FR3 and FR5–FR6, together with the amendments it proposes to current records, by commenting /approve-spec <full head SHA> on this pull request. Independently of that decision, two changes are needed:

  • Run the new interaction proofs inside an existing CI owner job or the required test join instead of the separately routed pr-interaction job, which spec:AST-030 FR11 and FR7 do not allow.
  • Rebase or retarget so this pull request carries only its own zoom commit once its predecessors land; as issued it carries 13,842 authored lines of the stack.

[Automated review]

Comment thread .github/workflows/ci.yml Outdated
# them, so a change to an instrument is proven by its negative controls
# before a component spec relies on it. Chromium only on these runners;
# see the harness README for running WebKit.
pr-interaction:

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 job routes itself (Check interaction scope) and no join requires it: the test join and .github/scripts/ci-test-join.mjs still list only test-ui, test-node, test-build, registry-contract and test-native-swift. spec:AST-030 FR11 says new coverage inside an existing surface must join those owners and must not create a separately routed lane or required context, and FR7 says an owned lane's failure must fail its join. Hosting these steps in an existing owner job (as pr-a11y hosts the other Chromium guards) or requiring this job from the test join would conform.

Comment thread packages/core/src/Lightbox/zoomStage.ts Outdated
* it rests with no gap at either edge. All constants come from
* motion/uikit/zoomMedia.ts and motion/uikit/scroll.ts.
*
* SYNC: When modified, update zoomStage.test.ts and useZoomStage.ts.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

useZoomStage.ts is not in the tree at this head (nor is the useZoomStage named on line 12), so this SYNC line points at nothing; point it at a file that exists, or add the hook together with its consumer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right, the line pointed at a file this commit doesn't have. Fixed in ff5cf88: the SYNC line names only zoomStage.test.ts here; #8148 adds useZoomStage.ts back together with the hook.

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

Modified Components

Lightbox (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 394B 394B 0B
Lines of Code 754 1045 -
Complexity Very High Very High (86) -

Component Size

Component Import before Import after Delta CSS before CSS after Delta
core/AlertDialog 56.9KB 57.2KB +294B 12.4KB 12.4KB 0B
core/AppShell 57.9KB 58.1KB +242B 11.6KB 11.6KB 0B
core/AvatarGroupOverflow 39.9KB 40.2KB +303B 6.4KB 6.4KB 0B
core/Banner 47.8KB 48.1KB +289B 9.9KB 9.9KB 0B
core/BaseTypeahead 54.3KB 54.6KB +251B 9.9KB 9.9KB 0B
core/BottomSheet 34.0KB 34.0KB 0B 7.9KB 7.9KB 0B
core/BottomSheetSwitcher 14.8KB 14.8KB 0B 3.3KB 3.3KB 0B
core/BreadcrumbItem 66.4KB 66.6KB +297B 11.0KB 11.0KB 0B
core/Button 43.3KB 43.5KB +273B 9.2KB 9.2KB 0B
core/Calendar 54.9KB 55.2KB +290B 10.0KB 10.0KB 0B
core/Carousel 50.2KB 50.5KB +316B 10.2KB 10.2KB 0B
core/ChatComposer 60.6KB 60.9KB +270B 10.8KB 10.8KB 0B
core/ChatComposerInput 55.7KB 56.0KB +264B 10.1KB 10.1KB 0B
core/ChatComposerTokenElement 55.7KB 56.0KB +264B 10.1KB 10.1KB 0B
core/ChatDictationButton 46.5KB 46.8KB +264B 9.5KB 9.5KB 0B
core/ChatLayout 48.8KB 49.1KB +273B 9.9KB 9.9KB 0B
core/ChatLayoutScrollButton 46.4KB 46.6KB +255B 9.6KB 9.6KB 0B
core/ChatSendButton 45.8KB 46.1KB +265B 9.2KB 9.2KB 0B
core/CheckboxInput 49.9KB 50.2KB +273B 9.3KB 9.3KB 0B
core/CheckboxListItem 54.0KB 54.3KB +244B 10.5KB 10.5KB 0B
core/ClickableCard 12.8KB 13.1KB +289B 6.9KB 6.9KB 0B
core/CodeBlock 60.5KB 60.8KB +278B 10.7KB 10.7KB 0B
core/Collapsible 11.7KB 12.0KB +291B 4.2KB 4.2KB 0B
core/CommandPalette 50.9KB 51.2KB +281B 11.8KB 11.8KB 0B
core/CommandPaletteItem 8.4KB 8.7KB +280B 3.5KB 3.5KB 0B
core/ComplexSelector 55.8KB 56.1KB +318B 10.6KB 10.6KB 0B
core/ContextMenu 77.7KB 78.0KB +275B 14.6KB 14.6KB 0B
core/ContextMenuItem 16.6KB 16.8KB +280B 5.2KB 5.2KB 0B
core/DateInput 99.9KB 100.1KB +243B 14.8KB 14.8KB 0B
core/DateRangeInput 69.4KB 69.6KB +266B 11.2KB 11.2KB 0B
core/DateTimeInput 110.1KB 110.3KB +248B 15.2KB 15.2KB 0B
core/DialogHeader 48.8KB 49.1KB +280B 9.8KB 9.8KB 0B
core/DrawerHeader 48.9KB 49.2KB +249B 11.2KB 11.2KB 0B
core/DropdownMenu 72.7KB 73.0KB +278B 14.7KB 14.7KB 0B
core/DropdownMenuCheckboxItem 16.1KB 16.4KB +284B 5.6KB 5.6KB 0B
core/DropdownMenuItem 15.4KB 15.7KB +287B 5.2KB 5.2KB 0B
core/DropdownMenuRadioItem 16.2KB 16.5KB +275B 5.6KB 5.6KB 0B
core/DropdownMenuSubMenu 58.1KB 58.3KB +270B 9.5KB 9.5KB 0B
core/FileInput 54.1KB 54.4KB +262B 10.3KB 10.3KB 0B
core/IconButton 43.3KB 43.6KB +273B 9.2KB 9.2KB 0B
core/Item 11.4KB 11.6KB +273B 4.7KB 4.7KB 0B
core/LayerProvider 63.3KB 63.6KB +288B 10.1KB 10.1KB 0B
core/Lightbox 51.2KB 51.4KB +249B 9.8KB 9.8KB 0B
core/Link 43.5KB 43.8KB +288B 7.6KB 7.6KB 0B
core/ListItem 53.5KB 53.7KB +268B 10.5KB 10.5KB 0B
core/Markdown 144.5KB 144.7KB +247B 17.4KB 17.4KB 0B
core/MobileNav 51.0KB 51.3KB +264B 10.1KB 10.1KB 0B
core/MobileNavToggle 46.2KB 46.5KB +259B 9.4KB 9.4KB 0B
core/MoreMenu 72.9KB 73.2KB +278B 14.7KB 14.7KB 0B
core/MultiSelector 76.2KB 76.4KB +270B 14.6KB 14.6KB 0B
core/NumberInput 55.7KB 56.0KB +278B 10.7KB 10.7KB 0B
core/Outline 28.6KB 28.9KB +290B 4.0KB 4.0KB 0B
core/Pagination 87.7KB 88.0KB +263B 15.3KB 15.3KB 0B
core/Popover 49.8KB 50.1KB +238B 12.1KB 12.1KB 0B
core/PowerSearch 155.1KB 155.3KB +263B 18.6KB 18.6KB 0B
core/RadioListItem 52.1KB 52.4KB +286B 9.7KB 9.7KB 0B
core/SegmentedControlItem 8.6KB 8.9KB +285B 3.9KB 3.9KB 0B
core/SelectableCard 12.8KB 13.1KB +294B 7.1KB 7.1KB 0B
core/Selector 79.1KB 79.4KB +268B 15.0KB 15.0KB 0B
core/SelectorOption 14.5KB 14.8KB +251B 5.1KB 5.1KB 0B
core/SideNav 58.0KB 58.3KB +272B 10.6KB 10.6KB 0B
core/SideNavCollapseButton 46.6KB 46.9KB +242B 9.5KB 9.5KB 0B
core/SideNavHeading 54.7KB 55.0KB +248B 10.1KB 10.1KB 0B
core/SideNavItem 54.5KB 54.8KB +266B 10.1KB 10.1KB 0B
core/Step 32.4KB 32.7KB +279B 5.3KB 5.3KB 0B
core/Stepper 47.4KB 47.7KB +265B 9.6KB 9.6KB 0B
core/Switch 48.4KB 48.7KB +285B 9.3KB 9.3KB 0B
core/Tab 9.7KB 10.0KB +294B 4.1KB 4.1KB 0B
core/TabMenu 51.6KB 51.9KB +276B 10.0KB 10.0KB 0B
core/Table 84.0KB 84.2KB +253B 15.1KB 15.1KB 0B
core/TableCell 78.8KB 79.1KB +267B 14.8KB 14.8KB 0B
core/TableHeaderCell 78.6KB 78.8KB +272B 14.8KB 14.8KB 0B
core/TableSelectionToolbar 52.4KB 52.6KB +278B 11.2KB 11.2KB 0B
core/TextInput 53.5KB 53.8KB +281B 10.5KB 10.5KB 0B
core/Thumbnail 50.7KB 50.9KB +274B 11.0KB 11.0KB 0B
core/TimeInput 86.8KB 87.1KB +266B 13.8KB 13.8KB 0B
core/Timestamp 27.5KB 27.5KB -1B 9.5KB 9.5KB 0B
core/Toast 59.8KB 60.1KB +258B 9.7KB 9.7KB 0B
core/ToggleButton 44.1KB 44.4KB +243B 9.3KB 9.3KB 0B
core/Token 29.6KB 29.9KB +289B 4.5KB 4.5KB 0B
core/Tokenizer 64.8KB 65.1KB +306B 11.7KB 11.7KB 0B
core/TopNav 51.9KB 52.1KB +277B 10.4KB 10.4KB 0B
core/TopNavHeading 54.2KB 54.4KB +231B 9.9KB 9.9KB 0B
core/TopNavItem 9.0KB 9.3KB +283B 3.8KB 3.8KB 0B
core/TopNavMegaMenu 54.2KB 54.4KB +268B 10.8KB 10.8KB 0B
core/TopNavMegaMenuItem 9.1KB 9.4KB +281B 4.1KB 4.1KB 0B
core/TopNavMenu 53.6KB 53.8KB +258B 10.1KB 10.1KB 0B
core/TreeList 32.5KB 32.8KB +274B 4.7KB 4.7KB 0B
core/Typeahead 61.5KB 61.7KB +268B 11.3KB 11.3KB 0B

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

Bundle Size Summary

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

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

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

55 of 502 shot(s) changed.

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

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

and 35 more.


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

@vjeux
vjeux force-pushed the uikit/c24-zoom-geometry branch from 8bdfcde to 64a8d76 Compare October 24, 2026 19:40
@github-actions github-actions Bot removed the needs:design-review Affects visuals — Design should review label Oct 24, 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.

This pull request is on hold for a spec decision: the zoom geometry and the motion and gesture foundation it carries rest only on draft spec:AST-094, whose FR4 and DEC-1 keep touch motion out of themes, while this head also ships theme motion roles and a platform adaptation condition outside the closed vocabulary of spec:AST-012 FR2. An engineering owner (the author qualifies, as one) records the decision by commenting /approve-spec <full head SHA> once the record and the code agree, or asks for the record to be amended first. Independently of that decision, these changes are needed:

  • Rebase or retarget so this pull request carries only its own zoom commit once its predecessors land; as issued it carries 29,365 authored lines of the stack.
  • Run the interaction proofs inside an existing CI owner job or the required test join instead of the separately routed pr-interaction job, which spec:AST-030 FR11 and FR7 do not allow.
  • Mark zoomMedia @experimental, since it is exported as uikit.zoomMedia from @astryxdesign/core/motion/experimental and spec:AST-017 FR21 requires the marker on every declaration there.

[Automated review]


import {registerMotionTable} from '../roles';

export const zoomMedia = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

zoomMedia is reachable as uikit.zoomMedia from @astryxdesign/core/motion/experimental (export * as uikit in motion/experimental.ts), but unlike the other declarations there it has no @experimental doc block, which spec:AST-017 FR21 requires ("Declarations carry @experimental"). Adding the doc block, and naming it in the uikit comment and in motion/experimental.test.ts, would conform.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right, FR21 asks for the marker on every declaration the experimental subpath exposes. Fixed in ff5cf88: zoomMedia carries a doc block with @experimental, the uikit comment names it, and motion/experimental.test.ts checks both (the doc-block check failed before).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Update in b4db6a7: with #8076's experimental-subpath drop, zoomMedia no longer sits on an experimental subpath. It stays inside core, re-exported from motion/uikit with registerZoomMediaTable(), so the marker and motion/experimental.test.ts went with the subpath.

Comment thread packages/core/src/Lightbox/zoomStage.ts Outdated
);
}

const ease = cubicBezier(zoomMedia.settle.curve);

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 fixes zoomMedia.settle.curve when the module loads, but the same curve is registered as the motion role zoomMedia.settle.curve and listed by astryx docs motion as something a theme can retune, so no theme value can reach the settle. Either keep the curve out of the role registry or let settleCurves take the curve resolved for the element, whichever the spec:AST-094 FR4 decision calls for.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right, the role was listed but no theme value could reach the settle. Fixed in ff5cf88: settleCurves takes the curve the element's theme resolves for zoomMedia.settle.curve (from #8148 on, the shared zoom stage, useZoomCore.ts, passes resolveMotion(stage, zoomMedia.settle.curve)). zoomStage.test.ts runs a linear theme value through it and failed before.

Comment thread .changeset/touch-presentation.md Outdated
'@astryxdesign/core': patch
---

[feat] `presentation="adaptive"` on DropdownMenu, ContextMenu, MoreMenu, Selector and MultiSelector chooses the bottom sheet on a compact touch screen: a coarse primary pointer and a viewport narrower than 744px or shorter than 500px (it was 768px wide and below). An iPad mini in portrait keeps the anchored popover; a phone in landscape now gets the sheet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The same query also decides sub-menu drill-in (useMenuDrillIn), and a sub-menu's presentation defaults to adaptive, so with no opt-in sub-menus now drill in on a phone in landscape and fly out on 744–768 px touch tablets. Worth naming here beside the root presentation.

@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 is on hold for its governing spec: the zoom geometry and the motion, gesture and theme-motion changes it carries rest on spec:AST-094, which is still proposed in #8378 and not on main, and no record yet covers the press-feedback selection lock or the new undefinedVariables build option. An engineering owner (the author is one) needs to land that record on main, or identify other current spec for these additions. Independently of that decision, these changes are needed:

  • Rebase or retarget so this pull request carries only its own zoom commit once its predecessors land; as issued it carries 34,536 authored lines of the stack.
  • Run the interaction proofs inside an existing CI owner job or the required test join instead of the separately routed pr-interaction job, which spec:AST-030 FR11 and FR7 do not allow.

[Automated review]

Comment thread packages/lab/tsconfig.json Outdated
@@ -2,7 +2,15 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
"rootDir": "./src",
"paths": {

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 paths let Lab typecheck imports of core modules that core's exports do not list (@astryxdesign/core/gestures/* includes the velocity tracker). A Lab file using them would fail for every Lab consumer, and architecture:interaction-modality INV10 says Lab cannot import the tracker. No Lab file uses them at this head; dropping the mappings here and in tsconfig.build.json would conform.

Comment thread .changeset/touch-selection-lock.md Outdated
'@astryxdesign/core': patch
---

[fix] Under a finger or pen, holding a control or a cell that shows the touch press (the press feedback surfaces) no longer starts text selection or the iOS callout, as on UIKit controls. Selection and the callout come back when the finger lifts. Text fields keep their selection, a link keeps its native menu and preview, and a mouse keeps desktop selection.

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-017 FR7 reserves [fix] for corrections; turning off selection and the callout on every touch press surface is a new behaviour no current record asks for, so it reads as a capability ([feat]) once its authority is settled.

'@astryxdesign/build': patch
---

[fix] `astryxStylex` resolves `defineConsts` values read from another file. With the `{stylexOptions}` form (the one Storybook uses), a file that read only constants from a `.stylex.ts` module lost that import once StyleX compiled it, so the constants never reached the CSS: a constant used as a media-query key failed the build ("Invalid empty selector") and one used as a value shipped as a custom property nothing declares. A constant defined on the library side of the layer split and read on the product side stayed unresolved in either form. A production build now also warns, naming the variable and the rule that reads it, when its stylesheet reads a StyleX-generated custom property that nothing declares; set `undefinedVariables: 'error'` to fail the build instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Besides the defineConsts correction, this [fix] adds a capability: the undefinedVariables option and a default production warning. spec:AST-017 FR7 tags compatible capabilities [feat], so splitting it into the fix and a [feat] for the option would conform.

/**
* Registers the table's springs and curves as motion roles a theme may
* retune; the limits, scales and duration law are geometry. Idempotent:
* Lightbox's touch module (Lightbox/useZoomStage.ts) calls it before its

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Lightbox/useZoomStage.ts does not exist at this head and nothing calls registerZoomMediaTable() yet; zoomStage.ts:12 has the same useZoomStage reference. Naming the module that will call it once it lands keeps the pointer true.

@vjeux

vjeux commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

On the review's points:

@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 is on hold for its governing spec: the zoom geometry and the motion, gesture and theme-motion changes it carries rest on spec:AST-094, which is still proposed in #8378 and not on main, and no record covers the press-feedback selection lock or the new undefinedVariables build option. An engineering owner (the author is one) needs to land that record on main, recording the decision with /approve-spec <full head SHA> on the pull request that carries it, or identify other current spec for these additions, and settle the selection lock and the build option too. Independently of that decision, these changes are needed:

  • Rebase or retarget so this pull request carries only its own zoom commit once its predecessors land; as issued it carries 37,668 authored lines of the stack.
  • Run the interaction proofs inside an existing CI owner job instead of their own routed interaction-scope and pr-interaction lane, which spec:AST-030 FR11 does not allow.
  • Drop Lab's tsconfig paths and the docsite and Sandbox resolvers for core modules core does not export: no Lab file uses them, and the gestures mapping reaches the velocity tracker Lab may not import.
  • Add real-browser evidence for the moved adaptive edges (852 × 393 and 768 × 1024 with a coarse pointer), as spec:AST-013 FR10 requires.

[Automated review]

Comment thread .github/workflows/ci.yml
echo 'applies=true' >> "$GITHUB_OUTPUT"
fi

pr-interaction:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The test join now requires this job, which meets spec:AST-030 FR7, but the proofs still run in a lane of their own with their own routing job (interaction-scope, line 3759). FR11 says new coverage inside an existing surface "MUST join those owners and MUST NOT create a separately routed lane or required context", and the same record names pr-a11y as the pull-request owner that keeps fast interaction guards. Running these steps inside that owner, or inside test-ui, would conform.

'springs.alertDismiss': 'spring',
'springs.alertPresent': 'spring',
'springs.banner': 'spring',
'springs.bouncy': 'spring',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Once released, a role id is theme input a theme pins, and this one (like springs.swiftUIDefault, springs.uikitDefault and timing.curves.caDefault) names a platform preset rather than a use. architecture:theme-tokens INV3 asks for purpose-led names, and #8378's FR4 renames these presets (springs.emphasis for .bouncy), so a theme that sets them now would break when that record lands.

Comment thread packages/cli/assets/docs/motion.doc.mjs Outdated
content: [
{
type: 'prose',
text: 'Touch motion has a table of springs, durations and curves measured on iPhone and iPad. Each value is a motion role named by its table path, such as `springs.sheetPresent` or `timing.curves.easeInOut`. A theme retunes a role through `motion.springs`, `motion.durations` or `motion.curves`, at its root or in an adaptation rule; a role it leaves alone keeps its measured default. A motion reads its roles as it starts, so a theme change applies from the next 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.

No shipped component calls resolveMotion or the animator at this head, so a theme that follows this section writes variables nothing reads yet. Saying so here and in the [feat] changeset, or shipping the fields with the first component that reads them, would keep the docs true.

@cixzhang

Copy link
Copy Markdown
Contributor

Posted by Cindy's agent on her behalf.

Cindy decided touch text selection (recorded in #8454, spec:AST-094 FR41): a tap never selects text; a long press on an element whose hold does something (a long-press menu, a stepper's hold-repeat, a slider thumb, other hold-driven controls) does that action with no selection or loupe; anything without a hold action keeps native long-press selection. The system adds no Copy item.

This PR's selection lock should follow that rule: suppress selection only on hold-driven elements, not on every pressable.

vjeux, could you update the PR to match?

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 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.

Approval waits on an engineering owner's decision on three deliberate departures from the current spec:AST-094 (the author may make it, as an engineering owner): either amend the record, and architecture:react-component-runtime INV13 where it repeats the rule, on main through a spec pull request approved there with /approve-spec <full head SHA>, or direct this code to conform.

  • FR4: the zoom settle's length (0.1 s + 0.3 s × |ln zoom ratio|, at most 0.4 s) stays plain constants instead of motion roles.
  • FR19 and FR24: a press tracker attached with perEventPointer registers touch listeners and paints a finger's press where the primary pointer is fine, which the code itself calls an exception pending an amendment.
  • FR19 and FR24: an element attached with actionGroup hands mouse, pen and finger presses to its recognizers where the primary pointer is fine, citing family:action-groups FR5, which is not on main.

Whatever that decision, please reduce this pull request to its 941-line zoom commit: the other 30,270 lines are open predecessor work, including two released [fix] changes (astryxStylex and the press controller) that the description's "Public delta and caller burden: None" does not mention.

[Automated review]

Comment on lines +319 to +328
export function settleSeconds(fromScale: number, toScale: number): number {
const {baseSeconds, secondsPerLnRatio, maxSeconds} = zoomMedia.settle;
if (!(fromScale > 0) || !(toScale > 0)) {
return baseSeconds;
}
return Math.min(
maxSeconds,
baseSeconds + secondsPerLnRatio * Math.abs(Math.log(toScale / fromScale)),
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The settle's length reads baseSeconds, secondsPerLnRatio and maxSeconds straight from zoomMedia.settle. Current spec:AST-094 FR4 says every spring, duration and curve a touch presentation plays MUST be a motion role, with a component's own measured roles joining through the generated table (DEC-5). This waits on an engineering owner: amend FR4 (or land component:Lightbox's roles) on main, or register these values as roles.

Comment on lines +207 to +208
* only (an FR19 exception for press feedback only, pending in
* `spec:AST-094`'s amendment): no control event, nothing kept from 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.

perEventPointer gives a press tracker passive touch listeners and paints a finger's press where the primary pointer is fine, which this comment calls an FR19 exception pending an amendment. Current spec:AST-094 FR19/FR24 and architecture:react-component-runtime INV13 allow a per-event pointer read only for spec:AST-057's row swipe, so this waits on an engineering owner's ruling.

Comment on lines +2073 to +2080
/**
* A press on an action group under a fine pointer (`family:action-groups`
* FR5): the group's recognizers and consumer take it, nothing else on the
* path does (`spec:AST-094` FR19), and the document listens only until it
* lifts (FR24). Where the primary pointer is coarse the host's own
* listeners serve the group, as every other recognizer.
*/
private beginGroupPress(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

beginGroupPress hands mouse, pen and finger presses to an action group's recognizers and consumer where the primary pointer is fine, citing family:action-groups FR5, which is not on main. Current FR19, FR24 and INV13 register no listener there, so this waits on an engineering owner's ruling.

Comment on lines +704 to +705
if (next === 'cancelled') {
this.releaseClaim(recognizer, this.movingIds);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Keeping a moving finger claimed after its recognizer cancels itself means cancelIfClaimed (GestureHost.ts:1650-1666) keeps cancelling that finger's touchmoves until it lifts, while no recognizer owns it. Current spec:AST-094 FR19 says a touchmove of a touch no recognizer owns MUST NOT be cancelled, with three listed exceptions, and this is not one of them; FR14, which the comment above cites, decides who takes a drag that starts inside a scroll area. Either the record gains this exception or the claim is released.

* @file zoomMedia.ts
* @input Nothing; data only
* @output Exports `zoomMedia`: UIScrollView
* zooming as a photo viewer uses * it — the overshoot past the zoom limits, the eased curve and duration

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 stray * it sits inside this @output sentence.

overshoot past the limits, the settle curve and its duration law, the absence
of scale momentum and of a two-finger-tap action, the content point under the
centroid staying put, Photos' aspect-fill double tap and its maximum zoom
(M-ZM2). A zoomed pan flicked into an edge coasts and bounces like any scroll

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 lists the maximum zoom as done "(M-ZM2)", but line 287 lists M-ZM2 as a capture still needed; one of the two should change.

* @input The source of utils/pressFeedback.ts and every module it imports
* @output The press feedback controller's import graph reaches no module
* under gestures/: a page whose only touch work is press feedback carries
* none of the gesture system (`spec:AST-094` FR24, FR43: pointer queries

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Current spec:AST-094 has no FR43, so this cites a requirement that is not on main.

Comment on lines +7 to +8
* @position Core's own barrel. Not exported from the package; Lab imports it
* through internal workspace imports.

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 has Core and Lab reach the touch modules through @astryxdesign/core/internal/touch, not through internal workspace imports.

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
@cixzhang cixzhang closed this Oct 28, 2026
@cixzhang cixzhang reopened this Oct 28, 2026
@cixzhang

Copy link
Copy Markdown
Contributor

Posted by Cindy's agent on her behalf: closed and reopened only to restart this PR's CI on the current workflow after tonight's queue-wide cancel; nothing in the PR changed.

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
…base of meta#8147

A busy-window proof can judge a component against a compositor-only control
animation recorded in the same window (startBusyControl, CONTROL_RGB,
busyWindowProof's control option), instead of a frame-rate bound that a
two-core runner's software compositor cannot meet.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
…interaction harness's busy-window control) into meta#8148
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
…ery pointer, wheel and trackpad scrolling; the harness's pooled busy proof) into meta#8147
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
…every pointer, wheel and trackpad scrolling; the harness's pooled busy proof) into meta#8148
@vjeux
vjeux marked this pull request as draft October 29, 2026 22:32

This branch was successfully deployed

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

Labels

CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants