Skip to content

Type hints for tmux 3.4+ format tokens#674

Draft
tony wants to merge 65 commits into
masterfrom
typed-format-fields-tmux-3-4-plus
Draft

Type hints for tmux 3.4+ format tokens#674
tony wants to merge 65 commits into
masterfrom
typed-format-fields-tmux-3-4-plus

Conversation

@tony
Copy link
Copy Markdown
Member

@tony tony commented May 17, 2026

Summary

Adds typed Python attributes for format tokens tmux added in releases
after 3.2a — covering 3.4 (`pane_unseen_changes`), 3.5
(`pane_key_mode`), 3.6 (`session_active`, `session_activity_flag`,
`session_alert`, `session_bell_flag`, `session_silence_flag`,
`client_theme`), and the forward-looking set expected to land with
tmux 3.7+ (`bracket_paste_flag`, `pane_flags`, `pane_floating_flag`,
`pane_pb_progress`, `pane_pb_state`, `pane_pipe_pid`,
`pane_zoomed_flag`, `synchronized_output_flag`).

Problem solved

IDE autocomplete and `mypy` awareness for newer tmux format tokens.
Today users can read these via raw `display-message -p`, but the
typed surface saves the field-name lookup and centralizes the
version gate.

Why this is its own shipment

The forward-looking tokens can't be validated in CI until tmux 3.7
reaches a tagged release. Holding the shipment lets the typed
declarations land alongside real version-checked tests instead of
declarations gated by a version number that doesn't yet exist.

Status

Draft, gated on tmux 3.7 reaching a tagged release. This branch is
parented on `parity-pt-2` (#672); once #672 merges, rebase onto
`master` and revisit. The 3.4 / 3.5 / 3.6 tokens are already
validatable today and could land sooner if needed — but the
forward-looking set drives the held-back timing.

Acceptance criteria for merge

  • tmux 3.7 has reached a tagged release and is reachable from CI
  • Test matrix asserts `get_output_format(list_cmd, version)`
    returns the correct field set for every combination of
    `list_cmd ∈ {list-sessions, list-windows, list-panes, list-clients}`
    \× `version ∈ {3.2a, 3.3, 3.4, 3.5, 3.6a, 3.7}`
  • Negative tests prove master-only tokens are excluded on 3.2a
  • Integration test against tmux 3.7 in CI confirms forward-looking
    fields hydrate as expected

Test plan

  • `uv run ruff check . && uv run mypy src tests` — clean
  • `uv run pytest --reruns 0 -q` — 1265 passed, 2 skipped (the
    pre-existing 3.2a control-mode skips)
  • CI matrix green on `parity-pt-2` rows (3.2a through master)
    after the rebase target merges

Refs

tony added 30 commits May 16, 2026 08:57
why: tmux's display-message entry uses CMD_FIND_CANFAIL so -t is optional,
but libtmux only wrapped Pane.display_message. Server-scoped reads like
#{version} / #{socket_path} had to drop to server.cmd("display-message",
"-p", "#{...}") with no wrapper path. libtmux-mcp carries three workaround
sites.

what:
- Add Server.display_message mirroring Pane.display_message's signature minus
  -t injection (Server.cmd never auto-injects -t).
- Cover -p/-a/-v/-l/-N/-c/-d/-F flags; gate -l on tmux 3.4+.
- Doctests demonstrate #{version} and all_formats=True usage.
- Tests in tests/test_server.py use control_mode() so display-message has a
  client to dispatch -p output through (target/pane is unneeded but a client
  is needed for stdout to materialize).
why: Pane.display_message exists but Window doesn't, forcing callers like
libtmux-mcp's resize_pane(zoom=...) to drop down to
window.cmd("display-message", "-p", "#{window_zoomed_flag}") to read
window-scoped state.

what:
- Add Window.display_message mirroring Pane.display_message; Window.cmd
  auto-injects -t @<window-id>, so window-scoped reads (window_zoomed_flag,
  window_active_clients_list, …) work without a pane handle.
- Cover -p/-a/-v/-l/-N/-c/-d/-F flags; gate -l on tmux 3.4+.
- Doctests demonstrate #{window_id} and #{window_zoomed_flag} reads.
- Tests in tests/test_window.py via WindowDisplayMessageCase NamedTuple
  (matches the Pane.display_message test shape). Includes a target_client
  case using control_mode().
why: tmux's format.c registers window_zoomed_flag as a first-class format
token (callback at format.c:2854, table entry at format.c:3557), but the
libtmux Obj dataclass never declared it. mypy rejected
window.window_zoomed_flag even after refresh(). libtmux-mcp's
resize_pane(zoom) workflow worked around this by going through
display-message.

what:
- Add window_zoomed_flag: str | None = None to Obj in neo.py (alphabetically
  between window_width and wrap_flag).
- Auto-included in the tmux -F format string via get_output_format(), so
  refresh() populates it without further wiring.
- Test toggles zoom on/off via Pane.resize(zoom=True) across two refresh
  cycles and asserts "0"/"1" round-trip.
why: The previous body called self.cmd("send-keys", r"-R \; clear-history")
which sends a single argv to tmux via subprocess. tmux's \; is the
*interactive* command separator and is only interpreted when tmux re-lexes a
full command line — argv never gets re-parsed. tmux saw "-R \;
clear-history" as a single token after -R and treated "\; clear-history" as
literal keys to send, never executing clear-history. The scrollback was
never cleared.

what:
- Split reset() into two separate self.cmd("send-keys", "-R") and
  self.cmd("clear-history") calls. Each goes through Pane.cmd which auto-
  injects -t <pane-id>, so both target the right pane.
