fix(dashboard): keep keyboard up on send tap; hide status bar with keyboard

Two mobile chat regressions addressed together:

1) ChatView send button: preventDefault now fires on pointerdown for
   touch pointers, before iOS blurs the textarea. The previous
   onMouseDown.preventDefault was too late on iOS (mousedown is
   synthesized after touchend, by which point the keyboard has
   already started dismissing). Click still runs the action so quick
   taps remain reliable.

2) ExecutorStatusBar: hidden on mobile while keyboard is open,
   mirroring MobileNavBar. The bar is position:fixed against the
   layout viewport, which iOS leaves anchored below the keyboard;
   during a swipe/pan it would slide over the messages list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-06 20:34:35 -07:00
parent ddf9f03ec8
commit 92d40bc519
4 changed files with 37 additions and 9 deletions

View File

@@ -0,0 +1,17 @@
---
"@runfusion/fusion": patch
---
Two mobile chat fixes:
1. Tapping the ChatView send button no longer dismisses the soft
keyboard. preventDefault now fires on `pointerdown` for touch
pointers (before iOS blurs the textarea — the synthesized mousedown
it previously relied on fires too late). Click still runs the send
action so quick taps remain reliable.
2. The bottom executor status bar is now hidden on mobile while the
keyboard is open, mirroring `MobileNavBar`. The bar is
`position: fixed` against the layout viewport, which iOS leaves
anchored below the keyboard — during a swipe/pan it would slide
over the message list.

View File

@@ -1336,6 +1336,7 @@ function AppInner() {
lastFetchTimeMs={lastFetchTimeMs}
currentProjectPath={currentProject.path}
onOpenProjectDirectory={handleOpenProjectDirectory}
keyboardOpen={mobileKeyboardOpen}
/>
)}
<MobileNavBar

View File

@@ -1994,14 +1994,19 @@ export function ChatView({ projectId, addToast }: ChatViewProps) {
<button
type="button"
className="chat-input-send"
// Mobile send pattern: previous code intercepted pointerdown
// and touchstart to call handleSend directly, which silently
// dropped quick taps on iOS (only long press worked). The
// canonical iOS pattern is preventDefault on mousedown to
// stop focus from leaving the textarea (keyboard stays up,
// viewport doesn't reflow), then run the action on click.
// This works for quick taps because click fires reliably
// from the synthesized touch sequence.
// Keep keyboard up when sending. preventDefault fires on
// pointerdown for touch pointers (BEFORE iOS blurs the
// textarea — the synthesized mousedown is too late on
// iOS), and on mousedown for desktop. Crucially we do NOT
// call preventDefault on touchstart and we do NOT run the
// action here — both of those broke quick taps. Click
// still fires from the iOS touch sequence and runs the
// action reliably.
onPointerDown={(event) => {
if (event.pointerType && event.pointerType !== "mouse") {
event.preventDefault();
}
}}
onMouseDown={(event) => {
event.preventDefault();
}}

View File

@@ -25,6 +25,10 @@ interface ExecutorStatusBarProps {
currentProjectPath?: string;
/** Opens the workspace-aware file browser to the project workspace. */
onOpenProjectDirectory?: () => void;
/** When true on mobile, hide the bar so it doesn't slide over messages
* during visualViewport pans (position:fixed is anchored to layout
* viewport, which iOS leaves below the keyboard). */
keyboardOpen?: boolean;
}
/**
@@ -74,7 +78,8 @@ function getStateDisplay(state: ExecutorState): { label: string; color: string;
* - Executor state badge (idle/running/paused)
* - Last activity timestamp
*/
export function ExecutorStatusBar({ tasks, projectId, taskStuckTimeoutMs, backgroundSessions, backgroundGenerating, backgroundNeedsInput, onOpenBackgroundSession, onDismissBackgroundSession, lastFetchTimeMs, currentProjectPath, onOpenProjectDirectory }: ExecutorStatusBarProps) {
export function ExecutorStatusBar({ tasks, projectId, taskStuckTimeoutMs, backgroundSessions, backgroundGenerating, backgroundNeedsInput, onOpenBackgroundSession, onDismissBackgroundSession, lastFetchTimeMs, currentProjectPath, onOpenProjectDirectory, keyboardOpen }: ExecutorStatusBarProps) {
if (keyboardOpen) return null;
const { stats, loading, error } = useExecutorStats(tasks, projectId, taskStuckTimeoutMs, lastFetchTimeMs);
const [isProjectPathVisible, setIsProjectPathVisible] = useState(false);