refactor(terminal): per-client zellij attach replaces shared PTY + replay ring#194
Merged
yyovil merged 2 commits intoJun 12, 2026
Merged
Conversation
…play 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>
…-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>
Contributor
There was a problem hiding this comment.
yyovil has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Each WebSocket client that opens a pane now gets its own server-side
zellij attachPTY (backend/internal/terminal/attachment.go), replacing the shared-PTY + bounded replay-ring model (ring.go,session.go— both deleted).Why
Zellij answers every fresh attach with its full init handshake (alt screen, SGR mouse tracking, bracketed paste) followed by a faithful repaint — but it emits that handshake once, at the head of the stream. The ring replay handed late subscribers everything except that handshake, so reconnecting clients lost mouse reporting and wheel scroll went dead. A fresh attach per client makes zellij re-send the handshake every time, by construction. The cost is one zellij client process per open pane per connection, which the zellij server is built for (yyork ships the same model).
Changes
attachment.go(+ unit/integration tests): per-client attach lifecycle, liveness gating, re-attach resiliencemanager.go: tracks per-client attachments instead of fan-out sessionspty_unix.go: answers every resize frame with an explicitSIGWINCHuseTerminalSession: after each debounced resize settles, one follow-up resize frame with the same grid (RESIZE_REASSERT_MS) — a resize update the zellij client loses (raced mid-attach, coalesced during a drag) would otherwise desync the session layout until the next real grid change; paired with the backend's explicit SIGWINCH this is a no-op when already in syncdocs/backend-code-structure.mdupdatedTesting
go build ./... && go test -race ./...green (terminal package: unit + integration)tsc --noEmit+ vitest suite green🤖 Generated with Claude Code