Skip to content

fix(core): the keyboard height reads the whole keyboard while Safari pans to a focused field - #9983

Draft
vjeux wants to merge 5 commits into
meta:mainfrom
vjeux:uikit/c08-keyboard-pan
Draft

vjeux wants to merge 5 commits into
meta:mainfrom
vjeux:uikit/c08-keyboard-pan

Conversation

@vjeux

@vjeux vjeux commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Stack: #9983 → #8217
Merge group: #9983, #8217

User impact

While Safari on iOS pans the page to a focused field near the bottom, the keyboard module measured no keyboard. Readers such as useMobileKeyboard's recovery check took it as down.

Problem and solution fit

layoutViewportHeight read innerHeight, but the root's client height keeps the layout viewport's 714. At a visual-viewport scale of 1, innerHeight can equal the visual viewport's height while the root is as wide as the visual viewport. In that state, taking the larger of the two restores the keyboard's height. Elsewhere nothing changes. A zoomed-out page's scaled innerHeight and unscaled root differ in units, and a wider-than-screen layout has a taller root. The scroll clamp reads as on main.

Expected behavior and authority

spec:AST-099 FR2: "A page pan or a visual-viewport offset is a pan to put back (FR4), never a smaller keyboard". The FR2 verification row now names Safari's pan to a focused field and the Simulator.

Smallest restoration

layoutViewportHeight returns the larger of its current value and the root's client height (the body's in quirks mode), in that one state. The keyboard module keeps the layout viewport's height it measured with each report (getKeyboardViewportHeight), for the keyboard's readers and the README's formula. No API change.

Evidence

The module's own reading on a page Safari pans (a local probe story, not in the diff), iOS 26.2 Simulator, hostname masked:

Before: keyboard height 0 px with the keyboard up; after: 337 px

  • Reproduction before the change: innerHeight 377, root 714, visual viewport 377 panned 337: the module reads 0 (2 takes).
  • Result after the change: it reads 337, the whole keyboard (2 takes).
  • Representative unchanged path: unit cases keep a zoomed-out page and a too-wide layout at no keyboard, and the scroll clamp at main's reading. On the Android emulator at 280bac1 (table in Testing), the root's client height equals innerHeight in every reading, so the floor returns main's value.
  • Regression test: layoutViewport.test.ts and keyboardHeight.test.ts pan cases, red on main.

Open PRs on these files: #9777 adds layoutViewport.ts from an older base and takes this fix when it rebases.

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.

Writing rubric results

R1 narration: none (the row states what verifies FR2). R2 research: none. R3 evidence: the row names its tests and the Simulator, no change. R4 rejected alternatives: none. R5 change references: none. R6 open questions: none.

Testing

  • At 348b522, merged with main b6b0c68. The conflicts were import blocks only: useChatKeyboard.ts takes main's interaction-modality and focus-return imports, and keyboardTarget.ts main's nearestScroller import. Neither imports layoutViewport now, as their readers use getKeyboardViewportHeight. The change's own lines are unchanged. At the merge, ChatLayout's INV11 browser test passes 3 of 3 in Chromium. The Chat, keyboard and Layer interaction specs pass: Chromium 60, WebKit 45 with 15 skipped. Keyboard, Chat, Layer and layoutViewport units: 1,262 of 1,262. pnpm test:ui: 965 files, 23,490 tests pass. Typecheck 0, CI=true pnpm lint 0 errors, pnpm check:knowledge clean.
  • Merged with main 9a690d5 (no conflicts). On the plain merge, the pan check re-read the visual viewport's height in every keyboard measurement and in every reader laying out against it. ChatLayout's INV11 browser test read it 3 times per report. The keyboard module now passes its one read to layoutViewportHeight, and keeps the layout viewport's height with each report (getKeyboardViewportHeight). ChatLayout's keyboard hook, useMobileKeyboard, the Layer's keyboard clearance and the keyboard target read it there. INV11 browser test, Chromium ×3: 3 of 3 fail on the plain merge, 3 of 3 pass here. Two new units count one read per report under the pan, the second with a reader laying out; each fails without the fix (2 reads). Chat, keyboard and Layer interaction specs: Chromium 53 passed; WebKit 41 passed, 12 skipped. Core UI suite (pnpm test:ui): 920 files, 22,546 tests pass; CI=true pnpm lint 0 errors.
  • Red-first: on main's layoutViewport.ts, the 2 pan cases fail (one per test file); with the change all pass. The wide-layout, zoomed-out and scroll-clamp cases pass on main and here. The wide-layout cases fail on the previous head (a 1,105 px keyboard with none shown).
  • vitest --project ui keyboard module and layoutViewport: 74/74. Core units: 21,147 of 21,151 pass. The other 4, in Switch.focusRing.source-build.test.mjs and RadioListItem.press.source-build.test.mjs, fail on main too (a fresh install has no @babel/preset-react at the root).
  • Core typecheck 0 errors; strict ESLint (CI=true) 0 errors; Prettier; pnpm check:knowledge.

