Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
121 changes: 121 additions & 0 deletions docs/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Documentation voice

This file covers the *voice* of prose under `docs/` — how to frame a
page so a reader meets the idea before its API surface. It complements
the repository-root `AGENTS.md`, which already governs code blocks,
shell-command formatting, doctests, changelog conventions, and MyST
roles. When the two overlap, the root file wins; this one only answers
the question it leaves open: how should the prose sound?

## Who you are writing for

The default reader writes Python and drives tmux through libtmux's
object API — `Server`, `Session`, `Window`, `Pane`. They are fluent in
tmux itself — servers, sessions, windows, panes, targets, formats — and
comfortable in Python, but you cannot assume they know libtmux's
internals: the format-string query layer, `neo`, the options and hooks
machinery, or when an object goes stale and needs `refresh()`.

A second, smaller reader works *on* libtmux or against its lower
layers: format tokens, the neo query interface, custom traversal, or
contributing. Serve them too, but mark their material opt-in ("for the
rarer cases", "advanced") so the default reader knows they can stop.
Never make the common case pay a comprehension tax for the advanced one.

## Voice

- **Second person, present tense, active.** "You split the window", not
"A pane is created". Address the reader who is doing the thing.
- **Concept before API surface.** Open by saying what the object or
method *is* and what it does for the reader. The signature — the
parameters, the flags — is the last detail they need, not the first.
A page that opens with a method signature has buried the idea under
its mechanics.
- **Say when they can stop.** Lead with the default and the
reassurance: most readers never reach for this, the defaults work,
the advanced parts are optional. Let a skimmer leave after one
paragraph.
- **Grant permission, don't demand attention.** "Reach for this
when…", "for the rarer cases" — tell readers they're in the right
place without implying they must read on.
- **Progressive disclosure.** Order by how many readers need it: the
common call → the one argument a few will tune → the lower-level
primitive → querying tmux directly. Each step is for a smaller
audience than the last.
- **Lean on the hierarchy.** The reader thinks Server → Session →
Window → Pane; reinforce that chain when you explain containment or
traversal. It is the mental model the whole library hangs on.
- **Name the trade-off.** If a call costs something — an extra tmux
round-trip, a stale object needing `refresh()`, a polling wait — say
so, and say what it buys ("a busy wait, not an event, but reliable").
State it; don't sell it.
- **Frame by concept, not by mechanism.** Don't headline a feature by
its tmux flag or format token in prose; that names the implementation
surface, which is the reader's last concern. Name the concept. The
mechanics vocabulary — a parameter table, a `#{format}` token, the
`-t` target — belongs in a reference table or the API docs, and only
there.

## Examples that run

Prose examples under `docs/` are doctests, and the root `AGENTS.md`
requires them to actually execute — `testpaths` includes `docs/`, so
pytest runs every one. Lead with a small, runnable example early rather
than after paragraphs of prose; libtmux is code-first.

- Use the `doctest_namespace` fixtures — `server`, `session`, `window`,
`pane` (and `Server` / `Session` / `Window` / `Pane` / `Client`) —
instead of building a server by hand.
- Fence a `>>>` session as a ```` ```python ```` block, and reach for
`# doctest: +ELLIPSIS` when output varies (ids like `@1`, `$2`,
socket names). Use a ```` ```console ```` block for shell commands at
a `$` prompt.
- The code blocks on a page share one doctest session, so a later
block can use a `pane` an earlier block created. That makes their
**order load-bearing**: never reorder, add, or drop a code block when
you reshape the prose around it.

## What stays precise

Warm the framing, never the facts. Resolution-order lists, value
tables, exact error strings, format tokens, and class or method
cross-references carry meaning in their exact form — leave them alone.
The friendly voice belongs in the sentences *around* a precise block,
introducing it, not inside it paraphrasing it into vagueness.

## Cross-references

Point the advanced reader at the deep-dive rather than inlining it, and
put the link where their interest peaks — on the phrase that made them
curious ("query tmux directly", "write your own traversal") — not as a
standalone footnote the eye skips. Use the MyST roles listed in the
root `AGENTS.md` (`{meth}`, `{class}`, `{func}`, `{attr}`, `{exc}`,
`{ref}`, `{doc}`, `{term}`). A `{ref}` must match its target's anchor
exactly — anchors mix underscore and hyphen forms across pages
(`context_managers`, `pane-interaction`). `just build-docs` catches a
broken cross-reference; the doctests do not — so build the docs before
you commit.

## A page that does this

`docs/topics/pane_interaction.md` is the worked example: a concept-first
intro that says what a `Pane` *is* and which two methods (`send_keys`,
`capture_pane`) cover most uses before any signature, an explicit "you
can stop after the first two sections" reassurance, sections ordered by
shrinking audience, honest trade-offs (polling is a busy wait; a resize
is a request, not a guarantee), methods named by what they do with
`{meth}` cross-references, and precise capture-flag and format tables
left exact. Read it before reshaping another page.

## Before you commit

- Does the page open with what the feature *is*, or with how to call it?
- Can a reader who needs only the common case stop after the first
paragraph?
- Is anything framed by its tmux flag or format token that should be
named by concept instead?
- Are the advanced and lower-level parts clearly marked opt-in?
- Do the doctests run, and did you leave every code block, table, error
string, and cross-reference exact?
- Did `just build-docs` stay clean — no new warning, no broken
cross-reference?
1 change: 1 addition & 0 deletions docs/CLAUDE.md
3 changes: 3 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,8 @@
html_css_files=["css/custom.css"],
html_extra_path=["manifest.json"],
rediraffe_redirects="redirects.txt",
# AGENTS.md (+ its CLAUDE.md symlink) is agent guidance, not a site
# page; keep Sphinx from treating it as an orphan document.
exclude_patterns=["_build", "AGENTS.md", "CLAUDE.md"],
)
globals().update(conf)
83 changes: 62 additions & 21 deletions docs/topics/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,31 @@

# Architecture

libtmux is a [typed](https://docs.python.org/3/library/typing.html)
abstraction layer for tmux. It builds upon tmux's concept of targets
(`-t`) to direct commands against individual sessions, windows, and panes,
and `FORMATS` — template variables tmux exposes to describe object
When you use libtmux, you work through a hierarchy of typed Python
objects — {class}`~libtmux.server.Server`, {class}`~libtmux.session.Session`,
{class}`~libtmux.window.Window`, and {class}`~libtmux.pane.Pane` — each a
proxy for the tmux entity it represents. You navigate from one to the
next (a server's sessions, a session's windows, a window's panes), and
every method you call turns into a tmux command directed at that exact
object.

You don't need anything on this page to use the API; the objects and
their methods work out of the box. This is reference material for when
you're curious how libtmux tracks those objects, keeps their identities
stable across refreshes, and lays out the code underneath. Skim the
first section and stop whenever you've seen enough.

Under the hood, libtmux is a [typed](https://docs.python.org/3/library/typing.html)
abstraction layer built on two tmux primitives: targets (`-t`), which
direct a command at an individual session, window, or pane, and
`FORMATS`, the template variables tmux exposes to describe each object's
properties.

## Object Hierarchy
## Object hierarchy

libtmux mirrors tmux's object hierarchy as a typed Python ORM:
libtmux mirrors tmux's object hierarchy as a typed Python ORM, so the
parent-child relationships you know from tmux carry over directly into
the objects you hold:

```
Server
Expand All @@ -28,19 +44,25 @@ Server
| {class}`~libtmux.pane.Pane` | None | {class}`~libtmux.window.Window` |
| {class}`~libtmux.client.Client` | None | {class}`~libtmux.server.Server` |

