Skip to content

refactor: Move-Based Server-Scoped Boards (Pin Sessions)#233

Merged
sahil-noon merged 4 commits into
mainfrom
260602-qn62-move-based-board-pin-sessions
Jun 2, 2026
Merged

refactor: Move-Based Server-Scoped Boards (Pin Sessions)#233
sahil-noon merged 4 commits into
mainfrom
260602-qn62-move-based-board-pin-sessions

Conversation

@sahil-noon
Copy link
Copy Markdown
Collaborator

Meta

ID Type Confidence Plan Review
qn62 refactor 5.0/5.0 21/21 tasks, 0/28 acceptance ✓ 1 cycle

Pipeline: intake ✓ → apply ✓ → review ✓ → hydrate ✓ → ship → review-pr

Summary

Replace the per-WebSocket relay ephemeral (rk-relay-*) + @rk_board server-option encoding with a move-based, server-scoped pin-session board model. Net ~830 lines removed. The ephemeral isolation layer and the @rk_board encoding were two independent subsystems carrying the bulk of the relay/board complexity; removing window sharing removes the root cause of the isolation requirement, letting the common case (a single terminal) be the dumb thing — a direct attach to a real session.

Changes

  • Single-location model: every window lives in exactly one place — a home session (SESSIONS view, direct relay attach) OR moved into its own single-window _rk-pin-<windowID> session (BOARDS view).
  • Board membership derived from tmux: _rk-pin-* sessions plus session vars @rk_board / @rk_home / @rk_board_order (fractional ComputeOrderKey). No DB (§II). Boards are server-scoped, empty boards vanish, and pins persist across rk restarts (no restore-sweep).
  • Deletions: the relay ephemeral path, NewGroupedSession, @rk_owner_pid stamping, cmd/rk/serve_sweep.go, and the @rk_board encoding + cross-server union + lazy/eager cleanup.
  • Untouched backstops: the _rk-ctl anchor + exit-empty off backstop are left as-is.

Intended behavioral changes

  • Pinned windows leave their home session's tab list until unpinned.
  • Multi-client active-window collisions on a shared real session are accepted.

Verification

  • just test-backend — pass
  • just test-frontend — pass
  • tsc --noEmit — pass
  • go build ./... — compiles clean

Note: just build and just test-e2e could NOT run in the authoring environment — the VERSION file is absent (also absent on main) and air is not on PATH. These are environment-provisioning gaps, not code issues. CI should run both.

sahil87 added 2 commits June 3, 2026 01:53
Replace the per-WebSocket relay ephemeral (rk-relay-*) + @rk_board
server-option encoding with a move-based, server-scoped pin-session
board model. Net ~830 lines removed.

Every window lives in exactly one place: a home session (SESSIONS view,
direct relay attach) or moved into its own single-window
_rk-pin-<windowID> session (BOARDS view). Removing window *sharing*
removes the need for the per-connection ephemeral isolation layer.

Board membership is derived from _rk-pin-* sessions plus session vars
@rk_board / @rk_home / @rk_board_order (fractional ComputeOrderKey). No
DB (Constitution II). Boards are server-scoped, empty boards vanish, and
pins persist across rk restarts (no restore-sweep).

Deleted: relay ephemeral path, NewGroupedSession, @rk_owner_pid
stamping, cmd/rk/serve_sweep.go, and the @rk_board encoding +
cross-server union + lazy/eager cleanup.

Intended behavioral changes: pinned windows leave their home session's
tab list until unpinned; multi-client active-window collisions on a
shared real session are accepted. The _rk-ctl anchor + exit-empty off
backstop are untouched.
@sahil-noon sahil-noon requested a review from Copilot June 2, 2026 22:00
@sahil-noon sahil-noon marked this pull request as ready for review June 2, 2026 22:00
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors run-kit’s “boards” and relay architecture to remove per-WebSocket rk-relay-* ephemerals and the @rk_board server-option encoding, replacing them with a move-based, server-scoped “pin session” model (_rk-pin-*) where each pinned window is moved into its own single-window tmux session and relayed via direct attach.

