Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
PR Analysis Report📚 Storybook Preview🧪 Sandbox PreviewModified ComponentsMultiSelector (@astryxdesign/core) · View in Storybook
Component Size
Sizes are gzip. Import counts the JavaScript the import loads eagerly; CSS counts the package stylesheet rules referenced by the modules it can load from its own package, lazily loaded ones included.
|
| Component | JavaScript | CSS |
|---|---|---|
| core/BottomSheet | +15.2KB |
+2B |
| core/ChatLayout | +16.2KB |
+39B |
| core/DateInput | +14.0KB |
+4B |
| core/DateTimeInput | +13.8KB |
+3B |
| core/PowerSearch | +12.1KB |
+4B |
| core/TimeInput | +14.3KB |
+4B |
Bundle Size Summary
Gzip figures measure each package's entry file, not the code an import pulls in.
| Package | Entry (ESM) | Entry gzip before | Entry gzip after | Delta |
|---|---|---|---|---|
| @astryxdesign/core | 4.9KB | 1.2KB | 1.2KB | 0B |
| Package stylesheet | Gzip before | Gzip after | Delta |
|---|---|---|---|
| @astryxdesign/core | 35.4KB | 35.4KB | +26B |
Accessibility Audit
Status: 2 accessibility violation(s) found — 2 serious.
ChatComposer - 1 issue(s)
- 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
- Rule:
color-contrast· Affects 1/19 stories · Learn more - WCAG: 1.4.3 (Level AA)
- Rule:
ChatComposerInput - 1 issue(s)
- 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
- Rule:
color-contrast· Affects 1/18 stories · Learn more - WCAG: 1.4.3 (Level AA)
- Rule:
Visual Regression
55 of 504 shot(s) changed.
A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.
| component | story | theme | mode | pixels |
|---|---|---|---|---|
| Selector | Indicator Space Bottom Sheet Narrow End RTL | neutral | light | 753,933 |
| Selector | Indicator Space Bottom Sheet Wide Start | neutral | light | 740,072 |
| RichTextEditor | Markdown parity: side by side | probe | dark | 558,840 |
| RichTextEditor | Markdown parity: side by side | probe | light | 540,756 |
| Chat | Full AI Chat | probe | light | 440,175 |
| Chat | Full AI Chat | probe | dark | 438,105 |
| AppShell | With Mobile Nav | probe | light | 217,010 |
| AppShell | With Mobile Nav | probe | dark | 216,960 |
| Chat | Empty State | probe | light | 122,185 |
| Chat | Empty State | probe | dark | 120,076 |
| Collapsible | Dividers | probe | dark | 66,445 |
| Collapsible | Dividers | probe | light | 66,445 |
| AppShell | Playground | neutral | dark | 65,198 |
| AppShell | Playground | neutral | light | 63,788 |
| Chat | Default | neutral | dark | 36,304 |
| Chat | Default | neutral | light | 35,767 |
| ChatAutoScroll | Scroll Behavior Comparison | neutral | dark | 15,378 |
| ChatAutoScroll | Scroll Behavior Comparison | neutral | light | 14,107 |
| Chat | With Attachments | probe | light | 13,406 |
| Chat | With Attachments | probe | dark | 11,232 |
and 35 more.
Generated by PR Enrichment workflow | Storybook | Sandbox | View full report
…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.
There was a problem hiding this comment.
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
lintcheck 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]
| 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 |
There was a problem hiding this comment.
spec:AST-090 does not exist on main or at this head, so owners are asked to rule on a departure from requirements nobody can read. Cite the record where FR1 (exits off by default) and FR10 live, or drop the departure until it lands; the same citation appears in Current-state impact (line 235).
# Conflicts: # packages/core/src/motion/README.md # packages/core/src/motion/uikit/index.ts
…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.
There was a problem hiding this comment.
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]
| return null; | ||
| } | ||
| const editable = element.closest<HTMLElement>( | ||
| '[contenteditable]:not([contenteditable="false"]), textarea, input', |
There was a problem hiding this comment.
This claims taps on textarea and input, and on anything inside a contenteditable="false" island, but focusAtPoint can place the caret only in a contenteditable, and recognizing the tap swallows the touch's click. In Chromium with touch, a tap 180 px into a textarea draft leaves the caret at offset 0 instead of 23, and a tap on a button inside a chip in an unfocused composer never fires its click. Claiming only contenteditable text, and skipping [contenteditable="false"], button, a, [role="button"] and [data-astryx-token] targets as ChatComposer's click-to-focus already does, would keep both working.
| subComponentOf: 'Chat', | ||
| displayName: 'Chat Layout', | ||
| description: 'Layout shell for full chat interfaces. Messages flow in normal page flow, composer is fixed to the bottom with a frosted glass dock. Set density (compact/balanced/spacious) to control spacing, message max-width, and blur layer sizing. Includes built-in auto-scroll, a "New messages" scroll-to-bottom button, and a frosted glass blur layer behind the composer. By default the layout root is the scroll container; pass scrollRef to delegate scrolling to a parent element or the document body.', | ||
| description: 'Layout shell for full chat interfaces. Messages flow in normal page flow, composer is fixed to the bottom with a frosted glass dock. Set density (compact/balanced/spacious) to control spacing, message max-width, and blur layer sizing. Includes built-in auto-scroll, a "New messages" scroll-to-bottom button, and a frosted glass blur layer behind the composer. By default the layout root is the scroll container; pass scrollRef to delegate scrolling to a parent element or the document body. On a phone the composer rides on top of the software keyboard, in step with it, and a conversation at its newest message stays there; tapping the composer focuses it without scrolling the page.', |
There was a problem hiding this comment.
Only taps on the composer's text are claimed; a tap on the composer's empty body still focuses through ChatComposer's click handler with a bare focus(), so Safari's reveal remains there. The changeset's wording ("A tap on the composer's text") matches what the code does.
There was a problem hiding this comment.
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 inChatLayout.doc.mjs.
[Automated review]
| const editable = element.closest<HTMLElement>( | ||
| '[contenteditable]:not([contenteditable="false"]), textarea, input', | ||
| ); |
There was a problem hiding this comment.
closest() walks past a contenteditable="false" island to the editor root, so a touch on a button inside a token (a custom token's render()) is claimed and its click is cancelled: a trusted touch tap on such a button clicks once on the base and never here. Return null when the target is inside [contenteditable="false"], button, a, [role="button"] or [data-astryx-token], as ChatComposer's own click-to-focus already does.
| const range = doc.caretRangeFromPoint?.(x, y); | ||
| if ( | ||
| range != null && | ||
| editable.isContentEditable && |
There was a problem hiding this comment.
For a claimed <textarea> or <input> this places no caret, and the cancelled click takes the browser's own placement with it: a tap mid-line leaves the caret at offset 0 (offset 23 on the base). Set selectionStart/selectionEnd from the tap point for text controls, or stop claiming them.
| subComponentOf: 'Chat', | ||
| displayName: 'Chat Layout', | ||
| description: 'Layout shell for full chat interfaces. Messages flow in normal page flow, composer is fixed to the bottom with a frosted glass dock. Set density (compact/balanced/spacious) to control spacing, message max-width, and blur layer sizing. Includes built-in auto-scroll, a "New messages" scroll-to-bottom button, and a frosted glass blur layer behind the composer. By default the layout root is the scroll container; pass scrollRef to delegate scrolling to a parent element or the document body.', | ||
| description: 'Layout shell for full chat interfaces. Messages flow in normal page flow, composer is fixed to the bottom with a frosted glass dock. Set density (compact/balanced/spacious) to control spacing, message max-width, and blur layer sizing. Includes built-in auto-scroll, a "New messages" scroll-to-bottom button, and a frosted glass blur layer behind the composer. By default the layout root is the scroll container; pass scrollRef to delegate scrolling to a parent element or the document body. On a phone the composer rides on top of the software keyboard, in step with it, and a conversation at its newest message stays there; tapping the composer focuses it without scrolling the page.', |
There was a problem hiding this comment.
Only touches on unfocused editable text are claimed; a tap on the composer's padding or empty footer reaches ChatComposer's click-to-focus, which calls focus() without preventScroll. Suggest "tapping the composer's text field focuses it without scrolling the page".
| 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 |
There was a problem hiding this comment.
There is no spec:AST-090 on main, the base or this head, so the owner cannot see what FR7 departs from. Cite the record that holds those requirements, or drop the departure (also at line 235).
| ## Touch presentation | ||
|
|
||
| Whether a component presents itself the touch (UIKit) way is answered once, | ||
| in `../utils/touchPresentation.ts`: |
There was a problem hiding this comment.
../utils/touchPresentation.ts and the test said to enforce it do not exist on this head or on main. Remove this section or mark it as planned with AST-094 FR13.
…llowance under every primary pointer The useMenuPress record's FR9 puts the trigger's held finger under spec:AST-094 FR29 under every primary pointer (FR43), so a finger on a touchscreen laptop may drift 13 pt before the open, as on a phone. The trigger read (pointer: coarse) as its press started and took the long press's 10 px where the pointer was fine. Three cases fail until it does not: a 12 pt drift opens under a fine pointer too, a (12, 12) settle opens there, and nothing reads a pointer query, the trigger's press included.
…lowance under every primary pointer The useMenuPress record's FR9 governs the trigger's held finger by spec:AST-094 FR29 under every primary pointer (FR43). The trigger now hands the long press FR29's 13 px drift allowance whatever the primary pointer, so it no longer reads (pointer: coarse) as its press starts, and the menus drop their coarsePointer import. ContextMenu keeps the long press's own allowable movement. The changeset says the same.
Strict ESLint reported six unused bindings in files this pull request carries: the host's HAPTIC_SWITCH_ATTRIBUTE import, two unused imports in the pan's tests (scrollProbe, ScreenEdgePanGestureRecognizer), the swipe test's drift, the presented-hit-testing test's gestures, and a GestureHost test's before, which now asserts what it was read for: a second finger's moves leave the pan where it was.
…eal angular speed The release velocity of a rotation whose listener sets rotation = 0 on each change, at 2 degrees every 16 ms, read 4.363 rad/s against the 2.182 rad/s of the same stream without a re-base: the test asserted only more than 1.5. It now asserts the 2.18 rad/s (spec:AST-100 FR2), and fails.
The rotation's velocity tracker samples the raw unwrapped angle, which setting rotation does not move, so the setter's shift of the samples read each re-base as extra turn and doubled the release velocity of a listener that re-bases on every change. The setter no longer touches the tracker.
…mates while it runs The proof read the element's animations 230 ms in, after the dip had ended, and asserted only that every animated key is opacity, which an empty list passes: with the cross-fade removed from the plan (the move still landing), the proof still passed. It now reads the keyframe keys right after the call starts and asserts they include opacity and nothing else; the same mutant fails it. Chromium and WebKit pass. (F1's in-flight read from c648034.)
…roller, and its claimed moves are cancelled under pointer events Two comparisons stop at the shadow boundary, each failing a case: - once the host's listener on a recognizer's inner node has seen the finger, isFingerDownIn reads it as outside a light-DOM scroller around that root, so a scroll under it is not the finger's (FR20); - under inputMode 'pointer-events', a claimed pan on a shadow host cancels none of its finger's moves over the inner content, since the finger's target is the inner node and its touches reach the host retargeted (FR19).
…the flat tree isFingerDownIn and the pointer-events finger match compare with containsAlongFlatTree, as the scroll watch already does: a finger on content inside an open shadow root is down in the scroller around that root, and its touches, retargeted to the shadow host, match its pointer finger, so a claimed pan cancels its moves there (spec:AST-094 FR19, FR20).
…achments asks, and only then The host let the last attachment decide whether an element is an action group (useGestureRecognizer always passes the option), and never recomputed it at a detach: a plain attachment after the group's turned the group off, and a group's attachment released left a recognizer that never asked taking presses under a fine pointer. Fails until the host counts the attachments that ask.
…ents asks The host counts the attachments that pass actionGroup: true and keeps the element a group while one remains, recomputed at each detach. An attachment that passes false, as useGestureRecognizer does by default, no longer turns a group off, and a group's last asking attachment taking its leave ends the group.
… that content The presented hit test takes the document's elementFromPoint, which answers a shadow host for anything inside its open root, and compares with Node.contains, which stops at the boundary: a recognizer on a control inside a moving sheet's shadow root gets no finger, and the tap's click goes to the host. Fails until the hit test descends into open roots and compares along the flat tree.
Where a moving element is drawn away from its layout box, the host hit-tests what is drawn under the finger with the document's elementFromPoint, which answers a shadow host for anything inside its open root. The hit test now descends through each open root's own elementFromPoint and compares with the movers along the flat tree, so a recognizer on a control inside a moving sheet's shadow root gets its finger and the tap clicks that control.
…nt lets go The host's touch-action rules sit in an adopted sheet or a style element per document or shadow root, and were never removed, so a page whose last recognizer detached kept them, against spec:AST-094 FR24 (nothing touch-related remains after the last instance unmounts). Fails until a root's rules go with its last marked element.
…th its last marked element The touch-action writer records which elements it marked in each document or shadow root, and when the last one is unmarked (its last recognizer detached) it removes that root's adopted sheet or style element, so nothing touch-related remains after the last instance unmounts (spec:AST-094 FR24). A later mark writes the rules again; an element marked in another root first lets go of the old one.
… the inline style it reads - The motion registry, the host's engine gate and the README name the presented hit test as an engine workaround (spec:AST-013 FR6) with its review condition (re-measure on each iOS release; remove it once iOS WebKit hit-tests a compositor-moved element where it is drawn) and its regression fixture. - While a curve runs the animator holds the value it starts from inline, so presentation.ts and the README no longer say the inline style holds the end.
…core/internal/touch Core ships the internal entry @astryxdesign/core/internal/touch, which re-exports the gesture primitives for Astryx's own packages, and the Lab-import guard fails a Lab import of core outside core's exports (architecture:react-component-runtime INV13). The README and the barrel's header named internal workspace imports and said nothing here is exported; they now name that entry.
…ill needs the element, and the listeners that cancel The release note, the stream's header, the host's comment and the README said the stream is one passive listener per event type and that a document-capture stopPropagation no longer starves the recognizers. Such a stop never hid a listener on the same node, and some subscriptions cancel. They now say what changed: an app's window-capture stopPropagation, or a document-capture stopImmediatePropagation registered before core, no longer hides a touch; a recognizer that must claim still needs the touch to reach its element; the gesture host's touchend, click and contextmenu cancel while a claimer is attached and the page hold's touchmove while it holds; and the stream stays ahead of an app's window-capture listener only until a type's listener is re-added. The README's list of blocking touchmove listeners gains the claimed touch's target and the document while a claiming element moves.
…default it moves
The {stylexOptions} form of astryxStylex now passes StyleX
treeshakeCompensation: true unless the caller sets it. spec:AST-017 FR3
asks the release note to name the old and new default and the option
that restores the old one; it now does.
…er and for the two stops the input stream fixes - A still finger with sub-pixel jitter over a scroll view keeps its tap and scrolls nothing (spec:AST-094 FR18–FR22). - A tap is still recognized under an app's window-capture stopPropagation, and under a document-capture stopImmediatePropagation registered before core's: the recognizers hear touches on the window's capture phase. Chromium and WebKit pass.
…s once Where nothing that claims is attached, the host's touchend listener is passive and cannot cancel the engine's own click after a retargeted tap clicks what is drawn. The shared click guard swallows that late click whatever is attached; this case pins it, as asked on meta#8293.
…esture host's subscriptions The gesture host subscribes no click: the one cancelling click subscriber is the click guard's, added at a window's first arm and kept. The release note, the stream's header and the gestures README listed click with the host's touchend and contextmenu; they now name the guard's own.
BottomSheet.test.tsx's imports from both sides are kept; the search goldens are regenerated after the remaining merges.
… guide The keyboard layout guide (this pull request) reads where focus moves to tell a keyboard that comes up from one that stays: meta#8559 removed the store's focus reports because nothing there read them, so they return here with their first reader. One pair of capture listeners on the document, attached while anything subscribes, reports each focusin and focusout with the current height; BottomSheet's reveal still acts on viewport reports only.
Conflicts were in this stack's own keyboard files: the browser-support sentence keeps ChatLayout's composer and takes meta#8559's page wording, BottomSheet's test imports follow the sheetKeyboard split, and the keyboard notes keep both the sheet's and ChatLayout's Chrome 124 findings. The search goldens are regenerated after the merge.
…op-level page The composer's tap route, its scroll-free focus and Done's park attach only where the keyboard is measurable, which excludes a page inside an iframe (spec:AST-099 FR7); there the browser's own focus applies. The docs and changeset now say so.
There was a problem hiding this comment.
Changes requested. The ChatLayout keyboard work follows spec:AST-099, but this branch now carries eight open pull requests (#8566, #8559, #8076, #8360, #8446, #8484, #8554 and #8549), and 196 of its 223 files, 28,674 of 38,099 changed lines, are identical to their current heads, so merging it would land their code without their own review. Please land or rebase onto them so this pull request shows only ChatLayout's layer, and move the keyboard/sheetKeyboard.ts split to #8208 or #8559.
- The carried gesture host still listens only under a coarse pointer and adds a
PressTrackerclass, against currentarchitecture:react-component-runtimeINV13 andspec:AST-094FR43; that belongs in #8076, or in #8665 landing first. rideKeyboard.ts,keyboardLayoutGuide.ts,BottomSheetPanel.tsxand the keyboard README should name BottomSheet's hook and its new file beside ChatLayout.spec:AST-099asks for simulator evidence of the home-screen app too, which the changeset promises; please add a take of the composer there.
[Automated review]
… ChatLayout BottomSheet's useSheetKeyboard moved to keyboard/sheetKeyboard.ts, and it subscribes to the keyboard layout guide and rides Gboard with rideKeyboard as ChatLayout does, but the two modules' headers named only ChatLayout, and BottomSheetPanel's related-files list and the keyboard notes still pointed at keyboardHeight.ts. They now name the hook and its file.
There was a problem hiding this comment.
Changes requested. The ChatLayout keyboard work follows spec:AST-099, but this branch still carries eight open pull requests (#8566, #8559, #8076, #8360, #8446, #8484, #8554 and #8549): 196 of its 223 files, 28,674 of 38,105 changed lines, are identical to their current heads, so merging it would land their code without their own review. Please land or rebase onto them so this pull request shows only ChatLayout's layer, and move the keyboard/sheetKeyboard.ts split to #8208 or #8559.
- The carried gesture host still takes input only under a coarse pointer and adds a
PressTrackerclass, against currentarchitecture:react-component-runtimeINV13 andspec:AST-094FR43; that belongs in #8076 and #8360, or in #8665 landing first. spec:AST-099lists the iOS home-screen app among the simulator states that gate merge, and the changeset promises it; please add a take of the composer there.
[Automated review]
| private isActive(recognizer: GestureRecognizer): boolean { | ||
| return this.isCoarse || this.perEventRecognizers.has(recognizer); | ||
| } |
There was a problem hiding this comment.
This makes a recognizer take input only under a coarse primary pointer (or with perEventPointer). Current architecture:react-component-runtime INV13 says the gesture host "attaches for every pointer, with no touch-only query", and spec:AST-094 FR43 says gestures attach for "a mouse, a pen, and a finger, not only a coarse primary pointer". Please conform this in #8076/#8360 (or land #8665 first) and keep it out of this pull request.
| return tap.numberOfTapsRequired === 1 && tap.numberOfTouchesRequired === 1; | ||
| } | ||
|
|
||
| export class PressTracker implements TouchConsumer { |
| '@astryxdesign/cli': patch | ||
| --- | ||
|
|
||
| [feat] ChatLayout keeps its composer on top of the software keyboard where the visual viewport shrinks for it, instead of leaving it underneath until the browser scrolls the whole page: in Safari on iOS (in the browser and as a home-screen app) and in Chrome on Android (on Gboard's measured slides) the dock rises and falls with the keyboard, in step with it, and in Firefox for Android it moves above the keyboard once Firefox reports it. Where a page sets `interactive-widget=resizes-content`, Chrome shrinks the page for the keyboard itself and the composer stays above it without moving. A conversation at its newest message stays there, and the rest of the transcript can still be scrolled above the composer. On a touch screen, in a top-level page, every way into the composer's text (a tap, the keyboard's previous and next buttons, Tab, the input's `focus()`) reaches it without the page scrolling; under a mouse or inside an iframe the browser's own focus applies. On a touch screen the composer's wrapper (without the scroll-to-bottom button) is a group named "Message composer"; in a top-level page, when the keyboard's Done ends typing, focus rests there (it is no tab stop), so the next tap or Tab starts from the composer. Under a mouse the wrapper is a plain container. Under Reduce Motion the composer never travels: it cross-fades to its place above the keyboard on a touch screen. Nothing changes where no software keyboard appears. The browser-support guide's section on the on-screen keyboard covers ChatLayout's composer. |
There was a problem hiding this comment.
This promises the composer's behavior in Safari on iOS "as a home-screen app", and spec:AST-099 lists the home-screen app among the simulator states that gate merge (Platform support, and the FR2–FR5 verification row). The description's iOS recordings are of Safari in the browser only; please add a simulator take of the rise, Done and a focus route with the page added to the home screen.
|
The iOS clips in the evidence table were recorded in a home-screen web app (the rows now say so), with a Safari-in-the-browser row beside them. |
Review only this PR's change: vjeux/astryx@39ab09d...7373545, plus its docs fixes vjeux/astryx@7f457b3...ba15b99, vjeux@4e590d0ade and vjeux@3fb83346bb (the merges between them add nothing of their own).
Stacked on #8566 (on #8559, #8360 and #8484), #8554 and #8549. The carried gesture code's open points are fixed below this PR: #8665 makes the gesture host listen for every pointer (FR43), and #8360 tells recognizers apart by
internals(recognizer).familyinstead ofinstanceof.Why
On a phone, the on-screen keyboard (337 px tall on an iPhone 17 Pro) covers ChatLayout's composer. Safari then scrolls the whole page to show it, and Chrome on Android makes it jump up once the keyboard is reported. In Messages, the text field sits on the keyboard and moves with it.
What led to it
Part of bringing Astryx's touch behavior to parity with iOS's UIKit, text entry first.
spec:AST-099(current) gives core one keyboard source (#8559) and asks surfaces that sit on the keyboard to move with it. ChatLayout's composer is the main one.Options and tradeoffs
spec:AST-099DEC-2).Why this choice
ChatLayout lays out from the keyboard's height: the composer rests on the keyboard, and the conversation gets room to scroll its newest message above it. On top, one compositor animation moves the composer with the keyboard's own motion (its spring on iOS, Gboard's measured slide on Android), so it stays in step with no per-frame work. On a touch screen, every way into the text field (a tap, Tab, the keyboard's arrows) focuses it without scrolling the page, and Done rests focus on the composer's group. With a mouse, nothing changes.
How to check it
Evidence
Unit tests and browser tests (Chromium, WebKit) cover the layout, the motion against each keyboard, Reduce Motion, focus under touch and mouse, iframes and zoom. Known failures from main, fixed elsewhere: slow first loads (#8353, #8471, #8352).
Size (INV6, gzip, main → head): ChatLayout JS 50,010 → 66,504 B, CSS 10,173 → 10,212 B.