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
+24 -2
View File
@@ -8,6 +8,7 @@ import type { DraftStarterRef } from '@/lib/draftStarters';
import { DEFAULT_MONO_FONT, DEFAULT_UI_FONT, type MonoFontOption, type UiFontOption } from '@/lib/fontOptions';
import { getStoredMobileKeyboardMode, type MobileKeyboardMode } from '@/lib/mobileKeyboardMode';
import { getRuntimeKey } from '@/lib/runtime-switch';
import type { TerminalShell } from '@/lib/api/types';
export type MainTab = 'chat' | 'plan' | 'git' | 'diff' | 'terminal' | 'files' | 'context' | 'diagram';
export type PendingDiffScope = 'working' | 'staged' | 'turn';
@@ -591,6 +592,8 @@ interface UIStore {
// Global draft welcome starters; null = unset (use the default built-in set).
globalDraftStarters: DraftStarterRef[] | null;
terminalFontSize: number;
terminalShell: TerminalShell;
terminalLoginShells: TerminalShell[];
editorFontSize: number;
uiFont: UiFontOption;
monoFont: MonoFontOption;
@@ -748,6 +751,8 @@ interface UIStore {
setFontSize: (size: number) => void;
setGlobalDraftStarters: (refs: DraftStarterRef[]) => void;
setTerminalFontSize: (size: number) => void;
setTerminalShell: (shell: TerminalShell) => void;
setTerminalLoginShells: (shells: TerminalShell[]) => void;
setEditorFontSize: (size: number) => void;
setUiFont: (font: UiFontOption) => void;
setMonoFont: (font: MonoFontOption) => void;
@@ -903,7 +908,9 @@ export const useUIStore = create<UIStore>()(
messageLimit: 200,
fontSize: 100,
globalDraftStarters: null,
terminalFontSize: 13,
terminalFontSize: 14,
terminalShell: 'auto',
terminalLoginShells: [],
editorFontSize: 13,
uiFont: DEFAULT_UI_FONT,
monoFont: DEFAULT_MONO_FONT,
@@ -1671,6 +1678,14 @@ export const useUIStore = create<UIStore>()(
set({ terminalFontSize: clamped });
},
setTerminalShell: (shell) => {
set({ terminalShell: shell });
},
setTerminalLoginShells: (shells) => {
set({ terminalLoginShells: [...new Set(shells)] });
},
setEditorFontSize: (size) => {
const rounded = Math.round(size);
const clamped = Math.max(9, Math.min(32, rounded));
@@ -2206,13 +2221,18 @@ export const useUIStore = create<UIStore>()(
{
name: 'ui-store',
storage: createDeferredSafeJSONStorage(),
version: 10,
version: 11,
migrate: (persistedState, version) => {
if (!persistedState || typeof persistedState !== 'object') {
return persistedState;
}
const state = persistedState as Record<string, unknown>;
// v10 -> v11: move the previous terminal font default forward.
if (version < 11 && state.terminalFontSize === 13) {
state.terminalFontSize = 14;
}
// v9 -> v10: remove obsolete single-file diff view mode setting
if (version < 10) {
delete state.diffViewMode;
@@ -2343,6 +2363,8 @@ export const useUIStore = create<UIStore>()(
fontSize: state.fontSize,
globalDraftStarters: state.globalDraftStarters,
terminalFontSize: state.terminalFontSize,
terminalShell: state.terminalShell,
terminalLoginShells: state.terminalLoginShells,
editorFontSize: state.editorFontSize,
uiFont: state.uiFont,
monoFont: state.monoFont,