Skip to content

fix(core): a resize during a pager's settle keeps where it was heading - #10592

Draft
vjeux wants to merge 1 commit into
meta:mainfrom
vjeux:pager-settle-resize-retarget
Draft

vjeux wants to merge 1 commit into
meta:mainfrom
vjeux:pager-settle-resize-retarget

Conversation

@vjeux

@vjeux vjeux commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

User impact

A pager resized mid-settle jumps or rests off its page. Narrowed 100 px, a three-per-view Carousel row ran back to the view it left. Widened, a row of row-wide items turned back too. A partial last page rested 60 px short; others jumped 60–190 px in one frame.

Problem and solution fit

  • Why? A resize stops the settle and sets the track on its index's new offset, which a renumbering makes another page. Held stops also ran ahead of the drawn frame.
  • Why does it harm the task? The row jumps, reverses, or stops short.
  • Why does it matter? A paging scroll view keeps heading where it was sent.

The settle now retargets with its velocity, on the stops the row draws.

Expected behavior and authority

spec:AST-094 FR6: "A new target MUST keep the current velocity". Amended here: "A resize during a settle retargets it. When the page offsets all scale by one ratio, the settle keeps its page and its fraction of a page. Otherwise a settle toward the last page ends on the last page. Any other ends on the page nearest its target's offset, a tie going the way it moves", with a resize row. component:Carousel FR7: "the last page rests where the row ends".

UIKit decays on toward the old target (iOS 26.2 simulator); FR6 and FR7 require the new pages.

Smallest restoration

  • Pages whose offsets all scale by one ratio keep their numbers, and the settle its fraction of a page.
  • Otherwise measure() maps a settle's target and the page last at rest by offset. The last page stays last; a tie goes the way the settle moves.
  • A resize mid-settle replays it from the track's value and velocity, read at one time, keeping its curve's kind.
  • A renumbered page is not reported back; a release's waiting report and a finger's waiting catch carry over to the new settle.
  • A held row's stops and span take each child's own translate off its rect, read right after it.

Evidence

  • Reproduction before the change: NOT-FOR-LANDING arms run these tests on main (7a7ca9f) and without the stop and span fix (36b98cd); each fails its cases.
  • Result after the change: every case passes (Testing).
  • Unchanged path: settles with no resize are frame-identical to main's.
  • Mutation-sensitive proof: twenty-four NOT-FOR-LANDING commits, under Testing.

Scope

  • One defect is restored.
  • Separable contradictory or unsettled tagalongs were removed or split.
  • No new public API, visual representation, default, or interaction model is hidden under the bug-fix label.
  • Public text and artifacts contain no internal Meta context.

