Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
28b69b2
TmuxText(feat): Accept names as byte references
tony Sep 13, 2026
4b8cea2
Query(feat): Compose owned iterator results
tony Sep 13, 2026
84fe07b
Scope(fix): Preserve operation and cleanup errors
tony Sep 13, 2026
2dbf17f
Control(fix): Yield terminal stream errors
tony Sep 13, 2026
848352b
Query(docs): Describe owned iterator composition
tony Sep 13, 2026
22eb2ab
CI(chore[cache]): Pin rust-cache to v2.9.2 by SHA
tony Sep 13, 2026
f39ae5c
Control(fix[actor]): Order the stop signal ahead of a pending EOF
tony Sep 13, 2026
fec481a
MCP(fix[wait]): Do not discard a built view on an ordinary close
tony Sep 13, 2026
0360610
Scope(fix): Show the operation cause instead of redacting it
tony Sep 13, 2026
8b1b80e
Scope(fix): Retain the operation's result when only cleanup fails
tony Sep 13, 2026
fca055c
Examples(fix[watch]): Run teardown even when the watcher errors
tony Sep 13, 2026
61782c6
Docs(fix[doctest]): Pin compile_fail examples and restore lost coverage
tony Sep 13, 2026
8f02dad
Test(add[control]): Cover PaneOutput::shutdown's specific terminal error
tony Sep 13, 2026
471cdad
Formats(fix[codec]): Accept next-3.9's widened #{q:} escape set
tony Sep 15, 2026
3fa864c
Server(fix[access]): Decode server-access group ACL markers
tony Sep 16, 2026
87b3d09
Server(fix[docs]): Give Principal a runnable example
tony Sep 16, 2026
5b9c318
Tests(fix[version]): Predict require-gated branches with require's rule
tony Sep 16, 2026
7ae66d8
Formats(fix[codec]): Stop pinning next-3.9's wire bytes as a defect
tony Sep 16, 2026
71a79e1
Pane(fix[pid]): Report a dead pane's pid as None, not a decode error
tony Sep 16, 2026
85fbce8
MCP(add[wait]): Cover wait_for_text's non-Closed shutdown branch
tony Sep 16, 2026
86c26a6
Tests(fix[docs]): Drop two hard-coded counts from comments
tony Sep 16, 2026
384ed32
Version(fix[has_behavior]): Make it real API, fix all three meets() s…
tony Sep 16, 2026
5d4fab0
Control(fix[stream]): End a killed pane's stream, pair resume with mute
tony Sep 16, 2026
953864d
Window(fix[layout]): Refuse a value that is not a layout before dispatch
tony Sep 16, 2026
a6dc5d4
MCP(fix[wait]): Report a pattern already on screen as present_at_entry
tony Sep 16, 2026
7a59a9e
Docs(fix[notes]): Record 3.2a's ignored size, the capture race, chang…
tony Sep 16, 2026
55f12d5
Window(fix[layout]): Accept a unique preset prefix, refuse an ambiguo…
tony Sep 17, 2026
14a3747
MCP(fix[errors]): Report tool failures as isError content, not JSON-RPC
tony Sep 17, 2026
a3f9595
Control(fix[stream]): End a stream when a never-active window closes
tony Sep 17, 2026
3a1e237
Control(fix[watch]): Scope watch_only's mute list to its own session
tony Sep 17, 2026
3bb2f47
Listing(fix[create]): Report a missing socket directory honestly
tony Sep 17, 2026
3c5d8af
MCP(fix[test]): Assert the layout guard's client-side refusal
tony Sep 17, 2026
45cd777
Server(feat[control]): Track owned control-client pids for exclusion
tony Sep 17, 2026
ef0dce9
Wait(fix[echo]): Tell a pending line apart from real output
tony Sep 17, 2026
d035909
Server(fix[channels]): Document lock_channel's cancellation hazard
tony Sep 17, 2026
e1ef718
MCP(fix[manifest]): Put a tool's own text before its safety sentence
tony Sep 17, 2026
e6fffda
Docs(fix[examples]): Stop counting the shipped examples
tony Sep 17, 2026
e68726c
Server(test[create]): Separate undecodable output from none at all
tony Sep 18, 2026
0bba1b1
API(fix[names]): Send caller text to tmux as text, not as a format
tony Sep 18, 2026
ace8b4d
API(add): Reach a pane's session, a scope's parts, a session's enviro…
tony Sep 18, 2026
2f0716e
Tests(add[names]): Find a session by the hostile name it was created …
tony Sep 18, 2026
621d63c
Window(fix[layout]): Resolve a preset against the release that is run…
tony Sep 18, 2026
b96e92b
Server(add[channels]): Hold a wait-for channel for one operation
tony Sep 18, 2026
7d0956b
API(remove): Drop the eleven lenient listing twins
tony Sep 18, 2026
13ef9ca
Server(add[buffers]): Let tmux read and write a buffer file itself
tony Sep 18, 2026
b140e51
Docs(add[timeouts]): Show the per-call deadline a caller already has
tony Sep 19, 2026
e8b2f04
Docs(design): Record that a tokio dependency is a non-goal to remove
tony Sep 19, 2026
c011dd9
API(fix[signatures]): Name what a respawn kills and what a menu item is
tony Sep 19, 2026
8fc6331
Server(feat[transport]): Route the typed API over one connection
tony Sep 19, 2026
726fa76
Client(add): Tell our own control connection from a person's terminal
tony Sep 19, 2026
7c1a63f
Build(fix[features]): Gate two items at the feature that uses them
tony Sep 19, 2026
74dd74c
Docs: Say why the executor is bypassed and the itertools overlap kept
tony Sep 19, 2026
c526fa2
mcp-swap(fix[tests]): Drop two wall-clock bounds that only measured load
tony Sep 19, 2026
7bcb7e6
Docs(add[cancel]): Say when an async fn owes a cancel-safety section
tony Sep 19, 2026
1c0afd0
ControlMode(fix[chain]): Read a block per command
tony Sep 19, 2026
03c35d1
Pane(feat[wait_until]): Wait on a predicate over lines
tony Sep 19, 2026
d50ce05
Docs(fix[examples]): Show Respawn and MenuItem on their own pages
tony Sep 19, 2026
39ff027
Docs(test): Say that real tmux is the supported test path, and how
tony Sep 19, 2026
04fa91f
Compat(fix[test]): Stop deadlines racing setup
tony Sep 19, 2026
91b2843
Tracing(feat[span]): Open one span per dispatch
tony Sep 19, 2026
786da38
Formats(feat[catalog]): List scroll_position
tony Sep 19, 2026
5d79033
Snapshot(feat[get]): Read any field by its handle
tony Sep 19, 2026
c279ef7
Options(feat[schema]): Record choices and ranges
tony Sep 19, 2026
37ae7f0
Options(feat[typed]): Check writes; one reader
tony Sep 19, 2026
14afc79
Control(fix[spawn]): Call a missing tmux missing on the attach path too
tony Sep 19, 2026
6a45690
Workspace(fix[errors]): Name the line and column to fix
tony Sep 19, 2026
38831b9
Workspace(fix[history]): Suppress history unless a file says not to
tony Sep 19, 2026
2cbcf73
Workspace(fix[panes]): Read tmuxp's pane and command shorthands
tony Sep 19, 2026
9272adf
Workspace(fix[directories]): Resolve start directories as tmuxp does
tony Sep 19, 2026
6718bf0
Workspace(fix[freeze]): Freeze a pane at its prompt as no command
tony Sep 19, 2026
67493f2
Workspace(docs[readme]): Say it is a library and where it meets tmuxp
tony Sep 19, 2026
7084030
Docs(fix[cancel]): Say what a dropped call leaves
tony Sep 19, 2026
f5b0a36
tmux-mcp(fix[env]): Withhold environment values
tony Sep 19, 2026
3994ae2
tmux-mcp(fix[socket]): Keep a shared daemon alive
tony Sep 19, 2026
4018571
tmux-mcp(fix[run]): Let an interrupt stop a run
tony Sep 19, 2026
621eff7
tmux-mcp(fix[session]): Accept session ids
tony Sep 19, 2026
a6c55de
tmux-mcp(fix[caller]): Read empty TMUX as absent
tony Sep 19, 2026
687c461
tmux-mcp(fix[errors]): Type errors rmcp raises
tony Sep 19, 2026
aa6223b
tmux-mcp(fix[text]): End escape strings where tmux does
tony Sep 19, 2026
c140bd8
Formats(fix[q]): Decode the tab that 3.8-rc escapes
tony Sep 19, 2026
0fc7f44
Fuzz(feat): Cover framing, rows and the text filter
tony Sep 19, 2026
c5411bc
CI(fix[fuzz]): Keep the corpus between weekly runs
tony Sep 19, 2026
9f48528
API(fix[time]): Return timestamps as SystemTime
tony Sep 19, 2026
b1db400
Server(feat[keys]): Read key bindings as fields
tony Sep 19, 2026
7a1d0dd
Test(fix[env]): Stop handing the fixture's tmux the caller's environment
tony Sep 19, 2026
8e7aa58
Channels(fix[wait-for]): Keep the client tmux cannot take back
tony Sep 19, 2026
8c23ddb
Channels(fix[routing]): Refuse a wait a connection cannot carry
tony Sep 19, 2026
da01c81
tmux-mcp(fix[docs]): Split a two-sentence summary the gate refuses
tony Sep 19, 2026
28cb27e
Options(fix[hooks]): Clear and write a replaced hook in one invocation
tony Sep 19, 2026
e6e2c47
tmux-mcp(fix[hints]): Derive annotations from rows
tony Sep 19, 2026
bfa0372
tmux-mcp(fix[schema]): Describe every input property
tony Sep 19, 2026
b2bc9d5
tmux-mcp(fix[hints]): Mark set_history_limit destructive
tony Sep 19, 2026
db6fa54
tmux-mcp(docs[streams]): Say streamed text is not the screen
tony Sep 19, 2026
0f03807
tmux-mcp(fix[meta]): Stop repeating each tool in its _meta
tony Sep 19, 2026
fa0e3ea
tmux-mcp(fix[schema]): State every optional input's default
tony Sep 19, 2026
4524f1b
tmux-mcp(test[hints]): Pin hints to the toolset lists
tony Sep 19, 2026
191072b
tmux-mcp(fix[toolsets]): Move set_history_limit to teardown
tony Sep 19, 2026
b5592eb
tmux-mcp(fix[effects]): Class every inspect tool as read-only
tony Sep 19, 2026
9d79fa7
Macros(fix[paths]): Name the crate ::libtmux inside its own package
tony Sep 19, 2026
683d5b6
Macros(test[ui]): Cover every derive error site, on one compiler
tony Sep 19, 2026
b2af448
Fuzz(feat[environment]): Fuzz the show-environment -s parser
tony Sep 19, 2026
d331d42
Server(fix[buffers]): List buffer names as framed bytes
tony Sep 19, 2026
f2a1a6f
Examples(fix[matrix]): Count the processes each mode starts
tony Sep 19, 2026
ebdef77
Plan(feat[pause]): Wait between a plan's operations
tony Sep 19, 2026
3109258
Workspace(fix[sleep]): Wait for tmuxp's sleeps
tony Sep 19, 2026
060cad4
Pane(fix[capture]): Trim tmux 3.2a's wrap padding
tony Sep 19, 2026
bc442f5
Test(fix[timeout]): Dodge run-shell's version gate
tony Sep 19, 2026
1b099ee
Docs(fix[readme]): Dodge run-shell's version gate
tony Sep 19, 2026
2f9a00b
Repo(docs[contributing]): Retire the _or_empty pairing rule
tony Sep 19, 2026
3b85e72
Docs(fix[doctest]): Use ? instead of expect in two examples
tony Sep 19, 2026
096f86d
Wait(fix[echo]): Never match a command's own echo
tony Sep 19, 2026
bd4349d
Echo(fix[coverage]): Mask paste and run's own echo
tony Sep 19, 2026
d9b288a
Test(fix[deadline]): Scale two cancellation waits
tony Sep 19, 2026
7c71d85
Plan(fix): Validate control-mode layouts
tony Sep 19, 2026
996ff03
Commands(fix): Keep leading-dash text literal
tony Sep 19, 2026
f4a648b
Control(fix): Preserve bootstrap errors
tony Sep 20, 2026
698fa17
Repo(docs[changelog]): Summarize API changes
tony Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,11 +334,12 @@ not in its generated output, and survive rerecording. A `missing` row is a
field a listing could carry and does not; adding one is ordinary work, leaving
it unrecorded is not.

**Parsers that read from outside are fuzzed.** The control-mode line parser,
the filter-expression wire format, and the workspace YAML loader each have a
target under `fuzz/`. Add one when adding a parser that reads bytes this crate
did not write, and seed it — an unseeded target proves only that arbitrary
input is not valid input.
**Parsers that read from outside are fuzzed.** `just fuzz-list` names a target
for each; `design.md` says what each checks. Add one when adding a parser that
reads bytes this workspace did not write, seed it from real output, and give it
an oracle where one exists — an unseeded target proves only that arbitrary
input is not valid input, and one without an oracle proves only that it does
not panic.

**Packaging is a gate.** `just package` builds the published crates and
verifies what the tarballs contain. A packaged crate ships its README, so a
Expand Down Expand Up @@ -371,6 +372,11 @@ The floor is stated in several places, and they have to agree:
then the `tmux-mcp` tests on 1.88.0. `rust-toolchain.toml` pins a much newer
toolchain for day-to-day work; it is not the floor and does not prove one.

The derive's compile-fail cases pin rustc's diagnostics, which change between
releases, so they run on the pinned toolchain only; the MSRV run checks the
passing cases. After a toolchain bump, `just macros-ui-bless` rewrites their
`.stderr` files; review the diff before committing it.

## Dependencies

- A declared version is the **minimum supported**, not the newest published,
Expand Down Expand Up @@ -400,11 +406,12 @@ follows is the rule.
invalid UTF-8, so they cross the API as `TmuxText`. Reading a tmux stream
with anything that requires UTF-8 fails the whole operation the first time a
pane prints a high byte.
- **Listings come in pairs, and the short name is the loud one.** `sessions()`
returns `Result`; `sessions_or_empty()` collapses failure into no rows. Both
halves are load-bearing, and which one gets the short name is the point: a
caller who writes the obvious thing gets the error, and a caller who wants
an empty list on failure has to say so. Add both when adding a listing.
- **A listing propagates; nothing collapses a failure into empty rows for
it.** `sessions()` returns `Result`, and a caller who wants an empty list on
failure writes `sessions().await.unwrap_or_default()` at the call site. An
`_or_empty` twin of each listing existed once; nothing called one, and a
method whose whole purpose is to discard a reason is a way to discard one by
accident, so they are gone.
- **A failure says what to do about it.** `Error::kind` reduces the variants
to a decision, and `is_object_gone` is the branch most callers write. tmux
reports a missing target and a bad argument with the same exit status, so
Expand Down
9 changes: 9 additions & 0 deletions .github/WRITING.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,15 @@ channel inside a type is not documentation.
restating that it can fail.
- `# Panics` where a caller could trip one. Where nothing can, "Never panics"
is worth writing: it is a promise, and inference is not.
- `# Cancel safety` on an `async fn` whose future, dropped partway, leaves
something a caller has to know about. Say which of three it is: nothing
happened; the effect may or may not have happened, so a retry can repeat it;
or something is left held -- a lock, a queue position, a half-sent input.
The crate-wide model is in the README's "Cancellation and shutdown": dropping
a dispatch signals its process group and reaps it. A method that adds nothing
to that model needs no section; one that does needs one, because a caller
racing it against a timeout or a `select!` cannot read the answer off the
signature.
- No `# Safety`. `unsafe_code` is `forbid` at the workspace level, so there is
no unsafe code here to document. If that ever changes, the section states
the proof obligation the caller must uphold, and the reason it holds.
Expand Down
40 changes: 33 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
with:
tool: just,cargo-hack,cargo-deny,uv

- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2

- run: just check

Expand All @@ -81,7 +81,7 @@ jobs:
- uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2

# `--no-fail-fast` because this lane runs only on master and bills at ten
# times a Linux runner. Stopping at the first failing test binary hides
Expand All @@ -107,7 +107,7 @@ jobs:
- uses: taiki-e/install-action@v2
with:
tool: just
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
- run: just api-check
# Same toolchain and the same rustdoc JSON, so it rides along here
# rather than paying for a second nightly build.
Expand All @@ -127,7 +127,15 @@ jobs:
strategy:
fail-fast: false
matrix:
target: [control_line, filter_expr_json, workspace_yaml]
target:
- control_block
- control_line
- environment_listing
- filter_expr_json
- format_rows
- text_filter
- list_keys
- workspace_yaml
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@v1
Expand All @@ -136,7 +144,16 @@ jobs:
- uses: taiki-e/install-action@v2
with:
tool: cargo-fuzz
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
# The corpus is what this job grows. Cache entries are immutable, so each
# run saves under its own key and the next restores the newest by prefix.
# An entry unused for seven days is evicted: a late run restarts from seeds.
- name: Restore the ${{ matrix.target }} corpus
uses: actions/cache/restore@v6
with:
path: fuzz/corpus/${{ matrix.target }}
key: fuzz-corpus-${{ matrix.target }}-${{ github.run_id }}
restore-keys: fuzz-corpus-${{ matrix.target }}-
- name: Fuzz ${{ matrix.target }}
env:
TARGET: ${{ matrix.target }}
Expand All @@ -146,8 +163,17 @@ jobs:
# statically, which a sanitizer cannot instrument, so the target
# has to be named here rather than left to how cargo-fuzz arrived.
host=$(rustc +nightly -vV | sed -n 's/^host: //p')
cargo +nightly fuzz run --target "$host" "$TARGET" "fuzz/seeds/$TARGET" \
# libFuzzer writes new inputs to the first directory only.
mkdir -p "fuzz/corpus/$TARGET"
cargo +nightly fuzz run --target "$host" "$TARGET" \
"fuzz/corpus/$TARGET" "fuzz/seeds/$TARGET" \
-- -max_total_time=300 -rss_limit_mb=4096
- name: Save the ${{ matrix.target }} corpus
if: always()
uses: actions/cache/save@v6
with:
path: fuzz/corpus/${{ matrix.target }}
key: fuzz-corpus-${{ matrix.target }}-${{ github.run_id }}
- name: Keep any crash that was found
if: failure()
uses: actions/upload-artifact@v7
Expand Down Expand Up @@ -180,6 +206,6 @@ jobs:
- uses: taiki-e/install-action@v2
with:
tool: just
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2

