fix(mobile-chat): hide executor footer and remove gap above keyboard (#1789)
## Problem On mobile, when the soft keyboard is up in Chat, the executor footer (task counts / **Running** indicator) stayed visible and there was a dead band of empty space between the composer and the keyboard. Root cause: `computeMobileBarKeyboardFlags` gated the footer-hide + padding-strip to **iOS only**. On Android `footerHidden` stayed `false`, so `ExecutorStatusBar` kept rendering **and** `.project-content` kept reserving `footer-height + nav-height` (~80px) of `padding-bottom`. The mobile nav bar is slid off-screen (`translateY(100%)`) when the keyboard is up, so that reserved space rendered as the empty gap. ## Fix Drop the `&& isIOS` gate on `footerHidden` so Android matches iOS. When the keyboard is open on mobile: - `ExecutorStatusBar` is hidden (no task-count/Running footer), and - `.project-content` reserves no footer/nav padding, so the composer sits flush above the keyboard. `footerKeyboardOpen` (the iOS `bottom: 0` footer-collapse class) stays iOS-only — it only matters when the footer is still rendered over a modal. Modal / Quick-Chat-overlay keyboard cases are unchanged (`boardLayoutSuppressed`). ## Before / After | Before | After | | --- | --- | | Footer shown + empty gap above keyboard | Footer hidden, composer flush above keyboard | _(Rendered with the real component CSS at a mobile viewport with the keyboard-open classes applied.)_ ## Notes / verification - File-scoped tests pass: `mobileBarKeyboardFlags`, `mobile-bottom-bars-keyboard-layout`, App keyboard-layout cases, `footer-safe-layout`, `dashboard-footer-mobile-layout`, `ChatView.mobile-render` (46 tests). - FN-5707 originally gated this to iOS over a concern that stripping nav padding mid-focus could make Android Chrome dismiss the keyboard. The strip is keyed off `keyboardOpen`, which only flips `true` after the visual viewport settles into its keyboard-open size — but **please confirm on a real Android device** that the keyboard stays up when tapping the composer. If it dismisses, the fallback is a transform-based hide that avoids the reflow. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- stage-review-badge-begin --> --- <a href="https://stagereview.app/Runfusion/Fusion/pull/1789"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg"> <img src="https://stagereview.app/assets/gh-open-in-stage-light.svg" alt="Open in Stage"> </picture> </a> <!-- stage-review-badge-end -->
This commit is contained in:
7
.changeset/mobile-keyboard-footer-gap.md
Normal file
7
.changeset/mobile-keyboard-footer-gap.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Mobile: hide the executor footer and remove the empty gap above the keyboard while typing.
|
||||
category: fix
|
||||
dev: computeMobileBarKeyboardFlags no longer iOS-gates footerHidden, so Android keyboard-open now hides ExecutorStatusBar and drops the reserved footer+nav padding-bottom (composer sits flush above the keyboard). footerKeyboardOpen stays iOS-only. Supersedes FN-5707's Android gate.
|
||||
@@ -440,18 +440,16 @@ function AppInner() {
|
||||
// viewport. Without this guard, modal keyboard state leaks into the app-level
|
||||
// layout, causing stale bottom-padding offsets after the keyboard closes.
|
||||
//
|
||||
// Android-gated: with `interactive-widget=resizes-content` the layout
|
||||
// viewport itself shrinks with the soft keyboard, so we DON'T want to
|
||||
// also hide the nav bar / strip its padding — that produces a layout
|
||||
// jump while the focused input is settling, which Android Chrome treats
|
||||
// as the focus target moving and dismisses the keyboard immediately.
|
||||
// iOS doesn't shrink the layout viewport, so the iOS path keeps the
|
||||
// hide-nav-on-keyboard behavior intact.
|
||||
//
|
||||
// FN-5707: keep nav pinning cross-platform, but only apply the footer
|
||||
// keyboard-collapse class on iOS. On Android, collapsing the footer to
|
||||
// `bottom: 0` overlaps it with the nav bar because the layout viewport
|
||||
// already shrinks and the stacked footer position is already correct.
|
||||
// FNXC:MobileChatKeyboardLayout 2026-06-26-09:04:
|
||||
// When the keyboard is up on mobile we now hide the executor footer and
|
||||
// drop the reserved footer+nav padding on BOTH platforms (see
|
||||
// computeMobileBarKeyboardFlags) so the composer sits flush above the
|
||||
// keyboard with no empty gap. This supersedes the earlier Android gate
|
||||
// (FN-5707), which kept the footer visible and left a ~80px dead band
|
||||
// where the off-screen nav bar's padding remained reserved.
|
||||
// `footerKeyboardOpen` (the footer `bottom: 0` collapse class) stays
|
||||
// iOS-only: it only matters when the footer is still rendered (e.g. over
|
||||
// a modal), where Android's resizes-content already stacks it correctly.
|
||||
const { footerHidden, navKeyboardOpen, footerKeyboardOpen } = computeMobileBarKeyboardFlags({
|
||||
isMobile,
|
||||
keyboardOpen,
|
||||
|
||||
@@ -2,7 +2,11 @@ import { describe, expect, it } from "vitest";
|
||||
import { computeMobileBarKeyboardFlags } from "../mobileBarKeyboardFlags";
|
||||
|
||||
describe("computeMobileBarKeyboardFlags", () => {
|
||||
it("keeps footer rendered and uncollapsed on Android when keyboard is open", () => {
|
||||
it("hides the footer on Android when keyboard is open, but does not apply the iOS bottom:0 collapse class", () => {
|
||||
// FNXC:MobileChatKeyboardLayout 2026-06-26-09:04:
|
||||
// Android now matches iOS: the keyboard-open footer is hidden (and its
|
||||
// reserved padding dropped) so the composer sits flush above the keyboard
|
||||
// with no dead band. `footerKeyboardOpen` stays iOS-only.
|
||||
const flags = computeMobileBarKeyboardFlags({
|
||||
isMobile: true,
|
||||
keyboardOpen: true,
|
||||
@@ -11,9 +15,9 @@ describe("computeMobileBarKeyboardFlags", () => {
|
||||
isIOS: false,
|
||||
});
|
||||
|
||||
expect(flags.footerKeyboardOpen).toBe(false);
|
||||
expect(flags.footerHidden).toBe(false);
|
||||
expect(flags.footerHidden).toBe(true);
|
||||
expect(flags.navKeyboardOpen).toBe(true);
|
||||
expect(flags.footerKeyboardOpen).toBe(false);
|
||||
});
|
||||
|
||||
it("hides and collapses footer on iOS when keyboard is open and no overlay is open", () => {
|
||||
|
||||
@@ -13,17 +13,18 @@ export interface MobileBarKeyboardFlags {
|
||||
footerKeyboardOpen: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* FN-5707: Android uses `interactive-widget=resizes-content`, so the layout
|
||||
* viewport shrinks with the keyboard and the footer's normal stacked bottom
|
||||
* position remains correct above the mobile nav. Only iOS should apply the
|
||||
* footer keyboard-collapse class (`bottom: 0`) used to let the keyboard cover
|
||||
* bars when visualViewport shifts independently.
|
||||
*
|
||||
* Fullscreen mobile overlays (for example Quick Chat's sheet) own their own
|
||||
* visual viewport handling. Treat them like modals for board-layout padding so
|
||||
* overlay-local keyboards never shift the underlying board.
|
||||
*/
|
||||
/*
|
||||
FNXC:MobileChatKeyboardLayout 2026-06-26-09:04:
|
||||
While the soft keyboard is up on mobile, the dashboard must NOT show the executor footer (task counts / Running indicator) and must NOT leave dead space above the keyboard. `footerHidden` drives both: it returns the ExecutorStatusBar null AND drops `.project-content`'s reserved footer+nav padding-bottom, letting the composer sit directly above the keyboard.
|
||||
|
||||
This now applies to BOTH iOS and Android. Previously `footerHidden` was iOS-only (FN-5707): on Android `interactive-widget=resizes-content` shrinks the layout viewport, so the footer's stacked bottom position was technically "correct" — but with the nav bar slid off-screen (`translateY(100%)`) on keyboard-open, its reserved ~80px (footer-height + nav-height) padding rendered as an empty gap between the footer and the keyboard, with the footer still visible. Matching iOS removes both the footer and the gap on Android.
|
||||
|
||||
FN-5707's original Android concern (stripping nav padding mid-focus could make Android Chrome treat the focused input as moving and dismiss the keyboard) is mitigated because the strip is keyed off `keyboardOpen`, which only flips true AFTER the visualViewport has settled into its keyboard-open size — not during the focus transition.
|
||||
|
||||
`footerKeyboardOpen` (the footer `bottom: 0` collapse class) stays iOS-only: it is only meaningful when the footer is still rendered (e.g. over a modal), and Android's resizes-content keeps the stacked position correct in that case.
|
||||
|
||||
Fullscreen mobile overlays (for example Quick Chat's sheet) own their own visual viewport handling. Treat them like modals for board-layout padding so overlay-local keyboards never shift the underlying board.
|
||||
*/
|
||||
export function computeMobileBarKeyboardFlags({
|
||||
isMobile,
|
||||
keyboardOpen,
|
||||
@@ -32,7 +33,7 @@ export function computeMobileBarKeyboardFlags({
|
||||
isIOS,
|
||||
}: MobileBarKeyboardFlagsInput): MobileBarKeyboardFlags {
|
||||
const boardLayoutSuppressed = anyModalOpen || overlayOpen;
|
||||
const footerHidden = isMobile && keyboardOpen && !boardLayoutSuppressed && isIOS;
|
||||
const footerHidden = isMobile && keyboardOpen && !boardLayoutSuppressed;
|
||||
const navKeyboardOpen = isMobile && keyboardOpen;
|
||||
const footerKeyboardOpen = navKeyboardOpen && isIOS;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user