fix(FN-000): harden mobile terminal focus capture for soft keyboard

This commit is contained in:
Fusion
2026-04-18 21:00:01 -07:00
committed by gsxdsm
parent 14cf34bef2
commit b1d9d43d2e
4 changed files with 26 additions and 7 deletions

View File

@@ -696,6 +696,14 @@ export function TerminalModal({ isOpen, onClose, initialCommand, projectId }: Te
if (!helperTextarea) return;
// Mobile Safari/Chrome soft keyboard heuristics are stricter than desktop:
// keep attributes explicit and focus from a direct user gesture.
helperTextarea.autocapitalize = "off";
helperTextarea.autocomplete = "off";
(helperTextarea as unknown as { autocorrect: string }).autocorrect = "off";
helperTextarea.spellcheck = false;
helperTextarea.setAttribute("inputmode", "text");
try {
helperTextarea.focus({ preventScroll: true });
} catch {
@@ -981,8 +989,9 @@ export function TerminalModal({ isOpen, onClose, initialCommand, projectId }: Te
ref={terminalRef}
className="terminal-xterm"
data-testid="terminal-xterm"
onPointerDown={handleTerminalGestureFocus}
onTouchStart={handleTerminalGestureFocus}
onPointerDownCapture={handleTerminalGestureFocus}
onTouchStartCapture={handleTerminalGestureFocus}
onClickCapture={handleTerminalGestureFocus}
/>
</div>

View File

@@ -3603,7 +3603,7 @@ describe("TerminalModal — xterm focus initialization (FN-1602)", () => {
});
});
it("focuses xterm helper textarea on user pointer gesture", async () => {
it("focuses xterm helper textarea on user pointer gesture (capture phase)", async () => {
render(<TerminalModal isOpen={true} onClose={mockOnClose} />);
await waitFor(() => {
@@ -3617,11 +3617,17 @@ describe("TerminalModal — xterm focus initialization (FN-1602)", () => {
const setSelectionRangeSpy = vi.spyOn(helperTextarea, "setSelectionRange");
terminalDiv.appendChild(helperTextarea);
fireEvent.pointerDown(terminalDiv);
// Simulate xterm child event handlers stopping bubble propagation.
const child = document.createElement("div");
child.addEventListener("pointerdown", (event) => event.stopPropagation());
terminalDiv.appendChild(child);
fireEvent.pointerDown(child);
expect(mockTerminalInstance.focus).toHaveBeenCalled();
expect(focusSpy).toHaveBeenCalled();
expect(setSelectionRangeSpy).toHaveBeenCalledWith(0, 0);
expect(helperTextarea.getAttribute("inputmode")).toBe("text");
});
it("focuses xterm helper textarea on touch gesture", async () => {
@@ -3640,6 +3646,10 @@ describe("TerminalModal — xterm focus initialization (FN-1602)", () => {
fireEvent.touchStart(terminalDiv);
expect(focusSpy).toHaveBeenCalled();
expect(helperTextarea.autocapitalize).toBe("off");
expect(helperTextarea.autocomplete).toBe("off");
expect(helperTextarea.autocorrect).toBe("off");
expect(helperTextarea.spellcheck).toBe(false);
});
});