{class}`~libtmux.common.TmuxRelationalObject` acts as the base container
connecting these relationships.
The Session, Window, Pane, and Client classes share a common dataclass
base (`Obj`) defined in {mod}`libtmux.neo`, which fetches each object's
fields from tmux; the parent and child links above are plain properties
on each class.

{class}`~libtmux.Client` is a *view*, not part of the ownership chain:
each attached terminal points at a Session/Window/Pane it is currently
displaying, but is not owned by them. See {ref}`clients` for the view-
vs-identity distinction.
One object breaks the ownership chain: {class}`~libtmux.client.Client` is a
*view*, not a child. Each attached terminal points at the
Session/Window/Pane it is currently displaying, but is not owned by
them — so its view can change the moment a user switches sessions. See
{ref}`clients` for the view-vs-identity distinction.

## Internal Identifiers
## Internal identifiers

tmux assigns unique IDs to sessions, windows, and panes. libtmux uses
these — via {class}`~libtmux.common.TmuxMappingObject` — to track objects
reliably across state refreshes.
When tmux state changes, libtmux needs a way to recognize that the same
window is still the same window. tmux assigns each session, window, and
pane a unique ID for exactly this, and libtmux stores it as a dataclass
attribute on each object (`session_id`, `window_id`, `pane_id`) to track
objects reliably across state refreshes rather than relying on names or
indexes that shift around.

