Skip to content

Experimental: pure-chain ChainWorkspaceBuilder + --builder=chain#1054

Open
tony wants to merge 3 commits into
masterfrom
chainable-commands-experiment-00
Open

Experimental: pure-chain ChainWorkspaceBuilder + --builder=chain#1054
tony wants to merge 3 commits into
masterfrom
chainable-commands-experiment-00

Conversation

@tony

@tony tony commented Jun 20, 2026

Copy link
Copy Markdown
Member

Summary

  • Add a ChainWorkspaceBuilder that constructs a workspace's entire session → window → pane tree from a single libtmux._experimental.chain.ForwardPlan, resolved over the minimum number of tmux invocations instead of one subprocess per new-window / split-window.
  • Add an opt-in --builder=chain flag to tmuxp load (Env: TMUXP_BUILDER); the default builder is unchanged.
  • Experimental: depends on libtmux's _experimental.chain API, pinned to a sibling worktree via [tool.uv.sources]. Not for merge — companion to the libtmux experiment.

Changes by area

src/tmuxp/workspace/chain_builder.py (new)

ChainWorkspaceBuilder, a subclass of WorkspaceBuilder, overriding build(): the session is created, then every window and pane is described in one ForwardPlan seeded from the session and resolved via ServerPlanRunner. Reuses the parent's helpers (_wait_for_pane_ready, config_after_window, focus handling, plugin hooks, before_script, session options).

src/tmuxp/cli/load.py

Adds --builder={default,chain} (Env TMUXP_BUILDER), threaded through command_loadload_workspace as builder_name, selecting the builder class. The rest of the load path is unchanged because the subclass mirrors the constructor, .session, and event hooks.

pyproject.toml

[tool.uv.sources] pins libtmux to the sibling ../libtmux worktree so the experimental chain API is importable.

Design decisions

  • Reuse the default window, don't orphan it. Seeding with ForwardPlan.from_session(session) and addressing window 1 through seed.initial_window / seed.initial_pane builds onto the session's default window instead of moving it aside and killing it (the move-to-99 + kill dance the classic builder performs).
  • send_keys_mode, default readiness. The chain batches split + send-keys without waiting for the new pane's shell prompt. readiness builds only the structure via the chain, then sends each pane's commands shell-ready (matching the classic builder, deterministic). batched folds send-keys into the plan for the fullest "all at once" — faster, but it can race an interactive prompt.
  • Sync, not async. tmuxp has no async today and a workspace build is causally serial (windows depend on the session; decorations depend on captured ids), so the chain's synchronous path (ServerPlanRunner / run_resolving) is the right fit.
  • Subclass, not fork. Extending WorkspaceBuilder keeps .session, find_current_attached_session, the on_* event hooks, and the plugin lifecycle identical, so load.py needs only to pick the class.

Test plan

  • Lint + format clean on the new builder, tests, and the CLI change
  • Type-check (mypy) clean
  • tests/workspace/test_chain_builder.py — two/three-pane structure, window + pane focus, readiness-mode commands landing, batched mode, and --builder flag parsing
  • Existing tests/workspace/test_builder.py and tests/cli/test_load.py still pass (the subclass leaves the default builder untouched)
  • Smoke: tmuxp load -d --builder=chain tests/fixtures/workspace/builder/two_pane.yaml builds editor (2 panes), logging, test, matching the default builder

Companion PR

Depends on the libtmux experimental chain API: tmux-python/libtmux#685.

tony added 3 commits June 20, 2026 11:19
why: Build the experimental ChainWorkspaceBuilder against the in-progress
libtmux._experimental.chain API on the sibling libtmux worktree.
what:
- Add [tool.uv.sources] libtmux = { path = "../libtmux", editable = true }
- Relock against the local editable checkout
why: Showcase libtmux's experimental chain API downstream: build a whole
workspace's window/pane tree in the minimum tmux invocations instead of
one subprocess per new-window / split-window.

what:
- Add ChainWorkspaceBuilder (subclass of WorkspaceBuilder): one
  ForwardPlan seeded from the session builds every window and pane,
  reusing the session's default window as window 1 (no orphan) via
  initial_window / initial_pane
- send_keys_mode: readiness (default, classic per-pane shell-ready
  delivery) or batched (folded into the plan)
- Reuse parent helpers (_wait_for_pane_ready, config_after_window, focus,
  plugins, before_script, options)
- Tests: two/three-pane structure, focus, readiness commands land, batched
why: Make the experimental ChainWorkspaceBuilder selectable from the CLI
without replacing the default builder.

what:
- Add --builder={default,chain} to `tmuxp load` (Env: TMUXP_BUILDER),
  threaded through command_load -> load_workspace as builder_name
- Select ChainWorkspaceBuilder when chosen; default builder otherwise
- Test the flag parsing (smoke-verified `tmuxp load --builder=chain`
  builds the expected window/pane tree)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant