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}