Testing

  • The held-stops change is folded in here: its stop and span fix, its units, its both-direction browser case and its mutants.
  • Runs marked for this head ran on f4dde6e and its mutants, which differ from these only in one comment.
  • core tsc 0; strict ESLint 0 and Prettier clean on the changed files; comment sentence lengths 0 over on the changed lines; check:changesets passes; check:knowledge reports the records aligned.
  • On this head (35eba30), usePager.test.ts passes 155 of 155 and SnapPaging.test.tsx 20 of 20. On main 15 of the 17 new usePager cases fail; the other two guard this change's earlier heads. The unit suites of Carousel, SnapPaging, Lightbox, hooks, gestures and motion: pending on this head.
  • Pending: the 1-worker evidence runs on this head (Chromium on two pinned cores, WebKit on the CI WebKit job's command). They will replace the browser numbers below, which ran at 2 workers as quick feedback only. The retarget lines ran on builds of ff42f5d and its mutants, before the spacing, catch, stop and span fixes.
  • Pages sized to the row (four child pages as wide as the viewport): a widening (300 to 450 px) and a narrowing (400 to 300 px) during a settle toward page 2 end on page 2 at its new offset. One frame on, the row moves at most 1.5 times the step before, in pages. Mapped by offset, the settle rested on page 1 and on page 3; main jumps to page 2 at once (0.85 pages against 0.09).
  • Stops read from a moving row can share a shift (0.56 px seen in Chromium). The scale is fitted on the spacings between pages. So offsets [0, 300, 600, 900, 1200] widened to [0.56, 450.56, 900.56, 1350.08, 1800] keep page 2; a fit on the offsets themselves rests on page 1.
  • A still finger on a release's settle still catches it 85 ms after it lands when a resize retargets the settle in between; with the catch dropped, the new settle slides on under the finger.
  • Gapped pages: a rotation from 390 to 844 px with 8 px gaps, during a settle toward page 2, ends on page 2 under the same bound. A ratio from the row's width calls these pages new and rests on page 1.
  • Fixed-width views that regroup at the same count (300 to 400 px: stops 0/308/616 to 0/411/516) keep the pixels and rest on 411; the page-fraction remap stepped 40 px in one frame, main 365 px.
  • A target within half a pixel of halfway between two pages goes on the way the settle moves: narrowed to 205.34/410.67, a settle toward 308 rests on 410.67; main and the rounding rest on 205.34.
  • On pages sized to the row, a widening (300 to 700 px) during a drag keeps the page the drag began on, and a release easing back from past half of it settles there. Mapped by offset, the drag began on page 0, and main and the offset mapping both settle there.
  • Uniform pages: a narrowing (400 to 200 px) and a widening (400 to 800 px) during a settle toward page 2 hold the same 1.5-step bound and end on page 2. Main lands on page 2 at once, and a retarget from the old pixels misses: both fail (1.10–1.20 and 0.36 pages against 0.26).
  • A release's report that still waits at the resize goes out two frames after the new settle starts, also when the resize comes in the old settle's first frames; main sends it at the resize.
  • The frame oracle, in the three-up narrowing case. From the lift to the hand-back, the page records each frame: where the row shows, when the read ran, and each settle's own animation clock. The settle before the resize fits the paging integrator's curve through its keyframes, to 0.00 px at every keyframe. Each frame after the resize must sit on the integrator's curve to the new settle's last keyframe, from where the settle before stands at the new settle's own start, at its velocity then. Chromium must hold 0.5 px at the frame's clock; WebKit must hold 1 px over the read's span. WebKit shows a frame at the time of the read, not the rAF stamp: taking out each read's delay collapses the clock spread of the frames before the resize from 1.1–2.8 ms to 0.1–0.2 ms, and WebKit reads its clocks to the millisecond (±0.5 ms). A control holds each moving frame before the resize to 1 px on its own settle. The first frame after the resize may still show the settle before it, or the new curve between its start and its clock, where the browser commits it inside that frame (FR6). Once the new settle ends, a held frame must show its end.
  • These builds: Chromium 30 of 30 on two pinned cores with two busy loops and 20 of 20 at the host's load, every frame within 0.08 px; WebKit 30 of 30, every frame within 0.01 px. On main, 5 runs per engine: the row of three runs back to 308, and the split end rests at 411, not 471.
  • The held row's stops, in both directions: a drag of 70% of a view and a still lift settle a row of three per view onto its second view. Once two frames show the settle, the page narrows the row by 30 px. The new settle's last keyframe must sit on the view's layout stop, within 0.002 px. Each WebKit run adds the settle's speed times the gap between the stop item's rect and style reads. WebKit resolves each read at its own time, and a gap over a frame fails by name. At 0.002 px alone, WebKit failed 1 of 40 runs on an earlier head: 0.2192 px at 0.299 px/ms, with a 3 ms read span. On earlier heads without the stop fix, it missed in both engines every run (quick feedback). Runs on this head are pending.
  • The row-sized case: a one-up row of items as wide as itself (flex: 0 0 100%) widens by half about 100 ms into a flick's settle. It must end on the item it was heading to. Without the stop fix (78edbda's build) it rests on the item it left, 450 px, in 2 of 2 Chromium runs at 1 worker; with it, 2 of 2 end on 900 px. Full runs pending.
  • The span, in units: seven items move 0.3 px a millisecond, and each rect read comes 1 ms after the one before. A style read right after a rect sees that time. itemsSpan lands on the layout's span to 6 decimals; the rects alone land 1.8 px short. Under a scaled row it takes the scale off each rect before the translate. Through the component, a held row narrowed by 30 px reports a travel of 441, the layout's; without the fix it reports 439.
  • Mutation-sensitive proof, each a NOT-FOR-LANDING commit removing one rule; the retarget mutants' units ran on 78edbda, whose usePager.ts and its tests are this head's, and their browser runs are quick feedback; 5 browser runs per engine where named:
    • a settle toward the last page by offset too (7266d68): the split end rests at 411 in both engines; 2 units fail.
    • the page last at rest keeping its old index (4b27d19): its unit fails.
    • no re-sync of the last report (da33a20): its unit fails.
    • a resize ending the settle at once (aac71b1): the three-up case fails in both engines, with no settle after the resize; 10 units fail.
    • the velocity dropped at the retarget (6d36879): the three-up case misses by 10.8 px in Chromium and 7.9 px in WebKit; the stall unit fails.
    • the retarget starting from the old settle's end (18a93f4): misses of 141–261 px in both engines; 8 units fail.
    • the read and the play at two times (19a6f3a): the stall unit fails, and Chromium misses by 0.91 px.
    • a uniform pager's retarget from the old pixels (c677220): both uniform-page units fail.
    • the waiting report sent at the resize (ee64bc9): both report units fail.
    • the old settle's report timer left live (17d3576): the first-frames report unit fails.
    • scaled pages renumbering too (6d1f3ec): the row-sized, gapped, shared-shift and drag units fail.
    • the same pages replaying from the old pixels (0e0b808): the row-sized and gapped settle units fail.
    • the old layout's velocity kept (8a4e3fa): the uniform narrowing and a settle back to the page it left fail.
    • a tie that rounds (a93ada9): the tie unit fails.
    • as many pages as before counting as the same pages (047a771): the regroup unit fails.
    • the row's width ratio as the scale (05a7c38): the gapped and shared-shift units fail.
    • a fit on the offsets, not the spacings (723119f): the shared-shift unit fails.
    • the retarget dropping a finger's waiting catch (ddb3921): the catch unit fails.
    • the held stops keeping the children's translate (5d6d107): pending.
    • the held stops adding the animator's position as well (a22325e): pending.
    • a right-to-left row taking the translate off backward (105df70): pending.
    • the held row's span read as at rest (e064e0e): pending.
    • the span keeping the children's translate (ab6b4b0): pending.
    • the span taking the translate off before the scale (5b0ca9a): pending.
  • A 75 ms stall between the read and the play, from a NOT-FOR-LANDING hook: on the two-times mutant (be19237) Chromium misses by 23.0–23.9 px, 5 of 5; on this head (72ffa58), 5 of 5 pass. One time for the read and the play also covers keeping the track moving through the swap: that version changed no frame once both share the time, so it is not here.
  • Settles with no resize are unchanged: a still lift and a flick on main and on 6f396fd, an earlier head of this change, fit the same integrator curve in Chromium (still: from 487.08 px at −7 px/s; flick: from 373.10 px at 824 px/s), every frame within 0.12 px of it, 5 runs each. The edits since touch only resizes.
  • Hand-back: the frame check stops where the row goes back to native scrolling, and the rest is checked on its own (716). The held settle ends on the native rest in all 50 Chromium runs and in 28 of 30 WebKit runs. In the other 2 it ended 1 px off and stepped 1 px at the hand-back: WebKit resolves each child's rect at the time of its read, so the held row's span could mix a moving row's times. Those builds predate the span fix here, which takes each child's translate off its rect.
  • Lightbox's interaction specs, ×3 per engine: WebKit 294 passed, 0 failed; Chromium 307 passed. LightboxDismiss's busy-frame dismissal check failed, as on main's usePager. LightboxDismiss's reopening case missed its 5 s close once, a timeout logged for its own fix; it then passed 10 of 10 here and on main's usePager.
  • Interaction specs on these builds, ×3 per engine, 2 workers, no retries: Chromium 234 passed, 0 failed; WebKit 241 passed, 2 stopped by the touch harness's guard ("the page's own touch and pointer listeners took 48–49 ms").