- Update docstring (uses r""" because of the literal \; explanation), with
  a working doctest that populates history and verifies reset.
- Test in tests/test_pane.py: spawn a shell pane, populate scrollback with
  "reset_marker_*" lines, call pane.reset(), assert the markers are gone
  from capture_pane(start=-100). Pre-fix this test would have failed (the
  markers stayed because clear-history never ran).
why: tmux's cmd-send-keys.c:223-225 deliberately handles count == 0 with -R
or -N set, returning CMD_RETURN_NORMAL without sending keys. The wrapper at
pane.py:619 always appended `prefix + cmd` to argv, so `pane.send_keys("",
reset=True, enter=False)` produced `tmux send-keys -R ""` — not the
flag-only path tmux explicitly supports. libtmux-mcp's clear_pane kept
`pane.cmd("send-keys", "-R")` for that reason.

what:
- Make cmd Optional[str] with default None. The previous positional-required
  signature is preserved for every existing caller (they pass cmd as a string).
- When cmd is None and copy_mode_cmd is None: emit `send-keys <flags>` with
  no trailing argv. Require at least one flag (reset, repeat, copy_mode_cmd);
  ValueError otherwise so degenerate `send_keys()` calls aren't silent no-ops.
- Skip the post-call self.enter() in flag-only mode (no keys → no Enter).
- Doctest demonstrates `pane.send_keys(reset=True)` working in flag-only mode.
- Tests use monkeypatch+stub of pane.cmd (the pattern from test_server.py:730)
  to capture the exact argv: flag-only reset emits `("send-keys", "-R")`,
  flag-only repeat=3 emits `("send-keys", "-R", "-N", "3")`. A separate test
  asserts ValueError when no flags accompany cmd=None.
why: tmux's cmd-list-buffers.c:39 declares `.args = { "F:f:", ... }`. The
libtmux wrapper passed neither, so callers got tmux's default template
`name: N bytes: "sample"` and had to regex-parse it. libtmux-mcp's buffer GC
carried `server.cmd("list-buffers", "-F", ...)` for that reason.

what:
- Add format_string and filter kwarg to Server.list_buffers. Default behavior
  (template output) preserved for backward compat.
- format_string follows the existing display_message convention (avoids
  shadowing Python's builtin `format`). filter shadows the builtin by design,
  with a per-line noqa: A002 + docstring note — it mirrors tmux's flag name
  for grep-friendly symmetry with the manual.
- Doctests cover all three modes (default, format projection, filter
  predicate); tests exercise raw-name projection and C-side filter matching
  (e.g. `#{m:gap6match_*,#{buffer_name}}` returns only the matching names).
…tmux C-side filter

why: tmux's cmd-list-panes.c:41 accepts `[-f filter]` and evaluates
`format_true(expanded)`, gating output server-side before any
data is returned. libtmux's `panes` / `windows` / `sessions` properties
return QueryList and force callers to filter post-hoc in Python — orders of
magnitude slower than pushing the predicate into tmux's C code.
libtmux-mcp's search_panes fast-path kept `server.cmd("list-panes", "-a",
"-f", ...)` for that reason.

Note: `list_panes()` / `list_windows()` / `list_sessions()` are already
defined as deprecated raise-only stubs (since 0.17) with pinned legacy-API
tests in `tests/legacy_api/`. Keep those intact and add the new methods
under `search_*()` — matches the verb libtmux-mcp uses for its consuming
endpoint and side-steps the legacy contract entirely.

what:
- Extend `fetch_objs` (neo.py:248) with `filter: str | None = None`. When
  set, append `-f <filter>` before the `-F` template. Single change feeds
  all the wrappers below.
- Add `Server.search_sessions`, `Server.search_windows`, `Server.search_panes`
  alongside the existing `sessions`/`windows`/`panes` properties.
- Add `Session.search_windows`, `Session.search_panes`.
- Add `Window.search_panes`.
- Each wrapper exposes a single `filter=` kwarg; the existing property is
  the no-filter form. Doctests demonstrate `#{m:gap7_*,#{window_name}}`-style
  predicates returning only matching objects.
- Tests across test_server.py / test_session.py / test_window.py exercise
  filter-by-id (m:pane_id) and filter-by-name (m:prefix_*).
…se_if_stderr helper

why: wrappers like session.last_window raise
exc.LibTmuxException(proc.stderr) and downstream consumers (libtmux-mcp's
handle_tool_errors) lose the "which tmux command failed" context. Pre-0.56
the MCP built `f"tmux {subcommand} failed: ..."` manually.

Split into two commits per planning direction:
* 8a (this commit): add the surface — LibTmuxException.subcommand attribute
  and raise_if_stderr helper. Backward-compatible; no call-site changes yet.
* 8b (next commit): mechanically migrate the ~12 existing raise sites to
  use raise_if_stderr.

what:
- LibTmuxException.__init__ accepts subcommand: str | None = None kwarg.
  Override __str__ to format as "<subcommand>: <stderr>" when set; otherwise
  preserves pre-0.57 output exactly. Verified backward-compat with a test
  that constructs exc with no kwarg and asserts no "subcommand:" prefix.
- common.raise_if_stderr(proc, subcommand) consolidates the
  `if proc.stderr: raise exc.LibTmuxException(...)` pattern. common.py
  already imports `exc`, so no new import. Documented with versionadded
  marker and a working doctest.
- Tests in tests/test_common.py cover both: the no-stderr no-op path
  (using session fixture for a started server) and the raises-with-tag path
  via list-clients against a fake session id.
why: mechanically thread the subcommand tag through every wrapper that
raises on tmux stderr. With 8a's surface in place
(LibTmuxException.subcommand + raise_if_stderr helper), this commit applies
the migration so every typed wrapper now produces an exception tagged with
the originating tmux subcommand.

what:
- Replace every `if proc.stderr: raise exc.LibTmuxException(proc.stderr)`
  pair with `raise_if_stderr(proc, "<subcommand>")` across the wrapper
  surface: server.py (sites), session.py (11), window.py (14),
  pane.py (22). Plus one explicit site in neo.py for fetch_objs's
  underlying tmux_cmd invocation.
- Migration was scripted with subcommand auto-extraction from the
  preceding `proc = …cmd("subcmd", …)` line; two unmapped sites
  (window.py's select_layout, neo.py's fetch_objs) migrated by hand.
- Add raise_if_stderr import to every touched module via ruff isort.
- New integration test in tests/test_session.py exercises the end-to-end
  tag: session.last_window() on a one-window session raises an exception
  with subcommand == "last-window" and str(exc) prefixed accordingly.
why: Server.cmd auto-injects -t <target> when the target= kwarg is set. A
caller's own positional -t produces `tmux <sub> -t %1 -t %1`; tmux's
args_get() applies last-wins so the positional -t is silently dropped. The
0.34 docstring at session.py:234 already documents this contract as ignored;
this commit promotes the documented contract into a runtime
DeprecationWarning so callers see the bug instead of getting silent no-ops.

what:
- Server.cmd: when target is not None and "-t" appears in *args, emit
  DeprecationWarning with stacklevel=3 so the warning surfaces at the
  caller (not the wrapper).
- Migrate tests/test_common.py:51 to use target= kwarg (the modern path).
- tests/legacy_api/test_common.py:204 now wraps the legacy call with
  pytest.warns(DeprecationWarning) to pin the new contract.
- New tests in tests/test_server.py exercise both the warning fires (legacy
  shape) and the no-warning case (target= alone).
- MIGRATION entry under "Upcoming Release" documents the deprecation, the
  migration path (target= kwarg), and the planned TypeError escalation.
why: tmux's format_table[] at format.c:3010-3563 registers 37 scope-relevant
format tokens that ship in 3.6a; libtmux's hand-curated allowlist in neo.py
declared only a subset. An earlier commit on this branch added window_zoomed_flag
specifically; this commit covers the remaining 36 so the typed dataclass
surface matches what tmux exposes.

what:
- src/libtmux/neo.py: add 13 pane_* (pane_dead, pane_format, pane_in_mode,
  pane_input_off, pane_key_mode, pane_last, pane_marked, pane_marked_set,
  pane_mode, pane_path, pane_pipe, pane_synchronized, pane_unseen_changes),
  12 window_* (window_active_clients_list, window_active_sessions_list,
  window_activity_flag, window_bell_flag, window_bigger, window_end_flag,
  window_flags, window_format, window_last_flag, window_silence_flag,
  window_start_flag, window_visible_layout), 11 session_* (session_active,
  session_activity_flag, session_alert, session_bell_flag, session_format,
  session_group_attached_list, session_group_many_attached, session_grouped,
  session_many_attached, session_marked, session_silence_flag) fields.
  Alphabetical insertion preserves existing layout. Each as
  `str | None = None`; get_output_format() auto-includes them in the tmux
  -F template.
- Tests: parametrized declaration + hydration tests per scope assert each
  field is registered on the dataclass and either None or a string after
  refresh(). On older tmux versions unknown tokens expand to empty strings,
  so older tmux still hydrates the rest of the fields fine.
- Focused live tests: pane.pane_synchronized round-trips through tmux's
  synchronize-panes window option; window.window_flags is always a string.
why: tmux's format.c at lines 3041-3110 registers twelve client_* format
tokens (client_activity, client_control_mode, client_created,
client_last_session, client_mode_format, client_prefix, client_readonly,
client_session, client_termfeatures, client_termtype, client_theme,
client_utf8) that the libtmux Obj dataclass didn't declare, and
Server.list_clients returned raw stderr-style strings instead of typed
objects. Multi-client coordination, read-only detection, theme/termtype
reads forced consumers down to server.cmd("list-clients", ...).

what:
- src/libtmux/neo.py: add the missing client_* fields to Obj
  alphabetically; extend ListCmd Literal with "list-clients".
- src/libtmux/client.py: new module with @dataclasses.dataclass class
  Client(Obj). refresh() uses obj_key="client_name", list_cmd="list-clients";
  classmethod from_client_name() mirrors the Session.from_session_id shape.
- src/libtmux/server.py: import Client; new Server.clients property returns
  QueryList[Client] via fetch_objs(list_cmd="list-clients").
- src/libtmux/__init__.py: export Client; add to __all__.
- conftest.py: register Client in the doctest_namespace.
- docs/api/libtmux.client.md: autodoc page.
- docs/api/index.md: card + toctree entry; updated lead to mention Client.
- tests/test_client.py: live tests using the control_mode() fixture exercise
  Server.clients listing, attached-session reporting, default readonly
  state, and refresh() rehydration.
why: tmux's cmd-run-shell.c at lines 156-162 reads two flags the wrapper
didn't expose: -c sets the shell command's working directory (independent of
any target pane's cwd) and -E sets JOB_SHOWSTDERR, which combines the
command's stderr into the captured output stream.

what:
- Server.run_shell gains `cwd: StrPath | None = None` (maps to -c) and
  `show_stderr: bool | None = None` (maps to -E). Both default-None, no
  behavior change for existing callers.
- Doctest demonstrates pwd in a custom cwd and stderr capture.
- Tests: `test_run_shell_cwd` runs `pwd` with `cwd=tmp_path` and asserts
  the directory appears in output; `test_run_shell_show_stderr` runs a
  shell snippet that writes to both streams and asserts both are in the
  result. Both gated by has_gte_version("3.5") because run-shell stdout
  passthrough requires tmux 3.5.
why: tmux's cmd-capture-pane.c at lines 231-232 branches on -P to call
cmd_capture_pane_pending, returning bytes tmux has buffered as input for the
pane but the program hasn't consumed yet. Useful for diagnosing hung
programs, copy-mode race conditions, and paste-buffer drains. The wrapper
covered 12 of 13 flags from "ab:CeE:JMNpPqS:Tt:" but skipped -P.

what:
- Pane.capture_pane gains `pending: bool = False` kwarg, present on both
  overload signatures and the implementation. When True, the wrapper
  appends -P alongside -p so stdout still flows back as a list.
- Docstring entry documents the distinction from the default capture
  (history vs unconsumed input).
- Tests: argv-assertion test confirms -P is emitted via the
  monkeypatch+stub pattern; a smoke test confirms the return type is
  list[str] (whether tmux has bytes to return depends on live input
  pressure and isn't reliably reproducible).
Document libtmux 0.57.0 per AGENTS.md changelog conventions: multi-sentence
lead paragraph, #### deliverables under ### What's new, the documented
Pane.reset fix under ### Fixes, the -t-in-args deprecation under ###
Deprecations, and the new Client autodoc page and migration entry under ###
Documentation. Every section describes ship-state user-visible behavior; the
bug history for stays scoped to ### Fixes where it is relevant to anyone
upgrading from 0.56.0.

Cross-links to autodoc'd APIs use {class}, {meth}, {attr}, {exc}, {func},
and {doc} roles so the changelog renders as live navigation in the docs
site.
why: the 0.34 versionchanged block on Session.cmd documented "Passing target
by -t is ignored. Use target keyword argument instead." Verified against
tmux source: tmux's args_get() returns TAILQ_LAST(...) — last value wins
(https://github.com/tmux/tmux/blob/3.6a/arguments.c). libtmux constructs
argv as ["-t", str(target), *args] (server.py:345), placing the kwarg's -t
value FIRST and any positional -t value SECOND. Under last-wins, the
*positional* value wins, not the kwarg. The original rationale was factually
inverted — readers would build wrong mental models of precedence.

what:
- Rewrite the .. versionchanged:: 0.34 block to describe the actual
  behavior. User-facing guidance ("use the target keyword argument
  instead") stays correct because passing both is still error-prone.
- Append a .. versionchanged:: 0.57 block noting the DeprecationWarning
  that Server.cmd now emits when both are set.
- No code change.

The same factual error appeared in the parallel Server.cmd versionchanged
0.57 block, fixed in the prior fixup commit. Window.cmd and Pane.cmd don't
carry the inverted note (verified).
why: CI matrix on tmux 3.2a continued to fail after the prior rollback with
`LibTmuxException: list-windows: ['server exited unexpectedly']`.
Cross-referencing libtmux's Obj.__dataclass_fields__ against
https://github.com/tmux/tmux/blob/3.2a/format.c showed eight tokens that
don't exist in tmux 3.2a's format_table[]:

  - client_theme
  - pane_key_mode
  - pane_unseen_changes
  - session_active
  - session_activity_flag
  - session_alert
  - session_bell_flag
  - session_silence_flag

tmux's format engine is documented as returning "" for unknown tokens
(format.c:4321 in 3.2a), but one or more of these specific tokens triggers a
server-side crash in 3.2a's format engine. Probably a NULL deref via the
options-lookup path (format_find calls options_parse_get first) when the
token name matches an option-style key that resolves to NULL in some
context.

The library's minimum supported tmux version is 3.2a per TMUX_MIN_VERSION in
src/libtmux/common.py, so a crash on that row blocks the matrix.

what:
- Drop the fields from Obj in src/libtmux/neo.py.
- Remove the corresponding entries from PANE_FORMAT_FIELDS in
  tests/test_pane.py and SESSION_FORMAT_FIELDS in tests/test_session.py.
- Keep the rest of the and token additions; the remaining
  ~tokens are all in 3.2a's format_table and don't cause crashes.

Follow-up: expose these fields via a version-gated mechanism (e.g. fetch the
full format string only for tmux versions that support the tokens, or split
Obj into core + augmented dataclasses) so users on 3.4+ / 3.6+ can still
read the tokens.

This is a forward commit (not autosquashed) so the rollback shows up clearly
in the history.
…a in list-windows

why: After the previous rollback that dropped tokens unknown to tmux 3.2a's
format_table, CI's 3.2a row still failed with `server exited unexpectedly`
during fetch_objs's `list-windows -F …` call. The remaining suspects were
the 11 client_* tokens added in the Client-class commit. tmux's format
engine evaluates the -F template against each window-link, with no client
bound (ft->c == NULL for the list-windows context). The callbacks check
ft->c != NULL and return NULL safely on 3.6a, but at least one of these
tokens triggers a server crash in 3.2a's format engine when invoked outside
its valid client scope.

Rather than chase the specific NULL-deref path through tmux 3.2a's C code,
drop these tokens from Obj. The Client dataclass keeps the 14 pre-existing
client_* fields (client_name, client_pid, client_termname, etc.) — enough to
populate Server.clients with attached-terminal identity, but no longer
covers the tokens added in this branch.

Dropped from Obj:
- client_activity, client_control_mode, client_created
- client_last_session, client_mode_format
- client_prefix, client_readonly, client_session
- client_termfeatures, client_termtype, client_utf8

what:
- src/libtmux/neo.py: remove the fields from Obj.
- src/libtmux/client.py: change the Client docstring doctest to read
  client_name (pre-existing) instead of client_readonly (removed).
- tests/test_client.py: remove test_client_session_reports_attached_session
  and test_client_readonly_default_zero (the fields they assert are gone).

Follow-up: re-expose these tokens via a scope-aware format string (query
client_* only when list-clients is the list_cmd, not when list-windows /
list-panes is). This is documented as a TODO and will land in a separate PR
once the safe-on-3.2a strategy is designed.

Forward commit (not autosquashed) so the rollback shows up clearly.
why: After dropping the new tokens that crashed tmux 3.2a's format engine,
four display_message tests now fail on the 3.2a CI row with empty stdout
from `display-message -p -c <control-mode-client>`:

tests/test_server.py::test_server_display_message_flags[version]
tests/test_server.py::test_server_display_message_flags[socket_path_format_string]
tests/test_server.py::test_server_display_message_target_client
tests/test_window.py::test_window_display_message_target_client

tmux 3.2a's display-message -p dispatch via a control-mode client does not
reliably deliver stdout back to the client process — the call succeeds (no
error) but the result list is empty. Later tmux versions (3.3+) fixed this
dispatch path.

The wrappers themselves work correctly on 3.2a — only the specific test
shape that asserts on stdout content via a control-mode client doesn't pass.
Skip these tests on tmux < 3.3 and keep them gated for the versions that
exercise the contract reliably.

what:
- tests/test_server.py: add `has_gte_version("3.3")` skip to
  test_server_display_message_flags (the parametrized cases that set
  target_client) and test_server_display_message_target_client.
- tests/test_window.py: same skip on test_window_display_message_target_client.
- Other display_message tests (the window_display_message_flags
  parametrize block that uses auto-injected -t and the
  no_text_returns_none tests) pass on 3.2a unchanged.

Forward commit (not autosquashed) so the version-gate decision shows up
clearly in the history.
why: the deprecation was reverted in the prior two commits because the rationale was factually inverted
(positional -t actually WINS via tmux's last-wins arg parsing — verified in
tmux's last-wins arg parsing in arguments.c via TAILQ_LAST). The
0.34 contract already requires target= for object-level cmd() overrides
(CHANGES:1075), so the 0.57 layer added misleading docstrings without new
user value.

what:
- Remove the "### Deprecations" section from the 0.57.0 entry.
- Remove the corresponding bullet under "### Documentation" pointing at
  the migration guide (the migration entry itself was removed when the
  Server.cmd warning commit was reverted).

The 0.34 contract stays in place. A future major release can re-evaluate
whether to enforce target=-only with a TypeError after a clean
pre-announcement.
why: 's CI matrix on tmux 3.2a crashed when the -F template included tokens
that don't apply to the calling list-* subcommand or don't exist in the
running tmux's format_table. The empirical crashers were 11 client_* tokens
queried during list-windows (no client context) and several post-3.2a tokens
that contributed cumulative risk.

what:
- src/libtmux/neo.py:
  - Add SCOPES_BY_LIST_CMD dict mapping each list-* to the set of token
    scopes its format engine can resolve (e.g. list-windows reaches
    universal + session + window; list-clients reaches universal +
    session + client).
  - Add FIELD_VERSION dict (initially empty) mapping field name → min
    tmux version; fields absent from the dict default to the project's
    floor (3.2a).
  - Add _SCOPE_PREFIXES table and _token_scope() helper that derive a
    token's scope from its name prefix (pane_*, window_*, session_*,
    client_*, buffer_*, etc.). Runtime-only tokens (mouse_*, cursor_*,
    selection_*, copy_cursor_*, popup_*) resolve to "event" and are
    excluded from all list-* templates.
  - Add _UNIVERSAL_TOKENS frozenset for cross-scope tokens without a
    scope prefix (pid, version, host, host_short, socket_path, etc.).
  - Add _normalize_tmux_version() helper that treats tmux master as a
    sentinel "newer than any tagged release" for comparison.
- Rewrite get_output_format() to take (list_cmd, tmux_version) and
  filter the field set accordingly. Cached via @functools.cache on the
  small number of (list_cmd, version) combinations a process sees.
- Rewrite parse_output() to take the same args so it reads the same
  filtered field order.
- Thread the live tmux version through fetch_objs() via
  get_version(server.tmux_bin) before calling get_output_format(),
  pass through to parse_output() per line.
- Doctests on the helpers and on get_output_format / parse_output
  demonstrate the new contracts.

No Obj field changes in this commit. The fields rolled back during the prior
CI bisect remain absent — they re-enter in follow-up commits that exercise
the new scope/version gating.
why: the scope-aware get_output_format introduced in the prior commit now
restricts each list-* subcommand's -F template to tokens whose scope is
reachable from that subcommand. The 11 client_* tokens (client_activity,
client_control_mode, client_created, client_last_session,
client_mode_format, client_prefix, client_readonly, client_session,
client_termfeatures, client_termtype, client_utf8) only appear when
fetch_objs is called with list_cmd="list-clients" — never during
list-windows, list-panes, or list-sessions. This eliminates the root cause
of the tmux 3.2a server crash that forced the original rollback.

what:
- src/libtmux/neo.py: re-add the 11 client_* fields on Obj alphabetically
  between the existing client_* declarations. No FIELD_VERSION entries
  are needed — all 11 ship in tmux 3.2a's format_table (verified against
  https://github.com/tmux/tmux/blob/3.2a/format.c).
- src/libtmux/client.py: restore the doctest reading client.client_readonly
  (a 0/1 string) to demonstrate the typed surface.
- tests/test_client.py: re-add test_client_session_reports_attached_session
  (asserts client.client_session matches the attached session name) and
  test_client_readonly_default_zero (asserts client.client_readonly is "0"
  for a normal attached client).

Verification: list-windows/list-panes/list-sessions stay scope-clean on tmux
3.2a — the format string for those subcommands contains no client_* tokens.
Confirmed via the runtime gate: tests pass on local tmux 3.6a; CI matrix run
will confirm 3.2a.
…cope prefix

why: the prefix-based scope router in _token_scope handles tokens whose
names start with pane_/window_/session_/client_/buffer_, but tmux's format
table contains tokens whose names don't carry a scope prefix (cursor_*,
mouse_*_flag, scroll_region_*, etc.). Without an override mechanism those
tokens either fall through to the wrong scope via a prefix rule or land in
the fail-closed "unknown" bucket and never appear in any list-* template.

Introduce _SCOPE_OVERRIDES as the targeted escape hatch: a per-token map
that wins before the prefix table. Subsequent scope-gate fix commits
populate it as misclassifications are audited against tmux's format_cb_*
callbacks.

what:
- Declare _SCOPE_OVERRIDES as an empty dict in src/libtmux/neo.py.
- _token_scope() consults it first, before _SCOPE_PREFIXES.
- Doctest in _token_scope() documents the override path without pinning
  a specific token (later commits add their own).
why: the 0.57.0 entry's "typed format-token fields" deliverable was
previously truncated to ~fields after the tmux 3.2a CI rollback. With
scope-aware + version-aware get_output_format in place, the full token set
re-enters the typed surface safely.

what:
- CHANGES: rewrite the deliverable section to describe scope+version
  gating (list-clients emits only client_* + universal; tokens added in
  tmux 3.4/3.5/3.6 are gated; 8 forward-looking master tokens are
  declared but hydrate only once tmux 3.7 ships). Cross-link each
  Pane/Window/Session/Client class.
why: Server/Window/Pane.display_message() called tmux then returned
proc.stdout without inspecting proc.stderr. On tmux errors (e.g. -F together
with positional cmd: "only one of -F or argument must be given") the wrapper
silently returned [] or None, hiding the failure from callers and
contradicting the rest of the typed-wrapper surface.

what:
- Insert raise_if_stderr(proc, "display-message") in all three
  display_message wrappers, matching the sibling display_popup /
  display_panes / display_menu wrappers in this branch.
- Add a negative test per scope verifying the wrapper raises
  LibTmuxException(subcommand="display-message") instead of swallowing
  tmux's "only one of -F or argument" error.
- Document the fix in CHANGES under Fixes.
why: The pending= docstring described tmux's -P as capturing "bytes tmux has
buffered as input for the pane but the running program hasn't consumed".
That's backwards: tmux is a terminal, it observes output from the running
program, not input to it. tmux's input_pending() returns ictx->since_ground,
an evbuffer that accumulates output bytes which begin an incomplete escape
sequence and are still pending the parser's ground state. Same wording
leaked into the CHANGES entry.

what:
- Rewrite the pending= parameter docstring in Pane.capture_pane() to
  describe parser-state semantics (incomplete escape sequences,
  since_ground), keeping the diagnostic-use mention.
- Mirror the same correction in the CHANGES entry for the kwarg.
why: The cwd= docstring framed the kwarg as a "working directory for the
shell command", parallel to subprocess.Popen(cwd=). tmux's -c is actually a
*start directory*: tmux/job.c:142 tries chdir(cwd), then chdir(home), then
chdir("/"), only fatal()'ing if all three fail. A user relying on Python
semantics expects a failed chdir to raise, but tmux silently falls back.
Reproduced live: run_shell('pwd', cwd='/definitely/not/a/path') returns
['<HOME>'].

what:
- Extend the cwd= parameter description to document tmux's home -> /
  fallback chain and contrast with subprocess.Popen(cwd=).
- Re-word the lead sentence "Working directory" to "Start directory"
  to match tmux's terminology.
…lient

why: After display_message() started propagating tmux stderr (the prior display_message-fix commit), tmux 3.2a's CI surfaced a latent issue in
test_server_display_message_no_text_returns_none: the control-mode client
path emits a usage error from tmux's argument parser on 3.2a, which the test
silently absorbed before but now raises. This is the same
control-mode-on-3.2a unreliability already gated on the sibling
test_server_display_message_flags and
test_server_display_message_target_client suites with
`has_gte_version("3.3")`.

what:
- Add the same `has_gte_version("3.3")` skip to
  test_server_display_message_no_text_returns_none, matching the
  existing gate on its sibling tests.
tony added 20 commits May 17, 2026 13:20
why: client.session_id / window_id / pane_id are hydrated from
tmux's downward format cascade at the moment the Client dataclass
is built and go stale as soon as the client switches view. The
existing class-level docstring warning isn't enough on its own —
users iterating over server.clients still reach for the raw fields
and treat them as identity.

what:
- Add Client.attached_session / .attached_window / .attached_pane.
  Each property re-reads list-clients before resolving and returns
  the live typed Session / Window / Pane (or None), mirroring the
  Session.active_window fresh-lookup convention.
- Tighten the Client class-level warning to point at the new
  properties as the safe accessors.
- Tests: typed resolution, fresh window tracking (selects a new
  active window post-hydration and asserts the property reflects
  it — proves the property re-queries rather than returning the
  snapshot), pane resolution, None propagation when session_id is
  absent.
- CHANGES: extend the Client what's-new entry to mention the
  attached_* accessors.
- MIGRATION: 0.57 section gains a "snapshots, not identity"
  subheading covering the snapshot vs. live access pattern.
why: The live attachment helpers promise None when a stored client no longer resolves through tmux list-clients.
what:
- Translate missing client refreshes to None for attached_session
- Cover real control-mode detach behavior for attached_* properties
why: The Client documentation should distinguish attached_* convenience behavior from explicit refresh lookups.
what:
- Clarify None behavior for missing live client rows
- Preserve refresh/from_client_name missing-object semantics
…e for 0.57.0 surface

why: The autodoc layer documents the new public symbols, but Client (view vs. identity), scope+version-gated typed fields, and C-side filter predicates each introduce a mental model that needs a topic-page home.
what:
- Add docs/topics/clients.md covering the Client view-vs-identity distinction, attached_session/window/pane live lookup, and None-on-detach semantics
- Add docs/topics/format-tokens.md explaining the two gates (scope and tmux version), the downward format_defaults cascade, the per-release compatibility table, and how to introspect via get_output_format
- Expand docs/topics/filtering.md with a (c-side-filtering)= section covering Python-side vs C-side trade-offs, predicate shapes, the silent-zero-match diagnostic recipe, and when to prefer which
- Add Client to docs/topics/architecture.md hierarchy diagram, table, core objects, and module map
- Add cross-links from docs/topics/traversal.md to c-side-filtering and from docs/topics/pane_interaction.md to capture_pane(pending=True) and send_keys(cmd=None)
- Wire the two new pages into docs/topics/index.md grid and toctree
- Rename autodoc anchor (clients)= to (api-clients)= in docs/api/libtmux.client.md so the conceptual page owns the readable {ref}\`clients\`
… docs

why: The 0.57.0 breaking-change docs in CHANGES and MIGRATION
promise str(exc) renders as "<subcommand>: <stderr>" and that
exc.args[0] holds the raw stderr string. raise_if_stderr passed
proc.stderr (a list[str]) directly to LibTmuxException, so
Exception.__str__ rendered the list's repr — yielding
"last-window: ['no last window']" instead of the documented
"last-window: no last window". The documented migration code
`exc.args[0] == "can't find window"` was always False because
exc.args[0] was the list, not the string.

what:
- Pass "\n".join(proc.stderr) to LibTmuxException so the message
  is a string. Multi-line tmux stderr renders as a multi-line
  string, matching how Python typically surfaces subprocess
  errors.
- Add test_raise_if_stderr_str_shape_exact that asserts the FULL
  str(exc) and exc.args shapes (no startswith, no substring) for
  a wrapper flowing through raise_if_stderr, so future drift
  surfaces as a test failure rather than a docs lie.
why: The two-call form raced under busy pane writers: send-keys -R
clears the visible grid (verified at ~/study/c/tmux/cmd-send-keys.c:225
→ input.c:923 → screen-write.c:335) and any output landing between
the two subprocess.Popen invocations could scroll into history via
scroll-on-clear (tmux's default), then be wiped by the second call's
clear-history. That destroyed output the caller produced after the
reset point — `reset()` should wipe state at reset time, not whatever
happens to be in the grid when clear-history runs.

A naïve one-call form `self.cmd("send-keys", "-R", ";",
"clear-history")` doesn't work either: Pane.cmd auto-injects
`-t <pane_id>` only before the first subcommand, so the `;` separator
leaves clear-history routed to tmux's cmdq default pane — empirically
verified on tmux 3.6a to clear the wrong pane.

what:
- Route through self.server.cmd to bypass Pane.cmd's auto-target, and
  pass `-t self.pane_id` explicitly on both subcommands so the `;`
  separator can't misroute clear-history.
- Update the docstring to describe the single-IPC semantics and why
  the explicit double-targeting is necessary.
- Add test_pane_reset_targets_non_active_pane that calls reset() on a
  non-active pane and asserts history_size goes to 0 on the target
  while the active sibling pane's history_size is preserved. Under
  the misroute bug, clear-history would have hit the active sibling
  instead of the target.
…rror

why: `Client` inherits `client_name: str | None` from `Obj`. The
`assert isinstance(self.client_name, str)` line vanishes under
`python -O`, letting `None` flow into `_refresh` and surfacing as
a less-clear downstream error. Keep the failure loud regardless of
optimization level.

what:
- Replace the assertion with an explicit `if self.client_name is
  None: raise ValueError(...)`, with the message documented in the
  Raises section of the docstring.
- Add test_client_refresh_raises_when_client_name_is_none asserting
  the explicit raise.
why: The fall-through `return "universal"` was fail-open — a future
`Obj` field added without a matching prefix, override, or known-token
entry would silently classify as universal and ship under every
`list-*` -F template. That defeats the scope-gating machinery on
exactly the case it's meant to protect: a future token whose class
hasn't been mapped, where emitting on tmux 3.2a may crash the
format engine or hydrate as nonsense.

Pre-flight confirmed: every currently-declared `Obj` field maps to
a known scope, so the flip is a no-op for the runtime template but
turns future drift into a deterministic test failure.

what:
- Change the final return in `_token_scope` from `"universal"` to
  `"unknown"`. `"unknown"` is absent from every SCOPES_BY_LIST_CMD
  entry, so an unclassified field is excluded from every list-cmd
  template.
- Document the fail-closed default in the docstring and show what
  it returns for an unrecognized name.
- Add test_token_scope_unknown_for_unclassified_field asserting the
  default and that `"unknown"` isn't in any allowed scope set.
- Add test_every_obj_field_classifies_to_known_scope as a guard:
  any new field added to Obj without classification breaks this
  test with a message pointing to the right table to update.
…s the triple

why: Code that needs all three of session/window/pane for a client
("where is this client attached *now*") naturally reads
client.attached_session, then client.attached_window, then
client.attached_pane. Each property re-reads tmux on access, so the
sequence costs three list-clients refreshes for one conceptual
read. The new helper shares a single refresh across the triple and
returns the three values together.

The helper catches NoActiveWindow and falls back to
(session, None, None). MultipleActiveWindows propagates — that
indicates a tmux invariant violation that callers should surface,
not absent attachment.

what:
- Add internal Client._resolve_attached returning
  tuple[Session | None, Window | None, Pane | None], with documented
  contract for the (None, None, None), (session, None, None), and
  full-triple cases.
- Update the class docstring to point readers at the helper for
  all-three access.
- Add three regression tests: live attachment → full triple,
  detach → (None, None, None), and a monkeypatch-driven
  NoActiveWindow → (session, None, None).
- attached_session / attached_window / attached_pane stay unchanged
  so per-access live semantics are preserved.
why: The 0.57.0 entry covered the LibTmuxException subcommand prefix
+ subcommand attribute under both `### Breaking changes` and
`### What's new` -> `#### Subcommand-tagged exceptions`. The
breaking-changes subsection already documents the behavior, migration
path, and rationale; the duplicate `####` heading restates the same
content without adding new information. Per CLAUDE.md's "deliverable
test," each `####` heading should be a distinct deliverable in user
vocabulary — this failed it.

what:
- Remove the `#### Subcommand-tagged exceptions (#670)` block from
  `### What's new`. The breaking-changes section at CHANGES:59-101
  is unchanged and remains the canonical reference for this
  deliverable.
why: MIGRATION's "Upcoming Release" header sat above an already-drafted
0.57.0 section without comment-bracket delimiters, so future-release
content didn't have an unambiguous insertion point. CHANGES uses
`<!-- KEEP THIS PLACEHOLDER -->` / `<!-- END PLACEHOLDER -->` to mark
where new entries land; MIGRATION should match for the same reason.

what:
- Wrap the placeholder body in matching HTML comment brackets,
  mirroring the CHANGES convention.
- New release content for the upcoming version lands below the END
  marker.
why: The doctest sent a key sequence then immediately read the pane's
scrollback to assert the marker landed. Without `retry_until`, this
trusted that the shell echoed before capture — a coin-flip under
parallel-test load. The dedicated functional test in
tests/test_pane.py::test_pane_reset_clears_history_and_sends_reset
already exercises the same path with retry_until; the doctest's
responsibility is to demonstrate the API, not to re-test timing.

what:
- Drop the send_keys + immediate capture_pane lines from the doctest.
- Keep the call + return-value check, which is timing-independent.
why: The filtering topic doc already covers when to pick `search_*()`
(C-side push-down) over `QueryList.filter()` (Python-side, post-fetch)
with a comparison table and "When to prefer which" guidance, but the
six `search_*` API entry points don't reference it. A caller landing
on `Server.search_panes` from autodoc has no path to discover the
comparison or the unfiltered `panes` attribute.

what:
- Add a See Also section to each `search_*` method (Server x3,
  Session x2, Window x1). Each block cross-links to (a) the matching
  unfiltered `panes` / `windows` / `sessions` attribute and (b) the
  `c-side-filtering` ref label in docs/topics/filtering.md.
…ic helper

why: The typed wrappers (`Server.search_*`, `Session.search_*`,
`Window.search_panes`, `Server.list_buffers`) all carry a warning
that tmux silently expands a malformed `-f` predicate to empty —
indistinguishable from "no matches". `fetch_objs` is the
documented public surface those wrappers route through, but its
docstring didn't carry the same caveat. A caller using
`fetch_objs(filter=...)` directly missed the warning.

what:
- Copy the malformed-filter warning into the `fetch_objs(filter=)`
  parameter docstring with the same wording as the typed wrappers.
- Cross-link to the `c-side-filtering` topic doc for the broader
  context.
why: Every 0.57.0 deliverable in CHANGES (and the MIGRATION header)
was tagged `(#670)`. Verified upstream: PR #670 does not exist on
tmux-python/libtmux. The actual branch PR is #672 ("Increase tmux
coverage: Client, typed fields, C-side filter"). The `(#670)` refs
appear to be from an earlier draft of the branch that never opened.

what:
- `sed 's/(#670)/(#672)/g'` across CHANGES and MIGRATION. Verified
  by `gh pr view` that #672 is open on the upstream and matches the
  branch's scope; #670 was a 404. The two pre-existing `(#672)`
  refs under `### Fixes` are unchanged.
…stead of raising

why: tmux's stderr from display-message conflates genuine argument-parser
errors (e.g. -F-with-positional rejection) with operational quirks like
3.2a's control-mode dispatch path silently failing without emitting stderr
at all. Raising LibTmuxException on every stderr forced an in-branch
workaround — pytest.skip patches gated on has_gte_version("3.3") — that
masked the underlying mismatch instead of solving it. Switch to
warnings.warn so callers see the stderr without losing the return value,
and the eventual raise/per-call-opt-in contract can land in a follow-up
shipment that exercises real tmux versions end-to-end.

what:
- src/libtmux/server.py, src/libtmux/window.py, src/libtmux/pane.py:
  replace raise_if_stderr(proc, "display-message") with
  warnings.warn("display-message: …", stacklevel=2). Wrapper return
  value unchanged on success and on warn paths.
- All three display_message docstrings gain a Notes block describing
  the warn-not-raise contract and showing the
  warnings.catch_warnings/filterwarnings("error") escalation pattern.
- tests/test_pane.py, tests/test_window.py, tests/test_server.py:
  rename test_*_display_message_raises_on_tmux_error to
  test_*_display_message_warns_on_tmux_error and switch to
  pytest.warns(UserWarning, match=…). Drop the 3.2a control-mode skip
  added by the prior commit on test_server_display_message_no_text_returns_none —
  with warn-not-raise the 3.2a control-mode stderr no longer fails the
  test (the test only asserts result is None on get_text=False).
- CHANGES: rewrite the display_message Fixes entry to describe the
  warn contract and how to escalate.
- MIGRATION: add a new section under 0.57.0 documenting the warn
  contract and the warnings.catch_warnings escalation pattern.
- MIGRATION: add a section noting that Pane.reset now dispatches via
  self.server.cmd; mocks targeting pane.cmd no longer intercept reset.
- docs/topics/pane_interaction.md: tighten the capture_pane(pending=True)
  wording to describe tmux's parser pending buffer rather than "slow
  consumer / paused program" (the latter framing implies a PTY/app
  buffering issue that pending= doesn't address).
- docs/topics/filtering.md: note that there is no search_clients();
  filter via Server.clients and Python-side QueryList.filter.
why: Server.new_session() called get_output_format() with no args, which
defaults to ("list-panes", "3.2a") and gates out every typed Obj field
whose FIELD_VERSION entry exceeds 3.2a. On tmux 3.3+ the returned Session
silently missed pane_dead_signal and pane_dead_time, and any 3.4+ tokens
that join FIELD_VERSION in a follow-up shipment would have the same
gratuitous gap. Match the pattern used by fetch_objs: thread the live
tmux version through, and use list-sessions as the scope (the format
context for tmux's new-session -P -F is the freshly created session).

what:
- src/libtmux/server.py:
  - Import get_version from libtmux.common.
  - new_session() now derives tmux_version via get_version(tmux_bin=…)
    and passes ("list-sessions", tmux_version) to both get_output_format
    (template build) and parse_output (output parse). Pair must be
    identical or the field order goes out of sync.

Verified: a new_session() on tmux 3.3+ now hydrates pane_dead_signal and
pane_dead_time on the returned Session, where master returned None
unconditionally.
why: tmux master (post-3.6a) registers eight new format tokens that the next
tmux release will ship: pane_zoomed_flag, pane_floating_flag, pane_flags,
pane_pb_state, pane_pb_progress, pane_pipe_pid, synchronized_output_flag,
bracket_paste_flag. Declaring them now means libtmux is ready when the tag
lands; older tmux releases expand unknown tokens to empty strings, so the
fields stay None until the user upgrades tmux.

what:
- src/libtmux/neo.py: add the fields alphabetically within the existing
  Obj layout (pane_* tokens among the pane_* block, bracket_paste_flag
  near buffer_*, synchronized_output_flag near start_time).
- tests/test_pane.py: parametrized test asserts each field is declared
  on the dataclass and hydrates either as None or as a string after
  refresh(). No runtime-value assertions — those will activate when the
  shipping tmux release exposes the tokens.
why: with the scope+version gating in place, the format string sent to older
tmux versions automatically excludes tokens that those versions don't
recognize. The tokens below first registered in tmux 3.4-3.6 — tagging them
with FIELD_VERSION makes them appear on supported tmux releases that include
them, and absent on older tmux without sending unknown tokens that bloat the
format string or trigger crashes.

what:
- src/libtmux/neo.py:
  - Re-add fields to Obj alphabetically: pane_key_mode,
    pane_unseen_changes, session_active, session_activity_flag,
    session_alert, session_bell_flag, session_silence_flag, client_theme.
  - Populate FIELD_VERSION with each token's minimum tmux release
    (3.4 for pane_unseen_changes, 3.5 for pane_key_mode, 3.6 for the
    five session_* tokens and client_theme).
- tests/test_pane.py: restore pane_key_mode and pane_unseen_changes in
  PANE_FORMAT_FIELDS (the parametrized declaration+hydration test).
- tests/test_session.py: restore the new session_* entries in
  SESSION_FORMAT_FIELDS.

Verification:
- On tmux 3.6a (local), all tokens hydrate via refresh(); tests
  pass.
- On tmux 3.2a, FIELD_VERSION skips all 8 — the -F template stays at
  its pre--rollback shape for that version.

Version anchors verified via: rg '"<token>"'
https://github.com/tmux/tmux/blob/<TAG>/format.c across 3.2a, 3.3a, 3.4,
3.5, 3.5a, 3.6, 3.6a.
why: tmux master post-3.6a registers new format tokens (verified via
https://github.com/tmux/tmux/blob/master/format.c grep on each format_cb_*
signature). Declaring them on Obj now means libtmux is ready when tmux 3.7
ships — the FIELD_VERSION gate keeps them silent on every currently-released
tmux, and they begin hydrating automatically once the user upgrades tmux.

what:
- src/libtmux/neo.py:
  - Add fields to Obj alphabetically: bracket_paste_flag, pane_flags,
    pane_floating_flag, pane_pb_progress, pane_pb_state, pane_pipe_pid,
    pane_zoomed_flag, synchronized_output_flag.
  - Tag each in FIELD_VERSION with "3.7" so they're absent in every
    list-* template on tmux <=3.6a.
  - Add _SCOPE_OVERRIDES dict for tokens whose name doesn't carry a
    scope prefix; map bracket_paste_flag and synchronized_output_flag
    to "pane" scope (their tmux callbacks dereference ft->wp). The six
    pane_* tokens scope correctly via the existing prefix table.
  - Update _token_scope() to consult _SCOPE_OVERRIDES first.

Verification: on tmux 3.6a (local), the fields stay None after refresh() —
confirmed because FIELD_VERSION blocks them from the -F template. All tests
pass.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 17, 2026

Codecov Report

❌ Patch coverage is 0% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.84%. Comparing base (0b4941d) to head (8db405d).

Files with missing lines Patch % Lines
src/libtmux/neo.py 0.00% 16 Missing ⚠️
Additional details and impacted files
@@               Coverage Diff               @@
##           parity-pt-2     #674      +/-   ##
===============================================
- Coverage        51.08%   50.84%   -0.24%     
===============================================
  Files               25       25              
  Lines             3465     3481      +16     
  Branches           682      682              
===============================================
  Hits              1770     1770              
- Misses            1400     1416      +16     
  Partials           295      295              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tony tony force-pushed the parity-pt-2 branch 6 times, most recently from d886138 to 61688af Compare May 18, 2026 02:31
Base automatically changed from parity-pt-2 to master May 18, 2026 02:36
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.

1 participant