# Prismattyc > Prismattyc is a standalone terminal emulator and multiplexer for people and agents. `prismattyc-host` is the desktop app (tabs, split panes, named workspaces called Spaces). `pmux` manages sessions from a terminal or script, and `pmuxd` keeps sessions running when you detach. `pmux mail` and the `pmux-mcp` stdio MCP server let local agent sessions message each other and inspect Spaces. This file is for agents. Full docs: https://prismattyc.com/llms-full.txt. Tool list: https://prismattyc.com/agents.json. Source: https://github.com/moonbase2090/Prismattyc (MIT). ## Prerequisites - Linux x86_64 or ARM64 with glibc 2.28 or newer (Wayland or X11). On Ubuntu: `sudo apt install libfontconfig1 libxkbcommon0 libxkbcommon-x11-0 libegl1`. - macOS, Apple silicon or Intel (universal, signed and notarized). - Windows x86_64 (signed MSI or portable zip). - From source: Rust 1.90 or newer. ## Install Releases: https://github.com/moonbase2090/Prismattyc/releases (checksums in `SHA256SUMS`, `SHA256SUMS-macos`, `SHA256SUMS-windows`). ### macOS Download `Prismattyc-vX.Y.Z-macos-universal.dmg`, open it, and move `Prismattyc.app` to `/Applications` or `~/Applications`. The app links its bundled `pmux` into `/usr/local/bin` when it can, otherwise `~/.local/bin`. ### Linux ```sh mkdir prismattyc-release tar -xzf prismattyc-x86_64-unknown-linux-gnu.tar.gz --strip-components=1 -C prismattyc-release # or the aarch64 archive ./prismattyc-release/install.sh ~/.local/bin/prismattyc-host ``` Add `~/.local/bin` to PATH. The installer adds the pmux agent skill for detected agents; skip with `--no-agent-skills` or `PRISMATTYC_NO_AGENT_SKILLS=1`. ### Windows Run `prismattyc-vX.Y.Z-x86_64-pc-windows-msvc.msi`. It prepends `%LOCALAPPDATA%\Programs\Prismattyc\cmd` to the user PATH, registers `pmuxd` at logon and starts it. Pass `ADDTOPATH=0` to leave PATH unchanged. Portable: extract `prismattyc-vX.Y.Z-x86_64-pc-windows-msvc.zip` and run `bin\prismattyc-host.exe`. ### Build from source ```sh git clone https://github.com/moonbase2090/Prismattyc.git && cd Prismattyc cargo build --workspace --release --locked ./target/release/prismattyc-host ``` Binaries in `target/release/`: `prismattyc-host`, `pmux`, `pmuxd`, `pmux-attach`, `pmux-mcp`, `prismattyc`. ### Update and uninstall `pmux update --check`, `pmux update`, `pmux update --rollback`. `pmux uninstall --dry-run`, `pmux uninstall [--keep-data]`. ## Configuration - Desktop config: `~/.config/prismattyc/config.toml` (override with `PRISMATTYC_CONFIG`). Most settings apply live, e.g. `font_px = 16.0`, `chrome_style = "classic"`, `automatic_update_checks = false`, `install_agent_skills = false`. - `pmux config init [--merge]` writes `[mux]` keys into config.toml. - Socket: `--instance NAME` selects `$XDG_RUNTIME_DIR/prismattyc/pmux-.sock` (default `pmux.sock`); `--socket PATH` overrides. - Mail identity: `--as `, else `$PMUX_AGENT`, else the agent bound to the current pane's session. - MCP: `pmux-mcp` needs an identity (`--as ` or `PMUX_AGENT`; no default). Use `--supervise` for hosts that do not respawn failed stdio servers. Example: `{"mcpServers": {"pmux": {"command": "pmux-mcp", "args": ["--supervise"], "env": {"PMUX_AGENT": "my-agent"}}}}` - Agent skill: `pmux skills install --agent detected [--check] [--force]`. ## Tools ### MCP tools (`pmux-mcp`) Each tool call reconnects to the pmux mail socket for one operation. Watching for mail is not a tool; use `pmux mail watch` from a shell. - `pmux_alias` — Bind a shorthand alias to this process's agent. Params: `name` (required). - `pmux_broadcast` — Send one letter to every bound agent except this agent. Single transaction. Params: `body`, `summary` (required). - `pmux_claim` — Claim this agent's uncommitted letters. Open letters become held; already-held letters are re-listed. Commit or release next. Params: none. Example: `{}` - `pmux_commit` — Commit held letters by id. They are gone for good. Params: `ids` (required). Example: `{"ids": [""]}` - `pmux_inbox` — Peek at mailbox depth without claiming. Returns open and held counts. Params: none. - `pmux_release` — Return held letters to open by id, making them claimable again. Params: `ids` (required). - `pmux_send` — Deliver a letter. to is an agent id (operator-id) or alias. Omitting body sends an empty body. Params: `body`, `summary` (required), `to` (required). Example: `{"to": "reviewer", "summary": "PR ready", "body": "Please review."}` - `pmux_space_attention` — Resolve or snooze an exact attention request returned by Space details. Rechecks ownership and request revision. Leaves mail delivery unchanged. Params: `action` (required), `name` (required), `pane` (required), `revision` (required), `session` (required). - `pmux_space_details` — Inspect a Space: session names, roles, live state, explicit attention requests, separate letter counts, and context links. Does not launch work or claim mail. Params: `name` (required). Example: `{"name": "my-space"}` - `pmux_space_link` — Add a labeled context link to a Space. The target must be an HTTP(S) URL or absolute local path. Does not open it or modify external task state. Params: `label` (required), `name` (required), `target` (required). - `pmux_space_result` — Read retained Space open results. These are historical operation results; use pmux_space_details for current liveness. Params: `name` (required). - `pmux_space_role` — Set the displayed role of a session in its owning Space. Params: `name` (required), `role` (required), `session` (required). - `pmux_template_create` — Create an independent Space from a previewed template. Requires explicit user intent. launch=false creates shells only; launch=true executes the previewed commands. Retry never blindly repeats a launch. Does not open a desktop view. Params: `launch`, `space` (required), `template` (required). - `pmux_template_list` — List saved team templates without launching work. Params: none. - `pmux_template_preview` — Preview the sessions, directories, programs, commands, and conflicts for a new team. Has no process or saved-Space side effects. Params: `space` (required), `template` (required). Example: `{"template": "my-team", "space": "my-space"}` - `pmux_template_save` — Save a reusable team template from a Space definition. Does not create or stop sessions and does not execute commands. Params: `space` (required), `template` (required). - `pmux_tutorial` — Get the pmux collaboration tutorial: identity, live presence, stored mail, intentional pane writes, authorized shell commands, and Space cleanup. Params: none. Example: `{}` - `pmux_who` — List live agent-bound sessions (presence), including this process. Params: none. Example: `{}` ### CLI (`pmux [--instance NAME | --socket PATH] [--session NAME] `) Sessions: - `pmux up [-- PROGRAM ARGS...]` — start the server (default `$SHELL -l`). - `pmux new [--attach|--no-attach] [--headless] [--agent NAME|--no-agent] NAME [-- PROGRAM...]` — create a named session; `--agent` binds a mailbox (default: the session name). Example: `pmux new work --no-attach` - `pmux attach [SESSION] [--watch] [--write TEXT] [--pane ID] [--json|--styled-json] [--read-only] [--fit]` — attach (interactive on a TTY, else a JSON dump). Detach with Ctrl+\ then d. - `pmux ls` — sessions, windows and panes. `pmux whoami [--json]` — this pane's session, id, pane and agent. - `pmux session name NAME [--session KEY]`, `pmux session clear [--all] [--keep NAME]`. - `pmux status`, `pmux doctor [SESSION]`, `pmux render-status [--json]`, `pmux versions`, `pmux clients [SESSION] [--json]`. - `pmux stop [SESSION]`, `pmux kick SESSION`, `pmux detach --other [SESSION]`, `pmux restart [--host|--daemon|--mcp|--all] [--plan]`. - `pmux login ` — start at login and restore the workspace. Panes: - `pmux send PANE TEXT [--enter] [--literal] [--force]` — write keys; refuses a busy pane unless `--force`. - `pmux pane-write PANE --text TEXT [--submit auto|enter|none] [--json]` — intentional text to exactly one pane. - `pmux save-buffer PANE|SESSION FILE [--history]` (`-` = stdout), `pmux pipe-pane PANE|SESSION (FILE | --exec CMD)`. - `pmux rename-pane PANE|SESSION [TITLE]`, `pmux break-pane PANE`, `pmux join-pane PANE --to TAB [-h|-v]`. - `pmux arrange SESSION main-vertical|main-horizontal|even-h|even-v|grid`, `pmux sync on|off|status [SESSION]`. - `pmux status-set TEXT [--pane ID]` | `--clear`, `pmux attention SESSION [MESSAGE]`. Tabs and Spaces: - `pmux attach --all` — open the desktop host with sessions as panes in tabs. - `pmux space open [NAME] [--add|--new-window]`, `pmux space ...`. - `pmux layout `. Mail (`pmux mail [--as ] `): - `send --summary S [--body B]` (body from stdin if omitted), `claim [--json|--ids]`, `commit ...`, `release ...`, `inbox`, `watch [--timeout SECS]` (default 300; exit 1 on timeout), `alias `, `who`, `broadcast --summary S [--body B]`, `status`. Other: `pmux tutorial [--play]`, `pmux skills install ...`, `pmux config init [--merge]`, `pmux update [--check|--rollback|--pre]`, `pmux uninstall [--dry-run] [--keep-data]`, `pmux completions `, `pmux --version`. Manual: `man pmux`. ## Usage workflows 1. Start and use a session: `pmux up`, `pmux new work --no-attach`, `pmux attach work`, `pmux ls`. 2. Agent onboarding: `pmux tutorial` (or MCP `pmux_tutorial`), then `pmux whoami --json`. 3. Message another agent: `pmux mail send --summary "..." --body "..."`; the recipient runs `pmux mail claim --json`, then `pmux mail commit ` (or `release`). Watcher loop: `while pmux mail watch; do pmux mail claim --json; done`. 4. Inspect a Space from MCP: `pmux_space_details`, then `pmux_space_attention` to resolve or snooze a request. 5. Start a team from a template: `pmux_template_list`, `pmux_template_preview`, then `pmux_template_create` only with explicit user intent (`launch=false` creates shells only). 6. Script a pane: `pmux pane-write PANE --text "make test" --submit enter`, then `pmux save-buffer PANE -`. ## Troubleshooting - `pmux-mcp: no identity`: pass `--as ` or set `PMUX_AGENT`. - `pmux` not found or the wrong one runs: put `~/.local/bin` (or `/usr/local/bin` on macOS) first on PATH. - `send` refused: the pane is busy; use `pmux pane-write` or `--force` deliberately. - `pmux mail watch` exits 1: it timed out with no mail, which is not a mux failure. - Session problems: `pmux doctor [SESSION]`, `pmux status`, `pmux versions`. - Linux host fails to open windows: `pmux attach --all` needs `WAYLAND_DISPLAY` or `DISPLAY`. - SmartScreen warning on Windows: choose More info, then Run anyway. ## Links - [Full agent docs](https://prismattyc.com/llms-full.txt) - [agents.json](https://prismattyc.com/agents.json) - [Source](https://github.com/moonbase2090/Prismattyc) - [Releases](https://github.com/moonbase2090/Prismattyc/releases) - [pmux command reference](https://github.com/moonbase2090/Prismattyc/blob/main/docs/mux-cli.md) - [Configuration](https://github.com/moonbase2090/Prismattyc/blob/main/docs/config.md) - [Update and uninstall](https://prismattyc.com/docs/update-and-uninstall.html) # Appendix: docs/mux-cli.md (from the Prismattyc repo) ## pmux — the mux front door One binary to remember for the Phase 2B mux. It wraps `pmuxd`, `pmux-attach`, and the control protocol control plane behind tmux-shaped verbs. ### Glossary | Noun | Meaning | |------|---------| | session | The pmuxd mailbox and agent unit. List with `pmux ls`. | | tab | A host Window. Sessions in one tab appear as panes. The inner pmuxd Window is what `pmux-attach` shows. | | pane | A host PaneRuntime, or a pmuxd Pane in `pmux ls`. | | space | An exclusive owner of sessions, persisted at `spaces/NAME.json`. One session can belong to only one space. | | spaces rail | The strip of space chips, at the bottom by default. Use `+` to create a fresh space. Live session names appear below the Space name. | | attach-tabs | The host cache `{stem}.attach-tabs.json` next to the mux socket. Never hand-edit it. | ### Agent skill Install the pmux Agent Skill in user-level agent directories with: ```bash ``` skills directory. The `all` selection installs every supported location. `--agent detected` installs only for agents whose config directory already exists or whose CLI is on `PATH`. It avoids creating config directories for agents that are not installed. `--check` reports destinations without changing them. Existing copies are left in place unless `--force` is supplied; feature flag: ```bash pmux skills install --agent detected --check ``` The macOS app installs this skill once per app version for detected agents. Set `install_agent_skills = false` in the host config or `PRISMATTYC_NO_AGENT_SKILLS=1` to opt out. The Linux archive installer also installs it after the binaries. To skip it, pass `--no-agent-skills`, set `PRISMATTYC_NO_AGENT_SKILLS=1`, or set the top-level `install_agent_skills = false` in the host config. The skill lives in [`skills/pmux/SKILL.md`](../skills/pmux/SKILL.md) and is also included in the embedded tutorial. Its description lets compatible agents load it automatically for matching pane, mail, and coordination tasks. Commands: ```bash pmux up # start the server detached ($SHELL -l) pmux up -- /bin/bash -l # ... with an explicit program pmux attach # attach; TTY = interactive, else JSON dump pmux attach work # attach to a named session pmux attach work --json # force the JSON ReadPane dump pmux attach --read-only # view only; never take the input lease pmux attach --all # open prismattyc-host; sessions as panes in tabs pmux attach --session-id 3 --space-id SPACE # only if SPACE still owns session 3; never starts a server (remote attach) pmux ls # sessions → windows (tabs) → panes (liveness, pids, leases) pmux whoami # this pane: session name, opaque id, pane, agent pmux skills install pmux skills install --agent detected --check pmux attention work "needs input" # send OSC 9 to the session pane pmux attention work # default message: needs your attention pmux new work # create a named session (TTY: attach) pmux new work --no-attach # create only; print the attach hint pmux doctor # pane shell, child pid, lease, VIEWER vs NESTED attach pmux doctor work # same, one session pmux render-status --json # live host render guards, frame age, and pane state pmux kick work # SIGTERM nested attach, else viewers; session stays pmux clients [work] [--json] # attach clients: pid, viewer/nested, session, pane pmux detach --other [work] # SIGTERM every attach but your own; sessions stay pmux status # socket liveness + server pid + log path pmux stop # ShutdownServer over the socket; TERM/KILL fallback pmux --session work stop # destroy one named session; server stays up pmux stop work # same as --session work stop pmux session clear [--all] [--keep NAME] # stop sessions except the caller pmux restart # restart safe components; defer active PTY owners pmux restart --plan # inspect restart impact pmux versions # installed and running component versions pmux update # verified GitHub release; macOS keeps the previous signed app for rollback pmux update --source --host # host only (macOS: also rebuild Prismattyc.app) pmux update --source --mux # mux / attach / server / pmux-mcp pmux uninstall --dry-run # show the removal plan; macOS keeps unrelated pmux files pmux uninstall # remove Prismattyc files and any owned macOS pmux link pmux completions bash # emit a completion script (also zsh, fish) pmux config init [--merge] # write [mux] keys into config.toml pmux space create NAME # one fresh shell pane in a new space pmux space save [NAME] [SESSION...] # save owned sessions (live cwd + agent) pmux space open [NAME] [--add] [--replace] [--no-attach] [--new-window] [--no-run] [--tty] # restore; switch is default pmux space attach [NAME] [--session S] # attach a space session in this TTY pmux space ls # list saved spaces pmux space catalog # running spaces as versioned JSON (read-only) pmux space rm [NAME...] [--all] # delete space files (`delete` is an alias) pmux space add NAME [--session S] [--tab TITLE] # fresh or unassigned session pmux space move NAME --session S # transfer session ownership pmux space move NAME --session-id ID # select an exact numeric session ID pmux space move NAME --pane P # transfer one live pane pmux space remove NAME --session S # release ownership; keep the session alive pmux space clear [--keep NAME] # delete every space file except kept names pmux layout save [SESSION] [--name NAME] # persist the session tree to a JSON file pmux layout save space [NAME] [SESSION...] # alias of `pmux space save` pmux layout apply NAME [--session TARGET] [--agent] pmux layout apply space [NAME] [--add] [--replace] [--no-attach] [--new-window] [--no-run] [--tty] # alias of `pmux space open` pmux layout apply --all [--replace] # restore every layouts/*.json file pmux layout ls # list saved layouts and spaces pmux sync on [SESSION] # fan typed input to every pane in the session pmux sync off [SESSION] # stop fanning input pmux sync status [SESSION] pmux status-set TEXT # set this pane's attach chrome status pmux status-set --clear # clear it pmux send PANE TEXT [--enter] [--literal] [--force] # write keys; no 750 ms lease pmux save-buffer PANE|SESSION FILE [--history] # write screen text (`-` = stdout) pmux pipe-pane PANE|SESSION (FILE | --exec CMD) # stream Output bytes until Ctrl-C or pane exit pmux rename-pane PANE|SESSION [TITLE...] # set a pane title; no TITLE clears pmux rename-pane --session KEY [TITLE...] # KEY = name or id; one pane; no pane id pmux break-pane PANE # move a pane into its own window pmux join-pane PANE --to TAB [-h|-v] # move a pane onto another window pmux arrange SESSION KIND # retile: main-vertical|main-horizontal|even-h|even-v|grid ``` ### Name a session and its mailbox A host pane has one session and one agent mailbox. Tabs group panes. A Space groups tabs. New sessions use one name for the session and agent ID. Suggested names use the Space name and the first unused number, such as `work-1`. 1. Create a tab or split a pane. 2. Accept the suggested name or type a name. 3. Select **Create** or press Enter. The popup creates the session only after you accept. Press Escape to cancel. Use lowercase letters, numbers, and single hyphens. Names must have 2 to 64 characters. A name must not start or end with a hyphen. Prismattyc rejects names already used by a session or forwarding address. The Space `+` popup first asks for the Space name. It then asks for the first session name. The default tab strip stays visible with one pane. `tab_strip = "multi"` shows the strip only with multiple tabs. Right-click a pane and select **Rename session** to change its visible name. Case, spaces, and other characters stay in that name. The mailbox address is the lowercase form, so `Hello World` is mailed as `hello-world`. Right-click a single-pane tab to open the same popup. A tab with multiple panes has a separate tab label. From inside an existing session, run: ```bash pmux session name astra-spaces pmux whoami pmux mail inbox ``` Use `pmux session name NAME --session KEY` to select a session by name or numeric ID. `pmux session rename` is an alias for `session name`. This also binds a session whose agent is unset. Mail uses the derived address, not the visible name. The session ID, pane ID, and running process stay the same. Saved Space references use the new name. Queued and claimed letters retain their IDs and state. Previous mailbox addresses forward to the new address, including after a daemon restart. Those addresses cannot be assigned to another mailbox. ```bash pmux space create work --session-name work-1 --no-attach pmux space add work --name reviewer pmux session suggest --space work ``` Omit `--session-name` or `--name` to use the suggested name automatically. After a restart, press Enter in an exited pane to reopen its saved session. Use `pmux session reopen NAME --space SPACE` to do the same from the CLI. The session keeps its saved name, mailbox, and Space owner. Its saved commands run as they do for `pmux space open`: `space_open_runs_commands` decides which run, and `--no-run` skips them all. `--no-claim` reopens the session without changing the Space file or the session's owner. `pmux mail alias NAME` creates a shorthand. It does not bind a session. ### Work in isolated spaces Each space owns its sessions. One space can contain many sessions. A session can belong to only one space. Select `+` to name a new space and open one fresh shell pane in the initiating window. The previous space keeps running. Use Save to update the current space. ```bash pmux space create work pmux space add work pmux space add work --session unassigned-session pmux space open work --new-window pmux space move work --session SESSION pmux space move work --session-id 15 pmux space move work --pane PANE pmux space move work --pane PANE --to-session DESTINATION_SESSION ``` Use `--session-id ID` when you have a numeric session ID, including in scripts. This selector never matches a session name. A missing ID fails even if a session has that number as its name. Select exactly one of `--session`, `--session-id`, or `--pane`. Use `--to-session` only with `--pane`. The host uses the exact ID for **Move session to space**. Add rejects a session owned by another space. Move transfers ownership; it does not share a session. A session move carries every pane. A pane move carries only that pane into a destination-owned session. Both keep the existing PTYs and processes. Moving the last session leaves an empty space. Click a chip to switch this window. Opening another space in a second window does not change the first window. Two windows may view the same space. `--add` cannot combine different spaces in one view. Space chips show the names of their live sessions in tab order. Full names remain in the chip menu when the chip must clip text. Set `space_rail_pane_names = false` to hide the session names. See [Spaces rail configuration](config.md). Right-click a pane for separate Move pane and Move session actions. Use Shift+right-click to send the event to the application. Use Menu or Shift+F10 on a focused rail chip to open its menu. Press Escape to close it. Host helpers pass `--view-path PATH` on create, open, and save. This absolute path selects one window layout. The helper does not write the global cache or wait for another window's ACK. A new window receives its own seeded layout through `PMUX_VIEW_PATH`. Version-2 files contain a stable space ID. `pmuxd` reports each session's owner in `SessionSnapshot.space_id`. Host writer connections bind to that owner. After a move, the daemon rejects stale input and resize requests. Version-1 files migrate only when their session references are unambiguous. Originals remain in `spaces/legacy-backups`. If several files reference the same session, open reports the conflict. It does not silently share or clone the running process. Create a fresh space to start independent work while you resolve old definitions. #### Rename a Space Run `pmux space rename OLD NEW`. The command retains the Space identity and live sessions. It serializes the file change with saves and moves. An existing destination name is rejected. Other windows follow the stable Space identity when its display name changes. ### Intentional pane writes Use `pmux pane-write PANE --text TEXT --json` to send literal text directly to an agent pane. The default `--submit auto` pastes the text and uses the foreground agent's submit sequence. `--stdin` reads the body from stdin. Use `--submit enter` or `--submit none` for explicit terminal input. The command targets one pane even when sync-input is on. It checks the current child PID and refuses busy or dirty input without taking a lease. Receipts distinguish complete and partial queueing. They do not claim that the recipient accepted the message. Read the [pane-write protocol](pane-write-protocol.md) for wire examples, error handling, and submission modes. ### Send keys `pmux send PANE TEXT` writes UTF-8 bytes to a pane through `WritePane`. It does not hold the controller lease for 750 ms the way `pmux attach --write` does. - No controller and a clean input ledger: write with no acquire (lease-free). - A live controller or a dirty input ledger (unsubmitted bytes): exit 1. Pass `--force` to take over, write, and release. `--force` does not hand the lease back, and taking the lease from a live attach also revokes that attach's rich viewer grant. An interactive attach stays up: it paints `[held]` and re-acquires on the next key after the force write releases. - The refusal is enforced by the server too: a lease-free `WritePane` into a pane whose ledger is dirty fails with `InputDirty`, even when the client's snapshot looked clean a moment earlier. If a controller appears between the snapshot and the write, `pmux send` re-checks once and exits 1 naming `--force` when the pane is still held. - `--enter` appends CR. - Default `TEXT` interprets `\n`, `\r`, `\t`, `\e`, and `\\`. `--literal` sends the characters as typed. - Extra words after `PANE` join with a space. `--` lets `TEXT` start with `-`. - Writes longer than 64 KiB are split into 64 KiB chunks. - Sync-input (if on) fans the same bytes to sibling panes. - Exit 1 if the pane is missing or its child is dead. `pmux send` is not `pmux mail send`. Mail is the mailbox. This verb is keys. ### Save buffer and pipe pane `pmux save-buffer PANE|SESSION FILE [--history]` writes the pane's visible screen text (ReadPane) to FILE. FILE `-` writes stdout. `--history` prepends scrollback by walking `ReadPaneStyled` view offsets. `pmux pipe-pane PANE|SESSION (FILE | --exec CMD)` opens `SubscribePane` from the current pane-log sequence and writes Output event bytes byte-exact to FILE (append) or to CMD's stdin. `--exec` runs `sh -c CMD`. A non-zero `--exec` exit prints a note to stderr and does not fail `pipe-pane` (tmux parity). Flush errors still fail the command. Resize and other events are ignored. The stream stops on Ctrl-C or when the pane exits. A SESSION name works when the session owns one pane. ### Break pane and join pane `pmux break-pane PANE` moves that pane into a new window in the same session (`CreateWindow` + `MovePane`). If the pane is already the only pane in its window, the command prints that fact and exits 0. `pmux join-pane PANE --to TAB [-h|-v]` moves the pane onto window `TAB` (a window id from `pmux ls` in the same session). `-h` splits beside the target pane (default). `-v` splits above or below it. `--help` prints help; `-h` is the axis, not help. Cross-session `--to` exits 1 and does not move the pane. The host palette actions `break_pane` and `join_pane` are unbound. `break_pane` extracts the focused host pane into a new tab. `join_pane` joins it into the previously active tab (else the next tab). Both use the existing move-pane path and rewrite the attach-tabs cache. ### Arrange `pmux arrange SESSION KIND` rebuilds the first window of `SESSION`. It does not spawn or close panes. | KIND | Layout | | --- | --- | | `even-h` | Even columns. | | `even-v` | Even rows. | | `grid` | Two even rows. | | `main-vertical` | Focused pane on the left. Other panes stacked on the right. | | `main-horizontal` | Focused pane on top. Other panes in a row below. | `C-\ a` in `pmux-attach` cycles these kinds in table order (`even-h`, `even-v`, `grid`, `main-vertical`, `main-horizontal`). Host palette actions `preset_main_vertical` and `preset_main_horizontal` retile the active tab the same way. They ship unbound. ### Mailbox verbs `pmux mail` speaks the Mail* protocol on the mux socket: ```bash pmux mail send operator-id --summary "hi" [--body "..."] # omitted --body: piped stdin, else empty pmux mail claim [--json | --ids] # fetch your letters (open become held) pmux mail commit ... # acknowledge held letters (gone for good) pmux mail release ... # return held letters to open pmux mail inbox # open/held depth pmux mail watch [--timeout 300] # exit 0 when mail arrives; exit 1 on timeout (not a mux failure); default 300s pmux mail who # bound agents (presence) pmux mail alias # bind a shorthand to your agent pmux mail broadcast --summary "..." # every bound agent except you pmux mail status # socket, reachability, bound agents (no identity) ``` `commit` and `release` take only letter ids. A flag is never treated as an id: `pmux mail commit --help` (or `-h`) prints this usage and exits 0 without touching the mailbox, and any other `-`-prefixed argument (e.g. a typo like `--comit`) is rejected with a non-zero exit that names the flag and points at the fix, the same way `claim` and `watch` handle unknown flags. Identity resolves in order: `--as `, the agent bound to this live pane, then `$PMUX_AGENT` outside a known pane. There is no other default — a CLI that guesses who you are sends mail as the wrong agent. Watcher recipe: `while pmux mail watch; do pmux mail claim --json; done`. Exit 1 means the wait timed out (default 300 seconds). The mux is not broken. Start the loop again if you still want to wait. The manual doorbell form `pmux mail SESSION [--pane ID]` still parses. The in-process doorbell makes it unnecessary for mux-native mail. It injects `PMUX_MAIL` even when the recipient pane is focused (the occupant is the agent). Two gates defer the write: an unsubmitted composer line (`dirty_input`, still set after lease release until a CR or 60 s with no input and no output), and recent typing (`last_input_at_ms` inside 10 s). Busy PTY output (≥ 8 KiB in 3 s) also defers. Those injects retry every 3 s until a successful write, peek, or claim. A letter is injected once. Empty inboxes do not ring again. The doorbell rings only an agent CLI. When the pane's foreground process is a plain shell (no known agent CLI in the terminal foreground; a background job does not count), `InjectMail` answers `deferred_no_agent`: nothing is typed, the `mail` cell stays lit, and the nudge retries once an agent starts (PT-94). ### Agent attention Send an attention signal through the mux control plane. The server stores the latest message for the pane and emits a `PaneAttention` event. `pmux-attach` re-emits the event as OSC 9 to its interactive parent terminal. ```bash pmux attention work "needs input" pmux attention work ``` The second command uses `needs your attention`. The command uses the additive `RaiseAttention` control request. It does not acquire a controller lease or write to the agent PTY. The host shows a coral tab badge and plays the attention cue by default. It sends an OS notification when the tab is not selected or the window is not focused. It limits OS notifications to one per pane every five seconds. The host accepts these producer forms: - `OSC 9;MESSAGE` from a pane. The mux validates and relays the message. - `OSC 777;notify;TITLE;BODY`. It stores `TITLE: BODY`. - Complete `OSC 99` messages without the `m=1` more-chunk marker. Producer status: | Producer | Status | |---|---| | `pmux attention` | Verified through `RaiseAttention` and `PaneAttention`. | | Pane OSC 9 relay | Verified through the live mux drain and interactive attach path. | | Producer A (external agent integration) | Unverified. Do not claim support for a specific agent until its emitted bytes are captured. | The host rejects invalid UTF-8, control characters, and messages larger than 512 bytes. BEL remains a bell. It does not create an attention signal. ### Layout verbs `pmux layout` saves and restores window/pane trees. It does not change the control-plane protocol. ```bash pmux layout save [SESSION] [--name NAME] pmux layout save space [NAME] [SESSION...] [--name NAME] pmux layout apply NAME [--session TARGET] [--agent] pmux layout apply space [NAME] [--add] [--replace] [--no-attach] [--new-window] [--no-run] pmux layout apply --all [--replace] pmux layout ls ``` #### Spaces: save the workspace, open it again The two commands most people need: ```bash pmux space create NAME # fresh space with one shell pane pmux space save NAME # save the current space pmux space open # restore them and open prismattyc-host on all of them pmux space attach NAME # attach the space's active session in this TTY pmux space ls # list saved spaces pmux space rm NAME # delete spaces/NAME.json (`delete` is an alias) pmux space rm --all # list then delete every space file pmux space add NAME --session S [--tab TITLE] # add an unassigned session as a new tab pmux space remove NAME --session S # release ownership; keep the session alive pmux space clear # delete every space file pmux space clear --keep NAME # delete every space file except NAME (repeatable) ``` `pmux space catalog` prints one JSON line for remote Space discovery over SSH (issue #24). It is read-only: it loads saved Space files and one daemon snapshot, and never creates sessions or replays saved commands. A Space is listed under `spaces` only when the daemon reports live sessions owned by its stable id. Every other saved Space is listed under `unavailable` with a `reason`: `missing_identity` (legacy file; `pmux space open` assigns an id), `duplicate_identity`, `no_live_sessions`, `unreadable`, or `out_of_limits`. ```json {"version":1,"producer":"0.2.19", "spaces":[{"id":"<32 hex>","name":"work", "sessions":[{"id":3,"name":"work-1"}],"active_session":3}], "unavailable":[{"name":"old","reason":"missing_identity"}]} ``` Readers reject other versions, unknown fields, control characters in names, and responses over 1 MiB. Names are display data; attach by id. The command exits non-zero with nothing on stdout when the daemon is not running. On a cold host launch, the registered host asks **Restore last space?** if its saved attach-tabs cache contains sessions. Choose **Restore** to restore the saved tabs and focus. Exited panes remain available; press Enter in an exited pane to reopen it. Choose **Start fresh** or press Escape to keep the fresh window without restoring panes. The saved cache and space files stay intact. The host asks once on its first window. Later windows and explicit attach targets do not show this prompt. Restore resolves saved session names and checks Space ownership before it attaches. It does not reuse a numeric session ID from a previous daemon. If an older cache has stale IDs, restore uses the saved Space layout. If the Space was deleted or replaced, restore reports the error and keeps the fresh window. You can still create a Space with `+`. Restore does not start saved commands. A stopped session shows **Enter to reopen**. Enter starts the daemon if needed and recreates only that saved session. Creating or opening a Space also starts a missing daemon. The Space name dialog accepts Ctrl+A and Ctrl+V. These keys stay in the dialog. If the first session name is already in use, the session dialog shows the error and keeps your entry. Choose another name or cancel. The current Space remains usable after a rejected name. `pmux layout save space` and `pmux layout apply space` are aliases of `save` and `open`. Create independent work with `pmux space create prismattyc-work`. Reopen it with `pmux space open prismattyc-work`. Delete a named file with `pmux space rm NAME`. `--all` prints the list then removes every space file. Unknown names exit nonzero and print the path that was missing. `pmux space clear` deletes every space file and prints `removed NAME` per file. `--keep NAME` (repeatable) preserves that file. Clear prints nothing when the store is empty. There is no prompt. Live sessions stay. `pmux layout rm NAME` does the same for `layouts/NAME.json`. The attach-tabs cache is not changed. `pmux space add NAME` creates another fresh session. Pass `--session S` to add a live unassigned session. `--tab TITLE` sets the host tab title. Use `pmux space move NAME --session S` for a session already owned by another space. Use `--pane P` to transfer one pane. Saving an existing Space replaces its membership with the sessions you save. Omitted sessions leave the Space. Their processes and mail remain available. They do not return when you reopen the Space. `pmux space remove NAME --session S` releases ownership and keeps the session running. To do this in the host, right-click the session tab or pane and select **Remove session from space**. This also closes its views in the current window. It works for an exited session. It keeps the session and mailbox. Select **Remove and kill session…** to remove the membership and terminate the session's processes. Confirm the action with Enter. This also works for an exited session. The CLI equivalent is `pmux space remove NAME --session S --kill`. Mail remains available. Empty spaces are allowed. The pane menu has separate pane and session move actions. The rail's `+` creates a new space with one fresh shell; it does not save the current arrangement. A space also records the host's tab arrangement: which sessions share a tab as panes, in order, with the tab titles. Arrange the window first (Ctrl+Shift+Alt+PageUp / PageDown moves the focused pane to the previous / next tab; Ctrl+Shift+R renames a tab), then save. `space open` puts the recreated sessions back into the same tabs. A space saved from a host that was never arranged opens one tab per session. A pane whose shell runs `pmux new NAME` or `pmux attach` inside the host is an attach pane the host did not spawn. Under `PRISMATTYC_HOST=1`, those commands write the attach-tabs cache and do not exec `pmux-attach`. The host opens a log-replica pane. If a leftover `bash` → `pmux-attach` child still appears, the host adopts it within a second and promotes that pane to a log replica. `PRISMATTYC_ATTACH_PTY=1` keeps the nested child. Then the mark drops when that attach exits (PT-210, PT-306). `save` writes JSON under `$XDG_DATA_HOME/prismattyc/layouts`, or `$HOME/.local/share/prismattyc/layouts` when `XDG_DATA_HOME` is unset. The file name is `NAME.json`. `NAME` defaults to the session name. `pmux` rejects an empty name and a name that contains `/` or `..`. A single-session file stores spawn-time cwd. A space file stores the live child cwd when `/proc//cwd` (or the macOS equivalent) is readable, else spawn-time cwd. Optional `program` on a leaf is the pane's root spawn binary. `layout apply` does not re-run it. A space file also stores optional `command` on each leaf: the pane's foreground process at save time (the shell's active child, via procinfo). The field is absent when the foreground is the shell itself. `space open` may type that command into the restored pane. `apply` does not attach. `apply NAME` creates the session when `TARGET` does not exist. It adds the saved windows when `TARGET` exists. `TARGET` defaults to `NAME`. It does not bind an agent unless you pass `--agent` (the target session name). `space save` also copies the host cache's active tab index, focused session name, and each tab's split tree into the space file. `space open` writes them back so the opened host selects that tab and pane, restores the saved axes and ratios, and unzooms. A tab with no saved tree (a single pane, or a file from an older build) still opens as a left-to-right row. Missing or unknown focus falls back to the first tab and its first pane. With no `SESSION` list and a non-empty attach-tabs cache, `space save` saves the sessions the live window shows (cache tab order). Detached but still-alive sessions are not saved. stdout includes `saved N session(s) from the live window`. With no explicit view and no usable cache, Save considers non-default sessions and then enforces exclusive ownership. An explicit `SESSION` list is unchanged. Save rejects sessions owned by another space. It never creates two space names for the same running session. A saved session omitted by the view cache receives its own tab when you explicitly select it for Save. `space save [NAME]` writes the definition under `$XDG_DATA_HOME/prismattyc/spaces`, or `$HOME/.local/share/prismattyc/spaces`. The name defaults to `default`. `--name NAME` also works. Open reuses a live session only when its owner matches the requested space. Missing version-2 sessions retain their saved optional agent binding; an unbound shell remains unbound. `--replace` adds saved windows. Reopening a running session never replays its saved command. Switch removes foreign-space panes from this view while their processes keep running. It does not keep a foreign caller pane. `--new-window` opens an independent view. An explicit empty view stays empty when saved; it never falls back to all daemon sessions. For newly restored sessions, `space open` may run saved leaf `command` values under the configured launch policy. It prints the list, then writes `` plus Enter (CR) to the pane (`WritePane`; lease acquired then released) and files under `spaces/`. A pane that already has a foreground process is not re-run. `[mux] space_open_runs_commands` selects who runs: `agents` (default: sessions that stored an agent id), `all`, or `none`. `--no-run` skips every command. `$PMUX_SPACE_OPEN_RUNS_COMMANDS` overrides the file. `layout apply NAME` still does not re-run programs or commands. If a `prismattyc-host` has registered `{stem}.host.pid` beside the socket, `space open` writes the cache and that host regroups. stdout starts with `reused host pid `. This reuse still runs when Linux has no `WAYLAND_DISPLAY` or `DISPLAY`. `--tty` skips reuse and prints the TTY recipe instead. `--no-attach` still writes the cache and reuses a live host; it only skips spawning. With no live host, `--no-attach` prints no host line. With no live host and no `--no-attach`, it opens a detached host as `pmux attach --all` does (`opened host pid `). `--new-window` always opens another host. It runs before the no-display TTY recipe (`--tty` cannot combine with `--new-window`). It does not overwrite a live pid file and does not write the cache while another host is registered. Chip and menu opens run in click order within each host window. The next helper starts after the previous helper exits and its requested cache update applies. If an open fails, the host reports the error and tries the next queued request. The current chip changes only when the host applies the layout. While an open is pending, the host suspends layout and selection writes to the cache. The save action also waits. A failed apply keeps writes suspended until a later cache update applies. Open a Space again to retry. These rules preserve version 1 Space files and the Restore / Start fresh choice. They do not serialize commands from separate CLI processes. An open result separates the host view from its sessions. The toast reports reused sessions and says that their live layouts are retained. The last result remains in each window's `last_space_open` field in `pmux render-status --json` after the toast expires. The result includes its per-window sequence, open A session can retain three live panes when its saved layout has two. Session results compare daemon snapshots before and after the helper. A matching live session ID means reused. A new ID means a new session. A missing session or a session with no live PTY child is unavailable. A failed snapshot means unknown. These observations do not prove that a saved command or agent is ready; `launch` remains `not observed`. Separate CLI processes can change sessions between these observations. A failed or timed-out apply does not select the requested Space. A partial host apply clears the current label. The result keeps the helper error and any observed session changes. A helper owned by the registered window has a 30-second deadline. On that deadline, the host stops and reaps the helper before the next queued request starts. Sessions that it created remain in `pmuxd`. The legacy unregistered-window open remains synchronous. A new-window request reports that this window has no view confirmation. The host polls the cache once a second, regroups (MovePane; attach missing sessions; never close a live session), raises the window, and touches `{stem}.host.ack`. A host that does not ack within 2 s still exits 0 with `host pid N did not reload; cache written`. When no live host is registered and Linux has no `WAYLAND_DISPLAY` or `DISPLAY`, or you pass `--tty`, `space open` restores the sessions and prints one `pmux attach SESSION` line per session in tab order. The space's `focused_session` is marked `(active)`. When stdin and stdout are a TTY and `--no-attach` is absent, it then attaches that active session in this TTY. Off a TTY it prints the recipe only and exits 0. `pmux space attach [NAME] [--session S]` creates missing sessions like `open`, then attaches in this TTY (default: `focused_session`, else the first). It exits 1 off a TTY. `pmux space` with no verb, or an unknown verb, prints usage and exits 2. `apply --all` applies every `layouts/*.json` file in name order. It implies `--agent`. It uses the same skip / `--replace` rule as `apply space`. Inner TUI state is out of scope. The last tree is not auto-saved. #### Session clear `pmux session clear` stops every live session except the one the command runs in (`pmux whoami`). When you are not inside a pane, no session is exempt. `--all` also stops the caller, last. `--keep NAME` (repeatable) preserves a session by name. The command prints `stopped NAME (id N)` for each stopped session. It exits 0 when nothing was stopped. Stopping uses the same path as `pmux stop NAME`. Mailbox letters stay. Saved spaces stay. `pmux ls` lists sessions. There is no `pmux session ls`. `pmux space clear` does not stop live sessions. #### Clients and detach-other `pmux clients` lists every live `pmux-attach` on the socket: pid, `viewer` (host-side) or `nested` (running inside a pane), the session and pane it targets, and `(you)` on the attach this shell runs under. `pmux clients SESSION` narrows to one session; `--json` prints the same rows as JSON. `pmux detach --other [SESSION]` (alias `-a`) sends TERM to every listed attach except your own and the registered host window's panes (marked `(host)`; they live under `{stem}.host.pid`), then prints what it signalled and what it kept. Host panes attached through the pane log (the default) are not `pmux-attach` processes and never appear here. Sessions stay; this is `tmux detach -a`. `pmux kick` remains the targeted form (nested first, else viewers). #### Where things live The attach-tabs cache includes a `session_names` map and the saved `space_id`. The host uses these fields to resolve a restart safely. The cache remains compatible with older files that omit these fields. | Thing | Who writes it | Where | | --- | --- | --- | | Session | `pmuxd` (`pmux new`) | In the mux. List with `pmux ls`. | | Tab arrangement | `prismattyc-host` | `{stem}.attach-tabs.json` next to the mux socket. Host cache. Never hand-edit it. Safe to delete. `pmux space open` overwrites it from the space file. The registered host is the single writer (`{stem}.host.pid`). | | Space | you, or `pmux space save` | Default daemon: `$XDG_DATA_HOME/prismattyc/spaces/NAME.json` (else `~/.local/share/prismattyc/spaces/NAME.json`). A non-default `--instance` or `--socket` uses `$XDG_DATA_HOME/prismattyc/spaces/instances/sha256-/NAME.json` (with the same HOME fallback). This is the workspace file. It records cwd, agent, and the pane foreground `command`. `space open` reads commands only from this daemon's directory. | | Spaces rail | `prismattyc-host` reads it | A view over the spaces directory, polled once a second. Rename and delete act on `spaces/NAME.json`; `+` runs `pmux space save`. The current chip is the space named in `{stem}.attach-tabs.json` (`space`), which `pmux space open` writes and the host preserves. | | Pane log | `pmuxd` | `$XDG_DATA_HOME/prismattyc/pane-log-.json` for a socket in the instance dir (`$XDG_RUNTIME_DIR/prismattyc/pmux.sock` or `pmux-NAME.sock`; `/tmp/prismattyc-/` fallback). `pmux.sock` uses instance `default`. Snapshot plus incremental event transactions (format 2; reads legacy format 1). An incomplete final transaction is ignored after a process crash. Older daemons cannot read format 2. The snapshot is `emulator-state-v1` (`serde_json` of `EmulatorStateV1`). A mid-sequence parser or pending graphics upload omits the snapshot and keeps the tail. A snapshot that fails import is dropped; restore replays the tail. Restore matches session name and pane index inside that session. A new session name gets nothing. A corrupt, version-mismatch, or `replay-v0` file starts empty with one reset line. `PMUX_PANE_LOG=off` disables persist. Any other non-empty `PMUX_PANE_LOG` is an explicit path. An explicit `--socket` outside the instance dir does not write the data dir. PTY children do not resurrect. | #### Host attach panes are log replicas `prismattyc-host` attaches a mux session by subscribing to that pane's event log, not by running `pmux attach --session-id ID` as a nested PTY child. Each attached pane opens two control connections: one parked in `SubscribePane`, feeding `Output` and `Resize` into the pane's own emulator, and one that sends keys with `WritePane` under the controller lease and host geometry with `Resize`. The lease is taken on the first key and released after 750 ms idle, as `pmux attach` does. NAME`, and interactive `pmux-attach --session NAME` under `PRISMATTYC_HOST=1` write `{stem}.attach-tabs.json` so the host opens a log replica. They do not leave a `bash` → `pmux-attach` child as the spawn, and it promotes a leftover nested attach to a replica. Consequences you can see: - The host scrollbar, bottom-right inverse ` N/M ` chip, `Ctrl+Shift+Up/Down`, and find work over the attached session's real scrollback. - `pmux ls` and `pmux doctor` report no NESTED attach for host panes. Each attached pane shows as two registered clients instead. - The replica never answers DSR/CPR/DA. Only the pane's PTY owner does. `PRISMATTYC_ATTACH_PTY=1` restores the old nested-child path. The host also falls back to it, with a message on stderr, when the subscription cannot be opened. A nested attach under `PRISMATTYC_HOST=1` then paints the host scroll chrome: an inverse bottom-right chip and a right-edge scrollbar, not the left `[scroll N/M]` line. `pmux attach SESSION` in a terminal outside the host is unchanged. #### Session-ended placeholder When a host pane that attaches a session loses it, the pane stays. It shows the session name, the exit reason, and `Enter to reopen`. The layout does not collapse. A zoomed pane unzooms first. Local shells still collapse. Enter respawns `pmux attach --session-id` when `pmux ls` still lists that session. When the session is gone, the host runs `pmux new --no-attach` from the last space `space open` passed as `PMUX_SPACE` (else `default`). It uses the saved agent and the first-leaf cwd. If that space has no row for the session, the pane prints `session NAME is gone` and stays. `Ctrl+Shift+W` closes a placeholder like a normal pane: an intermediate slot is removed; the last placeholder of a non-last tab closes that tab; the last placeholder of the last tab quits the host. The mailbox lives in `pmuxd`. After reopen, a letter waiting for that A nested `pmux-attach` TTY that sees `ChildExited` prints `session ended: NAME (pane N child exited)`. ### Sync input `pmux sync` fans typed keys and paste to every pane in a window. This is the tmux synchronize-panes model. The flag is per-window. Mouse reports stay per-pane. ```bash pmux sync on [SESSION] pmux sync off [SESSION] pmux sync status [SESSION] ``` In `pmux-attach`, `C-\ s` toggles sync for the attached pane's window. `pmux-attach` shows `[sync]` as a top-right overlay while the flag is on. The server writes the same bytes to every pane in the window. Only the focused pane needs the caller's controller lease. `prismattyc-host` in-process panes are out of scope. ### Status line A guest sets short status text for its pane. `pmux-attach` shows it in attach chrome: on the scroll row while you page history, and as a top-right overlay in live mode. A bad payload is rejected. It is not painted. ```bash pmux status-set TEXT [--pane ID] pmux status-set --clear [--pane ID] ``` Identity is `$PRISMATTYC_PANE_ID` unless you pass `--pane`. The command fails with "not inside a pmux pane" when that variable is unset and `--pane` is omitted. The text is at most 64 bytes and must not contain control characters. `pmux status` is the daemon liveness verb. It does not set pane status. ### Pane titles `pmux rename-pane PANE|SESSION [TITLE...]` sets a pane title (PT-128). Words join with a space; no `TITLE` clears it. A non-empty rename pins the title on the server (PT-230). An empty rename clears the pin. While pinned, a child's OSC 0/2 does not change the host pane title. A plain terminal attach (`pmux space attach` over SSH) still shows the child's OSC title; the pin holds in the host. `PANE` is a pane id; a session name works when the session owns one pane and is rejected with the pane ids otherwise. `--session KEY` names the session by name or opaque id (an all-digit positional is always a pane id). Do not pass a pane id with `--session`. A session with more than one pane still needs a pane id. Any client may rename; no lease is taken. - `pmux ls` prints the title after the pane state: `— title "build"`. - `pmux-attach` shows the title in the status slot of the attach chrome when no guest status is set (status wins). - `space save` stores the title on the pane's leaf (`"title"`). `space open` restores it onto new panes, and onto a live skipped session only when that pane is unpinned. - Same limits as status: at most 64 bytes, no control characters. In the host, a single-pane tab paints the pane title in place of the tab title while one is set, and hovering a pane handle on a multi-pane tab shows that pane's title in the title row (the attach relays the title as its OSC 2 window title; a local shell's own title shows the same way; a guest status set with `status-set` wins over the title). The palette action `rename_pane` (unbound) edits the focused pane's title in the tab chip and, for a live attach pane, runs `pmux rename-pane --session ID TITLE`. Right-click a pane handle or a tab with one pane to open its context menu. Select **Rename session** for an attached session or **Rename title** for a local shell. Right-click a tab with multiple panes to rename the tab. Press Enter to save the name or Escape to cancel. ### Shared attach Two `pmux-attach` clients can view the same pane at once. Both start as observers. The first client that types takes the write lease. The holder releases it after 750 ms of idle input. Other clients still render. They do not queue keystrokes. They see a toast `input held by client — wait or C-\ d` and a `[held]` chip. `pmux attach --read-only` (and `pmux-attach --read-only`) never takes the lease. Typed keys are dropped. Scroll and copy still work. The chrome shows `[ro]`. The pane geometry follows the most recently active client (tmux `window-size latest`). A TTY attach fits the pane to its terminal on attach and on SIGWINCH. A client that reports the same size does not change the pane. Input in the host makes the host the latest client again; the host re-fits on its next geometry pass. Set `[mux] remote_size = "host"` (or `PMUX_REMOTE_SIZE=host`) to keep the desktop size except when you force a fit. A non-holder viewer with a smaller terminal still paints a viewport when the policy is `host`: it clips to its rows and cols, scrolls so cell (never past the last row). The attach chrome shows `pane 83×57 > 120×40 · C-\ z fit`. A larger terminal anchors the pane at the top left and fills the rest with chrome background. `C-\ z` or `pmux attach --fit` resizes the pane to this terminal even when another viewer is attached. The host reflows on GeometryChanged. When a TTY client disconnects, the mux restores the last host geometry so the desktop window is not left small. Detach with `C-\ d`. The pane child keeps running. Disconnect does not destroy the session. ### SSH from macOS `pmux attach SESSION` is a TTY client. It needs a live Unix socket, not a compositor. That is the SSH path. The attach fits the pane to this terminal. Detach with `C-\ d` to restore the last host size. The host shows a `remote WxH` chip while a remote client owns the size. To restore a saved space over SSH: ```bash pmux space open NAME --tty # or, after the sessions already exist: pmux space attach NAME pmux space attach NAME --session fable-pc ``` `space open --tty` (and `space open` with no Linux display) prints one `pmux attach SESSION` line per session in tab order. The active session ends with ` # active` so the line stays pasteable. In a TTY it then attaches that session. `pmux space attach` does not write the attach-tabs cache and does not regroup a live host. Inside `pmux-attach`, `C-\ n` / `C-\ p` cycle the current space's sessions (`PMUX_SPACE` or `--space`; without one, every non-default session in `pmux ls` order). `--space` and `$PMUX_SPACE` are a hint: attach keeps that name only when `spaces/NAME.json` exists and lists this session. Otherwise it uses the first space file that lists the session. When no file lists it, those chords cycle every non-default session. `C-\ 1`–`C-\ 9` jump by index and are reserved (not forwarded to the child). Space-attach chrome (`--space` or `$PMUX_SPACE`) shows `space NAME · session X (i/n)` after the MailAttention letter cell, with one blank cell between the letter and the text. When that hinted attach no longer has a space file for the session, the chrome shows `session X` with no space prefix. A plain attach has no identity row. Nested attach inside `prismattyc-host` (`PRISMATTYC_HOST=1`) does not paint that overlay. The space rail and the tab title already name the space and the session. Mail letter, status chips, and the viewport hint stay. On Linux, `pmux attach --all` execs `prismattyc-host`. That needs `WAYLAND_DISPLAY` or `DISPLAY`. An SSH session from a Mac has neither. Do not forward Wayland or X11 to open the Linux host. If `status` or `ls` reports no server over SSH but the desktop mux is running, the CLI may be looking at `/tmp/prismattyc-/pmux.sock`. `status` then names live `pmux*.sock` sockets under `/run/user/` and `/run/user//prismattyc/`. Point `PMUX_SOCKET` at that path, or export `XDG_RUNTIME_DIR=/run/user/$(id -u)`. Full RCA and later windowed-remote plan: [Use sessions over SSH](ssh.md). ### Instances instead of socket paths `--instance NAME` (or `$PMUX_INSTANCE`, or `[mux] instance` in the config file) names the socket — `$XDG_RUNTIME_DIR/prismattyc/pmux.sock` for `default`, else `pmux-.sock` in that directory. The pidfile and log sit next to the socket (`.pid` / `.log`). `--socket PATH` (or `$PMUX_SOCKET`, or `[mux] socket`) remains the expert escape hatch and overrides the instance. Precedence is CLI, then `PMUX_*`, then the file. A CLI `--instance` also ignores `$PMUX_SOCKET` and `[mux] socket`, because every pmux pane exports its own server's socket; only `--socket` overrides an instance name. ### Guest discovery environment Each mux-owned child is stamped at `execve` with: | Variable | Value | |---|---| | `PRISMATTYC_PANE_ID` | Decimal pane id. Never reused for the Domain lifetime. | | `PMUX_SOCKET` | Absolute path of this server's control socket. | | `PMUX_AGENT` | Bound agent id of the pane's session. Absent when the session has no `--agent` binding. | | `PMUX_TUTORIAL_PACK` | In-repo tutorial name (`prismattyc-tutorial-pack`). Always stamped. Run `pmux tutorial` to read it. | The same tutorial ships in the binary. Run `pmux tutorial`. The embedded text is canonical. `pmux tutorial --play` runs the shared walkthrough catalog as text captions. It uses the same step IDs, expectations, and progress file as the host (`walkthrough.json` under `$XDG_DATA_HOME/prismattyc` or `~/.local/share/prismattyc`). `--level ID` starts at that level. `--reset` deletes the progress file first. On a TTY the command prints `Level N/M · step i/n — `, then the hint. `mux_event` steps wait for a real pmuxd control event. Other steps complete on Enter (`press Enter when done`). `s` skips. `q` quits. With no daemon, `mux_event` steps also complete on Enter. The boss step (`boss.reproduce-space`) polls a daemon Snapshot once a second and runs `boss_matches`. A match completes the step. Enter prints `not yet: `. `s` still skips. With no daemon, Enter completes with the usual note. Non-TTY stdin prints the current level's captions and exits 0. The client never mutates mux state to make a step pass. `PRISMATTYC_SESSION_ID` is not stamped. `MovePane` can change session without respawn, so a session key would go stale. Inherited or caller-supplied values of that name are stripped. `pmux mail` resolves identity in this order: `--as`, the live pane's session binding, then `$PMUX_AGENT` outside a known pane. `pmux new NAME` binds `NAME` as the agent by default, so the variable is The server overwrites any caller-supplied pane and socket values. Split starts a new child with a new pane id. Move keeps the same process, so pane id and socket stay correct. A later respawn would re-stamp the same pane id. Host in-process PTYs do not receive these keys. `PMUX_SOCKET` here is the same name the CLI uses to choose a socket. A guest that runs `pmux` talks to the mux that spawned it. Agents that scrub the environment can fall back to pid-in-tree matching. This stamp is discovery only. ### Safety properties - **Live sockets are never replaced or unlinked here.** Stale-leftover handling stays in the server's own bind logic (`probe_socket_liveness` → Missing/Live/Stale/Foreign). `up` on a live socket says "already running"; `up`/`attach`/`stop` all refuse a foreign path and leave it intact. - **Signals only go to verified pids.** `stop` signals a pid only after an exact-argv `/proc//cmdline` check (argv[0] is `pmuxd` and the socket path is its own argument) — substring hits like wrappers or prefix paths never match, and the check is repeated before any KILL escalation so a pid recycled inside the grace window is never killed. A lost pidfile falls back to a `/proc` scan with the same verification plus, when readable, proof that the pid holds the bound socket's inode. - **`up` proves the bind is ours.** Success is reported (and the pidfile written) only when the freshly spawned child holds the listening socket's inode — a concurrent `up`/`attach` race resolves to "already running" instead of overwriting the pidfile with the bind loser's pid. - The server is started in its own session (`setsid`), so closing the launching terminal never HUPs it. ### Relationship to the underlying binaries `pmuxd` and `pmux-attach` are still usable directly. `attach` verbs delegate to `pmux-attach` (`--watch`, `--write`, `--pane`, `--json` pass through). A TTY stdin enters the interactive client (raw mode, `WritePane`, resize on local winsize change, detach `C-\ d`). PageUp or `C-\ [` enters copy mode and scrolls the server-owned history (`[scroll N/M copy]`) when that history is non-empty. reach the child. An idle attach re-acquires the controller lease for that forward, then drops it after 750ms. Esc/q returns to the live tail. #### Copy mode Use copy mode to select text from server history. Copy mode is local to `pmux-attach`. It never forwards keyboard input to the child. | Key | Action | | --- | --- | | PageUp, PageDown, Home, End | Move through history. | | `/` | Search forward in the visible history window. Type the query; Enter keeps it; Esc cancels the prompt and stays in copy mode. | | `?` | Search backward in the visible history window. | | `n` / `N` | Next / previous match. Wrap inside the current viewport. | | Space or `v` | Start a selection. | | Space or `v` again | Yank the selection and return to the live tail. | | `y` or Enter | Yank the selection and return to the live tail. If no selection is active, yank the current line. Enter while a search prompt is open commits the query instead. | | Esc or `q` | Leave copy mode without yanking. Esc while a search prompt is open cancels the prompt only. | Yank sends the selected text to the outer terminal with OSC 52. Your terminal must support OSC 52 for the clipboard update to take effect. Wheel scrolling uses the ordinary scroll mode and does not start a selection. Search matches highlight on the painted grid: the current match is inverse; the others are underlined. The status line shows the query and `i/n` rank. Search does not pan to history outside the current viewport. The client disables outer-terminal autowrap so a full-width row keeps its last column. A non-TTY stdin or `--json` keeps the JSON `ReadPane` dump. `--styled-json` instead prints the bounded `ReadPaneStyled` projection, including workspace rows, rich styles, and semantic clipboard state, for local proof/debug tooling; it does not change the stable `--json` shape. `attach --all` execs `prismattyc-host` with live mux sessions grouped into tabbed panes. Each of those panes subscribes to its pane event log and runs its own emulator; it no longer spawns a nested `pmux attach --session-id ID` child (see "Host attach panes are log replicas"). Tab grouping comes from the attach-tabs cache next to the socket. With no cache, each session gets its own tab. The leftover `default` session from `up` is skipped when other sessions exist. On Linux, `attach --all` without `WAYLAND_DISPLAY` or `DISPLAY` does not exec `prismattyc-host`. It prints the TTY recipe and the resolved socket, then exits. Use `pmux attach SESSION` over SSH. Do not forward Wayland/X11 to run the Linux host. See [Use sessions over SSH](ssh.md). `Ctrl+Shift+X` detaches the current session tab. The last tab exits the host. Closing the window also detaches. Server-owned sessions stay. `ls`/`new`/`status`/`stop`/`doctor`/`kick` speak the control plane directly (`doctor`/`kick` also scan `/proc` for `pmux-attach`). `ls` prints each window (tab) with its title and size, then each pane, plus viewer and nested attach pids when present. `new` then attaches in the invoking terminal by default. TTY attach paints a viewport toast `session NAME is attached` for 3s (also on the child's alternate screen). Interactive `pmux-attach` paints from `SubscribePane` (event-driven). It does not poll `ReadPaneStyled` every 50 ms while the pane is idle. Keys, leases, and resize stay on the writer connection. Set `PRISMATTYC_ATTACH_POLL=1` to restore the 50 ms ReadPaneStyled loop (stderr note). `--json` / `--styled-json --watch` still poll. The chip uses the host focus-border color (`PRISMATTYC_FOCUS_BORDER` / `focus_border`, default blue). Opt out with `--no-attach`, `$PMUX_ATTACH_ON_NEW=0` / `$PMUX_ATTACH_ON_NEW=0`, or `[mux] attach_on_new = false`. A non-TTY `new` still creates and prints the attach hint unless `--attach` is passed. `doctor` classifies each attach as VIEWER (host-side) or NESTED (inside the pane child tree). `kick SESSION` SIGTERMs nested attaches if any, otherwise viewers; it never calls DestroySession and never signals the pane child. Create/destroy/move/switch window verbs are on the control plane (control protocol); the umbrella CLI still creates sessions via `new` and lists whatever windows the server has. `scripts/prismattyc-mux-daemon.sh` is now a thin compat wrapper over this binary (`start` → `up`). The wrapper accepts `PMUX_BIN` and `PMUX_SOCKET`. It derives the pidfile and log from the socket path (`.pid` and `.log`). The default socket is `$XDG_RUNTIME_DIR/prismattyc/pmux.sock`; with `XDG_RUNTIME_DIR` unset, the binary falls back to `/tmp/prismattyc-/pmux.sock`. Tier 2 (`ShutdownServer`) is implemented: `stop` sends the verb on a Live socket (registered `client_id`, no pane lease) and waits for the server to flush `ShutdownAccepted`, drop pane runtimes, unlink its socket, and exit 0. The verified-pid TERM→KILL path remains the fallback when the socket is not Live, the verb fails or times out, or the pid survives the grace window. The CLI still never unlinks a socket itself. Tier 3 (completions + `[mux]` config) is implemented: `pmux completions ` prints a script to stdout. `[mux]` lives in the same file as the host config (`docs/config.md`); the host accepts the table and ignores it. ### Inspect render guards Run `pmux render-status` to inspect the registered windowed host. The command reads a local snapshot. It does not trigger a repaint. Pass `--json` to print compact JSON for scripts. The default output is indented for interactive inspection. Use `--socket PATH` before the command to select another mux. The host publishes a snapshot at most once per second. The command rejects snapshots older than five seconds and snapshots from a different host PID. `status_seq` increases with each published snapshot. An all-occluded host may wait without publishing. Its snapshot reports `occluded: true` for each window and returns `snapshot_stale: true` while the registered host remains alive. A host that predates this command must restart on a supporting version before the command can return data. Each window reports `current_panes` at the snapshot time. Each pane includes its alternate-screen state, scroll offset, stored image count, and rich layout flags. `last_raster` reports the latest raster attempt. Its timestamp can be older than the snapshot when the window is idle. Check `present_succeeded` before treating that attempt as a displayed frame. `last_raster.guards` lists every active raster guard for that frame. Published `bell-toasts` follows the live chip list when linger ends between rasters. The receipt in `last_space_open` outlives that chip. Some guards force a full repaint. `layout-transition` forces a full repaint when pane slots change. `chrome-geometry` records a known chip or badge change; the compositor may repaint only its bounded boxes. `guard_mask` is the numeric form of that list. The existing `full_repaint_reason` keeps its priority order for the OSD and render bench. `raster_mode` is `partial` when the frame retained unaffected pixels and `full` when the host repainted the window. Render timing logs also include `full_repaint_guards` as a comma-separated list. A dash means no guard was active. The host promotes a frame to full raster when tab-strip text, spaces-rail chips, scrollbar geometry, or a text selection changes. These surfaces are outside the bounded damage boxes. The `osd` guard means that the render timer OSD is visible. OSD output forces full repaint for the frame. Each window's `pump` object reports the latest main-thread pump duration and the maximum observed in the current one-second window. `slowest_phase` names the most expensive phase in the latest pump. The `phases` map includes each phase's `last_us` and `max_1s_us`; socket round trips and synchronous subprocess waits include per-pump counts and time, plus one-second totals. The pump data is application-wide and is repeated under each window. `last_raster.timing.parse_us` accumulates PTY parsing across drains until the next completed paint. On macOS, `last_raster.present` reports the time spent writing tile images and committing them, dirty tile count, and bytes copied. `changed_tiles` is `null` unless `render_timer` is `osd`, `log`, or `both`; in those modes it counts dirty tiles whose pixels differ from the preceding frame. With timing off, the host does not allocate the comparison buffer. These fields describe the latest raster attempt, which can be older than the current pump snapshot when a window is idle. The `alt-screen` guard means a pane entered or left its alternate screen since the previous frame. A pane that stays on its alternate screen does not set this guard. The `scrollback` guard means the scroll offset changed, or output changed while the pane showed scrollback. Use `current_panes` to distinguish these events from the pane's current view state. ### Inspect a Space team Right-click a Space chip and select **Team details**. With keyboard focus on the rail, press Menu or Shift+F10. Select **Team details** with the arrow keys. Home and End select the first and last items. Escape closes details. Use `space_rail_focus` in the command palette to focus the rail. Details show session names, roles, process state, explicit requests for input, and separate letter counts. Select a running session and choose **View session** to focus it. A stopped saved session offers **Reopen session**. Reopen starts its saved process; it does not recover an agent conversation. **Last open result** retains the view result and session outcomes after the toast expires. **Retry opening this Space** reuses live sessions without repeating saved commands. Desktop Space switches use `--no-run`. Use these commands for the same details in a terminal: ```bash pmux space details project --json pmux space result project --json pmux space role project builder Reviewer pmux space link project Repository /absolute/path/to/repository pmux attention builder "Review the patch" pmux space attention project --json pmux space attention snooze project builder PANE REQUEST pmux space attention resolve project builder PANE REQUEST ``` Copy `PANE` and `REQUEST` from the details response. The daemon checks the session owner and request revision before changing attention. Snooze lasts 10 minutes. Repeated identical requests coalesce. Resolve, snooze, and inspection do not claim or commit mail. Typing into a pane clears its explicit attention request, as it clears the existing attention hint. When the daemon disconnects, details label retained request reasons as stale. They do not count as current requests for input. Roles follow session rename and moves. Context links belong to the Space. Links may use HTTP(S) or an absolute local path. Opening a link uses your system handler. Prismattyc does not change external task status. ### Reuse a team template 1. Save the source Space after arranging its sessions and tabs. 2. Open **Team details** and select **Save team template**. 3. Select **Team templates** and choose a saved template. 4. Enter a new Space name. 5. Read the generated session names, directories, programs, commands, and conflicts. 6. Select **Create shells only** or **Create and launch**. Preview does not create sessions or execute recipes. It also works without a daemon. Offline previews label the live checks that creation must repeat. Each new team gets its own sessions, PTYs, ownership, and mailbox names. A repeat of the same creation request reuses the recorded result. If a launch was interrupted at an uncertain point, creation reports that uncertainty and refuses to repeat the command. Inspect the session before starting further work. ```bash pmux space template save review-team --space project pmux space template ls pmux space template preview review-team next-project pmux space template create review-team next-project --no-attach # To execute the previewed recipes, explicitly select --launch instead. pmux space template create review-team another-project --launch --no-attach ``` `pmux-mcp` exposes `pmux_space_details`, `pmux_space_result`, `pmux_space_role`, `pmux_space_link`, `pmux_space_attention`, and `pmux_template_save`, `pmux_template_list`, `pmux_template_preview`, `pmux_template_create`. These tools use the same CLI operations and socket. Template creation through MCP prepares the team without switching a desktop view. Its `launch` parameter defaults to false. ### Adjust Spaces preferences Open the command palette and select `space_settings` in **Spaces**. You can also right-click a Space chip and select **Spaces settings…**. Right-click an empty part of the rail to open the same settings. - Select **Rail: bottom**, **Rail: left**, **Rail: top**, or **Rail: right**. The rail moves immediately. The preference applies to all windows. - Select **Autosave** to turn it on or off. It is on by default. Adding or closing a tab, terminal, or agent session, changing a split or its ratio, using Arrange, dragging tabs or panes, or moving a session between spaces saves the arrangement after a short idle, about one second. The rail shows **Saving…**, then **Saved**. Saving does not run saved commands. `[spaces] autosave = false` opts out. When that key is absent, `space_autosave` applies. When both are absent, autosave stays on. - Select **Startup: ask**, **Startup: restore**, or **Startup: fresh**. The default is **ask**. Restore reconnects running sessions. Stopped sessions stay stopped until you explicitly reopen them. The current Space chip shows its save state. **Saving…** means a change is waiting out the short idle. **Saved** means the file matches the live arrangement. **Unsaved** means autosave is off and the arrangement differs from the file. **Save failed** requires a retry with **Save current space**. Autosave stops retrying after a failure. **Save current space** still writes immediately. Normal terminal output and focus changes do not trigger layout saves. Autosave waits while a space is opening or being restored. Crowded chips show a shortened session list and a **+N more** count. Use **Team details** to inspect the full session list. Select **…** on a crowded rail to open the searchable Space picker. Keyboard navigation keeps the focused chip visible on every edge. ### Move a nested session to a Space The pane context menu moves the terminal that you see. If you run `pmux new NAME` or a nested `pmux-attach` inside a managed pane, the move picker shows the nested session name and pane ID. **Move session to space** moves that session. **Move pane to space** moves its focused pane. The parent shell stays in its original Space. The nested viewer detaches after the move. The moved terminal process keeps running. If the target changes while the picker is open, the move is cancelled. Open the picker again to select the current terminal. After upgrading from 0.1.314 or earlier, detach and reattach an existing nested viewer once. Older viewers do not report their current pane. The host refuses the move until it can identify the visible terminal. You do not need to restart `pmuxd`. ### Undo a session removal or move After **Remove session from space** or a Space move, right-click a Space chip and select **Undo last removal or move**. You can also run `undo_space_change` from the command palette. Undo restores the previous membership without restarting processes. It applies to the last recorded action in that window. It refuses to replace newer Space changes or act on a session from a restarted daemon. **Remove and kill session…** cannot be undone. For a scripted operation, retain an undo receipt: ```bash pmux space remove project --session builder --undo-file /absolute/path/remove.json pmux space undo /absolute/path/remove.json pmux space move review --session builder --undo-file /absolute/path/move.json pmux space undo /absolute/path/move.json ``` ### Update and restart See [Update and restart Prismattyc](update-and-restart.md) for release checks, rollback on all platforms, coordinated restart, and individual component controls. The macOS app menu also offers update checks and rollback. ### Inspect agent messages Select **agent_messages** in the command palette. The view lists pending mail and the latest intentional input queue receipt for each live pane. Press Enter to focus the exact pane. Navigation rechecks the pane and child process. It leaves mail unclaimed. Queue receipts never confirm execution. The latest receipt stays until the pane is removed or the daemon restarts. # Appendix: docs/config.md (from the Prismattyc repo) ## Prismattyc host config file Optional TOML config for the **windowed host** (`prismattyc-host`), hot-reloaded while the host is running. The classic nested host (`prismattyc`) does not read it. ### Location 1. `$PRISMATTYC_CONFIG` (explicit path override) 2. `$XDG_CONFIG_HOME/prismattyc/config.toml` 3. `~/.config/prismattyc/config.toml` On first run, `prismattyc-host` writes the full commented template when the file does not exist. After that, the file is the source you edit. An invalid file is ignored with a stderr message — at startup and on every reload — so a half-saved or broken config can never take down a running host. `prismattyc-host --write-config [PATH]` prints the template (`PATH` omitted or `-`) or writes it. `--write-config --merge` keeps user values and comments and appends missing keys. `pmux config init [--merge]` writes or updates the `[mux]` section. ### Precedence CLI flags and `PRISMATTYC_*` env vars always win over the file — at startup **and** across hot reloads. A value pinned on the command line (`--focus-border`, `--panes`, `--no-splash`) or by env (`PRISMATTYC_FOCUS_BORDER`, `PRISMATTYC_HOST_FONT`, `PRISMATTYC_HOST_FONT_FALLBACK`, `PRISMATTYC_NO_SPLASH`) never gets overwritten by a config edit. `splash = false` is the durable host opt-out; the flag and env still hide the splash when `splash = true`. The top-level `install_agent_skills = false` opts out of the macOS app's once-per-version pmux Agent Skill install and the Linux archive installer's automatic skill install; `PRISMATTYC_NO_AGENT_SKILLS=1` also opts out and wins over the config value. Automatic signed-release checks run on app launch and daily by default. Set the top-level `automatic_update_checks = false` or turn them off in the app menu to opt out. The app menu's manual check works regardless of this setting. `async_file_writes` enables the host's file-writer thread. It defaults to `false` and takes effect at startup, so restart the host after changing it. `snapshot_client` stays off unless the file sets it to true. The host reads that key at startup only; a later edit does not start or stop the cache. `macos_present` selects the macOS window presenter at startup. It defaults to `"tiles"`; `"iosurface"` enables the three-buffer IOSurface path. Unknown values warn and use `"tiles"`. Other platforms ignore this key. The classic `prismattyc` binary does not read this file. `chrome_style` defaults to `graphite`. Leave that key commented, or omit it, to follow the app default. `chrome_style = "classic"` keeps the previous chrome and applies live. `theme` defaults to `prismattyc` (follow the system appearance). Leave that key commented, or omit it, to follow the app default. ### Keys When `render_timer` is `osd`, `log`, or `both`, the macOS host compares dirty tiles with the previous frame and reports how many changed. With the timer off, it does not allocate the comparison buffer. The `osd` and `both` modes force full CPU repaints to redraw the timing panel. The panel reports `repaint=osd` for those frames, and `blit_sum` remains zero because scroll blits require partial rasterization. This does not switch the selected presenter. Use `render_timer = "log"` to measure partial repaints without the panel; each log entry names the actual `present_backend`. `pty_drain_budget_ms` caps PTY parsing in each event-loop pump. It defaults to 8 ms, accepts 1–1000 ms, and hot-reloads; remaining output is drained on a later event-loop turn. ```toml # Prismattyc host config. Every key is at its default. # Edit in place. CLI flags and PRISMATTYC_* env vars still win. # -- appearance / theme -- # Named theme: built-in slug, display name, sibling themes/ file, or absolute TOML path. Omit this key to follow the app default (prismattyc). theme slug or absolute path. # theme = "prismattyc" # Render timings and counters. off|osd|log|both. render_timer = "off" # macOS present backend; startup only, ignored on other platforms, unknown values warn and use tiles. tiles|iosurface. macos_present = "tiles" # Log every render frame when render_timer includes log; use for benches only. true|false. render_timer_log_every_frame = false # Maximum PTY parsing time per event-loop pump; hot-reloaded. 1-1000 ms. pty_drain_budget_ms = 8 # Show the launch splash on bare launches. true|false. splash = true # Start at login and restore workspace; existing saved workspaces default on, new installs off. true|false. # start_at_login = false # Animate the launch splash word art. true|false. splash_animation = true # Skip focus-change animation; the focus ring appears instantly. true|false. reduced_motion = false # Tab strip visibility: auto and always show one tab; multi needs two tabs. auto|always|multi. tab_strip = "auto" # Host chrome look. Omit this key to follow the app default (graphite). classic|graphite. # chrome_style = "graphite" # Graphite bar background: omit to follow the theme, or graphite, harbor, moss, or plum (Sand on light themes). graphite|harbor|moss|plum. # bar_color = "graphite" # Chrome arrangement: bars, or the sidebar tree. Classic honors sidebar too. bars|sidebar. # layout = "bars" # Expanded Spaces sidebar width in pixels. Names ellipsize; the width does not follow them. 200-2000. sidebar_width_px = 256 # Collapse the Spaces sidebar to the icon strip. true|false. sidebar_collapsed = false # Show agent harness glyphs on sidebar pane rows. true|false. sidebar_harness_icons = true # Multi-pane title row: focused pane OSC title, or handle hover only. focused|hover. pane_titles = "focused" # Immediate hover blend for interactive strip, rail, and scrollbar chrome. 0.0-0.3. hover_blend = 0.1 # -- file writes -- # Write render status, attach cache, and heartbeat files on a worker thread; startup only. true|false. async_file_writes = false # -- input -- # Open links on plain clicks; Cmd/Ctrl-click always remains available. plain|modifier. link_click = "plain" # Hand pastes to the pane writer thread so large pastes never pause the window. true | false. async_paste = false # -- agent integration -- # Install the pmux Agent Skill for detected agents once per app version. true|false. install_agent_skills = true # -- updates -- # Check for signed app releases on launch and daily; enabled by default. true|false. automatic_update_checks = true # -- focus border -- # Focus border color. coral amber yellow green blue violet ink, or 0-6. focus_border = "blue" # Focus-change animation. "none" | "light-cycle". focus_border_animation = "none" # Light-cycle sweep duration in ms. 50-5000. focus_border_animation_ms = 280 # Draw the bright vehicle box at the sweep head. true|false. focus_border_animation_head = true # -- font -- # Primary font path; falls back to the built-in chain if unset or unreadable. absolute path. # font = "/usr/share/fonts/TTF/JetBrainsMonoNerdFont-Regular.ttf" # Extra fallback faces, tried after the built-in chain. array of absolute paths. # font_fallback = ["/usr/share/fonts/TTF/SymbolsNerdFont-Regular.ttf"] # Cell size in px before display scaling. 6-72. font_px = 15.0 # Host-only OpenType ligatures for eligible terminal-grid text. true|false. font_ligatures = false # OpenType feature tags; prefix with '-' to disable. four ASCII alphanumeric characters or spaces. font_features = ["calt", "liga"] # -- keys -- # Enable additional standard macOS Command-key shortcuts; Cmd+Q and Cmd+N are already available. true|false. macos_shortcuts = false # -- layout -- # Initial pane count; startup only. 1-8. panes = 1 # Window edge to pane chrome, physical pixels. 0-128. window_padding_px = 3 # Space between pane chrome rectangles; used with 2+ panes. 0-128. pane_gap_px = 3 # Pane chrome to terminal cells and tab content, physical pixels. 0-128. pane_padding_px = 5 # Edge that shows the saved-spaces rail; off hides it. "bottom" | "left" | "top" | "right" | "off". space_rail = "bottom" # Enable drag and Shift+arrow reordering for saved spaces. true | false. space_reorder = false # Keep one pmuxd snapshot connection for periodic polls. Startup only. true | false. snapshot_client = false # Restore and re-stroke only border rings that change. true | false. selective_border_rings = false # Save changed Space arrangements after a short idle. true | false. space_autosave = true # Choose naming prompts, automatic sessions, or blank terminals. "ask" | "auto" | "blank". session_naming = "ask" # Startup choice; restore reconnects live sessions without launching stopped ones. "ask" | "restore" | "fresh". space_startup = "ask" # Fixed width of left and right Space rails; drag the edge to resize. 8-60. space_rail_width_cols = 18 # Recreate blank terminal tabs, split layouts, and directories with fresh shells. true | false. restore_blank_terminals = false # Keep full-screen TUI scrollback reachable in host scroll and selection. true | false. alt_screen_scrollback = false # Widest space chip in cells (chips fit their labels); 0 = 28. 0 or 6-40. space_rail_chip_cols = 0 # -- bells and attention -- # Flash the window on BEL (~120ms invert). true|false. visual_bell = true # Use a 120ms pane perimeter instead of the window flash and visible BEL toast; requires visual_bell. true|false. pane_visual_bell = false # Play the bundled Zen bell on BEL. true|false. audible_bell = true # Show a toast on the pane that rang BEL. true|false. bell_toaster = true # Bell toast linger in ms. 500-60000. bell_toaster_ms = 10000 # Show 'Moving tab NAME → target' while a tab or pane is dragged. true|false. drag_toaster = true # Status toasts: all, errors only, or off; hidden ones stay in Recent messages. all|errors|off. toasts = "all" # OS notification on BEL while the window is unfocused. true|false. os_notify_bell = false # Play the attention cue on OSC 9 / 777 / 99. true|false. attention_sound = true # Draw a coral attention badge on the tab. true|false. attention_badge = true # OS notification when the attention pane is not selected or the window is unfocused. true|false. os_notify_attention = true # Play bundled walkthrough narration; missing clips stay silent. true|false. walkthrough_audio = true # Narration profile for regenerating clips (generation only). narration profile. walkthrough_voice = "default" # -- background -- # Window background PNG; absolute path, PNG only. absolute path. # background_image = "/usr/share/backgrounds/example.png" # How much of the image shows (0 = flat theme bg, 1 = image). 0.0-1.0. background_opacity = 0.35 # Box-blur radius in pixels. 0-64. background_blur_px = 0 # Focused/zoomed pane surface opacity; try 0.6-0.8. 0.0-1.0. pane_opacity_active = 1.0 # Host overlay surface opacity; try 0.6-0.8 (remove to follow pane_opacity_active). 0.0-1.0. overlay_opacity = 1.0 # Every other pane surface opacity; try 0.6-0.8. 0.0-1.0. pane_opacity_inactive = 1.0 # Window ground opacity; text and explicit cell backgrounds stay opaque. Hot reload works on macOS; other platforms may need a restart below 1.0. 0.0-1.0. window_opacity = 1.0 # Tab strip, Space rail, and footer opacity; defaults to window_opacity. 0.0-1.0. chrome_opacity = 1.0 # Blur behind translucent window grounds; use window_opacity below 1.0. Hot reload works on macOS; no-op where unavailable. true|false. window_blur = false # -- theme overrides -- # Recolour the named theme without copying a theme file. Uncomment a key # to override it; values are #RRGGBB. Shown at the prismattyc-default # values. Hot-reloaded with the rest of this file; the theme picker keeps them. [theme_overrides] # Terminal text. #RRGGBB. # default_fg = "#d9dde4" # Terminal ground. #RRGGBB. # default_bg = "#181b21" # Chrome text: tabs, rails, footer. #RRGGBB. # chrome_fg = "#e6e9ee" # Chrome ground. #RRGGBB. # chrome_bg = "#15181d" # Active tab chip fill; follows the focus colour when unset. #RRGGBB. # tab_active_bg = "#26292e" # Frame ground behind panes; pane opacity blends toward it. #RRGGBB. # pane_backdrop = "#101216" # Unfocused pane border. #RRGGBB. # pane_border = "#262a32" # Toast and overlay ground. #RRGGBB. # overlay_bg = "#1a1d23" # Unseen-output tab badge. #RRGGBB. # unseen_badge = "#f2b84b" # Mail envelope ink. #RRGGBB. # mail_letter = "#f2b84b" # Working tab badge and breathing handle chip. #RRGGBB. # active_badge = "#4cc98a" # Agent attention tab badge. #RRGGBB. # attention_badge = "#ff7a6b" # Selection text; set together with selection_bg (unset: inverse video). #RRGGBB. # selection_fg = "#f2f4f7" # Selection ground; set together with selection_fg (unset: inverse video). #RRGGBB. # selection_bg = "#2a3a55" # ANSI colours 0-15. #RRGGBB. # ansi = ["#000000", "#cd0000", "#00cd00", "#cdcd00", "#0000ee", "#cd00cd", "#00cdcd", "#e5e5e5", "#7f7f7f", "#ff0000", "#00ff00", "#ffff00", "#5c5cff", "#ff00ff", "#00ffff", "#ffffff"] [mux] # A new pane uses [mux] shell when that value is set. # Otherwise it uses COMSPEC on Windows, or cmd.exe when COMSPEC is unset. # Elsewhere it uses SHELL, or /bin/sh when SHELL is unset. # One PowerShell pane: pmux new NAME -- powershell.exe # Instance name used to derive pmux.sock / pmux-.sock. non-empty name, no '/'. instance = "default" # Absolute socket path; overrides instance when set. absolute path. # socket = "/run/user/1000/prismattyc/pmux.sock" # Attach in a TTY after pmux new. true|false. attach_on_new = true # Re-run saved pane commands on space open. agents|all|none. space_open_runs_commands = "agents" # Who sets pane size when a TTY attach shares the host window. latest|host. remote_size = "latest" # Pane shell override. Unset on Windows preserves COMSPEC or cmd.exe. Set pwsh.exe or powershell.exe to opt in to PowerShell. Unset on Unix uses SHELL or /bin/sh. program name or path. # shell = "pwsh.exe" [keys] # Host actions. An entry replaces that action's default chords. # Value is a chord string or an array of chord strings. # Set macos_shortcuts = true above to add more standard Command-key actions. # split the focused pane to the right. split_right = ['ctrl+shift+\', "ctrl+shift+e"] # split the focused pane downward. split_down = ["ctrl+shift+-", "ctrl+shift+d"] # close the focused pane. close_pane = "ctrl+shift+w" # detach this session view (last tab exits). detach = "ctrl+shift+x" # focus the pane to the left. # Commented so this file does not pin a chord. Linux uses alt+left; macOS uses ctrl+alt+left. Uncomment to pin. # focus_left = "alt+left" # focus the pane to the right. # Commented so this file does not pin a chord. Linux uses alt+right; macOS uses ctrl+alt+right. Uncomment to pin. # focus_right = "alt+right" # focus the pane above. focus_up = "alt+up" # focus the pane below. focus_down = "alt+down" # cycle the focus border color forward. focus_border_next = "ctrl+shift+]" # cycle the focus border color back. focus_border_prev = "ctrl+shift+[" # cycle the Graphite bar color forward. bar_color_next = "ctrl+shift+b" # cycle the Graphite bar color back. # bar_color_prev = [] # swap the focused pane with the previous pane. # swap_pane_prev = [] # swap the focused pane with the next pane. # swap_pane_next = [] # rotate every pane one slot forward. # rotate_panes = [] # rotate every pane one slot back. # rotate_panes_back = [] # focus the previously focused pane in this tab. # focus_last_pane = [] # new tab. new_tab = "ctrl+shift+t" # new blank terminal tab. new_blank_tab = "ctrl+alt+shift+t" # new automatically named session tab. new_session_tab = "ctrl+alt+shift+n" # split right with a blank terminal. blank_split_right = "ctrl+alt+shift+e" # split down with a blank terminal. blank_split_down = "ctrl+alt+shift+d" # split right with an automatically named session. session_split_right = "ctrl+alt+shift+r" # split down with an automatically named session. session_split_down = "ctrl+alt+shift+b" # find a terminal across Spaces. terminal_switcher = "ctrl+shift+o" # view pending mail and pane input queue receipts. # agent_messages = [] # focus the next pane that needs your attention. jump_needs_you = "ctrl+shift+u" # update, restart components, and inspect versions. # update_restart = [] # show recent status messages, including hidden toasts. # recent_messages = [] # close the active tab. close_tab = "ctrl+shift+q" # rename the active tab. rename_tab = "ctrl+shift+r" # name the focused session and mailbox (local shells: pane title). # rename_pane = [] # previous tab. prev_tab = "ctrl+shift+pageup" # next tab. next_tab = "ctrl+shift+pagedown" # select the previously active tab. # last_tab = [] # select tab 1. select_tab_1 = "ctrl+shift+1" # select tab 2. select_tab_2 = "ctrl+shift+2" # select tab 3. select_tab_3 = "ctrl+shift+3" # select tab 4. select_tab_4 = "ctrl+shift+4" # select tab 5. select_tab_5 = "ctrl+shift+5" # select tab 6. select_tab_6 = "ctrl+shift+6" # select tab 7. select_tab_7 = "ctrl+shift+7" # select tab 8. select_tab_8 = "ctrl+shift+8" # select tab 9. select_tab_9 = "ctrl+shift+9" # move the focused pane to the previous tab. move_pane_prev_tab = "ctrl+shift+alt+pageup" # move the focused pane to the next tab. move_pane_next_tab = "ctrl+shift+alt+pagedown" # extract the focused pane into its own tab. # break_pane = [] # join the focused pane into the previous tab. # join_pane = [] # move the active tab one slot left. # move_tab_left = [] # move the active tab one slot right. # move_tab_right = [] # even 2-column layout. layout_2 = ["ctrl+shift+f2", "ctrl+shift+alt+f2", "super+shift+f2", "ctrl+alt+2"] # even 3-column layout. layout_3 = ["ctrl+shift+f3", "ctrl+shift+alt+f3", "super+shift+f3", "ctrl+alt+3"] # even 2×2 quadrant layout. layout_4 = ["ctrl+shift+f4", "ctrl+shift+alt+f4", "super+shift+f4", "ctrl+alt+4"] # even 5-column layout. layout_5 = ["ctrl+shift+f5", "ctrl+shift+alt+f5", "super+shift+f5", "ctrl+alt+5"] # even 6-column layout. layout_6 = ["ctrl+shift+f6", "ctrl+shift+alt+f6", "super+shift+f6", "ctrl+alt+6"] # even 7-column layout. layout_7 = ["ctrl+shift+f7", "ctrl+shift+alt+f7", "super+shift+f7", "ctrl+alt+7"] # even 8-column layout. layout_8 = ["ctrl+shift+f8", "ctrl+shift+alt+f8", "super+shift+f8", "ctrl+alt+8"] # even 9-column layout. layout_9 = ["ctrl+shift+f9", "ctrl+shift+alt+f9", "super+shift+f9", "ctrl+alt+9"] # retile to one pane when the tab has one pane; no-op otherwise. # preset_single = [] # retile existing panes into even columns. # preset_split_h = [] # retile existing panes into even rows. # preset_split_v = [] # retile existing panes into an even two-row grid. # preset_grid = [] # retile: focused pane on the left, others stacked on the right. # preset_main_vertical = [] # retile: focused pane on top, others in a row below. # preset_main_horizontal = [] # arrange the tab as one pane; extra panes zoom instead of closing. # arrange_single = [] # arrange the tab as a side-by-side split; extra panes zoom instead of closing. # arrange_split = [] # arrange the tab as a 2×2 grid; extra panes zoom instead of closing. # arrange_grid = [] # zoom the focused pane to the whole tab; again restores the split. zoom_pane = "ctrl+shift+z" # open a new OS window. new_window = "super+n" # quit Prismattyc. quit = "super+q" # edit the config file. # open_config = [] # open the command palette. command_palette = "ctrl+shift+p" # next command-palette filter chip. # palette_filter_next = [] # previous command-palette filter chip. # palette_filter_prev = [] # open theme settings. theme_picker = "ctrl+shift+," # open a saved space. # open_space = [] # delete a saved space. # delete_space = [] # move the focused pane to another saved space. # move_pane_to_space = [] # focus and reorder saved spaces. # space_rail_focus = [] # open the sidebar or vertical-rail context menu for the focused row. # space_rail_context_menu = [] # choose rail position, autosave, and startup behavior. # space_settings = [] # undo the last session removal or move. # undo_space_change = [] # open the next saved space. # space_rail_next = [] # open the previous saved space. # space_rail_prev = [] # save the current space arrangement. # save_space = [] # find in scrollback. find = "ctrl+shift+f" # clear the screen and scrollback. # clear_scrollback = [] # increase the font size. # increase_font_size = [] # decrease the font size. # decrease_font_size = [] # reset the font size. # reset_font_size = [] # open the walkthrough caption. # walkthrough = [] # delete walkthrough progress and restart at level 0. # walkthrough_reset = [] # copy the selection. copy = "ctrl+shift+c" # paste the clipboard. paste = "ctrl+shift+v" # select the visible viewport. select_all = "ctrl+shift+a" # scroll history up one line. scroll_line_up = "ctrl+shift+up" # scroll history down one line. scroll_line_down = "ctrl+shift+down" # open transparency settings (graphite chrome only). # transparency = [] # choose the Bars or Sidebar layout (graphite chrome only). # chrome_layout = [] # collapse or expand the spaces sidebar. sidebar_collapse = "ctrl+alt+shift+s" # toggle rich focus (--experimental-rich). rich_focus = "ctrl+shift+g" # -- accessibility -- [a11y] # Expose host chrome through AccessKit (VoiceOver / Orca). true|false. os_tree = true announce = true # -- spaces -- [spaces] # Save the arrangement whenever it changes, after a short idle. true|false. autosave = true # -- remote spaces -- # SSH destinations whose running Spaces the host can list (issue #24). # `ssh` names a Host alias from your SSH config. Authentication uses keys # or ssh-agent only; unknown host keys are never accepted automatically. # [[remote]] # id = "devbox" # a-z, 0-9 and '-'; unique # ssh = "devbox" # SSH config Host alias # label = "Dev box" # optional display name ``` All spacing values accept `0` through `128` physical pixels. #### Accessibility `[a11y]` exposes host chrome through AccessKit (VoiceOver on macOS, Orca on Linux). Both keys default on when the table is absent. | Key | Default | Effect | |---|---|---| | `os_tree` | `true` | Register the AccessKit adapter at window create. `false` leaves the window unchanged. Startup only. | The tree covers the window, tab strip, pane names, command palette, splash, find, theme picker, space rail, and the scroll chip. The focused pane's visible lines are one Document node (plain text, caret offset, host selection). Unfocused panes stay name and badge only. Scrollback is not in the live node. The tree always includes a live-region node named `announce`. A silent frame keeps that node with an empty value. Mail depth rises, attention prompts, and unfocused-pane title notices speak first (assertive). fires on pane focus or row change and coalesces to 400 ms. Two identical utterances toggle a trailing zero-width space so the value still changes. Audio is never the only channel. The host republishes the tree after each dirty paint. The `RedrawRequested` path calls `publish_a11y` after present. The adapter's `update_if_active` does nothing until a screen reader attaches. A compositor expose that is not dirty does not rebuild the tree. #### Transparency The following keys control how much shows through. The layers work from back to front: 1. the desktop and whatever windows sit behind Prismattyc; 2. the **window ground** — `pane_backdrop` behind each pane, `chrome_bg` in the gaps and window padding — painted at alpha `window_opacity`; 3. the **background image**, if `background_image` is set, with its own `background_opacity` tint and `background_blur_px`; 4. each **pane surface** — padding, structural outline, spare grid pixels, and default-background cells — scaled by `pane_opacity_active` or `pane_opacity_inactive`; `pane_opacity_active` and `pane_opacity_inactive` scale the alpha of each pane's padding, structural outline, spare grid pixels, and default-background cells. Cell colors still blend toward `pane_backdrop` (or toward the background image when one is set). Values above `0.8` are hard to see; **use `0.6`–`0.8`** for a visible difference between the focused pane and the rest. `1.0` leaves the pane at the window alpha. `pane_backdrop` is a theme key. Every built-in derives one: dark themes reuse `chrome_bg`, light themes take a 6% darker `default_bg`, and a theme whose `chrome_bg` is nearly its `default_bg` falls back to a darkened `default_bg`. A `themes/*.toml` file may set `pane_backdrop = "#RRGGBB"` to override it. It hot-reloads with the theme. `overlay_opacity` controls the tint weight of the frosted command palette, context menus, space picker, save-space prompt, and theme picker. It accepts `0.0` through `1.0`. Remove the key to follow `pane_opacity_active`. The in-frame blur still follows `background_blur_px` and `window_blur`. The key hot-reloads with the rest of the config. `tab_active_bg` is a theme key (PT-95). The active tab chip fills with it. Inactive chips keep `chrome_bg`. The default is `chrome_bg` blended toward `chrome_fg` (8% on dark themes, 6% on light). A theme file may set `tab_active_bg = "#RRGGBB"` to override it. It hot-reloads with the theme. There is no `config.toml` per-colour theme-override table. `hover_blend` controls immediate feedback on interactive chrome. It accepts `0.0` through `0.3` and defaults to `0.10`. Dark themes blend the hovered element toward `chrome_fg`; light themes use 80% of the value to darken it. Hover composes over the current state, including `tab_active_bg`, and does not animate. The key hot-reloads. `window_opacity` below `1.0` makes the window ground and every default- background cell translucent, so the desktop shows through — Ghostty's `background-opacity`. Cells that carry an explicit SGR background stay opaque, focused one: its surface is painted at `window_opacity × that pane's opacity`, so an inactive pane at `0.6` under a `0.8` window shows more desktop than the active pane at `1.0`. `chrome_opacity` sets the tab strip and the footer rail; it defaults to `window_opacity`. The bars themselves become translucent; their text and badges do not. Omit the key and the bars follow `window_opacity`. When `chrome_style = "graphite"`, the command palette action `transparency` opens a fixed 760×460 dialog that writes these keys. The list scrolls inside the dialog. The right side is a live preview of the ground, the bars, and the status dots, badges, the active tab chip, the command field, and the focus ring stay opaque. X11 without a compositor and `--gpu` stay opaque. Lowering `window_opacity` from `1.0` on X11 or Wayland needs a restart. When several windows attach to the same session, a pane is sized to the smallest attached window. A larger window shows that pane at the top left and fills the rest of its frame with the window ground — at `window_opacity`, so on an alpha-capable path the frame is see-through rather than a solid border. This is intentional; attach windows of similar size if the look bothers you. ##### Support matrix | Session | Present path | `window_opacity` | `window_blur` | | --- | --- | --- | --- | | Native Wayland (KWin, Hyprland, wlroots) | own `wl_shm` `ARGB8888` present (PT-118) | works | KWin 6.7+: works via `ext-background-effect-v1`. Hyprland: use a `windowrulev2 = blur` instead; see [docs/hyprland.md](hyprland.md) | | X11 with a compositor | softbuffer, depth-32 ARGB visual | works | ignored, with a startup notice | | XWayland (`unset WAYLAND_DISPLAY`) | softbuffer, depth-32 ARGB visual | works | ignored, with a startup notice | | macOS | alpha-capable Core Animation present | works for the window ground; text and explicit backgrounds stay opaque | works through an AppKit backdrop; hot-reloads without recreating the window | | Windows | softbuffer | untested, treated as unsupported | ignored, with a startup notice | | `--gpu` (`--features gpu`) | wgpu | ignored; the composite-alpha mode is not wired yet | ignored, with a startup notice | On native Wayland the host bypasses softbuffer and presents its own `wl_shm` `ARGB8888` buffers on the winit surface. ARGB8888 is one of the two formats every Wayland compositor must accept, so the path is portable by specification. softbuffer still serves X11, where its backend accepts depth-32 visuals and a window created with an alpha visual carries premultiplied ARGB. When no path can carry alpha the host prints one line at startup and treats `window_opacity` as `1.0`; nothing else changes. On macOS, the host uses an alpha-capable Core Animation surface. The and explicit SGR backgrounds stay opaque. When `window_blur` is `true`, the host adds an `NSVisualEffectView` behind the winit content view. It uses the `underWindowBackground` material, `behindWindow` blending, and the active state. Set `window_opacity` below `1.0` to make the backdrop visible. The host adds or removes the effect view when you change the setting. The host keeps the existing `background_image` and `background_blur_px` raster paths unchanged. `chrome_opacity` still needs a present path with per-pixel alpha, such as macOS, native Wayland, or X11 with a depth-32 visual. `window_blur` asks the compositor or AppKit to blur what is behind the window. On KWin 6.7+ the host installs an `ext-background-effect-v1` blur for the whole window when the key is `true`. Compositors without that protocol (Hyprland and other wlroots compositors) apply blur compositor-side; the key is then accepted and ignored with a startup notice. The alpha visual is chosen once, when the window is created. On Wayland and X11, raising or lowering `window_opacity` while the host runs applies on the next frame only if the window already has an alpha visual; going from `1.0` to anything lower needs a host restart, and the host says so on the next poll. On macOS, the Core Animation present path already carries alpha. You can change `window_opacity` while the host runs without recreating the window. #### Active tab and current space chip The active tab chip (and the current chip in the spaces rail) is painted in a variation of the focus colour: the focus colour blended 55 % over `chrome_bg` on dark themes (45 % on light), toned further toward `chrome_bg` only as far as needed for the title ink to reach WCAG AA (4.5:1). Ink is chosen for contrast — light text on a dark chip, dark text on a light one — so the selection reads at a glance whichever focus colour you pick. A theme file that sets `tab_active_bg` pins the chip colour instead; the ink is still chosen by contrast. #### Themes Prismattyc ships 32 built-in themes: | Config slug | Display name | | --- | --- | | `prismattyc-default` | Prismattyc Default | | `prismattyc` | Prismattyc (match system) | | `prismattyc-dark` | Prismattyc Dark | | `prismattyc-light` | Prismattyc Light | | `catppuccin-mocha` | Catppuccin Mocha | | `tokyo-night` | Tokyo Night | | `rose-pine-moon` | Rosé Pine Moon | | `monokai` | Monokai (classic) | | `monokai-pro` | Monokai Pro (CE) (MIT, Monokai Pro Community Edition) | | `omarchy-tokyo-night` | Omarchy Tokyo Night | | `omarchy-osaka-jade` | Omarchy Osaka Jade | | `omarchy-matte-black` | Omarchy Matte Black | | `monokai-dimmed` | Dimmed Monokai (MIT, iTerm2-Color-Schemes) | | `monokai-remastered` | Monokai Remastered (MIT, iTerm2-Color-Schemes) | | `monokai-soda` | Monokai Soda (MIT, iTerm2-Color-Schemes) | | `monokai-vivid` | Monokai Vivid (MIT, iTerm2-Color-Schemes) | | `dracula` | Dracula | | `ghost` | Ghost | | `japanesque` | Japanesque (MIT, iTerm2-Color-Schemes) | | `hive-monochromatic` | Hive Monochromatic | | `hive-two-tone` | Hive Two-Tone | | `hive-tri-tone` | Hive Tri-Tone | | `hive-complementary` | Hive Complementary | | `hive-split-complementary` | Hive Split-Complementary | | `hive-analogous` | Hive Analogous | | `hive-triadic` | Hive Triadic | | `hive-high-contrast-dual` | Hive High-Contrast Dual | | `hive-muted-professional` | Hive Muted Professional | | `hive-night-hive` | Hive Night Hive | | `hive-monochromatic-light` | Hive Monochromatic Light | | `hive-tri-tone-light` | Hive Tri-Tone Light | | `hive-muted-professional-light` | Hive Muted Professional Light | `prismattyc` is the default for new installs and for a config without `theme`. It follows the OS appearance (macOS, Windows, and desktops that report one) and paints Prismattyc Dark or Prismattyc Light, so the Graphite chrome, the one palette; it falls back to Dark when the system reports nothing. `prismattyc-dark` and `prismattyc-light` pin one side. `prismattyc-default` is the earlier palette and stays available by name. Any other theme, a theme file, or `[theme_overrides]` keeps its own colors; overrides are re-applied after an appearance switch. The Graphite chrome follows the selected theme too. The three Prismattyc themes paint the design brief's tokens exactly. Every other theme, and a Prismattyc theme whose `chrome_bg` or `chrome_fg` is overridden, derives them: the tabs bar, spaces bar, side rail, and sidebar from `chrome_bg` (the spaces bar a shade off it), text from `chrome_fg`, the active chip from `tab_active_bg`, panes, title rows, and dialogs from `default_bg`, and the status dots from the theme's badge colors. Derived text is nudged toward readable contrast when a theme's pair is too close. An explicit `bar_color` still recolors both bars; without one, the bars follow the theme, and `Ctrl+Shift+B` cycles Theme, Graphite, Harbor, Moss, and Plum. The focus-ring spectrum and the light-cycle head stay brand chrome. The Omarchy themes use palettes from the official Omarchy repository, with window-control colors mapped to Prismattyc. Their [MIT notice](../crates/prismattyc-host/themes/OMARCHY-LICENSE.txt) is included with the themes. Monokai Spectrum has been removed. If your configuration selected it, set `theme = "omarchy-tokyo-night"` or choose another theme from the picker. Slugs and display names are case-insensitive. A bare name first checks a `themes/` directory next to `config.toml`, then the embedded themes. This lets `~/.config/prismattyc/themes/rose-pine-moon.toml` intentionally override a built-in. Explicit paths must be absolute so theme selection does not depend on the directory that launched Prismattyc. Theme files are TOML data with identity/source metadata, terminal defaults, colors. The embedded files under `crates/prismattyc-host/themes/` are complete examples. Theme selection changes Default and ANSI 0–15; guest RGB colors and the stock xterm 16–255 cube/grayscale pass through unchanged. The seven-color Prismattyc focus spectrum is brand chrome and also remains independent. Press `Ctrl+Shift+,` in `prismattyc-host` to open the keyboard-only theme settings surface. `Up` / `Down` (or `Home` / `End`) previews the built-in themes live, including host chrome and an ANSI 0–15 strip. Families with two or more variants (Monokai, Hive) show a trailing `>` on the root list. `Right` opens that family. `Left` returns to the root. `PageUp` / `PageDown` scrolls when the current list is taller than the window. `Enter` atomically updates the `theme` key in `config.toml` while preserving its comments and other keys; `Escape` restores the exact pre-picker theme. Custom file themes remain valid config choices, but the picker lists the curated built-ins. Use the `open_config` palette action to open `config.toml` in `$VISUAL`, then `$EDITOR`, in a new terminal window. When neither is set, macOS and Linux use `nano` in that window, and Windows opens Notepad (`notepad.exe`) in its own window with no terminal window. The action creates the default template only when the file is missing. It does not parse the file before opening the editor, so you can repair malformed TOML. ```toml # Optional standard macOS Command shortcuts. Off by default. macos_shortcuts = true # Host keybindings (keybindings). Each entry replaces every default chord for # that action, including its macOS shortcut; list aliases explicitly; [] unbinds. # Chord = mod+...+key with # ctrl, shift, alt, super (cmd/meta/win) and a key: a letter, digit, # punctuation (`\` `-` `=` `[` `]` `;` `'` `,` `.` `/` or spelled: backslash, # minus, comma, ...), or enter, tab, backspace, delete, escape, space, # insert, up, down, left, right, home, end, pageup, pagedown, f1-f24. # A chord needs ctrl, alt, or super. Two actions on one chord, an unknown # action, or a reserved input (plain ctrl+c, ctrl+2, ctrl+space, # shift+insert, the ctrl+shift punctuation find fallbacks) reject the file. # An active IME may consume ctrl+space or ctrl+2 before the host receives it. # Use Shift+arrow or another available mark path when needed. # Action names and defaults: `prismattyc-host --help`. [keys] split_right = "ctrl+alt+enter" find = ["ctrl+shift+f", "ctrl+alt+f"] copy = "super+c" # replaces Ctrl+Shift+C paste = "super+v" # replaces Ctrl+Shift+V # close_tab = [] # disable this action, including Cmd+W command_palette = "ctrl+shift+p" theme_picker = [] open_config = [] # Layout presets retile the current tab's existing panes. They do not spawn # or close a pane. `layout_2` … `layout_9` still spawn until N panes. # Presets are unbound by default. Bind them here if you want a chord. # preset_single = [] # no-op when the tab has more than one pane # preset_split_h = "ctrl+alt+h" # even columns # preset_split_v = "ctrl+alt+v" # even rows # preset_grid = "ctrl+alt+g" # two rows; ceil(n/2) on top (3 panes → 2+1) # preset_main_vertical = [] # focused pane left; others stacked right # preset_main_horizontal = [] # focused pane top; others in a row below # Mux defaults. prismattyc-host accepts this table and ignores it. # pmux applies it; CLI flags and PMUX_* / PMUX_* still win. [mux] instance = "default" # $XDG_RUNTIME_DIR/prismattyc/pmux.sock # socket = "/run/user/1000/prismattyc/pmux.sock" # absolute; overrides instance # attach_on_new = false # default true: `pmux new` attaches in a TTY # space_open_runs_commands = "agents" # agents|all|none; `pmux space open --no-run` skips # remote_size = "latest" # latest|host; TTY attach fits, detach restores host # shell = "pwsh.exe" # unset: COMSPEC or cmd.exe on Windows ``` Cmd+Q (quit) and Cmd+N (new window) are available by default. `macos_shortcuts = true` adds Cmd+T (new tab), Cmd+W (close tab), Cmd+C/V/A (copy, paste, select all), Cmd+F (find), Cmd+K (clear screen and scrollback), Cmd+Shift+= (larger font), Cmd+- (smaller font), and Cmd+0 (reset font size). Use `prismattyc-host --list-bindings` to print the active chords after config overrides. An explicit `[keys]` entry that drops its Command chord keeps only the listed chords; the host prints a warning naming the suppressed shortcut so it can be added back as an alias. `[]` unbinds without warning. On macOS, with `macos_shortcuts = true`, Cmd+Left and Cmd+Right send `^A` and `^E` (line start and end) and Cmd+Backspace sends `^U`. The flag stays off by default. Those three are bytes for the shell, not host actions, so they do not appear in `[keys]`. Home, End, and the arrow keys follow DECCKM. When the child turns application (`ESC O H`, `ESC O F`, `ESC O A` through `ESC O D`). Otherwise they use CSI (`ESC [ H`, `ESC [ F`, `ESC [ A` through `ESC [ D`). A modifier keeps the xterm CSI form `ESC [ 1 ; mod letter` whether or not DECCKM is on. Kitty keyboard disambiguate also keeps the CSI form. Page Up, Page Down, Delete, Insert, and the function keys are unchanged. On macOS the runtime default for `focus_left` and `focus_right` is `ctrl+alt+left` and `ctrl+alt+right` (Ctrl+Option+Left / Ctrl+Option+Right) when `[keys]` does not set those actions. The generated template leaves both keys commented, so a fresh config follows that default and Option+Left / Option+Right reach the shell as word jumps (`ESC b` and `ESC f`), matching Terminal.app and iTerm2. Linux uses `alt+left` and `alt+right` when those keys are absent. Focus up and down stay Option+Up and Option+Down, and those lines stay live in the template. Uncommenting a line pins it on every OS: `focus_left = "alt+left"` focuses the pane and does not send `ESC b`. A config written by an older build has those two lines live at the portable chords; comment them out to follow the platform default. Any action can be rebound by its name in `[keys]`; a string binds one chord, an array binds aliases, and `[]` disables it. The list command shows actions with no active chord as `(unbound)`. Layout presets (`preset_single`, `preset_split_h`, `preset_split_v`, `preset_grid`, `preset_main_vertical`, `preset_main_horizontal`) retile the current tab's existing panes. They do not spawn or close a pane. That is the difference from `layout_2` … `layout_9`. They are unbound by default. Bind them under `[keys]` if you want a chord. `preset_single` is a no-op when the tab has more than one pane. `preset_split_h` is even columns. `preset_split_v` is even rows. `preset_grid` is two even rows with `ceil(n/2)` panes on top (three panes become two plus one). `preset_main_vertical` puts the focused pane on the left and stacks the others on the right. `preset_main_horizontal` puts the focused pane on top and the others in a row below. Zoom is cleared first, as for other retiles. A retile that cannot satisfy pane minima leaves the layout unchanged and shows a status message. Arrange actions (`arrange_single`, `arrange_split`, `arrange_grid`, unbound by default) grow or shrink the tab instead of only retiling it. Growing spawns empty shells to fill the target with focus unchanged; shrinking never closes panes, it zooms the focused pane and shows a "still running" toast (Ctrl+Shift+Z restores the split). ### Layout `layout` picks the arrangement. `bars` (the default) shows the tabs bar and the spaces bar. `sidebar` replaces both with one tree of Spaces and their tabs. Graphite and classic both honor `sidebar`. Open the command palette and run `chrome_layout`, or open **Spaces settings…**. Both show **Layout: Bars** and **Layout: Sidebar**, only with Graphite chrome. Classic sets `layout` in this file. A choice applies to the window at once, with no restart. It is saved to `layout`, so other open windows follow it. With `layout = "sidebar"`, drag the inner edge to resize the tree. The pointer classic, and the edge highlights while the pointer is on it. The width is saved as `sidebar_width_px` (200–2000, default 256). A long name does not change the width: names ellipsize, and the list scrolls. Double-click the edge to return to 256. Dragging narrower than 200 snaps the sidebar closed. Panes reflow on a short interval during the drag, and once more when the button is released. The sidebar can collapse to a 52 px icon strip. Use the chevron in the sidebar header, `sidebar_collapse` (`Ctrl+Alt+Shift+S`, also in the command palette), or drag the edge below 200. The strip shows an icon for each space and each session, with the status dot and a tooltip for the full name. Click a space icon to open that space. Click a session icon to focus that pane. `+ New tab`, `+ New space`, and Commands sit at the bottom as icons. The strip scrolls inside its fixed width. Expanding restores the last dragged width. The collapsed state is saved as `sidebar_collapsed`. `space_rail = "right"` docks that sidebar on the right, with the grip on its left edge. Other `space_rail` values keep the sidebar on the left. The spaces rail itself is not drawn while the sidebar is showing. ### Spaces rail The rail shows the saved spaces (`spaces/*.json`, see `pmux space ls`) as chips, the space counterpart of the tab strip. It is a view over the files: a chip never owns a session. Open the command palette and run `space_settings`. You can also right-click a Space chip and select **Spaces settings…**. Changes are saved to this configuration and apply to the running windows. ```toml space_rail = "bottom" # bottom (default) | left | top | right | off space_reorder = false # enable mouse and Shift+arrow reordering space_rail_chip_cols = 0 # horizontal chip limit, 6-40; 0 = 28 space_rail_width_cols = 18 # side rail width in cells, 8-60 space_rail_pane_names = true # show live session names in a second row ``` `[spaces] autosave` defaults to true. Adding or closing a tab, terminal, or agent session, changing a split or its ratio, using Arrange, dragging tabs or panes, or moving a session between spaces saves the arrangement after a short idle, about one second. The rail shows Saving…, then Saved. Manual Save writes the same file immediately. `[spaces] autosave = false` opts out. When that key is absent, the legacy `space_autosave` value applies. When both are absent, autosave stays on. Autosave does not run while a space is opening or being restored. Set `space_reorder = true` to drag saved Space chips in any rail or Graphite sidebar, or focus the rail and use `Shift+Left` / `Shift+Right` (horizontal) or `Shift+Up` / `Shift+Down` (vertical). The shared order is stored separately from each Space layout and is also used by `pmux space ls`. - `bottom` / `top`: one row of chips under the panes (above the Ctrl+Shift chord strip) or above the tab strip. Chips start at the left edge; each chip is as wide as its label plus the close cell, up to `space_rail_chip_cols` (longer names ellipsize). A compact `+` chip follows the last space. - `left` / `right`: a fixed-width column with chips stacked from the top. Drag the inner edge to resize it. The width is saved as `space_rail_width_cols`. A long name does not resize the terminal area. With `chrome_style = "graphite"`, the column is 220 px at the default width of 18 (other widths scale by `cols / 18`, and with the window scale). A 44 px header lines up with the tabs bar. Each Space is a two-line chip (the name, then live pane names) and the list scrolls inside the column, so every Space stays reachable. The working count, the attention count, and the "Hold Ctrl Shift" hint stay in the footer. and still omit chips that do not fit. - With `space_rail_pane_names = true`, each chip shows live session names in a second row at the terminal font size. Names update after a session moves or is renamed. A name disappears when its last live pane exits. Long lists are clipped inside the chip. Open its context menu for full names. - The rail is part of the host geometry: pane content shrinks by the rail's row or column, and PTY sizes follow. Chips that do not fit are not shown, except a Graphite left or right column, which scrolls them inside the fixed column instead. - Chips are listed oldest first. Saving or renaming keeps their order. The current space sits on `tab_active_bg` (the same lift as the active tab) with a focus-colour title, a marker line, and a dot where the `×` would be. The host knows its space from `PMUX_SPACE`, a chip click, or `pmux space open` from a terminal; when it does not (a bare launch or `pmux attach --all`), it marks the one saved space whose tabs match the live tabs. The rail re-reads the directory once a second, so `pmux space save` / `rm` from a terminal update it. Every click answers with a toast on the focused pane: "opening space NAME", "NAME is the current space", or why nothing happened. Mouse: | Press | Effect | |---|---| | left click on a chip | switch to that space in this window (`pmux space open NAME --no-attach`); a fresh bare window is populated too | | left click on `×` | arm the delete confirm on that chip; a second click, Enter, or Delete removes the file | | right click on a chip | open the Space context menu, including Rename | | middle click on a chip | arm the delete confirm | | left click on `+` | a dialog asks for a new Space name; Enter creates one fresh shell and opens it in this window; Esc cancels | Keyboard (palette actions, unbound by default; bind them under `[keys]`): | Action | Effect | |---|---| | `space_rail_focus` | move focus to the rail: ←/→ (↑/↓ on a side rail) or Tab step over the chips and `+`, Enter opens (or creates a fresh Space on `+`), F2 renames, Delete asks, Esc returns to the pane | | `space_rail_context_menu` | open the same menu as right-click on a space, session (tab), or pane row in the sidebar or vertical rail; Menu or Shift+F10 while the rail is focused also works | | `space_rail_next` / `space_rail_prev` | open the neighbour of the current space, wrapping | | `save_space` | save the current Space arrangement | | `open_space` / `delete_space` | the filtered pickers over the same files | | `move_pane_to_space` | move the focused session or blank terminal into another saved Space | Each window has its own Space view. Opening a Space in a second window leaves the first window unchanged. Windows on different Spaces use different sessions. Move pane and Move session transfer running work to another Space. The source view removes the transferred work. Moving its last session leaves an empty view. The current space cannot be deleted from the rail; open another space first. A name that already exists, or one with `/` or `..`, keeps the dialog open and shows why. #### Remote Spaces over SSH `[[remote]]` entries add one chip per SSH destination to the Space rail, after the `+` (issue #24). Select a chip to connect and list the running Spaces on that machine; select it again to refresh. Its glyph shows the state: `○` not connected, `…` connecting, `●` connected, `!` failed (the list shows the full error and a Retry row). Choosing a running Space opens a new tab attached to that Space's active session. Each entry has a unique `id` (`a-z`, `0-9`, inner `-`), an `ssh` Host alias from your SSH config, and an optional `label`. User, port, keys and proxies belong in `~/.ssh/config`; the alias may not start with `-` or contain spaces, `@`, `:` or shell characters. A catalog request runs `ssh -T -o BatchMode=yes -o ConnectTimeout=10 -- ALIAS pmux space catalog` only on an explicit connect or refresh, never in the background. `BatchMode` means authentication uses keys or ssh-agent only: a password or passphrase prompt, or an unknown or changed host key, becomes a visible error instead of a hidden prompt. Verify a new host once with `ssh ALIAS` in a terminal. Requests stop after 20 seconds or 1 MiB of output. `pmux` must be on the remote non-interactive `PATH`, and the remote `pmuxd` must already be running; the host never installs software or starts a daemon remotely. The attach tab runs `ssh -t -o BatchMode=yes -o ConnectTimeout=10 -- ALIAS pmux attach --session-id ID --space-id SPACE`. Only the session and Space ids from the catalog reach the remote command. The remote refuses with "refresh the Space list" when that Space no longer owns the session (for example after the remote daemon restarted), and it never starts a daemon. Resizing the tab resizes the remote session through SSH. Closing the tab ends the local `ssh` only; the remote session keeps running. The remote needs a terminfo entry for the tab's `TERM`. ### Hot reload The host polls the config file's mtime once a second (not inotify). Saving the file applies changes on the next poll tick: - `focus_border` — recolors immediately; deleting the key reverts to default. chrome immediately; deleting the key restores Prismattyc Default. - `selective_border_rings` — off unless the file sets it true. Partial frames then restore and re-stroke only rings that change (focus, pulse, sweep, or damage under the ring). Deleting the key turns that off again. - `focus_border_animation` — takes effect on the next focus change; switching to `"none"` (or deleting the key) settles any in-progress sweep. - `focus_border_animation_ms` / `focus_border_animation_head` — apply from the next sweep; deleting a key restores its default (280ms / head on). - `reduced_motion` — applies immediately; `true` settles any in-progress sweep into the ring, deleting the key restores the sweep. - `splash` — startup only; a reload does not show or hide an already-open splash. Default true. `--no-splash` and `PRISMATTYC_NO_SPLASH` still hide it when this key is true. - `splash_animation` — applies immediately while the splash is showing; `false` freezes the art static, deleting the key restores the animation. - `hover_blend` — applies immediately to strip, rail, and scrollbar hover; deleting the key restores `0.10`. - `[keys]` — the key table is rebuilt on the next poll and the Ctrl+Shift chord strip relabels; deleting an entry restores that action's defaults. A conflicting or unparsable entry rejects the whole save (banner), so the previous bindings stay live. - `background_image` / `background_opacity` / `background_blur_px` — re-decode on the next poll. Deleting `background_image` restores the flat theme background. - `pane_opacity_active` / `pane_opacity_inactive` — repaint the pane surface (padding, outline, spare grid pixels, and default-background cells) on the next poll. Deleting a key restores 1.0. - `window_opacity` / `chrome_opacity` — repaint on the next poll **when the window was created with an alpha visual**. Going from `1.0` to a lower value needs a restart; the host prints one line saying so. Deleting a key restores 1.0 (opaque). - `window_blur` — hot-reloaded. On macOS, the host adds an `NSVisualEffectView` behind the content view. It uses the `underWindowBackground` material with `behindWindow` blending. On native Wayland, it uses `ext-background-effect-v1`. It is accepted and ignored with a notice when the platform cannot provide blur. On Hyprland use a `windowrulev2 = blur` instead (see [docs/hyprland.md](hyprland.md)). - `font` / `font_fallback` / `font_px` — reloads the font chain and recomputes the cell grid at the current window size. - `font_ligatures` / `font_features` — update host-only terminal-grid shaping without changing PTY dimensions. The default is off; when enabled, the default features are `calt` and `liga`. - `window_padding_px` / `pane_gap_px` / `pane_padding_px` — reflows pane slots, terminal content, and tab content immediately, including PTY sizes and hit-testing; deleting a key restores its 5px default. - `space_rail_pane_names` — shows live session names in each chip. Set it to `false` to restore one-row chips. - `space_rail` / `space_rail_chip_cols` — moves or hides the spaces rail and reflows the panes around it on the next poll; deleting a key restores `bottom` / 0 (28-cell cap). - `pane_titles` — hot-reloaded. `focused` (default) shows the focused pane OSC title in the title row of a selected multi-pane tab. `hover` keeps the PT-148 handle-hover preview only. An unfocused pane whose title changes still lingers in the title row for `bell_toaster_ms` and tints its handle. Deleting the key restores `focused`. - `link_click` — hot-reloaded, default `plain`. This applies to OSC 8 links and detected HTTP(S) URLs. A click opens after release only when press and release stay on the same link within half a cell; drags select text and double/triple-clicks select a word/line. `modifier` requires Ctrl-click on Linux or Command-click on macOS. Unmodified clicks still go to programs using mouse reporting. - `drag_toaster` — applies to the next drag; deleting the key restores `true`. - `toasts` — hot-reloaded, default `all`. Status toasts are the chips that answer Space, move, save, and restore actions ("cairn: view applied; 3 reused sessions…"). `errors` shows only failures and refused requests; `off` shows none. A stricter level clears a live status chip it now hides. Every status message, shown or hidden, stays in Recent messages: Settings → Recent messages…, or the `recent_messages` palette action. Bell, pasted-image, write-fail, and drag chips keep their own keys. Settings → Toasts writes this key; deleting it restores `all`. - `visual_bell` / `audible_bell` / `bell_toaster` / `bell_toaster_ms` / `os_notify_bell` — apply to the next BEL; deleting a key restores its default (flash, sound, and toaster on, 10s linger; OS notification off). Reopening a log-backed session restores its output without replaying old bells or agent-attention alerts. New alerts still use these settings. - `pane_visual_bell` — hot-reloaded, default `false`. With `visual_bell = true`, replaces the full-window flash and visible-pane BEL toast with a two-pixel inverted perimeter for 120 ms. Repeated bells coalesce without extending the deadline. Occlusion, view replacement and geometry changes cancel the accent. Hidden-pane toast/attention handling, sound and OS notifications are unchanged. Disabling the option clears a live accent; it does not replay it in the old mode. - `walkthrough_audio` — apply to the next walkthrough clip. Deleting the key restores `true`. Missing clips, `false`, or no player leave captions unchanged. `walkthrough_voice` is generation-time only; playback uses bundled clips. - `attention_sound` / `attention_badge` / `os_notify_attention` — apply to the next agent-attention signal; deleting a key restores sound, badge, and OS notifications. Attention OS notifications still throttle per pane. Turning `visual_bell` or `bell_toaster` off settles a lit flash or live BEL toast immediately. A write-fail chip (` input disconnected — reopen the pane `) stays until it expires or you click it. - `[a11y] os_tree` — startup only. `false` skips AccessKit registration. The window looks and behaves as it did before this key existed. A reload that flips the key does not attach or detach the adapter; restart the host. Deleting the table restores the default (`true`). While the adapter is live, each dirty paint republishes the chrome tree. - `[a11y] announce` — hot-reloaded. `false` keeps the AccessKit tree and silences live-region speech. Deleting the key restores the default (`true`). - `panes` — startup only; a reload logs a reminder and changes nothing. A save that fails parsing or validation is rejected as a whole: current settings stay live, and the window shows a coral footer bar (`config rejected: `) until the next valid save clears it. A config that was already invalid at startup shows the same bar once the window opens. The bar yields to the Ctrl+Shift chord cheat-sheet while held. Live PTYs and scrollback are preserved by a reload. A font or spacing change resizes the grid the same way a window resize does — which, like a window resize, clears any in-progress selection. Reloads only ever apply real, parseable file content: a briefly missing or zero-length file (editors rename or truncate mid-save) is skipped. To reset everything to defaults at runtime, save a file containing just a comment (`# defaults`). The poller watches `config.toml`, not a custom theme file. After editing a custom theme, save `config.toml` again to load the new palette. #### Theme overrides `[theme_overrides]` recolours keys of the named theme without copying a theme file (PT-207). The template lists every key commented at the prismattyc-default value; uncomment one to change it. Values are `#RRGGBB`. `selection_fg`/`selection_bg` come in pairs. Overrides apply on top of any theme, including one picked with `theme_picker`, and hot-reload with the file. A bad value rejects the whole file with a stderr message, so the host keeps its last good palette. ```toml theme = "monokai" [theme_overrides] attention_badge = "#ff8800" ``` Unbound `[keys]` actions are listed commented (`# swap_pane_prev = []`) so the action name is discoverable. Uncomment and set a chord to bind one. ### New panes and tabs Choose a default in **Spaces settings → Session names**: | Setting | New tabs, splits, and layout expansion | |---|---| | `session_naming = "ask"` (default) | Show the naming popup. Choose Create or Blank terminal. | | `session_naming = "auto"` | Assign a suggested name and create a managed session. | | `session_naming = "blank"` | Open a local login shell without a session or mailbox. | The preference persists across launches. These direct actions override it: | Action | Default shortcut | Result | |---|---|---| | `new_blank_tab` | Ctrl+Alt+Shift+T | New blank tab | | `blank_split_right` | Ctrl+Alt+Shift+E | Blank split to the right | | `blank_split_down` | Ctrl+Alt+Shift+D | Blank split below | | `new_session_tab` | Ctrl+Alt+Shift+N | New automatically named session tab | | `session_split_right` | Ctrl+Alt+Shift+R | Session split to the right | | `session_split_down` | Ctrl+Alt+Shift+B | Session split below | You can rebind these actions under `[keys]` or run them from the palette. Blank terminals belong to the Space view where you open them. Switching Spaces hides that view and keeps its shells running. Returning restores its tabs, split layout, and focus. To move a blank terminal, right-click its pane and select **Move pane to space**. Select the destination Space. The terminal leaves the current view and appears in the destination. Its process, directory, and scrollback stay intact. #### Alternate-screen scrollback Full-screen TUIs keep the classic live-only view by default. Set `alt_screen_scrollback = true` (or `--alt-screen-scrollback`, or `PRISMATTYC_ALT_SCREEN_SCROLLBACK=1`) to keep rows the TUI scrolls off the top of its screen reachable in host scroll, scrollbar, and selection. The wheel targets the pane under the pointer without moving focus. Exiting the TUI leaves its retained rows in the shell's scrollback. The three sources OR together: there is no flag to force the setting off when the config file enables it, and toggling it hot-reloads only for panes opened afterwards — already-open panes keep the value from spawn. See `docs/scrollback-triage.md`. #### Restore blank terminals after restart Managed sessions can start automatically at login with **Start at login and restore workspace**, or `start_at_login = true`. New installations default off; existing saved workspaces default on after upgrade. See [Login and workspace restore](login-restore.md) for commands, platform services, and restore limits. Enable **Spaces settings → Restore blanks**, or set `restore_blank_terminals = true`. The default is `false`. Prismattyc saves blank terminal tabs, split ratios, directories, and focus for each Space view in this window. Reopening restores the active view. Other views restore when you first open them. Restoration opens fresh login shells in the saved directories. It does not restore shell variables, running commands, or scrollback. If a directory no longer exists, the shell starts in your home directory. Managed sessions keep their existing processes. Closing the window ends its local shells. Turning the setting off removes this window's saved blank terminal layout. #### Find an existing terminal Press **Ctrl+Shift+O**, or run `terminal_switcher` from the palette. Search by Space, session name, terminal title, or directory. Press Enter to switch to the existing terminal. Blank terminals in this window and live managed sessions appear together. A closed or moved target shows a message instead of creating a replacement session. #### Space order and Git tab labels Spaces appear oldest first. Saving or renaming a Space keeps its position. For older Space files, Prismattyc uses the file creation time when available. Otherwise, it uses the saved timestamp. Each tab shows Git information for its focused pane when that pane is in a local Git working tree. The label includes the repository and branch. An asterisk (`*`) indicates working-tree changes. A detached HEAD shows the short commit ID. The label refreshes in the background within a few seconds. Narrow tabs prioritize the branch and dirty marker. Hover over the tab to show its full title and Git information. Panes outside a Git working tree keep their usual titles.