fix(ui): add opt-in mobile keyboard resize mode and stabilize touch terminal input (#1107)

* fix(ui): add opt-in mobile keyboard resize mode

* fix(ui): stabilize touch terminal input and tab layout

* fix(ui): narrow touch terminal handling to mobile and tablet

* fix(ui): refine touch terminal input handling

* fix(ui): re-key terminal viewport on session id

* fix(ui): avoid touch-laptop terminal overlay regression

* fix(ui): hide ghostty system caret surfaces

* fix(settings): normalize mobile keyboard mode sanitization

* fix(ui): honor terminal quick keys toggle on mobile

* fix(ui): gate mobile keyboard resize mode on iOS

---------

Co-authored-by: vhqtvn <8930337+vhqtvn@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
vhqtvn
2026-05-05 12:26:34 +03:00
committed by GitHub
co-authored by vhqtvn Bohdan Triapitsyn
parent 63b4a5b996
commit 962ee41bba
25 changed files with 604 additions and 128 deletions
@@ -83,7 +83,7 @@ export const MainLayout: React.FC = () => {
const setMultiRunLauncherOpen = useUIStore((state) => state.setMultiRunLauncherOpen);
const multiRunLauncherPrefillPrompt = useUIStore((state) => state.multiRunLauncherPrefillPrompt);
const { isMobile } = useDeviceInfo();
const { isMobile, isTablet } = useDeviceInfo();
const isDesktopShellRuntime = React.useMemo(() => isDesktopShell(), []);
const sidebarWidth = useUIStore((state) => state.sidebarWidth);
const rightSidebarWidth = useUIStore((state) => state.rightSidebarWidth);
@@ -289,19 +289,24 @@ export const MainLayout: React.FC = () => {
rightSidebarAutoClosedRef.current = false;
}
const shouldCloseBottomTerminal =
height < BOTTOM_TERMINAL_AUTO_CLOSE_HEIGHT;
const canAutoOpenBottomTerminal =
height >= BOTTOM_TERMINAL_AUTO_OPEN_HEIGHT;
// Touch devices frequently resize when the on-screen keyboard opens.
// Treat bottom-terminal auto-collapse/restore as desktop-only so
// keyboard viewport changes do not churn terminal layout state.
if (!isMobile && !isTablet) {
const shouldCloseBottomTerminal =
height < BOTTOM_TERMINAL_AUTO_CLOSE_HEIGHT;
const canAutoOpenBottomTerminal =
height >= BOTTOM_TERMINAL_AUTO_OPEN_HEIGHT;
if (shouldCloseBottomTerminal) {
if (state.isBottomTerminalOpen) {
setBottomTerminalOpen(false);
bottomTerminalAutoClosedRef.current = true;
if (shouldCloseBottomTerminal) {
if (state.isBottomTerminalOpen) {
setBottomTerminalOpen(false);
bottomTerminalAutoClosedRef.current = true;
}
} else if (canAutoOpenBottomTerminal && bottomTerminalAutoClosedRef.current) {
setBottomTerminalOpen(true);
bottomTerminalAutoClosedRef.current = false;
}
} else if (canAutoOpenBottomTerminal && bottomTerminalAutoClosedRef.current) {
setBottomTerminalOpen(true);
bottomTerminalAutoClosedRef.current = false;
}
};
@@ -324,7 +329,7 @@ export const MainLayout: React.FC = () => {
window.clearTimeout(timeoutId);
}
};
}, [setBottomTerminalOpen, setRightSidebarOpen]);
}, [isMobile, isTablet, setBottomTerminalOpen, setRightSidebarOpen]);
React.useEffect(() => {
if (typeof window === 'undefined') {
@@ -351,7 +356,7 @@ export const MainLayout: React.FC = () => {
return () => {
unsubscribe();
};
}, []);
}, [isMobile, isTablet, setBottomTerminalOpen, setRightSidebarOpen]);
const secondaryView = React.useMemo(() => {
switch (activeMainTab) {