Android emulator readings at 280bac1, the pan floor's patch before the one-read report, in CSS px. A local probe page, not in the diff, reads the module built from that head. Android 16 BE2A.250530.026.D1, Chrome 133.0.6943.137 with Gboard 15.1.08, Firefox 159.0.1. Each reading taps a field at the page's bottom, then presses Back:

Browser, page Keyboard innerHeight Root client height Visual viewport (height @ offsetTop, scale) Module's keyboard height
Chrome + Gboard, default down 811 811 811.43 @ 0, 1 0
Chrome + Gboard, default up 811 811 499.43 @ 312, 1 311.57
Chrome + Gboard, resizes-content down 811 811 811.43 @ 0, 1 0
Chrome + Gboard, resizes-content up 499 499 499.43 @ 0, 1 0
Firefox, default down 796 796 795.6 @ 0, 1 0
Firefox, default up 796 796 486.2 @ 245.93, 1 309.8
Firefox, resizes-content down 796 796 795.6 @ 0, 1 0
Firefox, resizes-content up 486 486 486.2 @ 0, 1 0
  • Every row reads the same at normal animation scale, with animations removed (window, transition and animator scales 0), and under a slow-device profile. That profile holds the emulator to one host core. Only Firefox's slow default pass read offsetTop 0, before its pan. Back returns every pass to its "down" row. 12 passes, every screenshot viewed; a second run of the same 12 read the same numbers.
  • Under resizes-content, innerHeight equals the visual viewport's height at scale 1 with the keyboard up, so the new branch runs. The root shrinks with them, so it returns the same value as main.
  • Code Quality's readings on 76289f2's own deployment, on the Android 16 emulator with Chrome 133 and Gboard. They read the AI chat template's composer and the last field of BottomSheet's Mobile keyboard story. Default viewport: the page pans 288 px and the composer sits 193 px above the keyboard. resizes-content: no pan, and 193 px. BottomSheet's field sits 48.2 px above the keyboard (default) and on its top (resizes-content). The same numbers at 4× CPU slowdown and with animations removed, and Back returns the viewport to 783.2 px at offsetTop 0. In one take with animations removed, Back left the keyboard up; it hid in every other take.
  • Code Quality's retake at 7952b5f ran on the Android 16 emulator, in Chrome 133 with Gboard and in Firefox 159. Chrome ran at normal animation scale, with Remove animations, and at 4× CPU. Each take read the AI chat template's composer and BottomSheet's last field, keyboard up, then down with Back. Each ran in the default viewport and under resizes-content. Chrome: AI chat 64.2 px clear with no pan (default) and 193 px (resizes-content); BottomSheet 48.2 px (default) and flush (resizes-content). Firefox matches within 1 px, with no pan: 64.1, 193, 48.1 px and flush. Keyboard down returns the viewport to full at offsetTop 0 in every reading (Chrome 783.2 px, Firefox 779.3 px). With Remove animations (reduced motion active) and at 4× CPU, Chrome's eight readings equal those at normal speed, to 0 px. In Firefox a real tap on "Add a comment" opens the sheet, 3 of 3 on main and on 7952b5f.
  • The one change from 76289f2 is AI chat in the default viewport. There Chrome panned 288 px and the field ended 193 px clear; now nothing pans, and the composer card rests on the keyboard's top. It is intended: feat(ChatLayout): the composer rides the software keyboard #8176's dock rests above the keyboard (spec:AST-099 FR3, with FR4: focus never pans the page). It came in with the main merge. Chromium's phone emulation with a 312 px virtual keyboard shows it, as a proxy with no real keyboard and no browser pan. In 3 takes each, 76289f2 leaves the composer under the keyboard. The merge without the one-read commit, and 7952b5f, both put the card on the keyboard's top.
  • Not read there, as rig limits: AI chat with resizes-content at 4× CPU, whose composer did not render in time in 7 tries. Firefox was not read either: no keyboard report reached the page in 2 tries. The probe-page rows above read Firefox at 280bac1.
  • iOS 26.2 Simulator (iPhone 17 Pro, Mobile Safari): a tap focuses a field at the page's bottom. A local probe story shows the module's reading. There were 2 takes per build at 280bac1 (the same patch), order-balanced, with every frame viewed.

