fix(mobile-chat): hide executor footer and remove gap above keyboard

On Android the keyboard-open footer (executor status bar) stayed visible
and the off-screen mobile nav bar's reserved padding rendered as an empty
band between the composer and the keyboard. computeMobileBarKeyboardFlags
no longer iOS-gates footerHidden, so both platforms now hide the footer
and drop the reserved footer+nav padding-bottom when the soft keyboard is
up, letting the composer sit flush above the keyboard. footerKeyboardOpen
(the iOS bottom:0 collapse class) stays iOS-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-06-26 09:32:53 -07:00
parent 7d13f880ba
commit e48c75ccc5
4 changed files with 37 additions and 27 deletions

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

View File

@@ -440,18 +440,16 @@ function AppInner() {
// viewport. Without this guard, modal keyboard state leaks into the app-level // viewport. Without this guard, modal keyboard state leaks into the app-level
// layout, causing stale bottom-padding offsets after the keyboard closes. // layout, causing stale bottom-padding offsets after the keyboard closes.
// //
// Android-gated: with `interactive-widget=resizes-content` the layout // FNXC:MobileChatKeyboardLayout 2026-06-26-09:04:
// viewport itself shrinks with the soft keyboard, so we DON'T want to // When the keyboard is up on mobile we now hide the executor footer and
// also hide the nav bar / strip its padding — that produces a layout // drop the reserved footer+nav padding on BOTH platforms (see
// jump while the focused input is settling, which Android Chrome treats // computeMobileBarKeyboardFlags) so the composer sits flush above the
// as the focus target moving and dismisses the keyboard immediately. // keyboard with no empty gap. This supersedes the earlier Android gate
// iOS doesn't shrink the layout viewport, so the iOS path keeps the // (FN-5707), which kept the footer visible and left a ~80px dead band
// hide-nav-on-keyboard behavior intact. // where the off-screen nav bar's padding remained reserved.
// // `footerKeyboardOpen` (the footer `bottom: 0` collapse class) stays
// FN-5707: keep nav pinning cross-platform, but only apply the footer // iOS-only: it only matters when the footer is still rendered (e.g. over
// keyboard-collapse class on iOS. On Android, collapsing the footer to // a modal), where Android's resizes-content already stacks it correctly.
// `bottom: 0` overlaps it with the nav bar because the layout viewport
// already shrinks and the stacked footer position is already correct.
const { footerHidden, navKeyboardOpen, footerKeyboardOpen } = computeMobileBarKeyboardFlags({ const { footerHidden, navKeyboardOpen, footerKeyboardOpen } = computeMobileBarKeyboardFlags({
isMobile, isMobile,
keyboardOpen, keyboardOpen,

View File

@@ -2,7 +2,11 @@ import { describe, expect, it } from "vitest";
import { computeMobileBarKeyboardFlags } from "../mobileBarKeyboardFlags"; import { computeMobileBarKeyboardFlags } from "../mobileBarKeyboardFlags";
describe("computeMobileBarKeyboardFlags", () => { 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({ const flags = computeMobileBarKeyboardFlags({
isMobile: true, isMobile: true,
keyboardOpen: true, keyboardOpen: true,
@@ -11,9 +15,9 @@ describe("computeMobileBarKeyboardFlags", () => {
isIOS: false, isIOS: false,
}); });
expect(flags.footerKeyboardOpen).toBe(false); expect(flags.footerHidden).toBe(true);
expect(flags.footerHidden).toBe(false);
expect(flags.navKeyboardOpen).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", () => { it("hides and collapses footer on iOS when keyboard is open and no overlay is open", () => {

View File

@@ -13,17 +13,18 @@ export interface MobileBarKeyboardFlags {
footerKeyboardOpen: boolean; footerKeyboardOpen: boolean;
} }
/** /*
* FN-5707: Android uses `interactive-widget=resizes-content`, so the layout FNXC:MobileChatKeyboardLayout 2026-06-26-09:04:
* viewport shrinks with the keyboard and the footer's normal stacked bottom 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.
* position remains correct above the mobile nav. Only iOS should apply the
* footer keyboard-collapse class (`bottom: 0`) used to let the keyboard cover 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.
* bars when visualViewport shifts independently.
* 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.
* Fullscreen mobile overlays (for example Quick Chat's sheet) own their own
* visual viewport handling. Treat them like modals for board-layout padding so `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.
* overlay-local keyboards never shift the underlying board.
*/ 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({ export function computeMobileBarKeyboardFlags({
isMobile, isMobile,
keyboardOpen, keyboardOpen,
@@ -32,7 +33,7 @@ export function computeMobileBarKeyboardFlags({
isIOS, isIOS,
}: MobileBarKeyboardFlagsInput): MobileBarKeyboardFlags { }: MobileBarKeyboardFlagsInput): MobileBarKeyboardFlags {
const boardLayoutSuppressed = anyModalOpen || overlayOpen; const boardLayoutSuppressed = anyModalOpen || overlayOpen;
const footerHidden = isMobile && keyboardOpen && !boardLayoutSuppressed && isIOS; const footerHidden = isMobile && keyboardOpen && !boardLayoutSuppressed;
const navKeyboardOpen = isMobile && keyboardOpen; const navKeyboardOpen = isMobile && keyboardOpen;
const footerKeyboardOpen = navKeyboardOpen && isIOS; const footerKeyboardOpen = navKeyboardOpen && isIOS;