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:
committed by
GitHub
parent
f5b4a267c0
commit
d4a8c4d2e1
@@ -1,8 +1,57 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { getRuntimeApiBaseUrl, switchRuntimeEndpoint } from './runtime-switch';
|
||||
import {
|
||||
getRuntimeApiBaseUrl,
|
||||
getRuntimeKey,
|
||||
subscribeRuntimeEndpointChanged,
|
||||
subscribeRuntimeEndpointWillChange,
|
||||
switchRuntimeEndpoint,
|
||||
} from './runtime-switch';
|
||||
import { clearRuntimeUrlAuthToken, setRuntimeExtraHeaders } from './runtime-auth';
|
||||
|
||||
describe('runtime endpoint switching', () => {
|
||||
test('notifies listeners before and after mutating the active endpoint', () => {
|
||||
const previousWindow = Object.getOwnPropertyDescriptor(globalThis, 'window');
|
||||
const previousFetch = globalThis.fetch;
|
||||
const events = new EventTarget();
|
||||
const runtimeWindow = {
|
||||
addEventListener: events.addEventListener.bind(events),
|
||||
removeEventListener: events.removeEventListener.bind(events),
|
||||
dispatchEvent: events.dispatchEvent.bind(events),
|
||||
};
|
||||
|
||||
try {
|
||||
globalThis.fetch = (async () => new Response(null, { status: 404 })) as typeof fetch;
|
||||
Object.defineProperty(globalThis, 'window', {
|
||||
configurable: true,
|
||||
value: runtimeWindow,
|
||||
});
|
||||
switchRuntimeEndpoint({ apiBaseUrl: 'https://runtime-a.example', runtimeKey: 'runtime-a' });
|
||||
const observed: Array<[string, string, string]> = [];
|
||||
const unsubscribeWillChange = subscribeRuntimeEndpointWillChange((detail) => {
|
||||
observed.push(['will-change', getRuntimeKey(), detail.previousRuntimeKey]);
|
||||
});
|
||||
const unsubscribeChanged = subscribeRuntimeEndpointChanged((detail) => {
|
||||
observed.push(['changed', getRuntimeKey(), detail.runtimeKey]);
|
||||
});
|
||||
|
||||
switchRuntimeEndpoint({ apiBaseUrl: 'https://runtime-b.example', runtimeKey: 'runtime-b' });
|
||||
|
||||
expect(observed).toEqual([
|
||||
['will-change', 'runtime-a', 'runtime-a'],
|
||||
['changed', 'runtime-b', 'runtime-b'],
|
||||
]);
|
||||
unsubscribeWillChange();
|
||||
unsubscribeChanged();
|
||||
} finally {
|
||||
globalThis.fetch = previousFetch;
|
||||
if (previousWindow) {
|
||||
Object.defineProperty(globalThis, 'window', previousWindow);
|
||||
} else {
|
||||
Reflect.deleteProperty(globalThis, 'window');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('does not throw when Electron preload globals are read-only', () => {
|
||||
const previousWindow = Object.getOwnPropertyDescriptor(globalThis, 'window');
|
||||
const previousFetch = globalThis.fetch;
|
||||
|
||||
Reference in New Issue
Block a user