Changes:

  • Introduces _rk-pin-<id> pin-session naming + helpers, and filters pin-sessions from user-facing session lists.
  • Rewrites board membership to be derived from pin-sessions + session vars (@rk_board, @rk_home, @rk_board_order) and updates SSE/relay to match the new model.
  • Updates frontend docs/contracts and e2e specs to reflect server-scoped board derivation and direct relay attach.

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
fab/changes/260602-qn62-move-based-board-pin-sessions/plan.md Design/acceptance plan for pin-session refactor and removals
fab/changes/260602-qn62-move-based-board-pin-sessions/intake.md Intake rationale and decisions for move-based pin sessions
fab/changes/260602-qn62-move-based-board-pin-sessions/.status.yaml Change pipeline status metadata
fab/changes/260602-qn62-move-based-board-pin-sessions/.history.jsonl Pipeline history/event log
docs/memory/run-kit/ui-patterns.md Documents SESSIONS-vs-BOARDS exclusivity and unchanged board rendering path
docs/memory/run-kit/index.md Updates memory index entry for tmux sessions doc scope
docs/memory/run-kit/architecture.md Updates API/relay/boards/SSE architecture documentation for pin-sessions
app/frontend/src/api/boards.ts Updates board API doc comments for pin-session model
app/frontend/src/hooks/use-boards.ts Updates subscription comments for server-scoped derivation + multi-server summary
app/frontend/tests/e2e/boards-same-session-multi-pane.spec.ts Updates e2e assertions/docs for move-based isolation
app/frontend/tests/e2e/boards-same-session-multi-pane.spec.md Companion doc updated for pin-session model
app/frontend/tests/e2e/boards-multi-server.spec.ts Updates cleanup semantics for persistent pin-sessions
app/frontend/tests/e2e/boards-multi-server.spec.md Companion doc updated for server-scoped pins + unioned board-name view
app/frontend/tests/e2e/boards-mobile.spec.ts Updates cleanup semantics for persistent pin-sessions
app/frontend/tests/e2e/boards-mobile.spec.md Companion doc updated for pin-sessions cleanup semantics
app/frontend/tests/e2e/boards-desktop-suspend.spec.md Companion doc updated for pin-sessions cleanup semantics
app/backend/internal/tmux/tmux.go Adds pin-session helpers, filters pin sessions in parseSessions, removes relay sweep/ephemeral utilities, adds ListPinSessionNames
app/backend/internal/tmux/tmux_test.go Updates parseSessions and ResolveWindowSession tests for new model
app/backend/internal/tmux/active_window_test.go Updates baseGroupName tests after relay-ephemeral removal
app/backend/internal/tmux/board.go Rewrites boards to be pin-session derived; implements Pin/Unpin/Reorder with move-based semantics
app/backend/internal/tmux/board_test.go Rewrites board tests for new pin-session behavior and invariants
app/backend/internal/tmux/socketsweep_test.go Removes owner PID option tests (relay sweep deleted)
app/backend/internal/tmux/reaper.go Removes stale sweep reference from comment
app/backend/api/router.go Removes grouped-session/owner PID ops from TmuxOps interface and prod wiring
app/backend/api/sse.go Removes board bootstrap/cleanup logic; keeps only explicit board-changed events
app/backend/api/sse_test.go Deletes tests covering removed board bootstrap/cleanup behaviors; updates real-session filtering test
app/backend/api/sse_subscriber_test.go Deletes pure-function kill-diff test (function removed)
app/backend/api/sessions_test.go Removes grouped-session/owner PID fields/methods from mocks
app/backend/api/relay.go Removes ephemeral relay creation and attaches directly to resolved session
app/backend/api/relay_test.go Updates relay tests for direct-attach behavior and absence of ephemerals
app/backend/cmd/rk/serve.go Removes relay startup sweep and updates supervisor startup comment
app/backend/cmd/rk/serve_sweep.go Deleted: orphaned relay sweep implementation
app/backend/cmd/rk/serve_sweep_test.go Deleted: tests for relay sweep predicates and scoping

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/backend/internal/tmux/board.go Outdated
Comment on lines +276 to +280
if _, err := tmuxExecRawServer(ctx, server, "has-session", "-t", pinSession); err == nil {
current, readErr := showSessionOption(ctx, server, pinSession, BoardOption)
if readErr != nil {
return fmt.Errorf("read %s on existing pin %q: %w", BoardOption, pinSession, readErr)
}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — Pin now stamps @rk_home/@rk_board/@rk_board_order BEFORE moving the window into the pin-session, so a window can never enter a pin-session that lacks @rk_home (a stamp failure kills the still-empty pin-session; nothing is stranded). The idempotent re-pin path now also re-stamps @rk_board and repairs a missing @rk_board_order. (4eed733)

Comment thread app/backend/internal/tmux/board.go Outdated
Comment on lines +350 to +354
// Double-fault guard: only kill the pin-session if the move-back SUCCEEDED. If the
// move-back itself fails the window is still physically inside the pin-session;
// killing it would destroy a live window. Leaving the (still-named) pin-session
// keeps the window recoverable — the pin is unpinnable by window id, and the next
// same-window Pin sees the existing session and re-stamps via the idempotent path.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — chose the ordering fix you suggested: stamp-before-move makes a window-moved-but-unstamped state impossible, so the double-fault rollback is gone entirely and @rk_home durability is guaranteed at creation rather than via recovery. As a backstop, Unpin now recovers an @rk_home-less pin-session by renaming it to recovered instead of hard-failing, so a window is never stranded. (4eed733)

Comment on lines 390 to 392
}
if !ValidBoardName(board) {
return fmt.Errorf("invalid board name")
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — Unpin now reads the pin-session's @rk_board and treats a mismatch with the requested board as a no-op success, so a stale/mismatched /api/boards/{name}/unpin neither unpins the window nor causes the handler to emit a board-changed SSE event for a board the window was never on. (4eed733)

Comment thread app/backend/api/relay.go
Comment on lines +88 to +94
// Select the window on its real session so the attach renders the right
// window. The accepted tradeoff (#1 in the intake): the real session has a
// single active-window pointer shared across attachments, so multi-client
// navigation mutates the real session's active window. For a pin-session this
// is a no-op — its sole window is permanently active.
if err := s.tmux.SelectWindow(windowID, server); err != nil {
slog.Error("select-window failed", "err", err, "session", session, "windowID", windowID)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — handleRelay now uses the session-scoped SelectWindowInSession(session, windowID, server) (the resolved owning session) instead of a bare SelectWindow, so the selected active window matches the session attach-session -t attaches to, group-safe. (4eed733)

Comment on lines +299 to +303
// Filter run-kit's single-window board pin-sessions from every
// user-facing session list. A pinned window is physically MOVED into
// its `_rk-pin-*` session, so it leaves its home session's tab list;
// the pin-session itself is never a user-facing SESSIONS entry (it is
// rendered only as a BOARDS pane). This is the single chokepoint — every
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — good catch, this was the CI failure (boards rendered empty). handleBoardGet (and windowExistsOnServer) now resolve each pinned window via its own pin-session directly — tmux.PinSessionName(windowID) then ListWindows(pinSession), a by-name target query that is NOT subject to the parseSessions _rk-pin-* filter — instead of scanning the filtered ListSessions. Added a session-aware mock + regression test. (3bd1f04)

sahil87 added 2 commits June 3, 2026 03:39
handleBoardGet built its live-window join by scanning the user-facing
ListSessions, which the move-based board change taught to hide _rk-pin-*
sessions. Since pinned windows now LIVE in those hidden sessions, the join
matched nothing and GET /api/boards/{name} returned [] — the board rendered
zero panes (.xterm count 0). Fixed by joining each board entry against its
own pin-session directly via tmux.PinSessionName(windowID) -> ListWindows,
a by-name target query not subject to the session-list filter.

Also fix windowExistsOnServer to check the pin-session, so re-pinning an
already-pinned window to a different board reaches tmux.Pin's re-stamp path
instead of a spurious 404.

Adds a session-aware mock (listWindowsBySession) and a regression test
asserting the board renders its entry when the window lives only in its
pin-session — the integration gap that let CI catch what unit mocks could not.

Verified: just test-backend green; the 6 previously-failing board/relay e2e
specs (boards-pin-flow, boards-same-session-multi-pane, boards-multi-server,
boards-mobile, boards-desktop-suspend, shell-rotation) now pass locally.
Address Copilot PR-review findings, several of which interacted with the
earlier rework:

- Pin stamps @rk_home/@rk_board/@rk_board_order BEFORE moving the window into
  the pin-session. The window can no longer enter a pin-session that lacks
  @rk_home, which eliminates the prior double-fault 'un-unpinnable' state and
  removes the rollback-move complexity: a stamp failure simply kills the
  still-empty pin-session and the window is untouched in its home.
- Idempotent re-pin re-stamps @rk_board and repairs a missing @rk_board_order.
  It does NOT attempt to re-derive @rk_home (impossible once the window lives
  in its pin-session); durability is guaranteed by stamp-before-move instead.
- Unpin no-ops on a board-name mismatch, so the handler never emits a false
  board-changed SSE event for a board the window was never on.
- Unpin recovers an @rk_home-less pin-session by renaming it to recovered<id>
  rather than hard-failing — a window is never stranded.
- handleRelay uses session-scoped SelectWindowInSession instead of a bare
  SelectWindow, so the selected window matches the session it attaches to
  (group-safe).

Adds TestUnpin_BoardMismatchIsNoOp and TestUnpin_HomelessPinRecoversWindow.
just test-backend green; the 6 board/relay e2e specs pass locally.
@sahil-noon sahil-noon merged commit 11f467a into main Jun 2, 2026
5 checks passed
@sahil-noon sahil-noon deleted the 260602-qn62-move-based-board-pin-sessions branch June 2, 2026 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants