Skip to content

feat(ChatLayout): the composer rides the software keyboard - #8176

Merged
cixzhang merged 13 commits into
meta:mainfrom
vjeux:uikit/c08-keyboard-guide
Nov 7, 2026
Merged

cixzhang merged 13 commits into
meta:mainfrom
vjeux:uikit/c08-keyboard-guide

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

User need

On a phone, the keyboard covers ChatLayout's composer. Safari pans the page to show it, leaving it scrolled after Done. In Messages, the field sits on the keyboard.

Problem and solution fit

ChatLayout reads the keyboard module's height (spec:AST-099 FR2): the dock rests on the keyboard, and the message area's end padding keeps the newest message above it, via the stream scroll's lock (FR3). Under a finger, every route into the composer's text skips the browser's reveal, a claimed tap still clicks, and Done rests focus on the composer's group, ringed after typing (FR4). In Chrome on Android the dock rides Gboard's slide on the compositor (FR8). Under Reduce Motion it lays out once, with one cross-fade.

Current specification

spec:AST-099 FR2–FR4, FR8, and FR9's docked containers: a Core host applies the keyboard height on its own element, riding where FR8 applies. The records commit removes Gap 9, drops ChatLayout from Gaps 1 and 11, adds rows.

Public delta and caller burden

  • No new prop or API. In a top-level page under a finger, the composer's wrapper is a group with no name (component:ChatLayout AR4), where Done rests focus; no tab stop. A mouse or an iframe changes nothing.
  • The docs recommend a 100dvh container.

Design and alternatives

  • Main's keyboard module, plus three exports (deliverTap, followsKeyboard, watchPans): useChatKeyboard reads its height, gate, focus routes, tap delivery, guide.
  • Under Remove animations, Chrome reports Back's hide in steps. With the VirtualKeyboard API, the module's store holds them until none, a rise, or 300 ms of quiet (1 s at most); every reader shares it.
  • The ride attaches only where the guide publishes a slide (keyboardProfileOf(win) === 'chromium'), as BottomSheet's does.
  • Rejected: per-frame dock moves.

Evidence

Home-screen app: main pans the page, this PR doesn't; Done rings the group after typing, not after a tap.

iOS 26.2 Simulator (app, Safari) main this PR
Page pan, keyboard up 403 px, 337 px 0, 0
Document scroll after Done 32 px, 72 px 0, 0
Focus after Done the page, the text the composer group

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.
  • Public text and artifacts contain no internal Meta context.

Writing rubric results

spec:AST-099 (Gaps 1, 9 and 11, four verification rows): R1, R2, R4–R6 none; R3: each row names tests or the simulator.

Testing

  • Dropped from the earlier draft, because main's guide has no WebKit profile (feat(core): one keyboard module; a Tall BottomSheet reads it and rides Gboard #8559/feat(core): the keyboard module's docked ride, the scroller's reveal and the tap route #9776's design), not because they failed: the spring rise, the unconfirmed hide, focus returning mid-hide, the 600 ms block, the mid-rise block, the rise through a blocked main thread, and both iOS Reduce Motion proofs. The rise trace and the storyboard now run on Chrome's profile.

  • useChatKeyboard.test.tsx (31): one case moved to Chrome's profile, and a new case shows that without the VirtualKeyboard API nothing subscribes the guide. The composer group has no name, and in an iframe the region stays a plain container: both fail on dbd0285 and pass here. A claimed tap clicks once (fails on af17d9a), and the parked group's ring follows the input modality (fails on 44c7740). A reader 5 px above the end, whom useChatStreamScroll still follows, keeps the newest message (fails on 5377ca2): the end-keeping reads that hook's lock (its live mark on the scroller) and jumps with its instant scrollToBottom. The local caret code and its cases are gone: main's focusAtPoint and its tests cover the tap's caret. jsdom dated events on the epoch clock, so main's guide timers overflowed and every ride ended after 1 ms. The file now dates events on performance.now(), as a browser does, with no warnings left.

  • ChatLayoutKeyboard spec: Chromium 18/18; WebKit (CI-profile container) 12 passed, 6 skipped (Chrome's profile needs the VirtualKeyboard API). New: after typing, Done's park shows the shared focus ring (fails on af17d9a's build); after a tap and no typing it shows none (fails on 44c7740's build). New: under Reduce Motion, a hide reported in steps (60%, 35%, 15%, then none of the keyboard, 40 ms apart) draws the dock where the keyboard's top was or at rest, and the layout changes once. On b6803cc's build the dock steps down with each report (layout offsets 321, 186, 102, 35, 0 px; 3/3 fail); here 5/5 pass.

  • keyboardHeight.test.ts, ten new cases: under Remove animations with the VirtualKeyboard API, the store keeps the shown height through the steps and reports the hide once; the guide publishes one hide and no change; a lower keyboard that stays up is reported after 300 ms; a pan's scroll events leave that wait as it is; steps that go on are reported after 1 s; a rise, the last reader leaving and a show end or skip the hold. Seven fail on b6803cc; the three guards (a show, no Reduce Motion, no API) pass on both. Keyboard, Chat, BottomSheet and layer-clearance units at this head (main merged): 1,219/1,219.

  • The keyboard browser specs (ChatLayout, BottomSheet keyboard inset, Search, capped lists, Popover): Chromium 49 passed; WebKit (CI-profile container) 34 passed, 15 skipped. After the main merge, the spec's holds and storyboard name the dock's translate as the motion they judge, as main's harness now asks.

  • "the rise does no Layout or Paint per frame", on Gboard's slide, 125–220 ms after the report: it first checks that a ride runs through that window, so a missing or early-ended ride fails instead of reading 0. It isolates the ride: focus starts the composer's own feedback, a 125 ms box-shadow transition that paints every frame, and the proof lets it end before the trace. In CI at a303db7 (pr-interaction (1/6)) that transition's first style pass landed in the report's frame, so it painted from 14 to 131 ms and into the window. The proof also reads the ride's start in a microtask, where the engine sets it, instead of waiting on the animation's ready. That focus paint during a rising keyboard is tracked separately, as a design question outside this PR. At this head: 24/24 on a 2-thread pin (one physical core) at 1 worker and 24/24 in the CI-profile container. At 4 workers on the pin, a busy machine can put the caret's first blink or the ride's starved start frames in the window; test(ChatLayout): the rise proof keeps the caret out and names a starved start #10489, stacked on this one, makes the proof hold there.

  • The iOS 26.2 Simulator, builds from main d0d1f30 and a9c87ac (this head's ChatLayout code; the store's hold needs the VirtualKeyboard API, which Safari lacks): home-screen web-app takes (1 main; 4 this PR, 2 typed and 2 tapped before Done, one click per tap, no pan) and Safari takes at af17d9a. The image is home-screen frames, with no browser chrome. The numbers come from the page itself.

  • Android 16 emulator with Gboard, Chrome 133 and Firefox for Android 159, at this head, 3c1f5d1, on its own deployment, from a separate run: all 9 cells pass (show, with Back or a blur hide, at normal speed, under Remove animations and at 4× CPU), plus resizes-content and Firefox. The dock's bottom is 767.2 px at rest and the keyboard's top is 495.2 px; times are after the tap, Back or blur.

    • Show and Back, normal speed: the dock rides up to the keyboard's top with no visual-viewport pan, and Back rides it down to rest by 484 ms.
    • Show and Back at 4× CPU slowdown, a slow-device profile with animations on: the dock rests on the keyboard's top, and Back rides it down by 362 ms.
    • Show and a blur hide (a script's blur with the keyboard up), at normal speed and at 4× CPU: both ways ride with the keyboard, and the hide reaches rest by 1,028 ms and by 792 ms.
    • Remove animations, for Back at normal speed and at 4× CPU, and for the blur hide: one change each way, with one cross-fade and no steps. The hide holds the dock at the keyboard's top while it fades, then it appears at rest: at 822 ms, 533 ms and 187 ms. The show does the reverse. The Back take at normal speed is from a warm boot: a take after a cold boot stopped drawing frames 1.1 s after the tap, a fault of the emulator.
    • interactive-widget=resizes-content (set on the viewport meta before the tap): Chrome resizes the page (innerHeight 783 → 495), and the dock moves to 15.8 px above the keyboard in one step, with no ride and no pan.
    • Firefox for Android: one step onto the keyboard's top (489.1 px), and back to rest on hide.
    • Exactly one click on the first tap, in every take.
    • The Panel View story clipped its composer below its panel on main; fix(ChatLayout): the Panel View story's composer rests in its panel #10447 fixed the story.
  • Core UI suite (pnpm test:ui) at this head: 913 files, 22,385 tests pass, and this change's tests print no warning. Core typecheck: 0 errors. pnpm check:knowledge passes, and CI=true pnpm lint passes (0 errors), including the comment-provenance baseline check that failed CI's lint at b6803cc. ESLint (strict) and Prettier are clean on the changed TypeScript and Markdown files (lint-staged's set).

  • The touch-gate commit, a refactor, is behavior-neutral: the composer's focus and the takeover call the keyboard module's touch gate (followsKeyboard, now exported, with a unit test) and isCoarsePointer instead of copies, and ChatLayout passes scrollToBottom directly. The same suites pass unchanged, and the iOS takes match the previous head's.

  • The commit-path commit, from test(ChatLayout): the rise proof keeps the caret out and names a starved start #10489's review, is a refactor too. commit() listens to the keyboard module's reports itself, after the guide, instead of through a ChatLayout render per report, and the composer's reveal-pan watch calls watchPans instead of a copy. Timed in the page (probe, not for landing; median of 24 interleaved runs): in the harness's Chrome Android profile, the first frame after the report, 7.7 → 6.7 ms at 1× CPU and 33.0 → 28.7 ms at 4×; Safari's profile commits the lift in 2.9 → 2.2 ms and 13.1 → 9.5 ms. The ride starts in the report's own task either way. The same suites pass unchanged.

Review notes

@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 7, 2026 1:57pm UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 24, 2026
@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 7, 2026, 6:22 PM UTC

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · 3c1f5d1

Storybook · Sandbox · built from this PR's head

Modified components

Component Size LoC Visual · A11y · RTL
ChatLayout 🔴 78.2 kB (+16.5 kB) · 10.4 kB CSS (+48 B) — ✓ · ✓ · —
lab/Comments 🔴 326.8 kB (+4.7 kB) · 2.5 kB CSS (0) — — · — · —
lab/CommentThread 🔴 320.3 kB (+3.1 kB) · 2.4 kB CSS (0) — — · — · —
118 more components
Component Size LoC Visual · A11y · RTL
lab/CommentComposer 🔴 146.2 kB (+3.0 kB) · 1.9 kB CSS (0) — — · — · —
ChatComposer 75.8 kB (+311 B) · 11.7 kB CSS (0) — ✓ · ✓ · —
ChatComposerInput 73.8 kB (+281 B) · 11.2 kB CSS (0) — ✓ · ✓ · —
ChatComposerTokenElement 73.8 kB (+280 B) · 11.2 kB CSS (0) — ✓ · ✓ · —
charts/ChartTooltip 174.0 kB (+225 B) · 423 B CSS (0) — — · — · —
TabList 42.1 kB (+220 B) · 6.4 kB CSS (0) — — · — · —
charts/Chart 196.7 kB (+214 B) · 494 B CSS (0) — — · — · —
PowerSearch 280.6 kB (+213 B) · 23.5 kB CSS (0) — — · — · —
richtext/RichTextView 274.3 kB (+212 B) · 2.3 kB CSS (0) — — · — · —
StatusDot 29.6 kB (+210 B) · 5.3 kB CSS (0) — — · — · —
Switch 99.4 kB (+210 B) · 9.9 kB CSS (0) — — · — · —
FieldLabel 49.2 kB (+209 B) · 5.6 kB CSS (0) — — · — · —
MobileNav 68.8 kB (+209 B) · 10.5 kB CSS (0) — — · — · —
ChatSendButton 59.0 kB (+208 B) · 9.9 kB CSS (0) — ✓ · ✓ · —
Link 51.5 kB (+207 B) · 7.0 kB CSS (0) — — · — · —
Banner 60.6 kB (+206 B) · 10.4 kB CSS (0) — — · — · —
TextArea 60.5 kB (+206 B) · 9.2 kB CSS (0) — — · — · —
CommandPalette 71.3 kB (+205 B) · 12.4 kB CSS (0) — — · — · —
TextInput 65.9 kB (+205 B) · 11.0 kB CSS (0) — — · — · —
DropdownMenu 94.4 kB (+203 B) · 17.1 kB CSS (0) — — · — · —
ListItem 68.1 kB (+203 B) · 11.3 kB CSS (0) — — · — · —
Calendar 69.6 kB (+202 B) · 10.5 kB CSS (0) — — · — · —
ChatDictationButton 59.1 kB (+202 B) · 10.0 kB CSS (0) — ✓ · ✓ · —
ChatLayoutScrollButton 59.1 kB (+202 B) · 10.1 kB CSS (0) — ✓ · ✓ · —
Toolbar 41.4 kB (+202 B) · 7.9 kB CSS (0) — — · — · —
richtext/RichTextEditor 294.6 kB (+202 B) · 2.6 kB CSS (0) — — · — · —
AvatarGroup 44.3 kB (+201 B) · 5.6 kB CSS (0) — — · — · —
SideNavCollapseButton 59.4 kB (+201 B) · 9.9 kB CSS (0) — — · — · —
Toast 96.1 kB (+201 B) · 10.2 kB CSS (0) — — · — · —
ToggleButton 59.5 kB (+200 B) · 10.0 kB CSS (0) — — · — · —
Lightbox 110.0 kB (+199 B) · 10.3 kB CSS (0) — — · — · —
RadioList 54.5 kB (+199 B) · 7.2 kB CSS (0) — — · — · —
Slider 104.9 kB (+199 B) · 8.7 kB CSS (0) — — · — · —
Button 58.6 kB (+198 B) · 9.9 kB CSS (0) — — · — · —
DateRangeInput 90.3 kB (+198 B) · 12.3 kB CSS (0) — — · — · —
InputGroup 54.8 kB (+198 B) · 7.8 kB CSS (0) — — · — · —
MobileNavToggle 58.9 kB (+198 B) · 9.9 kB CSS (0) — — · — · —
IconButton 58.6 kB (+197 B) · 9.9 kB CSS (0) — — · — · —
Tooltip 27.9 kB (+197 B) · 4.3 kB CSS (0) — — · — · —
Typeahead 77.8 kB (+197 B) · 12.3 kB CSS (0) — — · — · —
lab/Schedule 103.1 kB (+197 B) · 3.9 kB CSS (0) — — · — · —
Avatar 41.5 kB (+195 B) · 5.5 kB CSS (0) — — · — · —
ComplexSelector 69.1 kB (+194 B) · 11.7 kB CSS (0) — — · — · —
Field 53.1 kB (+194 B) · 7.2 kB CSS (0) — — · — · —
TimeInput 157.1 kB (+194 B) · 15.7 kB CSS (0) — — · — · —
lab/EmojiPicker 84.5 kB (+194 B) · 751 B CSS (0) — — · — · —
DrawerHeader 61.6 kB (+193 B) · 12.8 kB CSS (0) — — · — · —
Selector 97.4 kB (+193 B) · 17.1 kB CSS (0) — — · — · —
TableHeaderCell 106.8 kB (+193 B) · 17.2 kB CSS (0) — — · — · —
CheckboxInput 62.6 kB (+192 B) · 9.1 kB CSS (0) — — · — · —
TableCell 107.3 kB (+192 B) · 17.4 kB CSS (0) — — · — · —
DialogHeader 61.4 kB (+191 B) · 11.4 kB CSS (0) — — · — · —
Markdown 177.9 kB (+191 B) · 20.0 kB CSS (0) — — · — · —
Stepper 60.2 kB (+191 B) · 10.0 kB CSS (0) — — · — · —
lab/TransferList 106.1 kB (+191 B) · 1.7 kB CSS (0) — — · — · —
HoverCard 26.3 kB (+190 B) · 4.3 kB CSS (0) — — · — · —
lab/ListInput 99.7 kB (+190 B) · 1.6 kB CSS (0) — — · — · —
BaseTypeahead 69.3 kB (+189 B) · 10.8 kB CSS (0) — — · — · —
CheckboxListItem 69.2 kB (+189 B) · 11.3 kB CSS (0) — — · — · —
Pagination 107.4 kB (+189 B) · 17.5 kB CSS (0) — — · — · —
Popover 87.0 kB (+189 B) · 13.9 kB CSS (0) — — · — · —
TabMenu 69.8 kB (+189 B) · 11.0 kB CSS (0) — — · — · —
TopNav 70.5 kB (+188 B) · 10.8 kB CSS (0) — — · — · —
MultiSelector 92.7 kB (+187 B) · 16.6 kB CSS (0) — — · — · —
lab/TransferListSelector 112.7 kB (+187 B) · 1.9 kB CSS (0) — — · — · —
ContextMenu 106.0 kB (+186 B) · 17.0 kB CSS (0) — — · — · —
DateInput 181.1 kB (+186 B) · 16.7 kB CSS (0) — — · — · —
richtext/RichTextEditorToolbar 249.2 kB (+186 B) · 454 B CSS (0) — — · — · —
CheckboxList 67.9 kB (+185 B) · 8.1 kB CSS (0) — — · — · —
Table 115.4 kB (+185 B) · 17.7 kB CSS (0) — — · — · —
Carousel 67.7 kB (+184 B) · 10.8 kB CSS (0) — — · — · —
CodeBlock 73.2 kB (+184 B) · 11.2 kB CSS (0) — — · — · —
TopNavMegaMenu 67.8 kB (+183 B) · 11.7 kB CSS (0) — — · — · —
MoreMenu 94.7 kB (+181 B) · 17.1 kB CSS (0) — — · — · —
SideNavHeading 73.6 kB (+181 B) · 11.2 kB CSS (0) — — · — · —
Tokenizer 89.5 kB (+181 B) · 18.3 kB CSS (0) — — · — · —
TopNavMenu 72.6 kB (+181 B) · 11.1 kB CSS (0) — — · — · —
TopNavHeading 73.0 kB (+179 B) · 11.0 kB CSS (0) — — · — · —
InfoTip 45.4 kB (+177 B) · 5.3 kB CSS (0) — — · — · —
BreadcrumbItem 82.5 kB (+174 B) · 12.0 kB CSS (0) — — · — · —
lab/DiffViewer 84.9 kB (+172 B) · 2.1 kB CSS (0) — — · — · —
SegmentedControl 91.7 kB (+171 B) · 6.8 kB CSS (0) — — · — · —
lab/ChatReactionBar 102.6 kB (+171 B) · 1.1 kB CSS (0) — — · — · —
lab/CommentPin 54.9 kB (+171 B) · 1.2 kB CSS (0) — — · — · —
RadioListItem 65.6 kB (+170 B) · 10.1 kB CSS (0) — — · — · —
lab/SelectableCardGroup 62.5 kB (+170 B) · 162 B CSS (0) — — · — · —
DateTimeInput 203.3 kB (+169 B) · 18.4 kB CSS (0) — — · — · —
SideNavItem 68.6 kB (+169 B) · 11.0 kB CSS (0) — — · — · —
SideNav 88.4 kB (+168 B) · 11.3 kB CSS (0) — — · — · —
lab/ChatEmojiPicker 101.5 kB (+168 B) · 773 B CSS (0) — — · — · —
FileInput 67.4 kB (+167 B) · 10.8 kB CSS (0) — — · — · —
AlertDialog 103.2 kB (+166 B) · 14.2 kB CSS (0) — — · — · —
AppShell 89.0 kB (+165 B) · 13.4 kB CSS (0) — — · — · —
TableSelectionToolbar 68.2 kB (+165 B) · 13.0 kB CSS (0) — — · — · —
BottomSheet 65.0 kB (+163 B) · 7.5 kB CSS (0) — — · — · —
Thumbnail 62.9 kB (+159 B) · 11.5 kB CSS (0) — — · — · —
AvatarGroupOverflow 46.7 kB (+157 B) · 5.5 kB CSS (0) — — · — · —
NumberInput 70.0 kB (+154 B) · 11.3 kB CSS (0) — — · — · —
DropdownMenuSubMenu 68.3 kB (+148 B) · 9.7 kB CSS (0) — — · — · —
lab/TourStep 93.3 kB (+147 B) · 590 B CSS (0) — — · — · —
ProgressBar 11.7 kB (+37 B) · 6.2 kB CSS (0) — — · — · —
charts/ChartLegend 47.1 kB (+4 B) · 224 B CSS (0) — — · — · —
Timestamp 31.4 kB (+4 B) · 10.2 kB CSS (0) — — · — · —
ChatComposerDrawer 17.9 kB (0) · 4.7 kB CSS (0) — ✓ · ✓ · —
ChatMessage 17.1 kB (0) · 3.7 kB CSS (0) — ✓ · ✓ · ✓
ChatMessageBubble 7.5 kB (0) · 3.7 kB CSS (0) — ✓ · ✓ · ✓
ChatMessageList 27.3 kB (0) · 7.0 kB CSS (0) — ✓ · ✓ · —
ChatMessageMetadata 20.2 kB (0) · 4.1 kB CSS (0) — ✓ · ✓ · —
ChatSystemMessage 7.0 kB (0) · 3.8 kB CSS (0) — ✓ · ✓ · —
ChatTokenizedText 7.2 kB (0) · 4.0 kB CSS (0) — ✓ · ✓ · —
ChatToolCalls 31.2 kB (0) · 8.3 kB CSS (0) — ✓ · ✓ · —
CommandPaletteInput 30.8 kB (0) · 7.5 kB CSS (0) — — · — · —
Heading 11.6 kB (0) · 5.4 kB CSS (0) — — · — · —
Text 11.0 kB (0) · 5.4 kB CSS (0) — — · — · —
Timer 13.1 kB (0) · 5.9 kB CSS (0) — — · — · —
Spinner 23.9 kB (−1 B) · 6.1 kB CSS (0) — — · — · —
chat — — — · ✓ · —
Chat — — ✓ · ✓ · —

Package size:

  • @astryxdesign/cli 3.49 MB packed (+52 B); no new dependencies
  • @astryxdesign/core 6.91 MB packed (+17.0 kB); no new dependencies

API changes: none in this pull request

Committed API report behind this build: cli 19 rows, core 25 rows; run pnpm api:report.

Touched components checked; add ci:run-visual, ci:run-a11y or ci:run-rtl to run the full suites.


Generated by PR Enrichment workflow | View full report

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 24, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 24, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 24, 2026
…rinks to stay above it

On iOS 26.2 a popover that would run under the software keyboard shrinks,
its top held on its source, so that its far edge stays 10 pt above the
keyboard; its content scrolls in the shorter box, and it grows back when the
keyboard goes down. Popover's height cap is the layout viewport, which the
iOS keyboard does not shrink, so the keyboard covered the bottom of a tall
popover and a Save button there could not be reached.

usePopoverKeyboardAvoidance follows the keyboard layout guide (meta#8176): while
the popover is open, a keyboard that would cover it caps its height
popover.keyboardClearancePx above the keyboard and makes it scroll, keeping
the focused field in view. UIKit animates the bounds; a height cannot change
on the compositor, so it changes once where the keyboard hides it: when a
rising keyboard has arrived, and as a falling one starts down. A Keyboard
Avoidance story exercises it.

@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 can't be approved yet: ChatLayout's new keyboard behavior has no governing spec, and the draft spec:AST-094 it builds on is unapproved and amends current records (architecture:theme-tokens INV3, design:user-states DR1 and DR3, family:buttons, spec:AST-025 FR14). An engineering owner needs to decide whether ChatLayout owns keyboard following as built here and which record states it, and whether to approve AST-094 and update the current records it amends; they record approval by commenting /approve-spec <full head SHA> on this pull request, and the author may do so if they are an engineering owner. Independently of that decision:

  • Land #8045, #8067 and #8074 first and rebase, so this pull request is the 1,751-line keyboard change rather than 26,380 lines.
  • Make the lint check pass; ESLint runs out of heap on this head.
  • Add the writing rubric results for AST-094 to the description, and fix its citation of spec:AST-090, which does not exist in the repository.

[Automated review]

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 24, 2026
# Conflicts:
#	packages/core/src/motion/README.md
#	packages/core/src/motion/uikit/index.ts
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 24, 2026
…rinks to stay above it

On iOS 26.2 a popover that would run under the software keyboard shrinks,
its top held on its source, so that its far edge stays 10 pt above the
keyboard; its content scrolls in the shorter box, and it grows back when the
keyboard goes down. Popover's height cap is the layout viewport, which the
iOS keyboard does not shrink, so the keyboard covered the bottom of a tall
popover and a Save button there could not be reached.

usePopoverKeyboardAvoidance follows the keyboard layout guide (meta#8176): while
the popover is open, a keyboard that would cover it caps its height
popover.keyboardClearancePx above the keyboard and makes it scroll, keeping
the focused field in view. UIKit animates the bounds; a height cannot change
on the compositor, so it changes once where the keyboard hides it: when a
rising keyboard has arrived, and as a falling one starts down. A Keyboard
Avoidance story exercises it.

@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 can't be approved yet: ChatLayout's new keyboard behavior has no governing spec, and the draft spec:AST-094 it builds on is unapproved and amends current records (architecture:theme-tokens INV3, design:user-states DR1 and DR3, family:buttons, spec:AST-025 FR14). An engineering owner needs to decide whether ChatLayout owns keyboard following as built here and which record states it, and whether to approve AST-094 and update the records it amends; they record that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so if they are an engineering owner. Independently of that decision:

  • Land #8045, #8067 and #8074 first and rebase, so this pull request is the 1,831-line keyboard change rather than 26,460 lines.
  • The claimed composer tap places the caret only in contenteditable editors, so a tap inside a <textarea> input's draft now leaves the caret at its previous position instead of under the finger.
  • The same claim walks past contenteditable="false" chips, so a tap on a button inside a chip in an unfocused composer focuses the editor and never fires the button's click.
  • The ChatLayout docs say tapping the composer focuses it without scrolling, but only taps on the text are claimed; a tap on the composer body still focuses without preventScroll.
  • Add the writing rubric results for AST-094 to the description, and fix its citation of spec:AST-090, which does not exist in the repository.

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

This can't be approved yet: ChatLayout's keyboard following has no governing spec, and the new draft spec:AST-094 (FR1–FR23, DEC-1, DEC-2, including its amendments of architecture:theme-tokens, design:user-states, family:buttons and spec:AST-025 FR14) is unapproved, so an engineering owner needs to identify or provide the spec for keyboard following and approve or reject AST-094 by commenting /approve-spec <full head SHA> on this pull request; the author may do so if they are an engineering owner. Whatever that decision, these fixes are needed:

  • A touch tap on a button inside the composer's text (a chip in a contenteditable="false" island) is swallowed by the new composer tap recognizer, and a tapped <textarea> gets its caret at the start instead of under the finger (useChatKeyboard.ts).
  • Land #8045, #8067 and #8074 on their own and rebase, so this pull request carries only the keyboard change of about 1,850 lines.
  • Add AST-094's writing-rubric results to the description, fix its reference to spec:AST-090 (no such record exists), and narrow the tap claim in ChatLayout.doc.mjs.

[Automated review]

Comment thread packages/core/src/gestures/README.md Resolved
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 24, 2026
@cixzhang

cixzhang commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 51552d9

@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. ChatLayout's composer now rests on the on-screen keyboard, its touch focus routes skip the page scroll, and a transcript the stream scroll follows stays at its newest message through useChatStreamScroll, so the earlier scroll-ownership item is fixed. Two items remain:

  • spec:AST-099 Platform support makes Android emulator evidence at the exact head a merge gate, so please add the Chrome-with-Gboard takes (show, hide, reduced motion, interactive-widget=resizes-content) and the Firefox for Android takes that the description says come from a separate run.
  • Miss: keyboardSurface.ts:38-46 and the takeover's isActive (useChatKeyboard.ts:481-483) re-implement the keyboard module's touch-and-keyboard gate (followsKeyboard in keyboard/keyboardTarget.ts) and isCoarsePointer from utils/coarsePointer.ts, so please export the module's gate and call it, and call isCoarsePointer, instead of keeping a second copy that can drift.

The record rows this pull request adds are owner-approved for this head, and the size is one inseparable change.

[Automated review]

The composer's focus and its focus takeover call the keyboard module's
touch gate, followsKeyboard, now exported, and isCoarsePointer instead
of their own copies. ChatLayout passes the stream scroll's
scrollToBottom to useChatKeyboard directly. Each Effect's comment names
what it synchronizes. Behavior does not change.
@cixzhang

cixzhang commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

/approve-spec b6803cc

@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 Android emulator takes at this head now cover what spec:AST-094 DEC-27 asks for, and the record rows are approved for this head; one blocking finding remains:

  • Miss: under Reduce Motion in Chrome on Android, each of Chrome's stepped hide reports on Back moves the dock at once and commits the layout (the keyboard module turns a report between two non-zero heights into a non-animated change, which useChatKeyboard.ts:277-285 lands immediately), so the dock steps down as the description's Remove-animations take says, while spec:AST-099 FR8 requires that nothing travels with the keyboard under reduced motion, one DR2 cross-fade with one layout commit at rest; please make the Reduce Motion hide a single cross-faded change, ideally in the keyboard module so BottomSheet shares it, with a stepped-hide test and a Remove-animations Back take.

[Automated review]

The keyboard spec's header says Done's park shows the focus ring after
typing only, as the code does. The changeset says nothing changes under
a mouse with no software keyboard, the case the touch gate leaves alone.
@vjeux

vjeux commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

Both text notes from the last review (the keyboard spec's header and the changeset's last sentence) go to the hide-ride follow-up, so this head keeps its device evidence.

vjeux added 2 commits November 6, 2026 23:05
Under Remove animations, Chrome on Android reports a hide as several
lower keyboards that are still up, then none. Each step reached every
reader, so ChatLayout's dock stepped down and laid out each time.
Where the VirtualKeyboard API exists and reduced motion is on, the
keyboard module now holds such a step and keeps the shown height. The
report of none, a report back up, or 300 ms without a new height ends
the hold, which lasts 1 s at most; a pan leaves it as it is. So every
reader lays out once and cross-fades once.
The composer's comments no longer cite INV11 or the theme-tokens
record, so the comment-provenance baseline lists four fewer sites.
@cixzhang

cixzhang commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

/approve-spec a303db7

@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. ChatLayout's composer now rests on the on-screen keyboard and keeps the page still on touch screens, and the spec:AST-099 record rows are approved for this head. Three items remain:

  • Regression: since #10438 landed on main, the interaction harness requires every hold to name the motion it judges, so ChatLayoutKeyboard.interaction.browser.spec.ts:652, :688 and :756 throw ("holdAnimations: name the motion that the test judges") and fail typecheck once merged with main; please pass {judges: {target: dock, property: 'translate'}} to both holdAnimations calls and judges: {target: dock, property: 'translate'} to animationStoryboard, then update the branch onto main.
  • Found by CI: the proof "the rise does no Layout or Paint per frame" (ChatLayoutKeyboard.interaction.browser.spec.ts:638) fails in CI's interaction job at this head, where the rise paints every frame for its first 131 ms and two of those paints land in the 125–220 ms window, so please find what repaints early in the rise and take it off the ride (or show it is not the dock and narrow the proof) so the required check passes reliably.
  • Please add the Android emulator takes at this head that spec:AST-099 Platform support requires before merge, at least Back under Remove animations at normal speed and under the slow-device profile, showing the dock held at the keyboard's old top and one cross-fade to rest.

The inline note on the hold's 250 ms figure does not block.

[Automated review]

vjeux added 4 commits November 7, 2026 01:05
Focus starts the composer's own feedback, a 125 ms box-shadow
transition that paints every frame. In CI its first style pass landed
in the keyboard report's frame, so it painted into the proof's
125-220 ms window. The proof now lets it end before the trace. It also
reads the ride's start in a microtask: a promise on the animation's
ready gave the main thread a frame of its own.
The comment names the measured hide, hideMs, instead of 250 ms.
CI runs on a branch updated onto main, as review asked.
Main's interaction harness now asks each hold and storyboard to name
the motion the test judges. The dock's translate is that motion here.
@cixzhang

cixzhang commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 6ed3557

@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 code is unchanged: ChatLayout's composer rests on the on-screen keyboard and keeps the page still on touch screens, the spec:AST-099 record rows are approved for this head, and CI is green.

One item remains:

  • Please add Android emulator takes at this exact head, as spec:AST-099 Platform support requires before merge: the Remove animations Back take in the description is from an earlier head, and the merge of main since then changed how the animator lands a Reduce Motion cross-fade (#10378), so at least Back under Remove animations at normal speed and under the slow-device profile need taking here.

The note on getKeyboardHeight's doc comment does not block.

[Automated review]

vjeux added 3 commits November 7, 2026 04:18
The layout took each keyboard height change through useKeyboardHeight,
which re-rendered ChatLayout on every report only to run an Effect that
called commit(). commit() now listens to the keyboard module's reports
itself, after the layout guide, so a ride still claims a change first.
The composer's reveal-pan watch repeated keyboardTarget's watchPans; it
now calls it, exported. The keyboard-surface lines in ChatComposerInput
move above the doc comments they sat under.
Main's scroll-anchoring owner module now writes the stream scroll's
mark. isFollowingNewest reads the lock from a set the hook keeps in
step with it, not from the old attribute.
Main now registers every Core context under a versioned key, so two
copies of Core share one context. The keyboard surface context, new in
this change, registers the same way.
@cixzhang

cixzhang commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 3c1f5d1

@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. ChatLayout's composer rests on the on-screen keyboard and keeps the page still on touch screens at this head, the spec:AST-099 record rows are approved for this head, and CI is green.

One item remains:

  • Please add Android emulator takes at this exact head, as spec:AST-099 Platform support requires before merge: the description dates its Chrome and Firefox takes to two earlier heads and says the full set at this head follows, and this head changes when the layout takes each keyboard report (useChatKeyboard.ts) and how the transcript's follow state is read, so show, Back and a blur hide at normal speed, Back under Remove animations, and the slow-device profile need taking here.

The note on getKeyboardHeight's doc comment does not block.

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

Approved. ChatLayout's composer now rests on the on-screen keyboard on touch screens and keeps the page still, the spec:AST-099 record rows are approved for this head by an engineering owner, the Android emulator takes in the description are at this exact head, and CI is green.

One non-blocking note:

  • getKeyboardHeight's doc comment (packages/core/src/keyboard/keyboardHeight.ts) still says a reader between two reports sees the current height; during the Remove-animations hold it sees the shown height, so a sentence saying so would keep the doc accurate.

[Automated review]

This branch was successfully deployed

1 active deployment
Preview — 3c1f5d19 Deployed Nov 7, 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. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants