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
+2 -15
View File
@@ -1,4 +1,5 @@
import React from 'react';
import { isTerminalEventTarget } from '@/lib/terminalFocus';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useSelectionStore } from '@/sync/selection-store';
import * as sessionActions from '@/sync/session-actions';
@@ -62,17 +63,6 @@ export const useKeyboardShortcuts = () => {
React.useEffect(() => {
const combo = (actionId: string) => getEffectiveShortcutCombo(actionId, shortcutOverrides);
const isTerminalEventTarget = (target: EventTarget | null) => {
if (!(target instanceof Element)) {
return false;
}
return Boolean(
target.closest('.terminal-viewport-container') ||
target.getAttribute('data-terminal-hidden-input') === 'true'
);
};
const dropdownTargetSelector = [
'[data-slot="dropdown-menu-content"]',
'[data-slot="select-content"]',
@@ -528,10 +518,7 @@ export const useKeyboardShortcuts = () => {
const target = e.target as Element | null;
const isInsideDialog = Boolean(target?.closest('[role="dialog"]'));
const isSettingsMounted = Boolean(document.querySelector('[data-settings-view="true"]'));
const isInsideTerminal = Boolean(
target?.closest('.terminal-viewport-container') ||
target?.getAttribute('data-terminal-hidden-input') === 'true'
);
const isInsideTerminal = isTerminalEventTarget(target);
const hasDropdownInteraction = isDropdownEventTarget(target) || hasOpenDropdown();
const {