| Object | Prefix | Example |
|--------|--------|---------|
Expand All @@ -49,25 +71,38 @@ reliably across state refreshes.
| {class}`~libtmux.window.Window` | `@` | `@3243` |
| {class}`~libtmux.pane.Pane` | `%` | `%5433` |

## Core Objects
## Core objects

Each level wraps tmux commands and format queries:
These are the five classes you'll actually hold and call methods on.
Each level wraps the tmux commands and format queries for its tier of
the hierarchy:

- {class}`~libtmux.server.Server` — entry point, manages sessions, executes raw tmux commands
- {class}`~libtmux.session.Session` — manages windows within a session
- {class}`~libtmux.window.Window` — manages panes, handles layouts
- {class}`~libtmux.pane.Pane` — terminal instance, sends keys and captures output
- {class}`~libtmux.client.Client` — attached terminal viewing a session, window, and pane

## Data Flow
## Data flow

Every interaction follows the same round-trip: you act on a Python
object, libtmux talks to tmux, and the result comes back as more typed
objects. Reading state and changing it both cost a tmux call, which is
why an object can go stale and why you refresh it rather than trust a
cached value indefinitely.

1. User creates a `Server` (connects to a running tmux server)
2. Queries use tmux format strings ({mod}`libtmux.constants`) to fetch state
3. Results are parsed into typed Python objects
4. Mutations dispatch tmux commands via the `cmd()` method
5. Objects refresh state from tmux on demand

## Module Map
## Module map

The codebase splits along the same hierarchy: one module per tier, plus
shared plumbing. The first block is what you import and use day to day;
the second is lower-level and mostly of interest to contributors or
deeper integrations.

| Module | Role |
|--------|------|
Expand All @@ -77,13 +112,19 @@ Each level wraps tmux commands and format queries:
| {mod}`libtmux.pane` | Pane I/O and capture |
| {mod}`libtmux.client` | Attached-client view and live-attachment lookup |
| {mod}`libtmux.common` | Base classes, command execution |

The remaining modules are advanced — reach for them only when the core
objects don't cover your case:

| Module | Role |
|--------|------|
| {mod}`libtmux.neo` | Modern dataclass-based query interface |
| {mod}`libtmux.constants` | Format string constants |
| {mod}`libtmux.options` | tmux option get/set |
| {mod}`libtmux.hooks` | tmux hook management |
| {mod}`libtmux.exc` | Exception hierarchy |

## Naming Conventions
## Naming conventions

tmux commands use dashes (`new-window`). libtmux replaces these with
underscores (`new_window`) to follow Python naming conventions.
Expand Down
Loading
Loading