Files
openchamber/bun-patches/bun-pty@0.4.8.patch
T
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

78 lines
2.5 KiB
Diff

diff --git a/src/terminal.ts b/src/terminal.ts
index ec248d46a939f8a09cd669e853cefb126922c80a..c0473bc625edda7be2ade987e8aa3bd99160ce67 100644
--- a/src/terminal.ts
+++ b/src/terminal.ts
@@ -11,6 +11,7 @@ export const DEFAULT_COLS = 80;
export const DEFAULT_ROWS = 24;
export const DEFAULT_FILE = "sh";
export const DEFAULT_NAME = "xterm";
+const INITIAL_OUTPUT_BUFFER_LIMIT = 512 * 1024;
/**
* Quote a string for shell-words compatible splitting on the Rust side.
@@ -136,6 +137,8 @@ export class Terminal implements IPty {
private _readLoop = false;
private _closing = false;
+ private _hasDataSubscriber = false;
+ private _initialOutput = "";
// TextDecoder with streaming mode to properly handle UTF-8 across chunk boundaries
// Without this, multi-byte characters (like box-drawing ─) that span chunks become
@@ -191,12 +194,29 @@ export class Terminal implements IPty {
}
get onData() {
- return this._onData.event;
+ return (listener: (data: string) => void) => {
+ const disposable = this._onData.event(listener);
+ if (!this._hasDataSubscriber) {
+ this._hasDataSubscriber = true;
+ const initialOutput = this._initialOutput;
+ this._initialOutput = "";
+ if (initialOutput) listener(initialOutput);
+ }
+ return disposable;
+ };
}
get onExit() {
return this._onExit.event;
}
+ private _emitData(data: string) {
+ if (this._hasDataSubscriber) {
+ this._onData.fire(data);
+ } else {
+ this._initialOutput = `${this._initialOutput}${data}`.slice(-INITIAL_OUTPUT_BUFFER_LIMIT);
+ }
+ }
+
/* ------------- IO methods ------------- */
write(data: string) {
@@ -235,13 +255,13 @@ export class Terminal implements IPty {
// This prevents corruption when multi-byte chars span chunk boundaries
const decoded = this._decoder.decode(buf.subarray(0, n), { stream: true });
if (decoded) {
- this._onData.fire(decoded);
+ this._emitData(decoded);
}
} else if (n === -2) {
// CHILD_EXITED - flush any remaining bytes in the decoder
const remaining = this._decoder.decode();
if (remaining) {
- this._onData.fire(remaining);
+ this._emitData(remaining);
}
const exitCode = lib.symbols.bun_pty_get_exit_code(this.handle);
this._onExit.fire({ exitCode });
@@ -250,7 +270,7 @@ export class Terminal implements IPty {
// error - flush decoder before breaking
const remaining = this._decoder.decode();
if (remaining) {
- this._onData.fire(remaining);
+ this._emitData(remaining);
}
break;
} else {