@vercel

vercel Bot commented Nov 4, 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 10, 2026 3:20am UTC

Request Review

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

astracat-bot Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

AI review status for this pull request.

Review status Updated
🟡 Reviewed (for maintainers only) Nov 10, 2026, 3:52 AM UTC

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

cixzhang commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 0dbf6f3

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

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

Requesting changes: the fix restores the keyboard height in the Safari focus pan it targets, but the new root-height floor makes a zoomed-out page report a keyboard that is not there.

  • layoutViewportHeight (packages/core/src/utils/layoutViewport.ts:49-52) compares the root's unscaled client height with the scaled innerHeight * scale reading, so a page with no width=device-width viewport or a fixed width=1024 viewport now reads a 984–1,246 px keyboard with none shown (0 before), against AST-099 FR2; apply the floor only where the units match (for example at a visual-viewport scale of 1) and add a no-keyboard test for a zoomed-out page.
  • The ride formula in packages/core/src/keyboard/README.md (line 96) still computes the keyboard's top as innerHeight − height, which is now 40 px during Safari's focus pan instead of the 377 px the hook uses, so it should read layoutViewportHeight(window) − height.
  • The Android claims (resizes-content still reading 0, Chrome's toolbar-hidden height kept) and the scroll-clamp change rest on unit fixtures with assumed root heights, while AST-099 DEC-3 asks for emulator evidence for Android claims, so please add emulator readings (Chrome with Gboard and Firefox for Android) and a Simulator reading of the clamp state at the fixed head.

The four red-first unit cases and the Simulator capture show the focus-pan fix itself.

[Automated review]

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Still requesting changes: the scale-1 gate restores the keyboard height during Safari's focus pan, but it lets a fixed-width page wider than the screen read a keyboard that is not there, and the Android readings the keyboard record requires are missing.

  • layoutViewportHeight (packages/core/src/utils/layoutViewport.ts:52-56) takes the root's client height whenever the scale is 1 and innerHeight equals the visual viewport's height, so a width=1024, initial-scale=1, user-scalable=no (or minimum-scale=1) page with no keyboard reads a 1,246 px keyboard in Chromium's Pixel 7 emulation where it read 0 before, against AST-099 FR2; also require the root to be as wide as the visual viewport, and add a no-keyboard test for a wider layout at scale 1.
  • The new branch also runs on Android, but the change has no emulator evidence, which AST-099's Platform support and DEC-3 require at the exact head before merge; please add keyboard-height readings in Chrome with Gboard (default and resizes-content) and in Firefox for Android at the fixed head.

The red-first pan tests and the Simulator capture show the focus-pan fix itself.

[Automated review]

@cixzhang

cixzhang commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 280bac1

@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 scale-1 root floor now restores the keyboard height during Safari's focus pan and keeps a layout wider than the screen at no keyboard, but the Android emulator readings the keyboard record requires at this head are not on the pull request.

  • The new branch in layoutViewportHeight (packages/core/src/utils/layoutViewport.ts:54-61) also runs on Android, and AST-099's Platform support and DEC-3 make emulator evidence at the exact head a merge gate, so please post here the readings the description attributes to another run: innerHeight, the root's client height, the visual viewport and the module's keyboard height, keyboard up and down, in Chrome with Gboard (default and resizes-content) and in Firefox for Android.

The red-first pan tests, the new wide-layout tests and the Simulator capture cover the iOS fix itself.

[Automated review]

expect(layoutViewportHeight(win)).toBe(377);
});

it('keeps innerHeight where it is taller than the root, as in Chrome on Android with its toolbar 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.

This case and the resizes-content case above encode Android readings from assumed root heights. AST-099's Platform support makes emulator evidence at the exact head a merge gate, so please post the emulator's own readings at this head on the pull request: Chrome with Gboard (default and resizes-content) and Firefox for Android, keyboard up and down.

…pans to a focused field

While Safari on iOS 26.2 pans the page to a focused field near the bottom,
it reports innerHeight as the visual viewport's height (377 of 714 with
the keyboard up). layoutViewportHeight read innerHeight, so the keyboard
module measured 0, as if no keyboard were up, against AST-099 FR2 ("a page
pan is never a smaller keyboard"). The root's client height keeps the
layout viewport's height. At a visual-viewport scale of 1, where
innerHeight equals the visual viewport's height and the root is as wide
as the visual viewport, layoutViewportHeight now takes the larger of the
two. Everywhere else it is unchanged: a zoomed-out page's scaled
innerHeight and the root's unscaled height are in different units, a
layout wider than the screen gives a root taller than the screen, and the
scroll clamp reads as before.

The README's ride formula now takes the keyboard's top from
layoutViewportHeight(window), not innerHeight.

@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 scale-1 root floor restores the keyboard height during Safari's pan to a focused field and keeps zoomed-out and wider-than-screen layouts at no keyboard, but the Android emulator readings the keyboard record requires at the exact head are still not on the pull request.

  • The new branch in layoutViewportHeight (packages/core/src/utils/layoutViewport.ts:54-61) also runs on Android, and AST-099's Platform support makes emulator evidence at the exact head a merge gate, so please post here the readings the description attributes to another run (innerHeight, the root's client height, the visual viewport and the module's keyboard height, keyboard up and down) in Chrome with Gboard, default and resizes-content, and in Firefox for Android.

The red-first pan tests and the Simulator capture cover the iOS fix itself.

[Automated review]

@vjeux

vjeux commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

The readings at this head are now in the description's Testing section. They come from the Android 16 emulator: Chrome 133 with Gboard, default and resizes-content, and Firefox 159, keyboard down and up. A local probe page reads the module built from 86c74e8.

  • Chrome default, keyboard up: innerHeight 811, root 811, visual viewport 499.43 @ 312, keyboard 311.57.
  • Chrome resizes-content, keyboard up: 499, 499, 499.43 @ 0, keyboard 0.
  • Firefox default, keyboard up: 796, 796, 486.2 @ 245.93, keyboard 309.8.
  • Firefox resizes-content, keyboard up: 486, 486, 486.2 @ 0, keyboard 0.
  • Keyboard down: all four browser and page pairs read 0, with innerHeight and the root equal (811 or 796).

The root's client height equals innerHeight in every reading, so the floor returns main's value. Under resizes-content the new branch runs, and the root shrinks with the viewport. The readings are the same with animations at normal scale, with animations removed, and under a slow-device profile.

@astracat-bot

astracat-bot Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

In reply to @vjeux

I'll run a full review to re-check this with your note.

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

Approving: the scale-1 root floor in layoutViewportHeight restores the whole keyboard height during Safari's pan to a focused field and keeps zoomed-out and wider-than-screen layouts at no keyboard, and the Android emulator readings at this head now cover Chrome with Gboard (default and resizes-content) and Firefox for Android.

Approved pending CI: CI has not run on this head yet, because its branch is in a fork; a maintainer adds the ci:run label so CI runs.

[Automated review]

@vjeux
vjeux marked this pull request as ready for review November 5, 2026 15:18
@cixzhang cixzhang added the ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) label Nov 6, 2026
@cixzhang
cixzhang marked this pull request as draft November 6, 2026 17:26
@cixzhang
cixzhang marked this pull request as ready for review November 6, 2026 17:27
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Nov 6, 2026
@cixzhang

