Commit Graph

6 Commits

Author SHA1 Message Date
yyovil 7c97ee79cd
refactor(terminal): per-client zellij attach replaces shared PTY + replay ring (#194)
* refactor(terminal): per-client zellij attach replaces shared PTY + replay ring

Each WebSocket client that opens a pane now gets its own `zellij attach`
PTY (attachment.go) instead of sharing one PTY whose output was replayed
from a bounded byte ring. Zellij answers every fresh attach with its full
init handshake (alt screen, SGR mouse tracking, bracketed paste) and a
faithful repaint — the ring replay lost exactly that handshake, leaving
late subscribers without mouse reporting (dead wheel scroll). The cost is
one zellij client process per open pane per connection, which the zellij
server is built for (yyork ships the same model).

ring.go and session.go (fan-out, replay buffer) are deleted; manager.go
now tracks per-client attachments with liveness gating, and pty_unix.go
answers every resize frame with an explicit SIGWINCH.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(renderer): re-assert settled terminal resize; align docs with per-client attach

After each debounced resize settles, send one follow-up resize frame with
the same grid (RESIZE_REASSERT_MS). xterm only fires onResize on actual
grid changes, so a resize update the zellij client loses (raced mid-attach
or coalesced during a drag) would otherwise desync the session layout from
the pane until the next real change. The backend answers every resize
frame with an explicit SIGWINCH, so the re-assert is a no-op when already
in sync.

Comments in the terminal hook/components now describe the per-client
attach model (fresh server-side `zellij attach` per open, no replay ring).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 15:19:38 +05:30
yyovil 0ee86a6314
fix(terminal): prevent zellij session resurrection 2026-06-11 16:38:19 +05:30
Pritom14 eda39a156a fix(terminal): guard subscribe-to-assign window in openTerminal
A session can exit and run onExit (which deletes c.terms[id]) in the gap
between subscribe returning exited=false and openTerminal assigning
c.terms[id]. The delete is a no-op there since the key isn't set yet, so
the later assign resurrects a stale entry for a dead pane, trapping every
future open for that id on the connection. Re-apply the delete after the
assign when onExit fired in the window, tracked by a c.mu-guarded flag.

Add a stress regression test that races the exit against the assign.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 23:42:59 +05:30
Pritom14 a766a80f76 fix(terminal): keep open re-servable after a pane exits
Opening a terminal whose session has exited left c.terms[id] set to a
no-op (already-exited path) or to a never-cleared unsubscribe (exit after
open), so the open guard silently dropped every later open for that id on
the connection until close/reconnect. Clients also saw exited/data before
the opened ack.

Ack opened before subscribe so it always precedes replay/data/exited;
have subscribe report whether the pane was already terminal and skip
registering in that case; and clear the connection entry from the exit
callback for panes that exit after open.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 23:04:15 +05:30
Pritom14 4d90d59bf0 feat(backend): wire terminal manager into the daemon at /mux
Construct the tmux runtime and a terminal.Manager fed by the CDC
broadcaster, and hand it to httpd.New so the /mux WebSocket surface goes
live. httpd.New now runs after the CDC substrate so the broadcaster exists
when the manager subscribes; the listener still binds before running.json
is written, preserving fail-fast on port conflict. The manager is closed on
shutdown alongside the CDC pipeline and lifecycle stack.
2026-05-31 19:23:35 +05:30
Pritom14 721b8b34a2 feat(terminal): PTY-attach terminal streaming feature package
Add internal/terminal: a transport-agnostic feature package that attaches
a PTY to a session's tmux pane and multiplexes the byte stream to WebSocket
clients, plus a session-state channel fed by the CDC broadcaster.

The PTY is reached through a small PTYSource interface (satisfied by the
tmux runtime adapter) and spawned via an injectable spawnFunc, so fan-out,
the 50KB replay ring, and re-attach resilience all test without a real
process, tmux, or network. A tmux-guarded integration test exercises the
real creack/pty path end-to-end.

Raw PTY bytes never touch the CDC change_log; only the sessions channel is
CDC-fed. Windows PTY spawning is stubbed pending a ConPTY path.
2026-05-31 19:23:35 +05:30