Files
Serhii Dziupin 88937ade72 fix(terminal): start PTY before viewport mounts without dropping output or startup replies
Terminal creation no longer waits for the Ghostty viewport to report its
size: it starts the PTY immediately with a container/font-derived
provisional size (falling back to 80x24), then resizes once the real
viewport dimensions are known, with a dedupe guard while sizing settles.

Starting the shell earlier means it can emit device/theme queries before
a browser terminal is attached to answer them, so the server now answers
primary device attribute queries itself (Fish blocks ~10s on this at
startup) and bun-pty buffers output emitted before a data subscriber
attaches. Also fixes a few WebSocket transport reconnect races surfaced
by session creation now overlapping renderer setup.
2026-08-03 12:29:32 +03:00

71 lines
2.7 KiB
JavaScript

import { describe, expect, test } from 'vitest';
import { consumeTerminalThemeQueries } from './theme-response.js';
const lightAppearance = {
themeMode: 'light',
foreground: '#1b1b1b',
background: '#faf8f0',
modeEnabled: false,
};
describe('terminal theme responses', () => {
test('answers the complete OpenTUI startup handshake', () => {
const result = consumeTerminalThemeQueries(
'',
'\u001b[?2031h\u001b]10;?\u001b\\\u001b]11;?\u001b\\\u001b[?2031$p',
lightAppearance,
);
expect(result).toEqual({
pending: '',
modeEnabled: true,
responses: [
'\u001b]10;rgb:1b1b/1b1b/1b1b\u001b\\',
'\u001b]11;rgb:fafa/f8f8/f0f0\u001b\\',
'\u001b[?2031;1$y',
],
});
});
test('handles a query split across PTY output chunks without duplicating it', () => {
const first = consumeTerminalThemeQueries('', '\u001b]11;', { ...lightAppearance, themeMode: 'dark' });
const second = consumeTerminalThemeQueries(first.pending, '?\u001b\\', { ...lightAppearance, themeMode: 'dark', modeEnabled: first.modeEnabled });
const third = consumeTerminalThemeQueries(second.pending, 'x', { ...lightAppearance, themeMode: 'dark', modeEnabled: second.modeEnabled });
expect(second.responses).toEqual(['\u001b]11;rgb:fafa/f8f8/f0f0\u001b\\']);
expect(second.pending).toBe('');
expect(third.responses).toEqual([]);
});
test('answers every repeated query in wire order', () => {
const result = consumeTerminalThemeQueries('', '\u001b[?996n\u001b[?996n\u001b]10;?\u0007\u001b]10;?\u0007', lightAppearance);
expect(result.responses).toEqual([
'\u001b[?997;2n',
'\u001b[?997;2n',
'\u001b]10;rgb:1b1b/1b1b/1b1b\u001b\\',
'\u001b]10;rgb:1b1b/1b1b/1b1b\u001b\\',
]);
});
test('answers a primary device attribute query when the fallback is enabled', () => {
const attached = consumeTerminalThemeQueries('', '\u001b[0c', lightAppearance);
const unattached = consumeTerminalThemeQueries('', '\u001b[0c', lightAppearance, {
respondToPrimaryDeviceAttributes: true,
});
expect(attached.responses).toEqual([]);
expect(unattached.responses).toEqual(['\u001b[?1;2c']);
});
test('answers a primary device attribute query split across PTY chunks', () => {
const first = consumeTerminalThemeQueries('', '\u001b[0', lightAppearance, {
respondToPrimaryDeviceAttributes: true,
});
const second = consumeTerminalThemeQueries(first.pending, 'c', {
...lightAppearance,
modeEnabled: first.modeEnabled,
}, { respondToPrimaryDeviceAttributes: true });
expect(first.pending).toBe('\u001b[0');
expect(second.responses).toEqual(['\u001b[?1;2c']);
});
});