From 368390666c78bae39bcb3551609b9726af0a5541 Mon Sep 17 00:00:00 2001 From: jesse23 Date: Mon, 23 Mar 2026 07:47:53 -0400 Subject: [PATCH 01/29] fix: apply graceful shutdown to HTTP stop path The /api/server/stop handler was calling httpServer.close() immediately, racing against WS close frame delivery. Apply the same pattern as SIGINT: let httpServer.close() drain connections with a 1s fallback timeout. --- src/server/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/server/index.ts b/src/server/index.ts index 68ded18..40cf9b1 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -15,7 +15,12 @@ const httpServer = http.createServer((req, res) => { for (const client of session.clients) client.close(1001, 'server stopped'); } wss.close(); - httpServer.close(() => process.exit(0)); + const exit = () => process.exit(0); + const shutdownTimeout = setTimeout(exit, 1000); + httpServer.close(() => { + clearTimeout(shutdownTimeout); + exit(); + }); }); }); From c00048f9a77ed640b3b2c2d49947ca57bde4d9b2 Mon Sep 17 00:00:00 2001 From: jesse23 Date: Mon, 23 Mar 2026 08:23:32 -0400 Subject: [PATCH 02/29] fix: update session close behavior and document browser restrictions --- docs/adrs/007.webtty.session-client.md | 10 +++++++++- docs/specs/client.md | 15 ++++++++++++++- docs/specs/webtty.md | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/adrs/007.webtty.session-client.md b/docs/adrs/007.webtty.session-client.md index 74ef5f9..2a7ae2d 100644 --- a/docs/adrs/007.webtty.session-client.md +++ b/docs/adrs/007.webtty.session-client.md @@ -39,7 +39,15 @@ When the PTY exits (e.g. user types `exit`), the server: 1. Deletes the session from the registry immediately 2. Closes all connected WebSockets with code `4001` -The client's existing `4001` handler (introduced in ADR 005 for deleted sessions) writes "Session removed." and calls `window.close()` after 2s. No delay is added before closing — the user explicitly typed `exit`, so immediate closure is the right behaviour. All open tabs for that session close together. +The client's existing `4001` handler (introduced in ADR 005 for deleted sessions) writes "Session removed." and calls `window.close()` after 500ms. No delay is added before closing — the user explicitly typed `exit`, so immediate closure is the right behaviour. All open tabs for that session close together. + +Server stop (`webtty stop` or SIGINT) sends close code `1001` instead. The client writes "Server stopped." and also calls `window.close()` after 500ms. + +**`window.close()` browser restriction**: browsers only permit `window.close()` on tabs that were opened via `window.open()` or duplicated from such a tab. Tabs opened by the OS `open`/`xdg-open` command or by the user typing a URL directly are treated as unowned — `window.close()` is silently ignored with a console warning. In practice this means: +- Tabs opened by `webtty run` and tabs duplicated from them → close automatically ✅ +- Tabs opened by manually navigating to `localhost:2346` → show "Server stopped." / "Session removed." but remain open ⚠️ + +There is no JS workaround for this restriction. It is browser-enforced by design. **Multi-tab: fanout to all connected clients** diff --git a/docs/specs/client.md b/docs/specs/client.md index f3a2ae0..92dbb70 100644 --- a/docs/specs/client.md +++ b/docs/specs/client.md @@ -1,7 +1,7 @@ # SPEC: Client **Author:** jesse23 -**Last Updated:** 2026-03-22 +**Last Updated:** 2026-03-23 --- @@ -15,6 +15,19 @@ The client has no build step in the initial slices — plain HTML + `