Skip to content

Open terminal grids with tmux in foreground runs #732

Description

@taras

Story

As a Deno or compiled xmd run user, I want an authored terminal grid to open as one foreground tmux workspace, so I can use the provider-neutral grid on a real terminal without putting tmux commands or identifiers in the document.

This is the first production-provider Story under #717. It implements the tmux adapter for #730's neutral state, convergence, and lifecycle contract. Closed #726 and PR #727 are accepted feasibility and teardown evidence; they are not product implementation by themselves.

Current gap

PR #747 implements tmux against the earlier imperative provider surface. It must be semantically restacked after the repaired #730 and #731 heads. The restack replaces direct update/display calls with one read-only revision stream, a serialized coalescing renderer, explicit convergence, and independently observed background failure while preserving the accepted worker, IPC, job-control, layout, attach, and teardown behavior.

Host contract

The Deno source entrypoint and compiled binary install the tmux grid provider for ordinary foreground runs when the invocation has a terminal and the required tmux capability is available. A missing terminal, tmux executable, or other live prerequisite refuses before any pane starts or partial grid appears. Node, Bun, and Workflow install no operational provider in this delivery.

After presentation admission, the provider acquires one TerminalGridHost resource for the exact request and read-only state view. It starts the view subscription, renderer, control observations, persistent workers, and hidden grid before the host is returned. The lifecycle watches the host's independent reader-close and provider-failure operations until release.

Subscribing atomically receives one current immutable state revision and then only strictly newer revisions. Tmux renders those complete snapshots through one scope-owned serialized lane. If commits arrive while rendering is blocked, it may replace pending snapshots with the greatest revision; it never applies an already applied or older revision. Applied revision advances only after every tmux display and layout effect for that snapshot succeeds.

converge(requiredRevision) waits until a complete snapshot at or beyond that revision has been applied. show(requiredRevision) converges first and only then attaches the already prepared grid atomically. Cell launch and shell operations are not invoked until the neutral lifecycle has converged all preceding cell output and launching state.

A renderer, subscription, control-client, worker, show, convergence, launch, shell, or close-observation failure settles the independent provider-failure operation and fails pending convergence. Premature renderer or subscription completion while the host remains acquired is also failure. Reader detach settles only reader close. Ordinary resource release cancels both observations without fabricating either event.

The provider creates one invocation-private tmux server per grid and one persistent initial worker and session leader per pane. Per-pane Unix sockets live under one short mode-0700 private directory. Each worker admits one authenticated connection through a mode-0600 token, then removes the token.

Exact argv, cwd, and environment cross the private channel and never tmux's command parser. The worker starts the interactive child with all three standard streams inherited from the pane terminal, provides the activity only after the runtime spawn event, writes provider display without reading child input, and admits one live child at a time. It runs under Effection run(), not main(), so terminal SIGINT remains job control for the foreground child.

The provider prepares hidden panes, applies an explicit layout, and swaps positions into authored row-major order because tmux ignores pane IDs in layout leaves. It keeps the visible inherited-stdio attach client separate from one no-output control-mode client. Reader detach, control-channel loss, server stop, and provider rendering failure remain distinct lifecycle events. Loss of the root host terminal through SIGHUP becomes structured grid cancellation.

Provider commands, socket and directory paths, authentication tokens, server, session, window, pane, process, attach-client, and control-client identifiers remain inside the provider closure. They appear in no component prop, live neutral state, binding, public result, durable record, or diagnostic.

Teardown boundary

The accepted #726 topology and the teardown boundary in architecture.md remain required behavior:

  • Before the first cancellation signal, snapshot every observable descendant of the live child and every other member of its pane process group.
  • Before reporting one launch settled, clearing the pane as active, or admitting a sequential launch, establish that the child, those descendants and group members stopped and that no other process holds the pane terminal.
  • Repeat the terminal-holder proof while each persistent worker still makes its terminal observable during grid teardown.
  • Detach the visible client before cancellation signals, quiesce every worker, close private channels, stop the exact server, establish server-process disappearance and session refusal, remove the private directory, restore the root terminal, and only then allow the grid task to settle.

A PID, successful signal delivery, attach-client exit, or elapsed timeout proves none of those facts. Failure to establish the bounded facts is a teardown failure and the document does not continue.

The boundary is deliberately honest on macOS: a descendant that already created a new session, closed every descriptor for the pane terminal, and outlived the parent that connected it to the launch is no longer observable through ancestry, process group, or terminal holders. The provider makes no claim to discover or terminate that detached daemon.

The initial terminal-holder sweep may use lsof -t. #726 measured that sweep as the dominant handoff and teardown cost. That is a design constraint, not an acceptance latency threshold. Holder enumeration remains behind the reusable host process observer so it can be optimized without weakening quiescence.

Acceptance

  • Deno source and compiled foreground hosts open the exact provider-neutral grid through one private tmux server; missing prerequisites refuse before cell start and leave no server, worker, socket, token, store subscription, or terminal change behind.
  • The first state snapshot cannot race subscription startup. The renderer applies strictly increasing revisions, coalesces blocked pending snapshots to the latest complete state, and never renders an older revision after a newer one.
  • show() does not attach before its required visible revision is applied. A launch or shell makes zero worker child requests while its required preceding-output revision is held, then proceeds after convergence.
  • A background renderer failure and premature subscription termination fail the grid even when no foreground action is waiting. They remain distinct from visible detach, control loss, server stop, SIGHUP, and parent cancellation.
  • Persistent workers authenticate over invocation-private IPC, preserve exact argv/cwd/environment bytes outside tmux parsing, give the child inherited terminal streams and real job control, and keep provider display out of child input.
  • A worker provides readiness only after the runtime spawn event and settles only after per-launch descendant, process-group, and terminal-holder quiescence. An immediate next launch refuses while that sweep is active.
  • Explicit layout plus pane swaps produces authored row-major order at different terminal sizes without using tmux's automatic tiled layout.
  • Startup, convergence, and presentation failure tear down the hidden server without exposing a partial grid.
  • Reader detach, control loss, server stop, root SIGHUP, renderer failure, and parent cancellation are classified independently and reach Execute concurrent terminal panes through a replaceable provider #730's specified close, failure, or cancellation outcome.
  • Teardown establishes the bounded process and terminal facts, removes every worker/client/server and private path, restores terminal state, and preserves the documented detached-daemon limitation.
  • Provider identifiers and private launch data enter no authored, state, result, retained, or diagnostic surface.
  • Node and Bun catalog and validate the syntax but refuse execution from provider absence before cell start. Workflow installs no grid provider.
  • The provider satisfies the tmux portions of TG18 and TG23–TG24 while the controlled non-tmux provider remains the authority for neutral lifecycle semantics.

Focused evidence

deno task test packages/terminal/tests/terminal-processes.test.ts
deno task test packages/terminal-tmux/tests/terminal-grid-tmux.test.ts
deno task test packages/cli/tests/session-launch-cli.test.ts

Use fake tmux processes, exact invocation-private handles, and controlled subscription, render, convergence, failure, detach, cancellation, and teardown signals. Fault probes must make the relevant row fail when serialization, coalescing, either convergence gate, failure supervision, or teardown is removed. Do not infer neutral results from tmux identifiers or elapsed time.

Dependencies and exclusions

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions