From 8ed5d00a09bb1f2e221b8b395433119e36311ba4 Mon Sep 17 00:00:00 2001 From: jesse23 Date: Mon, 23 Mar 2026 05:57:02 -0400 Subject: [PATCH 1/7] fix: specify close code and reason for client disconnections --- src/server/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/index.ts b/src/server/index.ts index efd77c8..0a423f4 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -12,7 +12,7 @@ const httpServer = http.createServer((req, res) => { handleRequest(req, res, distPath, wasmPath, () => { for (const session of sessionRegistry.values()) { session.pty?.kill(); - for (const client of session.clients) client.close(); + for (const client of session.clients) client.close(4001, 'server stopped'); } wss.close(); httpServer.close(() => process.exit(0)); @@ -25,7 +25,7 @@ process.on('SIGINT', () => { console.log('\n\nShutting down...'); for (const session of sessionRegistry.values()) { session.pty?.kill(); - for (const client of session.clients) client.close(); + for (const client of session.clients) client.close(4001, 'server stopped'); } wss.close(); process.exit(0); From fed228ff0d03b8e065f3f44adf40557adbbb62ec Mon Sep 17 00:00:00 2001 From: jesse23 Date: Mon, 23 Mar 2026 06:18:13 -0400 Subject: [PATCH 2/7] feat: update development workflow and debugging instructions in documentation --- AGENTS.md | 8 ++++++++ README.md | 11 +++++++++++ package.json | 6 ++---- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index efaf33a..02d4222 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,6 +16,14 @@ webtty/ └── README.md ``` +## Development Workflow + +Always build and stop the old server before testing: + +``` +bun run build && bun run webtty stop +``` + ## Rules - **Match existing patterns** — read 2-3 similar files before writing new ones. diff --git a/README.md b/README.md index b58bb7d..1befccc 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ # webtty A web TTY for running CLI/TUI applications in a browser tab, across platforms. + +## Debugging + +Build emits source maps (`dist/*.js.map`), so you can debug against the built output directly — no minification, original TypeScript line numbers preserved. + +``` +bun run build +bun --inspect run dist/server/index.js +# or +node --inspect dist/server/index.js +``` diff --git a/package.json b/package.json index 8016d64..08b7036 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,8 @@ "lint:fix": "tsc --noEmit -p tsconfig.lint.json && bunx biome check --write .", "test": "bun test", "build": "bun run scripts/build.ts", - "dev": "bun run src/server/index.ts", - "prod": "bun run dist/server/index.js", - "dev:node": "tsx src/server/index.ts", - "prod:node": "node dist/server/index.js", + "server": "bun run dist/server/index.js", + "server:node": "node dist/server/index.js", "webtty": "bun run dist/cli/index.js", "prepack": "bun scripts/clean-pkg-scripts.ts strip", "postpack": "bun scripts/clean-pkg-scripts.ts restore" From f700b3cc88b48c0daf1625fef98111b46233b6b1 Mon Sep 17 00:00:00 2001 From: jesse23 Date: Mon, 23 Mar 2026 06:20:34 -0400 Subject: [PATCH 3/7] docs: improve comments in startServer function for clarity --- src/cli/http.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cli/http.ts b/src/cli/http.ts index 1ecd0f2..2f48cdf 100644 --- a/src/cli/http.ts +++ b/src/cli/http.ts @@ -19,12 +19,16 @@ export async function isServerRunning(): Promise { } export async function startServer(): Promise { + // Detect whether we're running from source (.ts) or built output (.js) and + // resolve the matching server entry accordingly. const isTs = __filename.endsWith('.ts'); const serverEntry = path.resolve(__dirname, isTs ? '../server/index.ts' : '../server/index.js'); if (!fs.existsSync(serverEntry)) { console.error(`webtty: server entry not found at ${serverEntry}`); process.exit(1); } + // Reuse the current runtime (bun, node, etc.) to spawn the server so the + // server always runs under the same runtime as the CLI. const child = spawn(process.execPath, [serverEntry], { detached: true, stdio: 'ignore', From 0becc6da372b00423719813fda5b39805a075194 Mon Sep 17 00:00:00 2001 From: jesse23 Date: Mon, 23 Mar 2026 06:52:22 -0400 Subject: [PATCH 4/7] fix: address Copilot review comments - trim trailing whitespace in AGENTS.md code block - use close code 1001 (Going Away) for server shutdown instead of 4001 which was reserved for session-removed semantics; update client to show 'Server stopped.' message on 1001 rather than reconnecting - graceful SIGINT shutdown: wait for httpServer.close() before exit, with 1s fallback timeout so WS close frames are flushed - guard .ts server entry behind isBun check so Node never tries to exec a TypeScript file directly --- AGENTS.md | 2 +- src/cli/http.ts | 6 +++--- src/server/client.ts | 4 ++++ src/server/index.ts | 11 ++++++++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 02d4222..453ee8c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,7 +21,7 @@ webtty/ Always build and stop the old server before testing: ``` -bun run build && bun run webtty stop +bun run build && bun run webtty stop ``` ## Rules diff --git a/src/cli/http.ts b/src/cli/http.ts index 2f48cdf..dd83b6d 100644 --- a/src/cli/http.ts +++ b/src/cli/http.ts @@ -19,9 +19,9 @@ export async function isServerRunning(): Promise { } export async function startServer(): Promise { - // Detect whether we're running from source (.ts) or built output (.js) and - // resolve the matching server entry accordingly. - const isTs = __filename.endsWith('.ts'); + // Bun can run .ts directly; Node requires the built .js output. + const isBun = typeof (globalThis as Record).Bun !== 'undefined'; + const isTs = isBun && __filename.endsWith('.ts'); const serverEntry = path.resolve(__dirname, isTs ? '../server/index.ts' : '../server/index.js'); if (!fs.existsSync(serverEntry)) { console.error(`webtty: server entry not found at ${serverEntry}`); diff --git a/src/server/client.ts b/src/server/client.ts index b7a150d..97647f8 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -82,6 +82,10 @@ export function render(sessionId: string): string { setTimeout(() => window.close(), 500); return; } + if (event.code === 1001) { + term.write('\\r\\n\\x1b[33mServer stopped.\\x1b[0m\\r\\n'); + return; + } console.log('[webtty] disconnected, reconnecting in 2s...'); term.write('\\r\\n\\x1b[31mConnection closed. Reconnecting in 2s...\\x1b[0m\\r\\n'); setTimeout(connect, 2000); diff --git a/src/server/index.ts b/src/server/index.ts index 0a423f4..68ded18 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -12,7 +12,7 @@ const httpServer = http.createServer((req, res) => { handleRequest(req, res, distPath, wasmPath, () => { for (const session of sessionRegistry.values()) { session.pty?.kill(); - for (const client of session.clients) client.close(4001, 'server stopped'); + for (const client of session.clients) client.close(1001, 'server stopped'); } wss.close(); httpServer.close(() => process.exit(0)); @@ -25,10 +25,15 @@ process.on('SIGINT', () => { console.log('\n\nShutting down...'); for (const session of sessionRegistry.values()) { session.pty?.kill(); - for (const client of session.clients) client.close(4001, 'server stopped'); + for (const client of session.clients) client.close(1001, 'server stopped'); } wss.close(); - process.exit(0); + const exit = () => process.exit(0); + const shutdownTimeout = setTimeout(exit, 1000); + httpServer.close(() => { + clearTimeout(shutdownTimeout); + exit(); + }); }); httpServer.listen(HTTP_PORT, '127.0.0.1', () => { From f7cd205f38f8a07cd9821eb1c42d027f8b044f59 Mon Sep 17 00:00:00 2001 From: jesse23 Date: Mon, 23 Mar 2026 06:56:29 -0400 Subject: [PATCH 5/7] docs: enhance comments in startServer function for clarity --- src/cli/http.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cli/http.ts b/src/cli/http.ts index dd83b6d..7724858 100644 --- a/src/cli/http.ts +++ b/src/cli/http.ts @@ -19,7 +19,10 @@ export async function isServerRunning(): Promise { } export async function startServer(): Promise { - // Bun can run .ts directly; Node requires the built .js output. + // When running from source (e.g. `bun run src/cli/index.ts` during development), + // __filename ends with .ts and Bun is the runtime, so we can point at the .ts + // server entry directly. Built output always lands in .js, so Node is never + // asked to execute TypeScript. In production isTs is always false. const isBun = typeof (globalThis as Record).Bun !== 'undefined'; const isTs = isBun && __filename.endsWith('.ts'); const serverEntry = path.resolve(__dirname, isTs ? '../server/index.ts' : '../server/index.js'); From 0cdaac9856565f77a395f7fb407865348e2fd0c9 Mon Sep 17 00:00:00 2001 From: jesse23 Date: Mon, 23 Mar 2026 07:01:26 -0400 Subject: [PATCH 6/7] fix: close the window after server stops to improve user experience --- src/server/client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/client.ts b/src/server/client.ts index 97647f8..2553df0 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -84,6 +84,7 @@ export function render(sessionId: string): string { } if (event.code === 1001) { term.write('\\r\\n\\x1b[33mServer stopped.\\x1b[0m\\r\\n'); + setTimeout(() => window.close(), 500); return; } console.log('[webtty] disconnected, reconnecting in 2s...'); From 22ad6f0f0e883dbe4f50e69d7d3446f94f7e910d Mon Sep 17 00:00:00 2001 From: jesse23 Date: Mon, 23 Mar 2026 07:36:33 -0400 Subject: [PATCH 7/7] fix: address second round of Copilot review comments - fix README source map glob: dist/*.js.map -> dist/**/*.js.map - annotate removed dev scripts in ADR 001 as historical - restore window.close() for 1001 (server stopped) close code --- README.md | 2 +- docs/adrs/001.webtty.bootstrap.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1befccc..f7203c2 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A web TTY for running CLI/TUI applications in a browser tab, across platforms. ## Debugging -Build emits source maps (`dist/*.js.map`), so you can debug against the built output directly — no minification, original TypeScript line numbers preserved. +Build emits source maps (`dist/**/*.js.map`), so you can debug against the built output directly — no minification, original TypeScript line numbers preserved. ``` bun run build diff --git a/docs/adrs/001.webtty.bootstrap.md b/docs/adrs/001.webtty.bootstrap.md index 66d6302..f3a61d1 100644 --- a/docs/adrs/001.webtty.bootstrap.md +++ b/docs/adrs/001.webtty.bootstrap.md @@ -75,7 +75,7 @@ The original package requires `node-gyp` compilation on install. `@lydell/node-p ## Consequences - Full round-trip working on both Bun and Node.js runtimes -- `bun run dev` uses `Bun.Terminal` (native); `npm run dev:node` uses `node-pty` +- `bun run dev` uses `Bun.Terminal` (native); `npm run dev:node` uses `node-pty` _(these scripts were removed in a later cleanup; see `package.json` for current scripts)_ - No config file yet — all values hardcoded; acceptable for this slice - No auth — localhost-only, same security posture as ghostty-web/demo - Frontend is plain HTML/JS — no TypeScript in browser until a build step is added