Follow-ups

Follow-up When
A key press between a resize and the paging's next frame Not scheduled
A controlled index whose page a resize renumbers Not scheduled

Writing rubric results

R1 narration: none; the FR6 sentence and row state the system in the present tense.
R2 research: none in the record.
R3 evidence: the verification row names the tests and their cases, not a change.
R4 rejected alternatives: none added.
R5 change references: none.
R6 open questions: none added.

@vercel

vercel Bot commented Nov 7, 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 9, 2026 5:36pm UTC

Request Review

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

astracat-bot Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Nov 9, 2026, 5:58 PM UTC

@github-actions

github-actions Bot commented Nov 7, 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

@cixzhang

cixzhang commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 3d0c0b7

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

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

Requesting changes: the resize retarget and the split end behave as described in Chromium, and an engineering owner approved both record changes for this head, but three fixes remain.

  • After items are added or removed without the row resizing, the item whose stop the end page took keeps scroll-snap-align: none even once its stop sits more than half a step from the end, so keyboard and wheel scrolling skip a stop that a flick counts as a page (SnapPaging.ts:458-463); re-measure the paging when the row's children change.
  • The new spec:AST-094 FR6 sentence says a resize "retargets" the settle, but measure() ends it at once on the new page (a 218 px jump in Chromium), which FR6's own "A new target MUST keep the current velocity" rules out; reword the sentence to say the settle ends at once, or keep the velocity in code.
  • component:Carousel FR5 ("native snap's per-item stops") and FR7 ("each direct child's start edge") still say every item is a stop, which contradicts the new "Split end" state; amend them to name that exception.