cixzhang commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 76289f2

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Nov 6, 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 scale-1 root floor in layoutViewportHeight still restores the whole keyboard height during Safari's pan to a focused field and keeps zoomed-out and wider-than-screen pages at no keyboard, and CI passes on this head, but the merge from main needs the Android emulator readings taken again.

  • Regression: merging main moved the head after the Android emulator readings were taken, and AST-099's Platform support requires them at the exact head now that the merged docked ride, reveal and room checks read layoutViewportHeight, so please re-take the Chrome-with-Gboard (default and resizes-content) and Firefox for Android readings, keyboard up and down, with the three animation and device profiles, on a build of this head, and refresh the Testing section, which still describes the earlier rebase.

[Automated review]

vjeux added 2 commits November 7, 2026 14:01
The pan check in layoutViewportHeight read the visual viewport's height
a second time in each keyboard measurement, and every reader that laid
out against the keyboard measured the layout viewport again. The
keyboard module now passes the height it read, and keeps the layout
viewport's height with each report, which getKeyboardViewportHeight
returns. ChatLayout's keyboard hook, useMobileKeyboard, the Layer's
keyboard clearance and the keyboard target read it there.
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Nov 8, 2026
@cixzhang

cixzhang commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 7952b5f

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Nov 8, 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 scale-1 root floor in layoutViewportHeight restores the whole keyboard height during Safari's pan to a focused field, and the Chrome-with-Gboard readings at this head now cover normal animation scale, Remove animations and 4× CPU, but Firefox for Android still lacks two of the profiles the keyboard spec requires at the exact head.

  • Please take the Firefox for Android emulator readings (default and resizes-content, keyboard up and down) on a build of this head with Remove animations and under a slow-device profile, and add them to the Testing section, because AST-099's Platform support names Firefox as the Android proof of FR2–FR5 and requires both profiles at the exact head.

Nothing else in the change blocks.

[Automated review]

@vjeux

vjeux commented Nov 8, 2026

Copy link
Copy Markdown
Contributor Author

Re-taken at 7952b5f, in the description's Testing: Chrome with Gboard (default and resizes-content) and Firefox 159, keyboard up and down, at normal animation scale. Firefox matches Chrome within 1 px. The one change, AI chat in the default viewport (no pan, the composer card on the keyboard's top), is intended. It is #8176's dock (spec:AST-099 FR3 and FR4) and came in with the main merge, not the one-read commit. Chrome with animations removed and under a slow-device profile are still to be re-taken at this head.

@vjeux

vjeux commented Nov 8, 2026

Copy link
Copy Markdown
Contributor Author

Chrome with Remove animations (reduced motion active) and at 4× CPU are now re-taken at 7952b5f as well. All eight readings equal those at normal speed, to 0 px. With the normal-speed Chrome and Firefox 159 readings, they are in the description's Testing.

@vjeux
vjeux marked this pull request as draft November 10, 2026 02:47
useChatKeyboard.ts takes main's modality and focusReturn imports, and
keyboardTarget.ts main's nearestScroller import. Neither imports
layoutViewport: their readers use getKeyboardViewportHeight.
@cixzhang

Copy link
Copy Markdown
Contributor

/approve-spec 348b522

@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 scale-1 root floor in layoutViewportHeight restores the whole keyboard height during Safari's pan to a focused field (the pan cases fail on main and pass here), and the code shows no defect; one evidence item remains.

  • The Android emulator readings that spec:AST-099 Platform support requires at the exact head are missing: the newest are from earlier heads, and spec:AST-094 DEC-31 cannot carry them because this main merge changed useChatKeyboard.ts and keyboardTarget.ts (both changed here) and motion/animator.ts, which they import.

Next: take Chrome with Gboard and Firefox for Android in the emulator on a build of this head (default and resizes-content, keyboard up and down) at normal animation scale, with animations removed and under a slow-device profile, and add them to the Testing section. Required CI has not run on this head yet, as the pull request is a draft.

[Automated review]

This branch was successfully deployed

1 active deployment
Preview — 348b522b Deployed Nov 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants