GeezerVison is showing github.com — Experimental: pure-chain ChainWorkspaceBuilder + `--builder=chain` by tony · Pull Request #1054 · tmux-python/tmuxp
Conversation
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_load → load_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.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Jun 27, 2026
tony
added a commit
that referenced
this pull request
Jul 4, 2026
why: Land the experimental ChainWorkspaceBuilder from #1054 on the new
builder package. It builds the window/pane tree via libtmux's chain API
(libtmux#685), which is unreleased and absent from published libtmux, so
the scaffold must import cleanly and fail with an actionable error rather
than crash on import.
what:
- Add src/tmuxp/workspace/builder/chain.py: ChainWorkspaceBuilder
subclasses ClassicWorkspaceBuilder; guard the experimental import
behind _HAVE_CHAIN so the module imports without the API
- build() raises exc.WorkspaceBuilderImportError when the chain API is
absent; when present, port #1054's single-ForwardPlan build
- Register the `chain` entry point in tmuxp.workspace_builders
- Export ChainWorkspaceBuilder from the builder package
- Add tests: test_builder_chain.py importorskips the chain API; the new
test_builder_chain_unavailable.py asserts selecting `chain` without the
API resolves the class yet raises on build()
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)
tony
added a commit
that referenced
this pull request
Oct 24, 2026
why: Land the experimental ChainWorkspaceBuilder from #1054 on the new
builder package. It builds the window/pane tree via libtmux's chain API
(libtmux#685), which is unreleased and absent from published libtmux, so
the scaffold must import cleanly and fail with an actionable error rather
than crash on import.
what:
- Add src/tmuxp/workspace/builder/chain.py: ChainWorkspaceBuilder
subclasses ClassicWorkspaceBuilder; guard the experimental import
behind _HAVE_CHAIN so the module imports without the API
- build() raises exc.WorkspaceBuilderImportError when the chain API is
absent; when present, port #1054's single-ForwardPlan build
- Register the `chain` entry point in tmuxp.workspace_builders
- Export ChainWorkspaceBuilder from the builder package
- Add tests: test_builder_chain.py importorskips the chain API; the new
test_builder_chain_unavailable.py asserts selecting `chain` without the
API resolves the class yet raises on build()
This branch has not been deployed
No deployments
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
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.
|