Non-blocking suggestions are inline.

[Automated review]

Comment thread packages/core/src/hooks/usePager.ts Resolved
// The same page, renumbered: the owner hears it only through a
// release's report still waiting.
if (pendingReport == null) {
report('controlled');

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: with a controlled index, this silent sync leaves the owner's index naming another page, and usePager's controlled sync never re-runs because no onIndexChange fires; a later index equal to the new number then moves nothing. No caller passes a controlled index with child pages today, but the module doc's "A controlled index wins" no longer holds here.

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.

Left as is: no caller passes a controlled index with child pages. A caller that does would need its own sync, in a separate change.

@vjeux
vjeux force-pushed the pager-settle-resize-retarget branch from 3d0c0b7 to 6f396fd Compare November 9, 2026 08:08
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Nov 9, 2026
@cixzhang

cixzhang commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 6f396fd

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

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

Requesting changes. The Carousel fix works, and an engineering owner approved the spec:AST-094 FR6 change for this head, but one blocking issue remains.

  • retargetSettle() in usePager.ts restarts a uniform pager's settle from the old layout's pixel offset, so a Lightbox gallery resized or rotated mid-settle first shows an earlier photo (when the stage widens) or empty space past the last photo (when it narrows near the end) and then slides, where the base shows the target photo at once; keep the shown page fraction for uniform pages by scaling the offset and velocity by the new stride over the old one (or keep the immediate landing there), and add a widen and a narrow uniform-pager test.

Two smaller notes are non-blocking: the comment above the renumbering block restates the branches below it, and the silent renumber leaves a controlled index naming the old number.

[Automated review]

Comment thread packages/core/src/hooks/usePager.ts Resolved
Comment thread packages/core/src/hooks/usePager.ts Resolved

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

Requesting changes. Last round's two findings are fixed (uniform pages now keep their fraction of a page through a resize, and a release's report keeps its two-frame wait), and the spec:AST-094 FR6 change carries spec-owner approval at this head, but one blocking issue remains.

  • Miss: measure() remaps a renumbered settle to the page nearest its old pixel offset and replays child pages from the old pixels (usePager.ts:996-1011, :1036-1037), so a Carousel snap-paging row whose items follow the row or viewport width (cqi, vw) rests on the item the person left when it widens mid-swipe, and skips an item when it narrows, where the base rests on the swiped-to item; map the target and the shown position through the item they sit on (for example, keep the index and scale by the page fraction when the page count is unchanged, as the uniform branch does) and add a widen and a narrow case with viewport-relative items.

The silent renumber under a controlled index and the repeated animated-scroll solve stay non-blocking.

[Automated review]

Comment thread packages/core/src/hooks/usePager.ts Resolved
@vjeux
vjeux force-pushed the pager-settle-resize-retarget branch from 95aa8c8 to c4319b4 Compare November 9, 2026 13:03
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Nov 9, 2026

@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 decision on the spec:AST-094 FR6 change, and two fixes are needed either way.

Spec decision: this head adds a new FR6 requirement, "A resize during a settle retargets it. With as many pages as before, the settle keeps its page and its fraction of a page. With another count, a settle toward the last page ends on the last page, and any other on the page nearest its target's offset", with its "FR6 resize" verification row. The earlier /approve-spec comments name earlier heads, and the same-count sentence is new here. An engineering owner records the decision by commenting /approve-spec c4319b445d09484836f1d5f737952e75ef16ff61; the author may do so as an engineering owner.