- run: just compat
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
with:
tool: just,cargo-hack,cargo-deny,uv

- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2

- run: just check

Expand Down Expand Up @@ -90,7 +90,7 @@ jobs:
- uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2

# The tag is an assertion about the tree, and an assertion is worth
# checking: a tag naming a version the manifest does not carry would
Expand Down
174 changes: 174 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,180 @@ full.

## Unreleased

### Fixed

- Creation commands and plans, shell commands, messages, key bindings and
access rules treat caller text beginning with `-` as a value instead of a
tmux flag. (#28)

- `PaneOutput` ends when its pane or window closes. (#28)

- `Pane::stream_output` preserves frame-limit errors while opening its
output stream. (#28)

- `ControlSender::resume_pane` and `unmute_pane` restore output after
muting. (#28)

- `ControlSender::watch_only` leaves panes in other sessions unchanged. (#28)

- `Server::lock_channel` and `wait_for_channel` preserve locks and signals
when a waiting call is cancelled or times out. (#28)

- `Pane::capture_with` trims unwritten padding from joined lines on tmux
3.2a, so equality checks see the text the pane printed. (#28)

- `Server::environment_all` and `Session::environment_all` accept variable
names and values containing shell punctuation or newlines. (#28)

- Replacing `set_hooks` no longer leaves a hook cleared but unwritten when
the call is cancelled. (#28)

- Object listings decode the additional format escapes emitted by newer
tmux builds. (#28)

- `tmux-mcp`'s `wait_for_text` ignores echoed input from `send_keys`,
`paste_text` and `run_shell_command` when matching command output. (#28)

- `tmux-mcp`'s streamed captures and waits keep reading output after an
unterminated terminal escape string. (#28)

- `tmux-mcp` reports tool refusals as tool errors, with structured reasons
for clients deciding whether to retry. (#28)

- `tmux-mcp` accepts session IDs returned by its own listings. (#28)

- `tmux-mcp` treats empty `TMUX` and `TMUX_PANE` as detached operation. (#28)

- `tmux-mcp` accepts interrupt keys while `run_shell_command` owns a pane,
so callers can stop a running command. (#28)

- `tmux-mcp` keeps its shared daemon alive while another instance uses it.
(#28)

- `tmux-workspace` executes tmuxp's command shorthand and creates empty
panes from its blank-pane forms. (#28)

- `tmux-workspace` resolves inherited and relative start directories and
expands environment variables as tmuxp does. (#28)

- `tmux_workspace::freeze` records an idle shell as an empty pane, so
rebuilding it does not start a nested shell. (#28)

### Added

- `Server::over_control_mode` routes typed operations through an existing
control connection; blocking operations require the original server. (#28)

- `Server::owns_control_client` and `Client::is_own` identify this process's
control clients. MCP attachment reports exclude those clients. (#28)

- `plan::Pause` delays later operations in a plan. Control-mode plans
refuse pauses before running any operations. (#28)

- `Server::typed_key_bindings` reads key bindings as structured values on
tmux 3.7 and newer. (#28)

- `Pane::get`, `Window::get`, `Session::get` and `Client::get` read fields
already fetched by a listing, including fields without named getters. (#28)

- `set_typed_option` validates option values before writing them. (#28)

- `Server::load_buffer` and `save_buffer` transfer buffer contents through
files, including buffers too large for command arguments. (#28)

- `Server::with_channel_lock` releases its lock when the operation ends,
including failure or cancellation. (#28)

- `NewSessionOptions::environment` sets environment variables for a
session's first process. (#28)

- `Pane::wait_until` waits for a predicate over captured lines and reports
arrival, pane death or timeout. (#28)

- `TmuxVersion::has_behavior` checks capabilities on development builds
using the same rules as `Server::require`. (#28)

- `Pane::stream_output_with_limits` lets callers bound streamed output.
(#28)

### Changed

- **Breaking.** `Session`, `Window`, `Client` and `ServerGeneration`
timestamp accessors return `SystemTime`; convert through `UNIX_EPOCH`
when Unix seconds are needed. (#28)

- **Breaking.** Buffer names use `TmuxText` to preserve arbitrary bytes;
pass listed names directly to buffer reads and deletion. (#28)

- **Breaking.** Pane and window respawning use `Respawn::Replacing` or
`Respawn::OnlyIfDead` in place of a boolean. (#28)

- **Breaking.** `Server::display_menu` accepts `MenuItem` values in place
of tuples. (#28)

- **Breaking.** ID field handles carry their ID type; update explicit
`TextField` annotations as shown in the migration guide. (#28)

- **Breaking.** `ControlEvents` reports stream failures during iteration.
Handle each event's error before reading the event. (#28)

- **Breaking.** `with_session`, `with_window` and `with_pane` report
`ScopeError`, preserving both operation and cleanup failures. (#28)

- **Breaking.** Query iterators support owned values through
`matching_owned`; update explicit trait bounds using the migration guide.
(#28)

- **Breaking.** `AccessRule::name` replaces `user`, and `principal`
distinguishes users from groups. (#28)

- **Breaking.** `Pane::pid` can be absent for a dead pane; use `is_dead`
to check liveness. (#28)

- `Window::select_layout` and layout operations in plans, workspaces and
MCP refuse invalid layouts before dispatch. Unique preset prefixes remain
accepted. (#28)

- **Breaking.** `tmux-mcp` tool methods return `ToolError` for refusals.
(#28)

- **Breaking.** `tmux-mcp`'s `wait_for_text` distinguishes output present
at entry, pending input and output received during the wait. (#28)

- **Breaking.** MCP tool metadata omits repeated descriptions and schemas;
read them from the tool or `tmux://capabilities`. (#28)

- `tmux-mcp` requires the `teardown` toolset for `set_history_limit`. (#28)

- **Breaking.** `tmux-workspace` configuration errors include line and
column fields; update matches on `ConfigError`. (#28)

- **Breaking.** `tmux-workspace` suppresses shell history by default; set
`suppress_history: false` to record commands. (#28)

- **Breaking.** `PaneConfig::shell_commands` uses `ShellCommand` values to
support tmuxp's per-command Enter and delay settings. (#28)

### Removed

- **Breaking.** The `*_or_empty` listing helpers are removed. Handle
listing errors or apply `unwrap_or_default` at the call site. (#28)

- **Breaking.** `get_option` and its global variants are removed; use
`typed_option` and the corresponding typed global readers. (#28)

### Security

- Names, titles and start directories are literal text by default. Drop
manual format escaping; use `TmuxArg::format` for intentional templates.
(#28)

- `test::TestServer` limits inherited environment variables so tests do not
expose unrelated exported values through tmux. (#28)

- **Breaking.** MCP environment tools withhold values unless their names
appear in `LIBTMUX_ENVIRONMENT_VALUES`. (#28)

## 0.1.0-alpha.11 - 2026-09-12

`libtmux`, `libtmux-macros`, and `tmux-workspace` are 0.1.0-alpha.11;
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ You may be looking for:
transport switches, testing
- [`tmux-mcp`](crates/tmux-mcp/README.md) — the MCP server, a **separate
package**, if you want an agent to drive tmux
- [Examples](crates/libtmux/examples) — six programs that run and clean up
after themselves, from reading a server to watching one over control mode
- [Examples](crates/libtmux/examples) — programs that run and clean up after
themselves, from reading a server to watching one over control mode
- [Design notes](crates/libtmux/docs/design.md) — why it is shaped this way
- [Parity ledger](crates/libtmux/docs/parity.md) — capability-by-capability
against Python libtmux
Expand Down Expand Up @@ -190,7 +190,9 @@ a CLI, a config file, or an MCP tool call can carry one.
Three switches, each a Cargo feature, none of them the default. The same
workload under each, printed by `cargo run --example matrix --all-features`.
Every column but `wall` is exact and checked against this block by
`just example-tables`; the timings are one run on one developer machine:
`just example-tables`; the timings are one run on one developer machine.
`processes` is counted: the example runs tmux through a wrapper that logs
each start. `routed` is `Plan::run` on a `Server::over_control_mode` handle:

<!-- example-output: matrix --all-features -->

Expand All @@ -201,7 +203,8 @@ blocking/sequential plan 6 6 16ms per-c
async/sequential plan 6 6 16ms per-command 2 panes, 2 windows, 2 active
async/folded plan 3 3 9ms merged 2 panes, 2 windows, 2 active
async/marked-fold plan 3 3 10ms merged 2 panes, 2 windows, 2 active
control-mode/streaming plan,control-mode 6 1 6ms per-command 2 panes, 2 windows, 2 active
control-mode/streaming plan,control-mode 6 1 10ms per-command 2 panes, 2 windows, 2 active
control-mode/routed plan,control-mode 6 1 6ms per-command 2 panes, 2 windows, 2 active

every mode built the same thing: true
dispatches ranged 3..6, processes ranged 1..6
Expand Down
Loading
Loading