Skip to content

✨ feat(web): move the composer with the mobile keyboard - #36

Merged
martinezharo merged 2 commits into
mainfrom
t3/smooth-mobile-keyboard
Oct 20, 2026
Merged

martinezharo merged 2 commits into
mainfrom
t3/smooth-mobile-keyboard

Conversation

@martinezharo

Copy link
Copy Markdown
Owner

Summary

Two mobile/PWA fixes for the chat:

Composer travels with the keyboard (Chromium/Android). Chrome reports the on-screen keyboard once, ~100–170 ms into its slide, never frame by frame (measured on device). With interactive-widget=resizes-content the page jumped. Now, while the chat is mounted:

  • The keyboard overlays the page (navigator.virtualKeyboard.overlaysContent), and ui/keyboard.ts publishes --keyboard-inset.
  • On focus, the dock starts easing up straight away to the last keyboard height seen (stored per orientation), using Android's IME curve (285 ms, cubic-bezier(0.2, 0, 0, 1)). geometrychange only corrects it, and a timed resync handles hardware keyboards. Closing starts on blur.
  • .keyboard-dock takes max(safe-area, keyboard) instead of adding them.
  • The thread keeps its distance from the end when its height changes (usePinnedScrollEnd), and modals stay clear of the keyboard.
  • Engines without the API (Safari, Firefox) keep the existing viewport-meta behaviour. Other views keep the browser resize, which is what keeps focused form fields visible.

Dark navigation bar in the installed app. Chrome paints a WebAPK's system navigation bar with the manifest background_color (#f8f4f2, confirmed by sampling a device screenshot), and the manifest has no dark variant. Under the dark theme this showed a light strip, also visible while the keyboard opened. background_color is now #0c0a09.

Compatibility

  • No data or protocol changes; existing spaces are unaffected.
  • Light theme now gets a dark navigation bar and a dark splash screen. That was a deliberate trade-off.
  • Installed PWAs pick up the new colour on their next WebAPK update (or a reinstall).

Testing

  • typecheck, lint, format, unit tests ✅
  • Tested on an Android phone (Samsung, Chrome PWA) through a temporary tunnel, using an on-screen event log during the experiments.

Chrome on Android reports the on-screen keyboard once, partway through its
slide, so the chat jumped instead of travelling with it. In the chat view
the keyboard now overlays the page (VirtualKeyboard API) and the composer
starts easing up on focus using the last height seen, with Android's IME
curve; the browser's report only corrects it. The thread keeps its
distance from the end when its height changes, and dialogs over the chat
stay clear of the keyboard. Engines without the API are unchanged.

The manifest background_color is now the dark background: Chrome paints
an installed app's navigation bar with it and the manifest has no dark
variant, so the dark theme showed a light strip under the app (also
visible while the keyboard opened). Light mode now gets a dark bar and
splash instead. Installed apps pick it up on their next WebAPK update.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
sendself 5ae39e3 Oct 20 2026, 03:44 PM

@oli-review oli-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review

This PR makes the chat composer track the Android virtual keyboard by switching to the VirtualKeyboard overlay API, publishing a predicted/stored keyboard height as --keyboard-inset, easing the dock with Android's IME curve, and pinning the thread's scroll position across resizes. Modals also reserve the keyboard inset, and the PWA manifest background is darkened so the installed app's navigation bar is dark.

Risk: low


Automated review — opencode-go/deepseek-v4.1-flash · 1 comment(s)

Comment thread apps/web/src/ui/components.tsx Outdated
aria-labelledby={titleId}
tabIndex={-1}
class="animate-modal-in flex max-h-[calc(100dvh-2rem)] w-full max-w-[440px] flex-col overflow-hidden rounded-xl3 bg-elevated shadow-float outline-none"
class="animate-modal-in flex max-h-full w-full max-w-[440px] flex-col overflow-hidden rounded-xl3 bg-elevated shadow-float outline-none"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 minor — Keep the dvh cap for non-overlay browsers

Replacing max-h-[calc(100dvh-2rem)] with max-h-full also affects browsers that never set --keyboard-inset (iOS Safari, Firefox): the panel is now capped by the fixed backdrop's layout-viewport height, which can exceed 100dvh when mobile browser chrome is visible, so tall dialogs (Add device, recovery file) can extend below the visible area. Subtracting the inset keeps the old dvh cap when it is 0 and still shrinks the panel above an overlaid keyboard.

Suggested change
class="animate-modal-in flex max-h-full w-full max-w-[440px] flex-col overflow-hidden rounded-xl3 bg-elevated shadow-float outline-none"
class="animate-modal-in flex max-h-[calc(100dvh-2rem-var(--keyboard-inset,0px))] w-full max-w-[440px] flex-col overflow-hidden rounded-xl3 bg-elevated shadow-float outline-none"

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Applied in df2c371: the panel keeps the dvh cap and subtracts the keyboard inset (0 where it is not overlaid).

@oli-review oli-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review

This follow-up commit applies the previously requested fix to the shared Modal: the panel keeps a dvh-based height cap and now subtracts the overlaid keyboard inset, so dialogs stay within the visible viewport in both overlay and non-overlay browsers. No other behavior changes.

Risk: low


Automated review — opencode-go/deepseek-v4.1-flash · 0 comment(s) · incremental

@martinezharo
martinezharo merged commit 85cc55e into main Oct 20, 2026
2 checks passed
@martinezharo
martinezharo deleted the t3/smooth-mobile-keyboard branch October 20, 2026 16:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant