- M5: 📱 toolbar button → modal with a client-side QR of location.origin
(shareable when opened via the LAN IP) + localhost tip
- M4: manifest.webmanifest + icon.svg + sw.js (network-first, never intercepts
/term or /hook) + registration in main.ts; apple-touch + theme-color meta
- Browser-verified: QR canvas renders, SW 'controlled', manifest linked,
no console errors. 199 tests green, build ok.
Root cause: whole tab was draggable=true, so a click with any micro-movement
became a (no-op) drag instead of a click → switch failed. Plus every event
(activity/title/status) rebuilt the entire tab bar via replaceChildren, which
could destroy the element mid-click.
Fix:
- switch on pointerdown (fires before drag arms; also works on touch)
- build tab DOM once; update classes/dot/label IN PLACE (refreshTab); full
rebuild only on structural change (add/close/reorder/rename)
- close/rename-input stopPropagation on pointerdown so they don't re-activate
Verified: single pointerdown/click switches reliably across tabs; rename + drag
still work. 198 tests green.
- title-util.ts: folderFromTitle() parses 'user@host:~/path' → last folder
segment (home stays ~); pure, dependency-free, 7 unit tests
- terminal-session.ts: onTitleChange → folderFromTitle so the tab auto-names
to the cwd folder and updates live on cd
- Browser-verified: ~ → cd project → 'web-terminal' → cd docs → 'docs' → cd ~ → '~'.
198 tests green, typecheck + build ok.
- terminal-session.ts: track SessionStatus (connecting/connected/reconnecting/
exited) + onStatus callback; FIX: wire onTitleChange (onTitle was in opts but
never bound — auto-title was dead)
- tabs.ts: per-tab status dot (color = connection state), unread flag set on
background output + cleared on activate, HTML5 drag-to-reorder (preserves
active tab, persisted)
- style.css: .tab-dot colors, .unread ring, .dragging/.drag-over feedback
- Browser-verified: green dot connected, unread ring after bg output, reorder
AAA|BBB→BBB|AAA persists across reload. 191 tests green, typechecks + build ok.
- No hardcoded secrets; Origin+path+maxPayload checks verified wired in server.ts
- No eval/exec; input is verbatim to PTY by design (byte-shuttle)
- npm run build -> dist/server.js verified
6 cases against real startServer via ws client:
①bad-origin 401 ②wrong-path destroy ③oversized-frame 1009 ④spawn-fail exit(-1)
— all pass in sandbox; ⑤attach->output ⑥reconnect-replay gated by PTY_AVAILABLE
(auto-skip where posix_spawn is blocked). Orchestrator verified ⑤⑥ sandbox-off:
6/6 pass with real shell — F5/F6 confirmed (replay incl CJK/ANSI intact).
- All ARCHITECTURE §3 interfaces as pure types; dependency-free (no ws/node/DOM)
so both backend and frontend can import it
- Refinements vs §3 (documented): EnvLike (not NodeJS.ProcessEnv), WebSocketLike +
WS_OPEN (not ws.WebSocket), added Config.wsPath (invariant 8)
- ARCHITECTURE §3.1/§3.4 reconciled to match (anti-drift)
- tsc --noEmit passes
T2 of docs/PLAN.md