Blocking:

  • In usePager.ts (lines 1013-1026), a resize that keeps the page count maps the shown position by its page fraction, so a fixed-width view-paging Carousel whose views regroup (the three-per-view story widened from 300 to 400 px) jumps 94 px in one frame mid-settle, which FR6's retarget rule and its "a jump … fails" verification row forbid; decide "same pages" from the items (or from offsets that scaled with the row), keep the pixels otherwise, and add a fixed-width unit case.
  • The changeset says the row "no longer jumps", but the fixed-width same-count case above still jumps, so fix the jump or narrow the sentence to the cases the tests cover.

The twelve new unit cases and the two new browser cases fail on main and pass on this head in Chromium.

[Automated review]

Comment thread packages/core/src/hooks/usePager.ts Resolved
Comment thread packages/core/src/hooks/usePager.ts Resolved
@cixzhang

cixzhang commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

/approve-spec c4319b4

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

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

Requesting changes for two fixes. An engineering owner approved the spec:AST-094 FR6 resize change for this head, and in Chromium the pager does what that sentence says; the record change can stay as it is.

Blocking:

  • Miss: a still finger that lands on a release's settle no longer stops it when the row resizes inside the 85 ms catch window, because retargetSettle() in usePager.ts calls stopSettle(), which clears the armed catch, so the settle slides on under the finger against FR6's "A finger MUST be able to catch any animation mid-flight"; keep the pending catch across the retarget and add a unit case for it.
  • The changeset (.changeset/pager-resize-settle-retarget.md, line 5) says a resized row "no longer jumps" and plays on "from where it shows", but a fixed-width three-per-view row widened from 300 to 400 px mid-settle keeps its page count and still steps 94 px in one frame, so narrow it, for example to "no longer stops at once or runs back to an earlier page. The settle plays on at its speed, to the last page when it was heading there, and otherwise to the page nearest where it was heading."

The earlier comment on the same-count step in usePager.ts is settled by the approved FR6 sentence. The new unit and browser tests fail on main and pass on this head in Chromium.

[Automated review]

Comment thread packages/core/src/hooks/usePager.ts Resolved
@vjeux
vjeux force-pushed the pager-settle-resize-retarget branch from c4319b4 to c081881 Compare November 9, 2026 15:24
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Nov 9, 2026
@cixzhang

cixzhang commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

/approve-spec c081881

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

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

Requesting changes for three fixes. An engineering owner approved the spec:AST-094 FR6 resize change for this head, so the record change can stay as it is, and rows whose pages renumber, and Lightbox's uniform pages, now play on through a resize as it says.

Blocking:

  • A snap-paging Carousel whose items follow the row's width (for example width: 100%), widened mid-swipe, turns back and rests on the item the person left, because scaledPages() in usePager.ts needs every page offset within 0.5 px of one ratio while Carousel measures its stops on the moving row, where each stop carries the distance the row moved between the frame and the read; make those stops agree with the row's rects (or let the fit allow one shared shift) and add a browser case with row-sized items.
  • Miss: a still finger that lands on a release's settle no longer stops it when the row resizes inside the 85 ms catch window, because retargetSettle() calls stopSettle(), which disarms the armed catch, against FR6's "A finger MUST be able to catch any animation mid-flight"; keep the armed catch across the retarget, as the waiting report is kept, and add a unit case.
  • The changeset (.changeset/pager-resize-settle-retarget.md, line 5) says a resized row no longer runs back to an earlier page, which the first finding contradicts; fixing that finding makes it true, otherwise narrow the sentence to rows whose pages renumber.

Required CI has not run on this head yet.

[Automated review]

Comment thread packages/core/src/hooks/usePager.ts Resolved
Comment thread packages/core/src/hooks/usePager.ts Resolved

@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 decision on the spec:AST-094 FR6 record change, and two fixes are needed either way.

The FR6 resize sentence and its "FR6 resize" verification row add a requirement the current record does not state, and the last spec approval named an earlier head; this head adds two cases to the row and changes how "scale by one ratio" is judged. An engineering owner records the decision by commenting /approve-spec 78edbda58c9fd5b6714b981baaeec162f2a9cec0 on this pull request; the author may do so as an engineering owner.

Blocking:

  • A snap-paging Carousel whose items follow the row's width, flicked from item 2 toward item 3 and widened from 300 to 450 px about 100 ms after the lift, still turns back and rests on item 2 in Chromium (7 of 7 runs; the base rests on item 3), because scaledPages() in usePager.ts judges stops measured on the moving row, which an instrumented run read as [0, 640, 1090, 1350, 1800] for items at [0, 450, 900, 1350, 1800], and a stop clamped to the row's end never carries a shared shift (a variant of the shared-shift unit case with 0.7 px instead of 0.56 px rests on page 1); make the held row's stops agree with what it draws, or decide "the same pages" from layout that does not move, and add a browser case with row-sized items widened as the settle starts.
  • The changeset (.changeset/pager-resize-settle-retarget.md, line 5) says a resized row no longer runs back to an earlier page, which the first finding contradicts; fixing that finding makes it true, otherwise narrow the sentence to rows whose pages renumber.

A still finger now catches a release's settle through a resize, as FR6 requires. Required CI has not run on this head yet.

[Automated review]

Comment thread packages/core/src/hooks/usePager.ts Resolved
@vjeux
vjeux force-pushed the pager-settle-resize-retarget branch from 78edbda to f4dde6e Compare November 9, 2026 17:26
A resize that renumbered a children pager's pages mid-settle left the
settle on its old index, which then named another page: a row of three
per view narrowed mid-settle ran back to the page it had left. A resize
mid-settle also ended the settle at once, with a jump to the new page.

When every page offset scales by one ratio, as when items or uniform
pages follow the row's width, each page keeps its number and the
settle keeps its fraction of a page. Otherwise the settle retargets by
offset and plays on from where the track shows, at its velocity. A
settle toward the last page ends on the last page, a target halfway
between two pages goes on the way the settle moves, and the page last
at rest keeps its place too. The retarget reads the track and starts
the new curve at one time. A release's waiting report still goes out
two frames after the new settle starts, and a finger's waiting catch
still catches it.

The held row's stops and span came from the children's rects plus the
animator's position now, which runs ahead of the frame the row draws:
a row whose items follow its width turned back after a widening, and a
retargeted settle ended past its page. Each child's own translate now
comes off its rect, read right after it.
@vjeux
vjeux force-pushed the pager-settle-resize-retarget branch from f4dde6e to 35eba30 Compare November 9, 2026 17:31
@cixzhang

cixzhang commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 35eba30

@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 decision on the spec:AST-094 FR6 record change, and one split is needed either way.

The FR6 resize sentence and its "FR6 resize" verification row add a rule the current record does not state, including whether "scale by one ratio" means the page offsets, as written, or the spacings between pages, as scaledPages() checks (pages at 16/316/616/916 widened to 16/466/916/1366 keep page 2 in the code and get page 1 by the sentence). An engineering owner records the decision by commenting /approve-spec 35eba30a0c1c647f1aca6bf7d0a1ebc217066d14 on this pull request, and the author may do so as an engineering owner.

Blocking:

  • The change is 2,034 authored lines, over the 2,000-line limit, and its held-row stop and span fix (SnapPaging.ts, snapPagingValue.ts, SnapPaging.test.tsx and its two browser cases, about 500 lines) works on its own: open it as its own pull request, land it first, and rebase this one onto it.

The row-sized run-back from the last review is fixed in Chromium, and required CI has not run on this head yet.

[Automated review]

on a pager's settle after a release never reaches the pager's content: no
press feedback and no click. UIKit's content took none of 30 such touches
change during a drag starts from rest (DEC-25). A resize during a settle
retargets it. When the page offsets all scale by one ratio, the settle

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.

Part of the spec decision: scaledPages() in usePager.ts compares the spacings between pages, not the offsets. So a row whose first page is not at 0 behaves differently from this sentence. Pages at 16/316/616/916 widened to 16/466/916/1366 during a settle toward page 2 rest on page 2 in the code, but the "Otherwise" branch here gives page 1. If the code's rule is the intended one, this could read: "When the spacings between pages all scale by one ratio, the settle keeps its page and its fraction of a page."

This branch was successfully deployed

1 active (outdated) deployment
Preview — 78edbda5 Deployed Nov 9, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants