# Everything since v0.7.0

URL: https://tuios.dev/releases/since-v0-7-0

> Unreleased work on main: session layouts that come back after a daemon restart, sessions and panes on other machines, agents you can watch and message, one layout for every client, an interface that answers the mouse, and a large cut in memory use.

1,805 commits since v0.7.0, across 1,817 files, up to 22 September 2026. None
of it is in a tagged release yet. It will ship as the next one.

The page is grouped by what you notice, most important first. If you already
run tuios, read [Before you upgrade](https://tuios.dev/releases/since-v0-7-0#before-you-upgrade) first.

## In short

- [Before you upgrade](https://tuios.dev/releases/since-v0-7-0#before-you-upgrade): wire protocol 3, new defaults,
  SSH key checks, hooks in the daemon.
- [Layouts come back](https://tuios.dev/releases/since-v0-7-0#layouts-come-back-after-a-restart) after a daemon
  restart, with new shells in the old directories.
- [Other machines](https://tuios.dev/releases/since-v0-7-0#other-machines): remote sessions, remote panes, global
  sessions.
- [Agents](https://tuios.dev/releases/since-v0-7-0#agents): agent state in the rail, mail, worktree fan-out.
- [One layout for every client](https://tuios.dev/releases/since-v0-7-0#one-layout-for-every-client).
- [The mouse](https://tuios.dev/releases/since-v0-7-0#the-mouse) (including clickable links), [the rail](https://tuios.dev/releases/since-v0-7-0#the-rail), and
  [tiling and zoom](https://tuios.dev/releases/since-v0-7-0#tiling-and-zoom).
- [Keys](https://tuios.dev/releases/since-v0-7-0#keys): keybind manager, launcher, prefix repeat.
- [Look and feel](https://tuios.dev/releases/since-v0-7-0#look-and-feel): themes, glyphs, spotlight, screen saver.
- [Graphics and screenshots](https://tuios.dev/releases/since-v0-7-0#graphics-and-screenshots) and
  [the web client](https://tuios.dev/releases/since-v0-7-0#the-web-client).
- [Scripting and control](https://tuios.dev/releases/since-v0-7-0#scripting-and-control): 62 verbs, 156 options.
- [Project tapes and startup](https://tuios.dev/releases/since-v0-7-0#project-tapes-and-startup).
- [The terminal emulator](https://tuios.dev/releases/since-v0-7-0#the-terminal-emulator): a second backend and a
  conformance suite.
- [Faster and smaller](https://tuios.dev/releases/since-v0-7-0#faster-and-smaller), and
  [other fixes](https://tuios.dev/releases/since-v0-7-0#other-fixes).

## Before you upgrade

Most of these need nothing from you.

- **Restart the daemon.** The wire protocol is version 3, and version 2 is
  refused with a message that says which side is newer. (A message type added
  after v0.7.0 shifted every type number without a version change, so mixed
  builds misread each other.) Run `tuios kill-server` and start tuios again.
  This ends every program running in your panes. The layout, window names and
  working directories come back, each pane with a new shell; scrollback does
  not. The JSON verb protocol stays at version 1.
- **New defaults on a fresh install.** A bare `tuios` starts or joins the
  daemon (`startup.daemon = true`), starts tiled (`startup.tiled = true`), and
  draws window controls as dots on the left of the title bar. An existing
  config file keeps what it says: the `[startup]` booleans are only read from
  the file, so a machine that ran standalone and floating still does. The two
  window-button options are strings and do reach a config that never set them.
  `tuios --standalone` or `TUIOS_NO_DAEMON=1` runs without the daemon. If the
  daemon will not start, a bare `tuios` runs standalone for that run and says
  so.
- **The SSH server checks keys.** `tuios ssh` used to give a shell to every
  connection. It now admits the public keys in
  `~/.config/tuios/authorized_keys`, or in `~/.ssh/authorized_keys` when the
  first file is absent. `--authorized-keys` names another file. With no keys, a
  loopback bind still runs unauthenticated and prints a warning, and any other
  address is refused. `--no-auth` opens it on a network you trust. A keys file
  that cannot be read, does not parse, or holds no key stops startup.
- **Unnamed SSH connections.** A connection that names no session, and whose
  username maps to none, now gets a session called `ssh-session` when several
  exist. It used to get whichever session was listed first. With exactly one
  session, it still gets that one.
- **Hooks run in the daemon.** `after-new-window`, `after-close-window`,
  `after-focus-change`, `after-workspace-switch` and `after-agent-state` now
  run in the daemon, with the daemon's environment. They fire with nobody
  attached, and once rather than once per client. `after-attach`,
  `after-detach`, `after-resize` and `after-layout-change` stay in the client,
  and `after-detach` now also fires when an SSH connection or a browser tab
  goes away.
- **Window-mode keys.** Digits 1 to 4 select windows. They used to snap a
  window to a corner, which is now `ctrl+b L` then 1 to 4. A config still
  carrying the default corner-snap bindings is migrated on load. Bindings you
  changed are left alone. With tiling on, `h` and `l` now focus the pane on
  that side instead of doing nothing. In the layout picker, delete moved from
  `d` to `ctrl+d`, so typing a filter no longer deletes a layout.
- **Right-click.** In window-management mode, a right-click on a pane opens the
  pane menu and a right-drag resizes. It used to only resize. In terminal mode
  the right button belongs to the pane: ctrl or shift with right-click opens
  the menu, and `appearance.right_click_opens_menu = true` makes a plain
  right-click open it too. A program that asked for the mouse always keeps the
  plain right button.
- **Frame rate.** `appearance.max_fps` now tops out at 120, the renderer's own
  ceiling. Values up to 240 were accepted before and changed nothing on screen.
- **Selection colours.** The selection, search matches and copy-mode cursor
  are now settings under `[appearance.selection]`, with new defaults: a neutral
  grey selection, amber search matches, and a cyan copy-mode cursor. Selected
  text is no longer bold. `appearance.selection.bold = true` brings that back.
- **Sidebar settings** moved from flat `appearance.sidebar_*` keys into an
  `[appearance.sidebar]` table. The old keys are still read, the table wins
  where both are set, and the file is not rewritten in the old spelling.
- **Removed.** `appearance.separator_style`, which parsed and did nothing. The
  `colorshift` screen saver effect: a config that names it reads as `random`.

## Layouts come back after a restart

The daemon saves each session's structure under
`$XDG_STATE_HOME/tuios/sessions` and restores it when it starts. This is on by
default. A detach loses nothing, because the daemon keeps the shells running.
A daemon restart or a reboot ends them: what comes back is the layout, not the
programs.

- Each window comes back with its workspace, its place in the layout, its name
  and its working directory. The session keeps its display name and accent.
- Each shell is new, started in the old directory. Scrollback and running
  programs are not saved.
- Restored shells get `TUIOS_RESTORED=1`, and `tuios ls` marks the session as
  restored until you first attach.
- A window whose shell will not start is dropped rather than restored empty.
- A state file tuios cannot read is moved aside for two weeks instead of
  stopping the daemon.
- Working directories now come back on macOS too. The daemon used to read them
  from `/proc`, which macOS does not have.

`tuios daemon --no-restore` turns restore off. `tuios resurrect` lists what is
saved, or brings one session back by name.

The daemon is also easier to reach. A bare `tuios` uses it by default (see
above). `tuios attach` starts the daemon instead of refusing. `tuios ls` with
no daemon running lists the sessions saved on disk, marked as saved, and exits
3, so a script can tell a stopped daemon from a running one that holds nothing.

Daemon panes now honour two settings they used to ignore:
`appearance.scrollback_lines` (the daemon kept 10,000 lines per pane whatever
the file said) and `appearance.preferred_shell`.

## Other machines

tuios can now work across machines. Every link is one `ssh` process per host,
run with `BatchMode=yes`, so you need to be able to `ssh` to the machine
without typing anything. See [Sessions](https://tuios.dev/docs/sessions) for the full model.

### Adding a machine

```bash
tuios hosts add build gaurav@buildbox   # writes [hosts.build] in config.toml
tuios hosts test build                  # dials it now and says what it found
tuios hosts remove build
tuios hosts                             # every link and its state
```

- The daemon follows the config file, so none of these needs a restart. The
  settings page has a Hosts section that does the same.
- An address is anything `ssh` understands. Your `~/.ssh/config` aliases are
  offered in shell completion and on the settings page.
- `tuios hosts tailnet` lists the machines on your tailnet and which ones it
  would offer. `tuios hosts add NAME --tailnet` takes the address from the
  machine of that name. It asks the `tailscaled` already running, with no root
  and no auth key.
- The link finds tuios on the far machine by itself: on the `PATH`, in the
  usual install paths, then through the login shell. `tuios hosts test` prints
  where it looked when it cannot find it.
- A link that drops is dialled again with a backoff. `tuios hosts` shows each
  link as `up`, `connecting`, `reconnecting`, `unreachable`, `no_daemon`,
  `no_tuios` or `incompatible`, with how many times it dropped and why.

### Sessions on another machine

```bash
tuios attach --host build api   # attach the session api on build
tuios new --host build          # create a session on build and attach it
```

The session is drawn by this client, with this machine's theme, config and
prefix key. Nothing is nested. If the link drops, the pane stays on screen, the
dock says it is reconnecting, and the client tries again for three minutes
before it returns to the session it left on this machine. `--ssh` runs the
tuios on the host inside this one instead, for a host whose tuios is too old.

In the rail, sessions are grouped under their machine. Enter on a session under
a host attaches it, and enter on the `+` beside a host creates one there. A
machine's header shows how many of its sessions want a person, and each row
shows what its agents are doing. A machine that is not answering is muted and
its rows cannot be clicked, because they are a cached listing.

### A pane on another machine

```bash
tuios new-window deploy --host build
```

The window belongs to this session and is laid out here. Only its process runs
on `build`. The title bar says `build:deploy`, and the rail row names the
machine, because the same typed line does different things on different
machines. The rail's file section and agent detection work for these panes. The
pane ends when the link does.

### Global sessions

Once a second machine is reachable, the rail shows a `global` group above the
machines. A session in it holds panes from several machines, and every way of
making a window there asks which machine to run it on. Anywhere else, a new
window opens on the session's machine without asking.

```bash
tuios new deploy --global
```

`appearance.global_session = false` hides the group. Making a new session (`N`
in window mode, the new `+` at the start of the dock's session strip, or the
command palette) asks which machine when more than one is reachable.

### Addressing another machine from the CLI

Verbs take a host-qualified target: `-s build:api` for a session, `-w
build:api:0` for a window. `local:` addresses a local session whose name
contains a colon. `list-windows`, `capture-pane`, `send-keys`, `send-text`,
`wait-for`, `focus-window`, `kill-session`, `session-info`, `get-agent-state`,
the agent mail verbs and the stash all take it. Every pane gets `TUIOS_HOST`,
the hostname of the machine it runs on.

## Agents

### The rail shows what your agents are doing

The rail has an agents section. Every pane running a recognised coding agent
is listed with its state, ordered by who needs you: errored, then waiting for
input, then finished but not yet seen, then working. A finished agent you have
looked at moves down the list. Rows show the harness and the last thing the
agent said it was doing. The section header counts blocked and finished panes,
and clicking the count filters the list. In the command palette, `@n` finds
panes that need input and `@w` finds working ones, across every session.

When a state changes, tuios can tell you: a dock message, an OSC 9
notification (which reaches you over SSH), and an optional sound. The
`[notifications.agent]` table has per-state switches, a settle window so a
flapping state does not spam, and quiet hours. A blocked agent's alert now
includes the question it asked. The `after-agent-state` hook receives the
transition in environment variables, never in argv, so an agent's text cannot
become shell arguments.

### Detection

- 22 agent CLIs are described by TOML manifests. `tuios explain-agent-detect`
  shows why a pane was or was not recognised.
- A process is identified by what it calls itself: its name, `argv[0]`, its
  executable, or the script an interpreter runs. A directory called `claude` or
  `codex` is no longer an agent. An agent started through `sh -c`, `timeout`,
  `npx`, `uvx`, `mise` or `nix` is found.
- State comes from six ranked sources: the agent's own report, its transcript,
  escape codes (including window-title rules, such as Codex's "Action
  Required"), screen rules, the foreground process, and last of all an output
  stall. A lower source never overwrites a higher one that is still fresh.
- The transcript reader reads only the last 128KB, so a 151MB transcript costs
  652 microseconds.
- A state clears when the agent exits back to its shell. Agents in panes on
  other machines are detected too.
- `get-agent-state` and `list-agents` report a confidence and whether the pane
  needs you.

### Agents can message each other, and you

```bash
tuios list-agents                      # every agent pane and its state
tuios send-agent-message -w build 'rebased onto main, please retest'
tuios read-agent-messages              # your own inbox
tuios ask-agent -w review 'which test is flaky?'   # ask and wait
```

- A message is at most 8KB, with a subject of up to 120 characters and up to
  eight attachments, sent as paths, never as bytes. A session's mailbox holds
  256 messages or 512KB. A sender gets a burst of ten, then thirty messages a
  minute. Mail lives in memory and ends with the daemon.
- `--reply-to <id>` answers a message and joins its thread.
  `read-agent-messages --thread` and `wait-for agent-message --thread` filter
  by thread.
- `ask-agent` refuses a question that would close a loop of agents waiting on
  each other, not only a question to yourself. Loop detection needs
  `--from "$TUIOS_PANE_ID"`.
- The person at the keyboard has an inbox called `human`. An agent writes to it
  with `send-agent-message -w human`. The mail overlay opens on `ctrl+b M`,
  from the palette, or with `i` on a rail row, and you can reply from it.
  `list-agents` reports `human_unread`.
- `tuios stash put <file>` copies a file into a store owned by the session and
  prints its path, ready for `--attach`. The file stays until the session
  ends, even if the sender deletes the original. The limits are 16MB per file
  and 256MB per session. `stash get` copies a file back out, also across a
  link.
- Mail from another machine is marked as such, and a session holds at most 32
  unread messages and 32 notices from links.

### One prompt, several worktrees

```bash
tuios worktree new fix/retry          # a git worktree and a session in it
tuios fan 3 --agent claude 'Add a retry with backoff to the HTTP client.'
tuios fan keep api-fan-add-a-retry-with-2
```

`tuios worktree new` creates a worktree under the tuios data directory and a
session in it named `<repo>-<branch>`. `worktree ls`, `worktree diff` and
`worktree rm` manage them. `rm` refuses a worktree with uncommitted changes
unless you pass `--stash` or `--force`, and it never deletes a branch. `tuios
fan` creates several worktrees, starts the agent in each, and types the prompt
once each agent is ready. `fan keep` removes the others. The rail groups
worktree sessions under their repository.

## One layout for every client

Two clients on one session used to disagree about pane sizes, because each
subtracted its own rail and dock and retiled. Now each client reports how much
space it reserves on each edge, and the daemon takes the largest reserve per
edge and hands every client one geometry. A client with less chrome draws a
blank band instead of moving the panes.

The same rule now covers the rest of the layout:

- A zoom belongs to the session, so every client draws it.
- The master-stack ratio is kept per workspace and shared.
- Where the scrolling layout's strip is scrolled to is shared, so a client
  that scrolls takes the others with it, and a client that joins lands where
  the session is looking.
- A workspace you arranged by hand stays arranged when another client visits
  it for the first time.
- A BSP tree built on one client is adopted by the others.
- Session display names and accent colours are kept by the daemon, so a rename
  reaches every client and survives a reattach.
- The rail's width is yours: switching to another session no longer changes it.

Fixes found along the way: broadcasts now reach a client in the order they were
made, two clients resizing one pane can no longer leave its emulator at a size
nobody draws, and opening or closing a pane no longer leaves a neighbour at its
old size on another client. Reattaching to a full-screen program such as `top`
no longer draws its rows one blank line apart
([#123](https://github.com/Gaurav-Gosain/tuios/issues/123)).

## The mouse

- Every overlay row highlights under the pointer and acts on a click: settings,
  the command palette, the launcher, the theme, glyph and effect pickers, the
  session and workspace lists, the layout menu, and dialogs. The keybind
  manager's rows select but do not run, and the help panel only scrolls.
- Right-click opens a menu for what is under the pointer: a pane, a dock
  segment, a workspace pill, the dock, the desktop, a session row, or a
  machine heading in the rail. Every row is an action from the keybind
  registry, and the key it shows comes from the registry too.
- Alt and the left button move a pane from anywhere in its content. Alt and the
  right button resize it.
- The left button selects text: double-click for a word, triple-click for a
  line.
- Dragging a tiled pane by its title bar moves it with the pointer in all three
  layouts, and it swaps on release.
- A click that only focuses a pane no longer resizes it, and a drag that puts a
  pane back where it was resizes nothing, so your shell no longer reprints its
  prompt.
- Sideways trackpad drift during a vertical scroll no longer jumps lists or
  walks the scrolling layout. `appearance.niri_scroll_cells` (default 8) sets
  how far one scroll event moves the strip.
- Links in a pane can be clicked. `appearance.links` sets which: `off`,
  `marked` for OSC 8 links only, or `all` (the default), which adds bare URLs,
  including one wrapped across rows. The link under the pointer is underlined,
  the pointer turns into a hand, and a label shows the target. A click opens
  it. A `file://` link opens in a new pane running `$EDITOR`, on the machine
  the panes run on, and a directory opens in the rail's file view. An `http`
  link opens a browser only for a local client. Under `tuios ssh` or
  `tuios-web` it copies the URL to your clipboard instead.
- A shake of the mouse can toggle the [spotlight](https://tuios.dev/releases/since-v0-7-0#look-and-feel), with
  `spotlight.shake = true`.

The dock is now a status line without powerline caps. Its contents are a
`[dock]` table: `left`, `center` and `right` are ordered lists of component
names, `[dock.clock]` sets the time format, and a `[dock.custom.NAME]` entry
runs a shell command and shows what it prints. A command that hangs is killed
with its whole process tree at its timeout. Workspace pills can be dragged into
a display order, which does not renumber the workspaces.

## The rail

The rail was rebuilt from a collapsible tree into flat sections. Which sections
it draws, in what order, and how tall each is, is one string,
`appearance.sidebar.sections`, with an editor in the settings panel. `spacer`
may repeat. Drag the line above a pinned section to move the split.

- **Sessions.** Each session has its own colour, derived from its name, so it
  is stable across clients and restarts. A session with a generated name is
  labelled by its directory, and any session shows its git branch when there
  is room. Sessions and workspaces can be renamed from one dialog, and session
  rows can be dragged into an order. `appearance.session_border = true` tints
  every pane border with the session's colour.
- **Files.** The working directory of the focused pane, following the pane as
  focus moves, with nerd font icons coloured by file type.
  `appearance.sidebar.folder_click` decides whether clicking a folder
  navigates the listing, sends a `cd` to the pane, or both. Six file actions:
  create, rename, delete, copy, cut and paste. Only delete asks first. Delete
  moves the file to the freedesktop trash in `$XDG_DATA_HOME/Trash`, and
  `appearance.sidebar.file_delete = "permanent"` skips it. A file on another
  filesystem is refused, and on Windows a delete is permanent and says so.
  Paste never overwrites: a name that exists gains a number.
- **Git.** Add `git` to the sections string to see the focused pane's
  repository, branch, how far it is ahead of and behind its upstream, and,
  with `appearance.git_dirty` (on by default), how many files are changed.
- **Agents**, described [above](https://tuios.dev/releases/since-v0-7-0#agents).

A program in a pane can print an OSC 7 escape claiming any directory. When the
kernel says the pane's shell is somewhere else, the file actions are switched
off and the rail says "read only: wrong folder", so a pane cannot aim a delete
at a folder it is not in. This now also works for daemon panes and on macOS.

The rail answers the mouse throughout: rows highlight on hover, a truncated row
scrolls so you can read it, the collapsed strip pops a label beside the row
under the pointer, and the rail's edge can be dragged to change its width.

## Tiling and zoom

There are three tilers: BSP, master-stack, and a scrolling layout in the style
of niri, which puts columns on an endless horizontal strip. All three honour
`appearance.gap`. A pane can be floated out of a tiled layout on its own, or
pinned so it follows you across workspaces. "All windows" in the command
palette searches every window in every workspace and jumps to the one you pick.

Windows created in a daemon session now tile. Before, only windows the attached
client made itself were placed, so daemon-created windows stacked on each other
or floated over the tiles. BSP and master-stack tiles no longer overlap when
panes shrink.

*[An interactive figure goes here. Open the page to use it.](https://tuios.dev/releases/since-v0-7-0)*

- **Zoom** slides into place instead of cutting (`appearance.zoom_animation`).
  `appearance.zoom_size` below 100 zooms like a camera: the layout stays on
  screen around the zoomed pane, with its neighbours running off the edges.
  Moving focus hands the zoom to the pane you land on
  (`appearance.zoom_follows_focus`). A tiled pane's title bar has a zoom
  button (`appearance.window_button_zoom`).
- **Scrolling layout.** `appearance.scroll_column_max` (20 to 100, default 90)
  lets a column fill the screen. Clicking, hovering with focus-follows-mouse,
  or moving focus with the keyboard brings the whole column on screen
  (`appearance.niri_click_reveals`, `appearance.niri_hover_reveals`). Column
  widths and stacks survive a session switch
  ([the post](https://tuios.dev/blog/a-session-switch-forgot-my-column-widths)), and the strip keeps its scroll
  position across a workspace switch and a retile.
- **Percentage resize.** `ctrl+b L` then 5 to 9 sets the focused pane's width
  to 50 to 90 percent of its region, and shift with 5 to 9 sets its height.
- **New windows open where you are.** A new window starts in the focused pane's
  directory. `appearance.new_window_inherit_cwd = false` turns this off.
- **Popups.** `tuios popup -- fzf` runs a command in a floating pane centred
  over the layout, and closes it when the command exits. `--width` and
  `--height` take cells or percentages (default 80% and 60%). Esc in window
  mode closes the focused popup. This is how fzf, gum and other pickers become
  tuios overlays.
- Turning tiling on after floating brings back the arrangement you had.
  Floating windows are no longer drawn over the clock, the log viewer or other
  overlays, and raising one floating window no longer reshuffles the others.

Resize was rebuilt ([the post](https://tuios.dev/blog/one-divider-five-windows)): dragging a
shared border moves only the two panes that share it, an untouched split no
longer drifts, and a drag no longer eases toward a target that has moved.

## Keys

- **One registry.** Every key tuios answers to is in the keybind registry,
  including the palette on `ctrl+p` and the launcher on `alt+space`, which sit
  in a `[keybindings.global]` section consulted in every mode. Any action can
  now be bound there.
- **Keybind manager** on `ctrl+b k`: what is bound, where tuios conflicts with
  itself, where it conflicts with the pane's program, and a Record tab that
  captures the next key you press. The same analysis runs as `tuios keybinds
  doctor` and `tuios keybinds explain <key>`. Each finding says how it knows:
  `certain` from tuios routing, `observed` from a live pane, or `reference`
  from a list of program defaults.
- **Giving a key back.** `ctrl+x` on a row, or `tuios keybinds free <key>`,
  removes that key from every action in every scope so the program in the pane
  receives it. `tuios keybinds unbind` removes one action from one key.
- **Launcher** on `alt+space` or `ctrl+b a`: anything on `$PATH`,
  fuzzy-matched and ranked by frecency, with names and icons from desktop
  entries where the platform has them. Enter runs the selection in a new pane.
  Tab types it into the focused shell so you can add arguments. All four
  search boxes in tuios now share one fuzzy matcher.
- **Prefix repeat.** After a command worth repeating, the prefix stays armed
  for half a second, so `ctrl+b` then left left left walks three panes.
  `appearance.prefix_repeat_time` sets the window, and 0 turns it off. New
  prefix bindings: the arrows walk panes, `(` and `)` switch sessions, and `a`
  opens the launcher. These work on macOS with no terminal settings.
- **New window-mode keys.** `N` makes a new session, and `b` toggles the
  spotlight.
- **Entering terminal mode on focus.** `appearance.auto_enter_terminal_on_focus`
  can be `off` (the default), `targeted` (focus by selection and arrows) or
  `all` (including Tab).
- **macOS.** An `opt+` binding on a key that composes no character, such as
  `opt+esc`, never fired. It does now, so Option+Escape leaves terminal mode as
  documented. When Option+Left or Option+Right arrives as the readline word
  motions, tuios says so and names the terminal setting to change. The default
  config no longer warns about its own bindings.
- Keys typed within 150ms of entering terminal mode are no longer dropped.

## Look and feel

- **Themes.** `tuios import-theme` converts a kitty, ghostty, alacritty or
  wezterm colour scheme, detecting the format from the file.
  `tuios list-themes` lists the 342 built-in themes plus your themes
  directory, and an imported theme works without a restart. Switching theme
  recolours every open pane in place. A theme can describe itself: each colour
  is measured against the background, and colours under the contrast floor are
  named.
- **Chrome colours.** A theme file may carry a `chrome` object with `accent`,
  `accent_bright`, `success`, `warning`, `error` and `info`, so the interface
  can use an amber accent without turning every blue in every pane amber.
  `chrome.surface` sets the colour dialogs are drawn on, and the rest of the
  chrome is derived from it at readable contrast.
- **Glyph sets.** `appearance.glyphs` picks the shapes: borders, window
  controls, rail marks, folders. Four ship (`default`, `unicode`, `heavy`,
  `ascii`), each glyph with its own ASCII fallback, and a custom set is a file
  that can inherit from a built-in. `tuios list-glyphs` lists them, and the
  settings panel has a picker with a preview.
- **Colour picker.** Colour settings open a picker with a hue strip, a shades
  grid, HSL and RGB sliders, a hex field, a harmony wheel, and the theme's own
  colours as slots that follow the theme. Settings rows show a swatch of the
  colour in force. Numeric settings open an editor with a slider and a typed
  value.
- **Zen mode.** `appearance.zen_mode` hides unfocused borders always, or only
  while the mouse is still. Content never shifts.
- **Window controls** can sit on the left, and a `dots` style draws them as
  traffic lights. The scrollbar has two styles: a single stroke in the pane's
  own ink, or a track with a half-cell thumb.
- **Copy flash.** A copy sweeps a band of light over the copied text.
  `appearance.selection.flash` turns it off, and `flash_ms`, `flash_color` and
  `flash_style` (`diagonal`, `diagonal-reverse`, `horizontal`, `vertical`) tune
  it. Only the background changes, so the text stays readable.
- **Spotlight.** `b` in window mode dims everything outside a circle around the
  mouse. The `[spotlight]` table sets the radius, how dark the rest goes
  (`dim`, default 75), and whether it follows the mouse or the cursor.
- **Screen saver.** Off until you set `screensaver.enabled`. It starts after
  `screensaver.idle_minutes` idle, with 36 effects ported from
  terminaltexteffects (`screensaver.effect`, or `random`). The picker previews
  each effect over your real screen. Effects now run at the right speed at any
  frame rate, and on an unthemed session the saver asks the terminal for its
  palette instead of guessing xterm's.
- **Keycast.** tuios can draw your keypresses in the bottom right as you type,
  with repeats coalesced, for screen recordings. It toggles from the command
  palette and the setting persists.

*[An interactive figure goes here. Open the page to use it.](https://tuios.dev/releases/since-v0-7-0)*

## Graphics and screenshots

`tuios screenshot` writes a pane to a file as `png`, `svg`, `ansi`, `html` or
`txt`. It runs in the daemon and reads the emulator's cell grid, so it works
on a detached session from a script: `tuios screenshot -w build`. On macOS it
now finds your fonts without fontconfig, including fonts inside `.ttc`
collections, so Nerd Font icons draw instead of boxes.

- **Kitty graphics over SSH.** Passthrough used to be decided by asking the
  server's terminal, and a server has none, so graphics were off for every SSH
  client. The client's capabilities now come from the SSH connection: `TERM`,
  the forwarded environment, and the pixel size it reports.
- **Animation.** The kitty animation protocol, dropped in v0.7.0, is
  implemented. Animated images send only the changed regions, which took one
  measured page from 330MB forwarded in 21.6 seconds at 17fps to 22MB at
  73fps. A flood of graphics is paced so it cannot starve the keyboard.
- **Unicode placeholders.** Images drawn with placeholder cells, such as those
  in glow's pager, now show inside a pane and scroll and clip with the text.
  They need a host that supports placeholders (kitty, Ghostty, WezTerm), so
  not tuios-web. `appearance.kitty_placeholders` forces the choice.
- **Covered images are cropped.** A window over part of an image now cuts
  that part away instead of hiding the whole image. An image no longer draws
  over the rail or the dock, and an image in a pane's last rows no longer
  scrolls the whole screen up.
- **Bounded memory.** An animated gif under chafa grew tuios to 5.3GB in two
  minutes. The same run now stays at 189MB.

## The web client

- **Text shaping.** The web client can use sip's WebGL renderer with HarfBuzz
  shaping (opt-in). Arabic letters join, ligatures form, and combining marks
  land where they belong. Grapheme widths now match a reference terminal, and
  a freeze where synchronized output was left on is fixed.
- **TLS and phones.** `tuios-web` can serve a LAN address over TLS, and
  `--auto-tls` creates and keeps a certificate for the hostname and every
  interface address. `tuios-web cert` inspects or replaces it. On a phone, a
  row of tuios commands sits near your thumb, built from the keybind registry,
  a long press opens the pane menu, and pane dividers are big enough to grab.
- **Your theme.** A browser now shows the theme you picked, and the tab shows
  tuios instead of "Sip".
- Settings changed from a browser session apply to that session only, instead
  of writing the server's config file.
- `tuios-web` takes every interface flag `tuios` takes.
- The Docker image now includes `tuios-web`.

*[An interactive figure goes here. Open the page to use it.](https://tuios.dev/releases/since-v0-7-0)*

## Scripting and control

The daemon speaks 62 verbs over a line-delimited JSON socket, one JSON object
per line each way. `tuios list-verbs` lists them with every error code a verb
can return. See [the control protocol](https://tuios.dev/docs/control-protocol).

- **Strict parameters.** A parameter a verb does not take is refused with the
  closest spelling and the accepted list, instead of being dropped.
- **Options.** One registry holds all 156 settable options. It backs
  `set-option`, `get-option` and `list-options`, and the settings panel is
  built from it. `set-option` refuses a path that does not exist, with
  suggestions, and validates colour values. It also fixed a bug where a dozen
  settings, from border style to the leader key, were saved and then read back
  as defaults on every reload, and a boolean turned off could not be saved.
- **Config reload.** Editing `config.toml` reaches the running client, even
  with editors like vim that save by renaming. A file with an error is
  reported on screen and the running config stays.
- **`new-session`** creates a session and its first window from a program,
  with no client attached.
- **`run-command`** refuses a command name it does not know, instead of
  reporting success. `tuios run-command --list` lists the names.
- **Hooks say what happened.** `tuios list-hooks` shows each hook command's
  run count, last exit code, last run and last error. A failing hook logs its
  exit code and the tail of its stderr.
- **Daemon logs.** The daemon writes errors and basic events to
  `$XDG_STATE_HOME/tuios/daemon.log`, so a crash leaves a record.
  `tuios set-config daemon.log_level messages` raises the detail without a
  restart. `tuios daemon --pprof` serves profiling data.
- **Crash report.** A panic in the interface now shows a full-screen report
  with the build, terminal, layout and last actions. `c` copies it, and `g`
  opens a prefilled issue. It never includes pane contents, titles, paths or
  session names.
- **`tuios update`** replaces the binary in place, but only when it installed
  it. It recognises a release download, the install script, `go install`,
  Nix, Homebrew and system packages, and for the others it names the
  installer that owns the binary.
- **Agent skill.** `tuios --skill` prints the recipes for driving tuios from a
  pane. A test parses every command in it against the real command tree.
- `tuios send-keys` no longer pulls a pane you scrolled back to the bottom.
- `tuios stash put` and `stash get` print only the path on stdout, so
  `path=$(tuios stash put FILE)` works.

Most of this hardening came from driving the socket the way a program would,
which [got its own post](https://tuios.dev/blog/the-daemon-said-yes-to-an-option-that-did-not-exist).

## Project tapes and startup

A `.tuios.tape` in a project directory describes the session for that project:
which windows to open, what to run in them, and how to lay them out. When the
focused shell changes into a directory that has one, tuios notices, like direnv
for terminal layouts.

A tape runs commands, so nothing happens until you have read it. The review
dialog shows the whole file, and you choose Run once, Trust and run, or Never.
Trust is tied to the file's content hash, so any edit makes it ask again. By
default a dock badge and a notification tell you a tape is there.
`auto_review` opens the review dialog on detection. `autorun = "auto"` runs a
tape without asking, but only one you trusted and have not edited since.
Running a tape builds a session named after the project and switches to it.

*[An interactive figure goes here. Open the page to use it.](https://tuios.dev/releases/since-v0-7-0)*

Three startup settings remove the steps every session used to start with:
`startup.open_default_window`, `startup.tiled` and
`startup.start_in_terminal_mode`. Only `tiled` is on by default, and only for a
fresh install (see [Before you upgrade](https://tuios.dev/releases/since-v0-7-0#before-you-upgrade)). With all three
on, tuios opens tiled with one terminal and your next key goes to the shell.
Startup also asks the terminal everything in one round trip, which took the
time to the first frame from 566ms to 68ms.

*[An interactive figure goes here. Open the page to use it.](https://tuios.dev/releases/since-v0-7-0)*

The settings menu now covers everything the config file does, with an inline
editor for text values, and changes persist.

## The terminal emulator

Behind a `ghostty` build tag, panes can run on libghostty-vt, the emulation
core of the Ghostty terminal, instead of the pure Go emulator. Both backends
implement one interface. A differential suite feeds both the same bytes and
compares screens, cursors, scrollback, modes and the output a host would
receive, after every chunk. The first version of that suite passed while the
ghostty backend painted `ls` on hot pink, which is
[its own post](https://tuios.dev/blog/my-differential-tests-passed-and-the-screen-was-pink).
Releases ship ghostty builds beside the default ones.

A conformance corpus checks byte strings on a 6x4 screen against literal text
dumps, citing esctest and vttest, and can compare against a real tmux. A
grammar-based fuzzer feeds the emulator sequences that have broken terminals
before. Two more checks need no expected output: an emitted frame must redraw
the screen the emulator holds, and the same bytes must draw the same screen
however the reads are split. Bugs they found, any of which a program in a pane
could hit:

- The cursor position report had line and column swapped.
- Under `TERM=screen`, `linux` or `vt100`, box drawing came out as `lqk`,
  because SO and SI were unreachable.
- OSC 8 hyperlinks had their fields swapped and never worked.
- DECSTBM accepted a scroll region taller than the screen, and the next scroll
  crashed the daemon and every session in it.
- A mode map read without its lock, and two captures of one busy pane, could
  each crash the daemon.
- NEL, DECALN, DECSTR and DECRQSS were missing, and insert mode was recorded
  but ignored.
- ED 3 cleared the visible screen, REP repeated the wrong character, and a wide
  character cut at a margin left half of itself behind.
- A window title containing a character from the Dingbats block, which Claude
  Code puts in its title, ended early and printed the rest into the pane,
  because a byte inside the character was read as an eight-bit string
  terminator. Invalid bytes in titles are now dropped.
- A single shift (SS2, SS3) before a multi-byte character leaked into the next
  character.
- Underline and other attributes were dropped on the focused pane's render
  path.

Where the two backends still disagree, the difference is pinned as a known
divergence rather than left to pass quietly.

## Faster and smaller

Every number here comes from the commit that made the change.

### Speed

- Idle: with idle shells and the rail open, tuios schedules no timer-driven
  renders. The render ticker now runs at `max_fps`, which at the default of 60
  took an idle client from 1.0% to 0.6% of a core.
- Typing a letter into a shell: 18.9us and 177 allocations down to 1.06us and
  14\. A keystroke over the socket: 4,009ns and four writes down to 967ns and
  one.
- Keypress to echo: the median went from 9.75ms to 1.78ms. Typing into one pane
  while another floods went from an 18 to 28ms median round trip to 1 to 5ms,
  against tmux's 1.
- A pane that falls more than 4MiB behind drops to catch-up rate until it
  recovers, and a busy pane can no longer starve the renderer.
- Workspace switch: the client asks only for rows it does not have. At four
  panes that is 11.5MB of encoding down to 0.6MB, and a caught-up switch went
  from 47.6ms to 2.4ms per pane.
- Drawing a pane's frame: 6.53ms to 1.58ms when dirty, 3.51ms to 62.7us when
  cached. The pane renderer stopped re-wrapping content that was already the
  right width, which was
  [a fifth of every frame](https://tuios.dev/blog/a-fifth-of-every-frame-went-to-changing-nothing).
- The rail's rows: 82,000ns and 178 allocations per frame down to 288ns and
  none.
- Moving the pointer over plain text no longer composes a frame per cell.
- A truecolor repaint: allocations down from 757,200 to 189,600 per run of the
  benchmark. Scrolling short lines: 65 to 79 percent less CPU.

### Memory

- Scrollback is stored as text with styles marked where they change. A
  175-character line went from 4,864 bytes to 216, and a full 10,000-line
  history from 48MB to 4.2MB per pane, in the daemon and again in each client.
- An empty 207x55 pane held 1.7MB before printing anything, 1.3MB of it the
  screen grid. A row is now allocated only when something is printed on it,
  and an empty pane's grid holds 28KB. The alternate screen is built on first
  use.
- Eight empty panes: heap from 47.5MiB to 14.9MiB.
- On the ghostty backend, `scrollback_lines` now holds. The library pruned by
  bytes first, so a pane asked for 10,000 lines kept about 400 at 207 columns.

## Other fixes

- The last output of a pane, often its prompt, sometimes stayed invisible until
  the next key. It is now drawn at once.
- A pane scrolled back stays on the line you stopped at while new output
  arrives.
- Ctrl+P opens the palette even when the terminal reports Num Lock in the key
  event.
- Quitting a session says "Killed", not "detached", and names the session you
  were in.
- A grapheme cluster split across two writes renders as one.
- Programs in tuios are told graphics forwarding is available, so image tools
  stop falling back to blocks.
- Clipboard copy and paste work on GNOME Terminal and other VTE terminals, and
  over a loopback SSH connection, using the native clipboard tool.
- Ctrl+D in a pane on another machine closes it at once instead of on the next
  key.
- The log viewer's copy keys (`E` for errors, `A` for all) now work.
- Help opened from the leader starts at the top. In help, `q` closes it
  outside a search and can be typed inside one.
- Mouse scroll speed and window title format are configurable, in the file and
  in the menu.
