Workbench
I Built a tmux Dashboard, Then Removed Half the Dashboard

I built a tmux dashboard, gave it separate screens for everything, and eventually made it better by removing half the dashboard. Session names were not the problem anymore. I needed to see which coding agent had stopped for me, what was actually in its pane, and how to get into that work without losing the map.
The current source and Linux releases are public in ttpears/tad. I maintain the project. It is a Rust terminal interface for tmux, released under the MIT license.
The session list stopped answering the question
tad began as the kind of shell function that grows in a dotfiles repository: attach to a named tmux session if it exists, create it if it does not. That works while the question is simply “which session do I want?”
It stops working once several sessions contain coding agents, background shells, remote hosts, and worktrees. A name such as api-fix says where the work started. It does not say whether the agent is still producing output, waiting at a prompt, or sitting untouched because I forgot about it.
The first full dashboard tried to solve that by making every kind of object a destination. Sessions, projects, groups, hosts, and agents each had a view. That was orderly in the code and tiring in use. The information I checked constantly was separated by the same navigation as reference data I touched occasionally.
In the v0.13 redesign, I deleted the Projects abstraction and removed more than a thousand lines net. A project had become another name layered over tmux state, not a distinct thing I needed to manage. The same change added a confirmation before killing a session or agent. Pressing d now opens a y/N prompt; only y or Enter proceeds. A habitual double-tap of d cancels instead of killing anything.
That commit was less about shrinking a feature list than admitting what the dashboard was for. It was not a database of everything I might connect to. It was a way to find live work and act on it quickly.
A label is not live state
The next problem was the preview. Metadata can tell me a working directory, process, window name, and last activity time. It still cannot tell me that an agent has stopped on a permission question, a failed test, or the last line of a stack trace.
The v0.14 preview overhaul changed the pane beside the list from a description into a capture of the actual tmux pane. The current implementation runs tmux capture-pane -p against the selected pane’s unique target, trims trailing blank rows, removes control characters, expands tabs, and shows a bounded tail. If the pane disappears between discovery and rendering, the preview falls back to a metadata card instead of turning into an empty box.
Using the pane itself matters when two agents share a working directory. Choosing the newest transcript by directory can make both rows show the same conversation. A tmux target such as session:window.pane is unique, so the preview stays attached to the row I selected.
The distinction is small on a feature checklist and obvious in use. I do not need a dashboard to repeat the label already visible in the sidebar. I need it to show the piece of information that determines my next action.
“Active” was still too vague
An updating pane is useful evidence, but modification time alone cannot answer whether an agent needs me. Recent transcript activity might mean the agent is working, or it might mean the agent just finished its answer and is waiting at the prompt.
The current agent scanner starts with every tmux pane, walks the descendant process tree under its shell through Linux /proc, and asks a provider implementation whether a process is a supported coding agent. For Claude Code, it then finds the most recent session transcript for that working directory and inspects a bounded 256 KiB tail.
The classifier works backward from the newest useful event. An assistant end_turn with no later user event means blocked: the next move belongs to me. A tool call, tool result, or new user message means working. An away_summary can downgrade a finished turn to away so an abandoned session does not keep shouting for attention. Unknown transcript shapes do not crash the dashboard; they fall back to recent file activity and eventually idle.
● blocked — needs my input
◐◓◑◒ working — actively producing output
○ idle — quiet for a while
◌ away — snoozed or no longer active
The scanner sorts recent work first, groups agents under their tmux sessions, and gives blocked work priority in the section count. The symbols are compact because they sit beside the thing I need to open; they are not pretending a heuristic is a detailed progress report.
I wanted to work beside the map
Opening a row used to solve one problem by recreating another. It took me to the pane, but the dashboard disappeared. Going back and forth was still navigation overhead.
In the first pane-pinning change, the o key began pulling the selected pane into a split beside tad. The current cockpit expands that to four pinned panes in a small grid. This is not a copied terminal view. It is the real pane, moved with tmux, so I can focus it and work while the sidebar continues to update.
Moving a real pane creates a recovery obligation. tad records the pane ID, original window ID, session, window name, and index before the move. Unpinning joins it back to the original window when that window still exists. If the window vanished, it breaks the pane into the original session, restores the name, and tries to restore the index. If the session vanished too, the pane becomes its own window instead of remaining stranded beside the dashboard.
Quitting the dashboard returns every pinned pane first and restores the window’s previous border setting and width. Refresh also drops a pin whose pane was killed elsewhere. The feature is useful because it is reversible, including when the surrounding tmux state changes while the pane is away.
The cockpit still had too much permanent furniture
The v0.16 sidebar cockpit put Sessions and Agents into one scrollable column with a preview beside it. It also added mouse support, collapsible sections, the four-pane grid, remembered sidebar state, and the semantic agent statuses.
Groups and Hosts were still permanent sections, though. I use them to open a known collection or connect to another machine. I do not need to stare at them while watching active work.
Version 0.17 moved Groups and Hosts into on-demand pickers. Pressing g or h opens a filterable overlay; choosing an item opens the group or connects to the host. The data remains available without taking vertical space from the two sections that change constantly.
Host discovery also stays local. tad reads concrete entries from SSH config, non-hashed known_hosts entries, and repeated SSH commands from shell history. It does not probe the network or perform DNS discovery. History-only names must appear more than once by default, which keeps a typo from becoming permanent interface clutter.
This is the design rule I kept rediscovering: frequency should determine prominence. A capability does not earn permanent screen space merely because it exists.
What I verified for this article
I inspected the current 0.17.0 source at commit 7ff281a, the redesign commits above, the release workflow, and the project’s current GitHub Actions results. The CI run for the current main-branch commit completed successfully. The 0.17.0 release commit also has successful CI and release runs.
The CI job uses Rust 1.94.1 and runs formatting checks, a locked release build with warnings treated as errors, Clippy across all targets with warnings treated as errors, and the locked test suite. The release publishes a static Linux x86_64 binary, Debian and RPM packages, and shell completions.
I did not rerun the Rust suite on this Windows machine because neither the Windows environment nor its WSL installation has a Rust toolchain. I am treating the successful repository CI as the build and test evidence, not presenting it as a local run.
What the dashboard is for now
tad still manages tmux sessions, opens host groups, provides shell completions, and can install a popup key binding. Those features are useful. They are not the reason the dashboard finally feels coherent.
The cockpit answers three questions in one place: what work exists, which agent needs me, and what is actually happening in that pane. Everything else can wait behind a key until I need it.
The biggest improvement was not adding the live preview or the pin grid by itself. It was removing the screens that made those signals harder to see.
Disclosure: I maintain tad. It is open-source software under the MIT license. Repository links are not affiliate links. The hero is project artwork from the repository.