Repository navigation
feat(mobile): native compose bar for the chat composer - #2355
Conversation
Convert the chat FlatList from inverted to oldest-first on iOS and Android. useFollowLatest now measures the distance from the end, pins streaming growth with one non-animated scroll per frame, keeps maintainVisibleContentPosition on for prepends, and reveals the list only after it has settled at the end. Older history loads from onStartReached, the markdown viewport measures a non-inverted content view, and the working status and history footer swap ends. The chat uses the system stack header: transparent with the automatic scroll edge effect on iOS (no header background colour, no glass on bar items), an opaque Material top app bar on Android. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
Update DESIGN.md's Chat header, list and code-rule bullets and the ios-hig skill's chat edge-effect note, and add the change fragment (placeholder PR number 0000). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
…yboard gap - Header: on iOS the title chip (avatar + name, no state dot) is a native leading bar item beside Back, separated by a zero-width spacing item so UIKit draws a round Back and its own glass capsule; the computer button is a native trailing item and headerTitle is empty. Android keeps a Material top app bar with the chip at the start. - Follow latest: only a drag, the status-bar scroll to top or an explicit hold leave the newest message. Layout-driven scroll events (the automatic inset adjustment, MVCP corrections, batch growth) no longer release following, which left the chat opened at the oldest message. - iOS keyboard frame: the keyboard already covers the home indicator, so take the composer's safe-area padding back out of the keyboard offset. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… drag to dismiss - The default and colour chat backgrounds are the frame's backgroundColor instead of a view in front of the list, so the message list is in the screen's first-subview chain, where UIKit and React Native Screens look for the content scroll view of the header's soft scroll edge. - Cancel UIKit's automatic home-indicator bottom inset on the list while the keyboard is closed; the composer height already carries it. - Dismiss the keyboard by dragging the list: interactive on iOS, on-drag on Android. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
… to dismiss Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
iOS follows iOS 26 Messages: a separate round + button and a field capsule with the mic or Send inside, both on Liquid Glass on iOS 26 and on opaque surface fills on older iOS or Reduce Transparency. Android follows Material 3: + as a ripple icon button beside a filled rounded field. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 22 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
…DM rest Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…at header The computer button and the first-screen back control are native bar buttons with SF Symbols, and the title chip uses the system label colour, so dark mode with Reduce Transparency keeps them legible. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
…-07shzq-native-chat
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A local Expo module gives the composer container a UIScrollEdgeElementContainerInteraction for the message list, so UIKit softens messages under the composer like under a system toolbar. Builds without the module keep the drawn fade. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
On iOS the composer's Send and recording stop are a filled fg circle, and the jump-to-latest button is a Liquid Glass circle at the trailing edge. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Messages under the field showed through the clear glass and competed with the draft; the + and the field now tint their glass with surface at glass.tint. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
…eyboard The iOS + circle and field capsule are 40pt like Grok's compose bar, with a 44pt hit area. The glass jump button is 36pt and sits closer to the composer. Text fields open the keyboard in the app's appearance instead of the system's. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The scroll edge element container gets a near-invisible fill over its bounds, so UIKit shapes the bottom edge to the whole composer instead of only the placeholder label, which disappears once there is text. The + and capsule glass are tinted with the opaque surface colour. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
…state Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
UIKit shapes the bottom edge when the interaction is added, so a taller draft or an open keyboard left it stale. The container now re-adds it whenever its height or the keyboard state changes. The fill marker, which made no difference, is removed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ttles Dismissing the keyboard with a tall draft left the bottom edge shaped for the raised composer, so the strip below the capsule stayed sharp. The view now also re-adds the interaction once the keyboard has finished showing or hiding. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
The mention suggestion list above the composer floats on the same glass as the composer capsule, with an xl corner like an iOS 26 menu. Older iOS, Reduce Transparency and Android keep the sheet layer. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The simulator showed it made no difference: the strip below a tall capsule stays sharp even when the keyboard never opened, so the extra observers are removed. This restores the c17b5b1 behaviour. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1
Captured on the Xiaomi phone (Android 16, HyperOS 3, zh-CN) running a Release build of main d220792, light and dark: the Material chat composer with the keyboard open, the chat header, the Unpair confirmation dialog (cancelled, nothing unpaired) and the message actions menu. The same set is filed under each PR. Status bar, computer names and the keyboard suggestion strip are redacted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Android phone result: PASS (Xiaomi, Android 16 / HyperOS 3, isolated QA Host, deepseek-v4.1-flash), release build of main d220792 Screenshots: 2355-android-hig/phone (commit 73da8de): Generated by Claude Code |
Requested by Vain · project thread
Why the change
Before: the chat composer is one opaque pill with +, the field and the mic inside it.
After: the composer follows each platform's own compose bar.
Vain asked for this: "就差底下这个输入框也应该用原生的,看原生怎么设计". This lifts the earlier "don't change the composer" rule.
Special things to note
modules/scroll-edge-elementgives the bottom soft edge. A build without it falls back to the drawn fade, so an OTA update alone is safe but shows the old edge.surface, so it reads near-white.pnpm lint, mobiletscandoxfmtpass, andvitest apps/mobile/testreports 877 passed, 9 skipped.Change outline
composer-chrome.ios.tsx:ComposerCircleButtonandComposerFieldMaterial.surfacefill with aborderStronghairline.composer-chrome.tsx(Android and web):raisedfill.composer.tsx[+ | cancel | hold dot]then the capsule, bottom-aligned. The capsule inset makes one line match the + circle.composer-*testIDs are kept.modules/scroll-edge-element: aUIScrollEdgeElementContainerInteractionon the composer's container for the message list. It is re-added whenever the composer's height or the keyboard changes.xlcorner on iOS 26.iOS 26 Simulator screenshots
Composer states (82808b8, before the merge with main, old header)
🤖 Generated with Claude Code
https://claude.ai/code/session_01Pfa9gqyZUnwgh2fwSFVQQ1