feat(terminal): refactor runtime and add mobile workspace (#2280)

Replace the legacy terminal flow with a shared authenticated WebSocket
runtime used across web, desktop, relay, and mobile surfaces.

- introduce the v3 terminal protocol with scoped attachments, snapshots,
  ordered output, bounded replay history, reconnects, and explicit lifecycle
- harden PTY creation, restart, resize, close, force-kill, idle cleanup,
  shell selection, login mode, environment sanitization, and appearance sync
- add runtime-aware terminal APIs with relay authentication and Electron parity
- add a fullscreen mobile terminal workspace with touch scrolling,
  long-press selection, safe-area controls, quick keys, and Ctrl/Alt input
- add terminal selection attachments, preview detection, project actions,
  shell settings, and localized UI
- harden Ghostty rendering, resize recovery, Unicode handling, block
  characters, line height, and stale-row behavior
- remove the obsolete terminal SSE path and update reverse-proxy guidance
- expand terminal runtime, transport, input, selection, and store coverage
- avoid duplicate web builds when preparing mobile assets in root CI builds
This commit is contained in:
Bohdan Triapitsyn
2026-07-17 13:17:21 +03:00
committed by GitHub
parent f5b4a267c0
commit d4a8c4d2e1
103 changed files with 4085 additions and 4496 deletions
+14 -21
View File
@@ -3,6 +3,7 @@ import { updateDesktopSettings } from '@/lib/persistence';
import type { DesktopSettings } from '@/lib/desktop';
import type { MonoFontOption, UiFontOption } from '@/lib/fontOptions';
import type { MobileKeyboardMode } from '@/lib/mobileKeyboardMode';
import type { TerminalShell } from '@/lib/api/types';
type AppearanceSlice = {
showReasoningTraces: boolean;
@@ -34,6 +35,8 @@ type AppearanceSlice = {
sessionRetentionAction: 'archive' | 'delete';
fontSize: number;
terminalFontSize: number;
terminalShell: TerminalShell;
terminalLoginShells: TerminalShell[];
editorFontSize: number;
uiFont: UiFontOption;
monoFont: MonoFontOption;
@@ -79,6 +82,8 @@ export const startAppearanceAutoSave = (): void => {
sessionRetentionAction: useUIStore.getState().sessionRetentionAction,
fontSize: useUIStore.getState().fontSize,
terminalFontSize: useUIStore.getState().terminalFontSize,
terminalShell: useUIStore.getState().terminalShell,
terminalLoginShells: useUIStore.getState().terminalLoginShells,
editorFontSize: useUIStore.getState().editorFontSize,
uiFont: useUIStore.getState().uiFont,
monoFont: useUIStore.getState().monoFont,
@@ -90,26 +95,6 @@ export const startAppearanceAutoSave = (): void => {
gitChangesViewMode: useUIStore.getState().gitChangesViewMode,
};
let pending: Partial<DesktopSettings> | null = null;
let timer: ReturnType<typeof setTimeout> | null = null;
const flush = () => {
const payload = pending;
pending = null;
timer = null;
if (payload && Object.keys(payload).length > 0) {
void updateDesktopSettings(payload);
}
};
const schedule = (changes: Partial<DesktopSettings>) => {
pending = { ...(pending ?? {}), ...changes };
if (timer) {
return;
}
timer = setTimeout(flush, 150);
};
useUIStore.subscribe((state) => {
const current: AppearanceSlice = {
showReasoningTraces: state.showReasoningTraces,
@@ -136,6 +121,8 @@ export const startAppearanceAutoSave = (): void => {
sessionRetentionAction: state.sessionRetentionAction,
fontSize: state.fontSize,
terminalFontSize: state.terminalFontSize,
terminalShell: state.terminalShell,
terminalLoginShells: state.terminalLoginShells,
editorFontSize: state.editorFontSize,
uiFont: state.uiFont,
monoFont: state.monoFont,
@@ -221,6 +208,12 @@ export const startAppearanceAutoSave = (): void => {
if (current.terminalFontSize !== previous.terminalFontSize) {
diff.terminalFontSize = current.terminalFontSize;
}
if (current.terminalShell !== previous.terminalShell) {
diff.terminalShell = current.terminalShell;
}
if (current.terminalLoginShells !== previous.terminalLoginShells) {
diff.terminalLoginShells = current.terminalLoginShells;
}
if (current.editorFontSize !== previous.editorFontSize) {
diff.editorFontSize = current.editorFontSize;
}
@@ -252,7 +245,7 @@ export const startAppearanceAutoSave = (): void => {
previous = current;
if (Object.keys(diff).length > 0) {
schedule(diff);
void updateDesktopSettings(diff);
}
});