diff --git a/apps/mobile/src/screens/session-composer.test.tsx b/apps/mobile/src/screens/session-composer.test.tsx index 12dfdf2..ec9c4e5 100644 --- a/apps/mobile/src/screens/session-composer.test.tsx +++ b/apps/mobile/src/screens/session-composer.test.tsx @@ -2,6 +2,7 @@ import { expect, jest, test } from "@jest/globals"; import type { AgentInfo, ModelInfo, ModelRef } from "@opencode2-mobile/opencode-adapter"; import { fireEvent, render, screen, within } from "@testing-library/react-native"; import { useState } from "react"; +import { Keyboard } from "react-native"; import type { PromptDelivery } from "./prompt-admission-model"; import { SessionComposer } from "./session-composer"; @@ -31,6 +32,22 @@ test("keeps the native prompt multiline and submits through an explicit control" expect(onSubmit).toHaveBeenCalledTimes(1); }); +test("dismisses the keyboard and collapses the composer after sending", () => { + const dismissKeyboard = jest.spyOn(Keyboard, "dismiss").mockImplementation(() => undefined); + render(); + + const input = screen.getByLabelText("Prompt"); + fireEvent.changeText(input, "Ship it"); + fireEvent(input, "focus"); + expect(input.props.numberOfLines).toBe(4); + + fireEvent.press(screen.getByRole("button", { name: "Send" })); + + expect(dismissKeyboard).toHaveBeenCalledTimes(1); + expect(screen.getByLabelText("Prompt").props.numberOfLines).toBe(1); + dismissKeyboard.mockRestore(); +}); + test("keeps controls collapsed until the editor is focused", () => { render(); diff --git a/apps/mobile/src/screens/session-composer.tsx b/apps/mobile/src/screens/session-composer.tsx index 5e51a01..18d1c42 100644 --- a/apps/mobile/src/screens/session-composer.tsx +++ b/apps/mobile/src/screens/session-composer.tsx @@ -1,6 +1,6 @@ import type { AgentInfo, ModelInfo, ModelRef } from "@opencode2-mobile/opencode-adapter"; -import { useDeferredValue, useRef, useState } from "react"; -import { Pressable, ScrollView, StyleSheet, Text, TextInput, View } from "react-native"; +import { useDeferredValue, useState } from "react"; +import { Keyboard, Pressable, ScrollView, StyleSheet, Text, TextInput, View } from "react-native"; import { ModalSheet } from "../components/modal-sheet"; import { palette, radius, space, typeRamp } from "../theme"; @@ -43,7 +43,6 @@ export function SessionComposer({ onModelChange: (model: ModelRef) => void; onSubmit: () => void; }) { - const inputRef = useRef(null); const [agentPickerOpen, setAgentPickerOpen] = useState(false); const [focused, setFocused] = useState(false); const [modelPickerOpen, setModelPickerOpen] = useState(false); @@ -69,7 +68,8 @@ export function SessionComposer({ function submit() { if (!canSubmit) return; onSubmit(); - requestAnimationFrame(() => inputRef.current?.focus()); + setFocused(false); + Keyboard.dismiss(); } return ( @@ -91,7 +91,6 @@ export function SessionComposer({ onFocus={() => setFocused(true)} placeholder={active ? "Add a follow-up" : "Ask OpenCode"} placeholderTextColor={palette.dim} - ref={inputRef} returnKeyType="default" scrollEnabled={expanded} selectionColor={palette.signal}