From 1ebcc0e57aebd02623274e69c887c67f495c8555 Mon Sep 17 00:00:00 2001 From: Tyrone Robb Date: Sat, 14 Mar 2026 13:23:57 +0000 Subject: [PATCH 1/3] fix(browse): make timeout and buffers configurable, add bun.lockb setup compat --- browse/src/buffers.ts | 2 +- browse/src/cli.ts | 2 +- setup | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/browse/src/buffers.ts b/browse/src/buffers.ts index 7cd19a4..2208d0e 100644 --- a/browse/src/buffers.ts +++ b/browse/src/buffers.ts @@ -20,7 +20,7 @@ export interface NetworkEntry { export const consoleBuffer: LogEntry[] = []; export const networkBuffer: NetworkEntry[] = []; -const HIGH_WATER_MARK = 50_000; +const HIGH_WATER_MARK = parseInt(process.env.BROWSE_BUFFER_SIZE || '50000', 10); // Total entries ever added — used by server.ts flush logic as a cursor // that keeps advancing even after the ring buffer wraps. diff --git a/browse/src/cli.ts b/browse/src/cli.ts index 931b85c..9fd2ac9 100644 --- a/browse/src/cli.ts +++ b/browse/src/cli.ts @@ -153,7 +153,7 @@ async function sendCommand(state: ServerState, command: string, args: string[], 'Authorization': `Bearer ${state.token}`, }, body, - signal: AbortSignal.timeout(30000), + signal: AbortSignal.timeout(parseInt(process.env.BROWSE_TIMEOUT || '30000', 10)), }); if (resp.status === 401) { diff --git a/setup b/setup index 73c6503..c94e820 100755 --- a/setup +++ b/setup @@ -14,7 +14,8 @@ elif [ -n "$(find "$GSTACK_DIR/browse/src" -type f -newer "$BROWSE_BIN" -print - NEEDS_BUILD=1 elif [ "$GSTACK_DIR/package.json" -nt "$BROWSE_BIN" ]; then NEEDS_BUILD=1 -elif [ -f "$GSTACK_DIR/bun.lock" ] && [ "$GSTACK_DIR/bun.lock" -nt "$BROWSE_BIN" ]; then +elif { [ -f "$GSTACK_DIR/bun.lock" ] && [ "$GSTACK_DIR/bun.lock" -nt "$BROWSE_BIN" ]; } || \ + { [ -f "$GSTACK_DIR/bun.lockb" ] && [ "$GSTACK_DIR/bun.lockb" -nt "$BROWSE_BIN" ]; }; then NEEDS_BUILD=1 fi From 5600729f252d45433d10e77a8dd1b4ea18f1a7a2 Mon Sep 17 00:00:00 2001 From: Tyrone Robb Date: Sat, 14 Mar 2026 13:49:47 +0000 Subject: [PATCH 2/3] Merge branch 'main' into fix/browse-configurability: resolve buffers.ts conflict --- browse/src/buffers.ts | 121 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 107 insertions(+), 14 deletions(-) diff --git a/browse/src/buffers.ts b/browse/src/buffers.ts index 2208d0e..e61d92d 100644 --- a/browse/src/buffers.ts +++ b/browse/src/buffers.ts @@ -1,8 +1,95 @@ /** * Shared buffers and types — extracted to break circular dependency * between server.ts and browser-manager.ts + * + * CircularBuffer: O(1) insert ring buffer with fixed capacity. + * + * ┌───┬───┬───┬───┬───┬───┐ + * │ 3 │ 4 │ 5 │ │ 1 │ 2 │ capacity=6, head=4, size=5 + * └───┴───┴───┴───┴─▲─┴───┘ + * │ + * head (oldest entry) + * + * push() writes at (head+size) % capacity, O(1) + * toArray() returns entries in insertion order, O(n) + * totalAdded keeps incrementing past capacity (flush cursor) */ +// ─── CircularBuffer ───────────────────────────────────────── + +export class CircularBuffer { + private buffer: (T | undefined)[]; + private head: number = 0; + private _size: number = 0; + private _totalAdded: number = 0; + readonly capacity: number; + + constructor(capacity: number) { + this.capacity = capacity; + this.buffer = new Array(capacity); + } + + push(entry: T): void { + const index = (this.head + this._size) % this.capacity; + this.buffer[index] = entry; + if (this._size < this.capacity) { + this._size++; + } else { + // Buffer full — advance head (overwrites oldest) + this.head = (this.head + 1) % this.capacity; + } + this._totalAdded++; + } + + /** Return entries in insertion order (oldest first) */ + toArray(): T[] { + const result: T[] = []; + for (let i = 0; i < this._size; i++) { + result.push(this.buffer[(this.head + i) % this.capacity] as T); + } + return result; + } + + /** Return the last N entries (most recent first → reversed to oldest first) */ + last(n: number): T[] { + const count = Math.min(n, this._size); + const result: T[] = []; + const start = (this.head + this._size - count) % this.capacity; + for (let i = 0; i < count; i++) { + result.push(this.buffer[(start + i) % this.capacity] as T); + } + return result; + } + + get length(): number { + return this._size; + } + + get totalAdded(): number { + return this._totalAdded; + } + + clear(): void { + this.head = 0; + this._size = 0; + // Don't reset totalAdded — flush cursor depends on it + } + + /** Get entry by index (0 = oldest) — used by network response matching */ + get(index: number): T | undefined { + if (index < 0 || index >= this._size) return undefined; + return this.buffer[(this.head + index) % this.capacity]; + } + + /** Set entry by index (0 = oldest) — used by network response matching */ + set(index: number, entry: T): void { + if (index < 0 || index >= this._size) return; + this.buffer[(this.head + index) % this.capacity] = entry; + } +} + +// ─── Entry Types ──────────────────────────────────────────── + export interface LogEntry { timestamp: number; level: string; @@ -18,27 +105,33 @@ export interface NetworkEntry { size?: number; } -export const consoleBuffer: LogEntry[] = []; -export const networkBuffer: NetworkEntry[] = []; +export interface DialogEntry { + timestamp: number; + type: string; // 'alert' | 'confirm' | 'prompt' | 'beforeunload' + message: string; + defaultValue?: string; + action: string; // 'accepted' | 'dismissed' + response?: string; // text provided for prompt +} + +// ─── Buffer Instances ─────────────────────────────────────── + const HIGH_WATER_MARK = parseInt(process.env.BROWSE_BUFFER_SIZE || '50000', 10); -// Total entries ever added — used by server.ts flush logic as a cursor -// that keeps advancing even after the ring buffer wraps. -export let consoleTotalAdded = 0; -export let networkTotalAdded = 0; +export const consoleBuffer = new CircularBuffer(HIGH_WATER_MARK); +export const networkBuffer = new CircularBuffer(HIGH_WATER_MARK); +export const dialogBuffer = new CircularBuffer(HIGH_WATER_MARK); + +// ─── Convenience add functions ────────────────────────────── export function addConsoleEntry(entry: LogEntry) { - if (consoleBuffer.length >= HIGH_WATER_MARK) { - consoleBuffer.shift(); - } consoleBuffer.push(entry); - consoleTotalAdded++; } export function addNetworkEntry(entry: NetworkEntry) { - if (networkBuffer.length >= HIGH_WATER_MARK) { - networkBuffer.shift(); - } networkBuffer.push(entry); - networkTotalAdded++; +} + +export function addDialogEntry(entry: DialogEntry) { + dialogBuffer.push(entry); } From 6c45be89409b92f75bd1dc2eaaacc938878b9f9e Mon Sep 17 00:00:00 2001 From: Tyrone Robb Date: Sat, 14 Mar 2026 13:54:04 +0000 Subject: [PATCH 3/3] chore: add personal plan and roadmap to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 2d3e850..8b37f3e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ browse/dist/ *.log bun.lock *.bun-build +PR_ROADMAP.md +ssd.md