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

@@ -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);