Skip to content

fix(DateInput, DateTimeInput): the sheet's calendar pages like UIDatePicker - #8200

Draft
vjeux wants to merge 18 commits into
meta:mainfrom
vjeux:uikit/c22-month-pager
Draft

vjeux wants to merge 18 commits into
meta:mainfrom
vjeux:uikit/c22-month-pager

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Batch: B4 · DateInput calendar month pager

Stack: #8209 → #8200

Depends on #8209. Review only this pull request's change: vjeux/astryx@a98a089...9797905

User need

Under a finger, the sheets' calendar scrolls natively, so a 300 pt flick can cross two months. A mouse keeps the scroller.

Problem and solution fit

Under a finger, MonthPager pages the months on the shared pager (usePager) with UIDatePicker's measured values:

  • Release. One month by UIKit's paging rule (300 pt/s, or past half); the ends rubber-band.
  • Title. It changes at half a month, during the drag and the settle.
  • Arrows. One month per tap on the shared pager's animated scroll; a second tap retargets.
  • Sheet split. At 10 px of travel either way, the drag is the grid's if it moved 10 px sideways, else the sheet's (UIKit's measured rule, useOwnScrollGesture's 'pager' mode).
  • Trackpad. A two-finger swipe pans and pages at the release.
  • Reduce Motion. Steps and settles cross-fade (200 ms).
  • Keys. PageUp/PageDown move a month of the active calendar, Shift a year, on every pointer; only the shown month is tabbable.
  • Shared code. MonthPane.tsx and useMonthGridFocus.ts, for the scroller and the pager.

Current specification

spec:AST-094 FR2, FR17 and FR36 (settled Oct 25). component:DateInput PR2 and AR8 already state the pager and the keys.

For the owner: main's component:DateInput still says neither is built: AR8's "Implementation gap: PageUp and PageDown do not page by a month…" and PR2's "Implementation gap: under a finger the touch calendar moves through the native month scroller…", with their Current-state bullets. This removes those gap lines, cites the new tests in the two verification rows, and adds MonthPager's markup to the Compatibility/readiness row and Compatibility class. It also adds the Current-state bullet "Arrow step motion": an arrow's step plays the pager's 300 ms sinusoid, not the motion spec:AST-094 FR45 asks for, the pager's own gap under FR4. It needs /approve-spec at this head.

Public delta and caller burden

  • No API change.
  • Size (INV6, analyze-pr.js): added here when the build lands.
  • Batch size: 2,655 added lines, 1,471 of them tests. The pager, its sheet split and their proofs are one intent.

Writing rubric results

Over the removed gap lines, the two verification rows, the Compatibility/readiness row and the Compatibility class: R1 narration, R2 research, R4 rejected alternatives, R5 change references, R6 open questions: none. R3 evidence: test names only.

Scope

  • One capability is added.
  • Unrelated fixes, cleanup, and policy changes were removed or split.
  • Consumer docs, focused tests, and migration evidence ship with the feature.

Testing

This head, 9797905, has twelve commits on #8209's head a98a089: the earlier head ea7ceae's nine, rebased past #9643/#9842 (see "Changed by the rebase onto #9643/#9842" below), then three test commits: the Hebrew paging case, the trackpad cases on fake timers, and the toggle proof's spy.

  • The record bullet "Arrow step motion" now reads "not the motion spec:AST-094 FR45 asks for", which holds under main's FR45 and under the rewrite docs(AST-094, AST-057): motion guidance; gesture springs; swipe-row returns #9915 proposes.
  • "The month/year toggle leaves the calendar's days alone" spied on useCalendarDays through ../Calendar, but MonthPane imports it from ../Calendar/hooks/useCalendarDays, so the case could not fail. It now spies on that module, and first checks that opening the sheet records calls. With MonthPane unmemoized, the old case passes and the new one fails with 3 calls (commits 82842b2 and e9c1933, not part of the stack).
  • At this head: check:knowledge 0; check:changesets 0; check:motion-roles 0; pnpm -F @astryxdesign/core typecheck 0; CI=true pnpm lint 0 errors; the DateInput, TimeInput, DateTimeInput, Calendar, utils and hooks unit tests: 3,500 passed; pnpm review:pre 0.
  • At abedba6, this code before the record's wording and the toggle proof's spy: pnpm install --frozen-lockfile 0; MonthPager, MonthScroller, MonthScrollerSteps and MonthYearWheels against Storybook built there: Chromium 23 passed, 2 skipped; WebKit (--cpus=4, LP_NUM_THREADS=4), twice: 48 passed, 2 skipped.
  • The earlier proofs' positive controls (d23979b, 7ba2833 and 7c7d33a, commits not part of the stack) are unchanged by the rebase.

Evidence

real-device iOS: card 35, carried; Android: Code Quality at the final head

At this pull request's preview, the deploy of 3704441, whose product code this head keeps (it adds one test commit): iOS 26.2 Simulator, Mobile Safari, iPhone 17 Pro. The preview carries no touch overlay, so each row names its stroke; a stroke reaches the page every 16 ms. I reviewed every frame against the paging rules above and the earlier recordings below; each row gives its largest frame gap during motion. Persian and Hebrew need Temporal, which this Safari lacks, so the calendar row uses the Japanese calendar; the Hebrew months are the unit test's.

Stroke this PR
300 pt flick in 0.1 s: one month; the title turns at half (gaps ≤ 20 ms)
30 % of a month in 1.2 s: back to October (one 32 ms gap, in the return)
70 % in 2.8 s: pages to November; the title turns at half (gaps ≤ 20 ms)
Japanese calendar, 70 % in 2.8 s: 令和8年12月 to 令和9年1月, the era year turning with the month (one 28 ms gap, at the start)
Next, twice: one month per tap; the title turns at the tap (gaps ≤ 27 ms)

Earlier, at 693fafc, against main; the motion is unchanged since.

Path before this PR
300 pt flick
30 % (stays), then 70 % (pages)
Next, twice
35°, then 60°
Main thread blocked 600 ms
Reduce Motion

Changed by the rebase onto #9643/#9842

Main renders each month pane in the active calendar (#9643/#9842). This head keeps that in the shared pane and in month paging:

  • MonthPane takes the monthModel and reads its month through monthAt, useCalendarDays with the calendar, formatCalendarMonthLabel, isInCalendarMonth and formatCalendarDate, as main's scroller pane did.
  • monthGridKeyTarget moves PageUp and PageDown by the calendar's months, and with Shift by its years (calendarShiftMonths, calendarShiftYears): a Hebrew leap year has 13 months. useMonthGridFocus indexes months through the model.
  • DateInputTouch.calendar.test.tsx "pages the keyboard focus by calendar months, and by calendar years with Shift": PageUp from 1 Nisan 5784 lands in Adar II; Shift+PageUp from 10 Shevat 5785 lands on 10 Shevat 5784, 13 months back across Adar I. Gregorian month arithmetic fails the first case, and twelve calendar months for Shift fail the second.
  • No record of this pull request renumbers; PR2 and AR8 read as before, and the AR8 row cites the new case. The DateInput FR renumbering is fix(DateInput, DateTimeInput): under a finger the month/year wheels move the date like UIDatePicker's #8209's.

Rule-47 fix in this pull request's tests

DateInputTouch.test.tsx's two trackpad cases now run on fake timers: the gesture host ends a trackpad swipe 220 ms after its last wheel, and an 8 ms real-timer step let a runner stall end it early.

  • Evidence commits (not part of the stack): a real 300 ms stall halfway turns the old case red, 3 of 3 (March, not April); the same stall leaves the fake-timer case green.
  • Controls: 1,000 ms of fake time halfway still ends the swipe short (red), and a 205 px slow swipe still pages (red). 300 ms of fake time stays April, because the release at 220 ms still carries the swipe's velocity past UIKit's 300 pt/s.

Review notes

  • Follow-ups, outside this pull request: the doc comment withLayoutAsync left above beforeAll in DateInputTouch.test.tsx, and MonthScroller's coarse-pointer Reduced Motion dip, which can no longer run now that the pager handles touch.

@vercel

vercel Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
astryx Ready Ready Preview Nov 6, 2026 5:49pm UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 24, 2026
@astracat-bot

astracat-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

AI review status for this pull request.

Review status Updated
🟡 Reviewed (for maintainers only) Nov 6, 2026, 6:38 PM UTC

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

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


Generated by PR Enrichment workflow | View current CI run

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This review waits on an engineering owner: one month per release with the 45° sheet split, the reworded draft PR2 in DateInput.spec.md and the new draft spec:AST-094 have no current spec authority, so an engineering owner needs to approve or reject them by commenting /approve-spec 39408932bf15c4b71cf42bb94ee91e9fe674496f on this pull request, which the author may do as an engineering owner. Whatever that decision, please also make these changes:

  • Land the predecessor pull requests, or rebase onto them, so this one carries only its own commit.
  • Keep the usePager effects at lines 681-683 and 698-702 from calling onIndexChange, and name the system each synchronizes.
  • Correct the changeset's 45° line: drags between about 26.6° and 45° move neither the sheet nor the calendar.

[Automated review]

pager.pan.setEnabled(isPanEnabled);
}, [pager, isPanEnabled]);

useEffect(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This effect (and the setOptions effect at lines 681-683) can call the owner's onIndexChange through goTo(..., {reason: 'controlled'}), and neither names the system it synchronizes. Current architecture:react-component-runtime INV2 keeps consumer callbacks out of effects and INV3 asks each effect to name its external system: name the pager here and keep a 'controlled' move from calling onIndexChange, or drop the unused controlled index option.

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.

usePager is no longer copied here: at 68b7c0c this PR stacks on #8146 and imports it, so that Effect is #8146's (its head is 49564e7).

- A swipe moves at most one month, chosen at the lift by iOS's paging rule (a quick flick, or past half a month). It settles on iOS's paging curve instead of letting a fling run across several months.
- The month title follows the drag. It changes when the next month is more than half in view.
- The arrows scroll the month in 0.3 s; a second tap mid-scroll goes on to the month after.
- Inside the sheet, a drag steeper than 45° moves the sheet, and a flatter one stays with the calendar.

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.

Drags between about 26.6° and 45° move neither the sheet nor the calendar (MonthScroller.tsx lines 457-464), so "a flatter one stays with the calendar" promises a response that does not happen. Suggest: "a drag steeper than 45° moves the sheet, one flatter than about 27° pages the calendar, and one in between moves neither, as on iOS."

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.

The changeset now says it: a drag steeper than 45° moves the sheet, one flatter than about 27° pages the calendar, and one in between moves neither, as on iOS.

}
} else if (state === 'ended' || state === 'cancelled') {
// The release has chosen its month by now.
report(pager.virtualIndex);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This reports the release month synchronously at the end of the pan, so when a flick pages before the drag crossed half a month the owner's render runs inside the lift handler. usePager.ts lines 307-314 defer the pager's own report by two frames for exactly that Mobile Safari case; reporting from the deferred onIndexChange('drag') (lines 366-375) would keep the two consistent.

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.

Fixed in 68b7c0c: the title changes at half a month, not at the lift (see the newer thread).

// Typed-field only: the wheels step by their own options and the sheet
// opens on tap, so neither reaches this surface.
increment: _increment,
increment,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The comment above ("Typed-field only ... neither reaches this surface") no longer holds: increment now steps the sheet's minute wheel.

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.

Fixed in #8179's 662b5c5: the comment now says the sheet's minute wheel steps by increment under a finger, and that with a mouse today's wheels list every minute.

hourFormat = '12h',
// Desktop-only: mobile uses wheels instead of typed arrow stepping or a preset list.
timeIncrement: _timeIncrement,
timeIncrement,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The comment above ("Desktop-only: mobile uses wheels instead of typed arrow stepping") no longer holds: timeIncrement now steps the sheet's minute wheel.

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.

Fixed in #8179's 662b5c5: the comment now says the sheet's minute wheel steps by timeIncrement under a finger, and that with a mouse today's wheels list every minute.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This review waits on an engineering owner: one month per release with the 45° sheet split, the reworded draft PR2 in DateInput.spec.md and the new draft spec:AST-094 have no current spec authority, so an engineering owner needs to approve or reject them by commenting /approve-spec <full head SHA> on this pull request, which the author may do as an engineering owner. Whatever that decision, these changes are still needed:

  • Land the predecessor pull requests or rebase onto them, so this one carries only its own commit.
  • Keep the usePager effects at lines 681-683 and 698-702 from calling onIndexChange, and name the system each synchronizes.
  • Stop naming the release month at the lift in MonthScroller.tsx (lines 418-421), which turns the title earlier than the measured UIKit behavior and the changeset describe.
  • Page on a horizontal trackpad or wheel gesture again (MonthScroller.tsx line 106), and correct the changeset's 45° line, the two stale increment/timeIncrement comments and the citation of a missing AST-090 in spec:AST-094.

[Automated review]

}
} else if (state === 'ended' || state === 'cancelled') {
// The release has chosen its month by now.
report(pager.virtualIndex);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This names the release month at the lift, so a flick released before half a month turns the title then. The head's own UIKit table says the title changes "when the content crosses half a page, during the drag (and during a settle), not at the lift" (motion/uikit/datePicker.ts lines 64-67), and the changeset says it changes "when the next month is more than half in view". Letting the pager's deferred onIndexChange('drag') (lines 366-375) report the release would also keep the owner's render out of the touch handler, as usePager.ts lines 307-314 intend.

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.

Fixed in 68b7c0c: under a finger the calendar is MonthPager. Its title follows the drag and the settle and changes at half a month, not at the lift (interaction spec: the title at half a month).

// nothing to fight over, and a downward drag on the calendar can go back
// to meaning swipe-to-dismiss.
touchAction: 'pan-x',
overflow: 'hidden',

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.

With the viewport overflow: 'hidden' and no wheel handling in the pager or the gesture host, a two-finger trackpad swipe or Shift+wheel over the calendar no longer pages; on the base it scrolled the months natively. Mapping a horizontal wheel gesture onto pager.goTo one month at a time would keep that path.

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.

Fixed in 68b7c0c: MonthPager pages one month per horizontal trackpad swipe or Shift+wheel gesture (interaction spec).

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
Contributor

Choose a reason for hiding this comment

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

Neither main nor this branch has an AST-090 record, so this amendment (and the one at line 235) points at text a reader cannot open; cite it once the record exists, or drop it.

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.

At 68b7c0c this branch no longer edits docs/specs/AST-094; that record is #8316's.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This review waits on an engineering owner: one month per release with the 45° sheet split, the reworded draft PR2 in DateInput.spec.md and the new draft spec:AST-094 have no current spec authority, so an engineering owner needs to approve or reject them by commenting /approve-spec <full head SHA> on this pull request, which the author may do as an engineering owner. Whatever that decision, these changes are still needed:

  • Land the predecessor pull requests or rebase onto them, so this one carries only its own commit.
  • Keep the usePager effects at lines 681-683 and 698-702 from calling onIndexChange, and name the system each synchronizes.
  • Stop naming the release month at the lift in MonthScroller.tsx (lines 418-421), or document it, so the title, the changeset and the measured UIKit behavior agree.
  • Page on a horizontal trackpad or wheel gesture again (MonthScroller.tsx line 106), and correct the changeset's 45° line, the two stale increment/timeIncrement comments and the citation of a missing AST-090 in spec:AST-094.

[Automated review]

@cixzhang

Copy link
Copy Markdown
Contributor

Posted by Cindy's agent on her behalf.

Parts of this PR re-implement behavior that already exists, or is being specified, as a shared part of core. Converging keeps one implementation per behavior:

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

@vjeux

vjeux commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Agreed: one implementation.

@vjeux
vjeux force-pushed the uikit/c22-month-pager branch from 161f142 to 68b7c0c Compare October 25, 2026 02:22
@vjeux

vjeux commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Converged in 68b7c0c: MonthPager stacks on #8146 (49564e7) and imports its usePager, so the copy is gone. Touch keys on the resolver, as in #8179.

@cixzhang

cixzhang commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

/approve-spec ea7ceae

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Nov 4, 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.

Changes are needed before this can be approved. The month pager follows the current DateInput spec (PR2, AR8) and the touch-presentation spec, and the record edits it carries (DateInput FR20, DateTimeInput FR11, the Compatibility rows and the arrow step's recorded gap) have an engineering owner's approval at this head.

Required changes:

  • The branch is 3,977 authored lines without a valid stack: #8206's body still lists the merged #8179 and #8206 does not sit on this head, so re-parent it with the same Stack: #8209 → #8200 → #8206 line, or land #8209 first.
  • The phone behaviour still lacks the evidence the touch-presentation spec requires: real-device iOS Safari recordings, and Android emulator runs at this head with animations at normal scale and removed plus a slow-device profile.

Fixed since the last review: the arrow step's curve is recorded as the shared pager's gap and no longer promised in the changeset, and the title reads the pager's own page geometry. A new commit here needs a fresh /approve-spec.

[Automated review]

Comment on lines +62 to +66
// No native pan on either axis: horizontal drags are the pager's and
// vertical ones the sheet's, both in JavaScript, as on the sheet's own
// surface. The pager leaves the viewport `pan-y`. In a sheet that lets the
// browser start a vertical scroll and throttle the sheet's drag moves.
// Pinch-zoom stays the browser's.

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 (INV29): "In a sheet that lets the browser start a vertical scroll and throttle the sheet's drag moves" has no clear subject. Shorter:

// The pager would leave the viewport `pan-y`. In a sheet, `pan-y` lets the
// browser start a vertical scroll and throttle the sheet's drag. So the track
// takes no native pan. Pinch-zoom stays the browser's.

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.

Deferred to the follow-up pull request after this stack merges, with your wording as written. A new head here would re-version #8206, which holds at its current head until #9915 merges.

@astracat-bot astracat-bot Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This pull request is reviewed as part of its stack; see the review on #8206.

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

Changes are needed before this can be approved. The month pager follows the current DateInput spec (PR2, AR8) and the touch-presentation spec, the stack line now holds, and an engineering owner approved the record edits at this head. What remains:

  • Regression: the branch no longer merges with main, and the new MonthPane.tsx, MonthPager.tsx and useMonthGridFocus.ts page Gregorian months where main's DateInput FR21 now requires the sheet to show the selected calendar's months, so rebase, carry main's monthModel into the shared pane, the pager and the focus hook, and ask for a new /approve-spec on the rebased head.
  • Miss: DateInputTouch.test.tsx:2632 ("a trackpad swipe past half a page pages one month") waits 8 ms on real timers against the gesture host's 220 ms trackpad end, so it fails on a loaded runner; drive it with fake timers.
  • The phone behaviour still lacks the evidence the touch-presentation spec requires: real-device iOS Safari recordings, and Android emulator runs at this head with animations at normal scale and removed plus a slow-device profile.

This pull request lands only when it and #8209 below it are approved.

[Automated review]

paneStyle,
}: MonthPaneProps) {
const locale = useLocale();
const {year, month} = fromMonthIndex(monthIndex);

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.

Main's MonthScroller pane now lays out monthModel.monthAt(monthIndex) with calendarYearMonthOf, formatCalendarMonthLabel, isInCalendarMonth and formatCalendarDate (DateInput FR21). This pane was extracted before that, so the rebase has to give it monthModel too; otherwise the pager shows Gregorian months under a finger for a Persian or Hebrew calendar.

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.

Done in the rebased stack: MonthPane, MonthPager and useMonthGridFocus take main's monthModel, and PageUp/PageDown move by the calendar's months (calendarShiftMonths, calendarShiftYears). 00d9d68 adds "pages the keyboard focus by calendar months, and by calendar years with Shift" (Hebrew, across Adar I and II).

// 40 steps of 6.5 px: 260 px toward the next month.
for (let i = 0; i < 40; i++) {
act(() => trackpadScroll(scroller, 6.5));
await wait(8);

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.

These 8 ms waits run on real timers, and the gesture host ends a horizontal trackpad stream after 220 ms with no wheel (WHEEL_FOLLOW_END_MS). One stall past that ends the swipe short of half a page; this case failed that way once in a four-worker run. vi.useFakeTimers() with vi.advanceTimersByTime(8) keeps it deterministic, as the file already does elsewhere.

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.

Done in 3704441: both trackpad cases run on fake timers. A real 300 ms stall halfway fails the old case 3 of 3 and leaves the new one green; 1,000 ms of fake time halfway still fails it, so it is not vacuous.

vjeux added 14 commits November 5, 2026 00:58
…ove the date like UIDatePicker's

Under a finger, turning the month or year wheel behind the sheet
calendar's title moves a chosen date to the wheels' month and keeps its
day: October 15 becomes May 15, and a day the month lacks becomes its
last day (January 31 becomes February 28), inside min and max, by the
months of the calendar the sheet shows; DateTimeInput keeps the time. A
date dateConstraints refuses is not chosen, and with no date chosen the
wheels move only the calendar. With a mouse the wheels move the calendar
only, as today.

The month wheel does not loop.

DateInput FR22 records the rule under a coarse primary pointer, and
DateTimeInput FR14 does the same and keeps the time. Each has a
verification row.
…n end, not on still frames before a deadline

`openWheels` waited for the sheet, and then for the wheels, with the
harness's `waitForRest`: three still frames within 5 s. On a loaded
runner the page can present no frame for 6 s right after the sheet
opens, so the wait ran out with nothing moving ("still moving", while
the page held no animation). The proof now checks the sheet's open
transform, and then the wheels' full opacity, once a frame with no
deadline of its own, and waits on each animation's `finished` promise.
A starved runner only makes it later.
The comments above both sheets' handleWheelChange keep only the reason:
UIDatePicker moves a chosen date with the wheels. The day and bound
clamps read from dateInMonth itself, which moves the date by the
calendar's own months through calendarShiftMonths.
…nScrollGesture

useSheetAxisSplit kept a bottom sheet off the month pager's drags beside
useOwnScrollGesture, which does that job for the wheels and the month
scroller. Its rule, UIKit's 10 px decision with touch pointers held, is now
useOwnScrollGesture's 'pager' mode, and the separate hook goes.
…s pane width uses the shared resize observer

Each of MonthPager's Effects now names the system it syncs. Its pane width
comes from observeResize instead of its own ResizeObserver, and the owner's
callback is read through a ref written in render, with no Effect.
… pager alone

MonthPager paged a month after 25 px of horizontal wheel travel, beside
usePager's own trackpad pan, so a slow 30 px swipe paged the month. The
listener, its constants and their pending measurement go: the pager follows
a trackpad's scroll and pages on its release. The Chromium-only wheel test
goes too, since Chromium's driven wheels read as a mouse's wheel, which the
pager leaves to the page.
MonthPane.tsx now holds the month grid, its day styles and the grid keys
that MonthScroller and MonthPager each kept a copy of, in step by a SYNC
comment. Each surface passes its own pane style (the scroller's snap, the
pager's layer) and whether the pane takes a tab stop. Nothing renders differently.
…cus hook

useMonthGridFocus holds the focused day, the grid keys and the focus
restore that MonthScroller and MonthPager each kept a copy of. Nothing
behaves differently.
The proof read the sheet and the grid with two evaluates per touch move.
On a loaded runner that slowed the script, and the fast flick lost the
speed that pages it. The page now samples both itself, every frame.
…ads the pager's page

`spec:AST-094` FR45, now on main, has motion a tap starts play the
theme's duration and easing tokens. An arrow's step plays the shared
pager's animated scroll, UIKit's 300 ms sinusoid, which AST-094 FR4
already records as the pager's gap. DateInput's record now names that
gap, and the changeset, the story and the browser spec stop promising
0.3 s. The arrow's proof checks that the step travels one month, one
way, without fitting the curve.

The title read the nearest month from MonthPager's own pane width. It
now reads the pager's own page geometry through `nearestPage`.
…nner's timing

WebKit stamps an injected touch when it arrives, so on a loaded runner
the moves after a stalled dispatch arrive back to back and read as a
much faster finger, and a late lift reads as a resting one. Four proofs
failed this way.

"An arrow tapped right after a release" claims the title's order, so its
drag now rests before the lift and releases at no speed: the settle runs
by position. The page holds that settle as soon as it runs, so the arrow
lands mid-settle on any runner. The sheet split claims who takes the
drag, so each of its drags now counts the moves that reach the sheet,
which the split keeps from it for the grid's drags. Where the fast 55°
drag then rests is read, not asserted, and the 60° drag is checked
against it.

"The arrow names the month at the tap" holds the step's motion to sample
it. Its hold is now armed in the page before the tap, so a busy page
holds the step as soon as it runs instead of after it has ended.

"A flick released before half a month" lifts right after its last move,
at 2500 px/s. WebKit still reads a slower release after a long stall, so
there the proof checks the title at the lift, and Chromium, where the
script sets each touch's time, also checks that the release pages.

The spec's opening waits for the sheet's resting transform, checked once
a frame with no deadline of its own, and then for each animation's own
`finished` promise, instead of three still frames within 5 s.

The two storyboards film a step and a settle. Each now arms its hold in
the page before the action, so a busy page holds the motion before it
ends.
The grid's PageUp and PageDown move by the active calendar's months, and
with Shift by its years. Every paging case so far was Gregorian, so a
Gregorian or twelve-month mutant passed them all. Two Hebrew cases pin
it: a month back from 1 Nisan 5784 is Adar II, and a year back from
Shevat 5785 is Shevat 5784, 13 months back across Adar I. Gregorian
month arithmetic fails the first, and twelve calendar months for Shift
fail the second. The AR8 verification row cites the case.
The two trackpad cases stepped their wheels on real timers, 8 ms and
40 ms apart. The gesture host ends a trackpad swipe 220 ms after its
last wheel, so one runner stall past that ended the fast case's swipe
short of half a page, and it read March, not April. Both cases now
advance fake timers, which the host's clock, its timers and the events'
time stamps all read, so no stall of the runner can end a swipe.

A real 300 ms stall halfway turns the old fast case red and leaves the
new one green. 1000 ms of fake time halfway still ends the swipe short,
and a 205 px slow swipe still pages, so neither case passes vacuously.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approval waits on an engineering owner's decision on the record edits, and changes are needed either way. The month pager follows the current DateInput spec (PR2, AR8) and the touch-presentation spec, and after the rebase it pages the active calendar's months and the branch merges with main.

Spec decision needed: the rebase renumbered the two new requirements to component:DateInput FR22 and component:DateTimeInput FR14, with the words of the approved FR20 and FR11, and the earlier /approve-spec names the previous head. Any engineering owner, the author included if they are one, records approval by commenting /approve-spec <full head SHA> on this pull request.

Required changes:

  • The branch is 4,065 authored lines without a valid stack, because #8206's head still sits on this pull request's previous head; re-parent #8206 onto this head with the same Stack: #8209 → #8200 → #8206 line, or land #8209 first.
  • Miss: DateInputTouch.test.tsx:3160 ("the month/year toggle leaves the calendar's days alone") spies on useCalendarDays through ../Calendar, but MonthPane.tsx imports it from ../Calendar/hooks/useCalendarDays, so the spy records no call and the test cannot fail; mock the module MonthPane imports and first check that opening the sheet records calls.
  • The phone behaviour still lacks the evidence the touch-presentation spec requires: real-device iOS Safari recordings, and Android emulator runs at this head with animations at normal scale and removed plus a slow-device profile.

[Automated review]

renderAndOpen();
vi.mocked(useCalendarDays).mockClear();
fireEvent.click(document.querySelector('[data-title="month-year"]')!);
expect(vi.mocked(useCalendarDays)).not.toHaveBeenCalled();

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.

Miss: this spy never sees a month pane. vi.mock('../Calendar') wraps the Calendar index's useCalendarDays, but MonthPane.tsx imports it from ../Calendar/hooks/useCalendarDays, so a rendered MonthPane records no call and this assertion passes even if every pane re-renders on the toggle. Mock ../Calendar/hooks/useCalendarDays instead, and first assert that opening the sheet records calls.

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.

Done in 9797905: the spy wraps ../Calendar/hooks/useCalendarDays, and the case first checks that opening the sheet records calls. With MonthPane unmemoized, the old case passed and the new one fails (3 calls).

"the month/year toggle leaves the calendar's days alone" spied on
`useCalendarDays` through `../Calendar`, but `MonthPane` imports it
from `../Calendar/hooks/useCalendarDays`, so the spy saw no pane and
the case could not fail. The spy now wraps that module, and the case
first checks that opening the sheet records calls.

With `MonthPane` unmemoized, so every pane re-renders on the toggle,
the old case still passes and the new one fails (3 calls).
@cixzhang

cixzhang commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 9797905

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

Changes requested on this stack: #8209 is approved, and #8200 needs the Android evidence the touch-presentation spec requires. A pull request lands only when it and every pull request below it are approved.

#8209

Approved pending CI: no blocking findings; CI has not run on its head yet and runs once a maintainer adds the ci:run label.

#8200

Changes needed:

  • The month pager's touch behaviour has no Android run at this head, while spec:AST-094 Platform support and DEC-27 require Chrome with Gboard in the Android emulator at the exact head under review, with animations at normal scale and removed, plus a slow-device profile; the description places that run at the final head, so record it on this pull request at the head that merges.

The engineering owner's iPhone check now covers the real-device iOS requirement. Non-blocking: MonthScroller.tsx keeps a coarse-pointer Reduced Motion dip that can no longer run now that the scroller renders only for a fine pointer.

Whole stack

Fails on the missing Android evidence; otherwise the two pull requests are consistent and each spec edit has an engineering owner's approval at its exact head.

[Automated review]

@cixzhang

cixzhang commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Specs & Decisions agent for Cindy: real-device iOS for this paging is card 35 (passed on Cindy's iPhone at 5025fd7; carried: the calendar-months change is a DateInput FR21 defect fix, verified on the iOS Simulator and in unit tests); Android: Code Quality at the final head.

vjeux added 3 commits November 6, 2026 08:53
Main gained meta#10285, which adds a `getOnlyButton` import beside the
testing-library import this pull request widens in
DateTimeInputTouch.test.tsx. The merge keeps both imports; this pull
request's own changes are unchanged.
Main gained meta#10285, which adds a `getOnlyButton` import beside the
testing-library import this pull request widens in
DateTimeInputTouch.test.tsx. The merge keeps both imports; this pull
request's own changes are unchanged.
This pull request now sits on meta#8209's merge with main, so the stack
is tested and merged together. Its own changes are unchanged.
@cixzhang

cixzhang commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

/approve-spec c6b74ea

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

Changes requested on this stack; a pull request lands only when it and every pull request below it are approved.

#8209

Approved pending CI: no blocking findings. CI has not run a build or test on this head (the run found no room under the CI ceiling), so a maintainer re-runs it.

#8200

Changes needed:

  • The month pager's touch behaviour has no Android run at this head, while spec:AST-094 Platform support and DEC-27 require Chrome with Gboard in the Android emulator at the exact head under review, with animations at normal scale and removed, plus a slow-device profile, so record that run on this pull request at the head that merges.

Non-blocking: MonthScroller.tsx keeps a coarse-pointer Reduced Motion dip that can no longer run, DateInputTouch.test.tsx:194 carries a stray doc comment above beforeAll, and DateInput FR22 does not say whether the moved date is announced.

Whole stack

Fails on the missing Android evidence; otherwise the two pull requests are consistent, and each record edit carries an engineering owner's approval at its exact head.

[Automated review]

This branch was successfully deployed

1 active deployment
Preview — c6b74ea8 Deployed Nov 6, 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