Three of the things in this release are the same idea: jmux should work out
what you meant instead of asking you to assemble it. Your repos and teams are
Projects now, the Command Center works out its own membership, and a new
user gets a flow instead of a checklist.
A first run that goes somewhere
The old checklist had eight rows, a progress figure that disagreed with them,
four unexplained glyphs, and four steps that closed themselves and dropped you
into the settings screen with no explanation and no way back. One of them
printed jmux-control skill: installed to … straight onto the rendered frame.
It's a flow now — one modal that owns its own steps. It opens when there is no
config.json, and from Setup in the palette (Ctrl-a p) forever after. It
starts by asking what you came for:
- Just run agents — 3 steps, about a minute
- Agents, wired to my issue tracker — 6 steps
- I'll do it myself — nothing configured, nothing claimed
That answer decides which pages exist, which is what removes "you can't do
this yet" from the flow entirely: a page that needs the tracker comes after the
tracker page. esc always zooms out — page, then map, then closed — and no
step has to be completed to move past it, so "no tracker account today" stays
recoverable. Nothing advertises an action it can't perform: the workflow page
drops its "use these" hint when there are no statuses to use, rather than
offering a button that quietly does nothing.
Existing users see no new screen. Having a config.json is what suppresses
it.
Projects
issueWorkflow.teamRepoMap and repos are gone. A Project is one repo, at
most one team, and its own settings:
{
"projects": [
{
"id": "01J…",
"title": "api",
"dir": "~/Code/api",
"teamId": "…",
"settings": { "defaultBaseBranch": "main", "agentCommand": "claude" }
}
]
}Settings resolve in three tiers — built-in, then projectDefaults, then the
Project's own — sparse by key presence. Pinning a value that happens to equal
the global is still a deliberate override, so changing the global later doesn't
silently move the Project with it.
Starting work on an issue routes to a Project with five outcomes rather than
two: resolved, unclaimed, ambiguous, conflict, orphaned.
Disagreement is a distinct answer from absence — a stored route
contradicting a linked MR is a different problem from having no information at
all, and collapsing them produces a confident wrong answer. Each says what it
knows: TRA-123 → api (linked MR), or TRA-123 has conflicting routes — issue route → api; linked MR → web.
An ambiguous issue is answerable, so jmux asks instead of falling through to
the manual picker having learned nothing — and then offers to remember the
answer: Just TRA-123, or Always for "Billing". The second is withheld
when the issues jmux has actually seen say that Linear project has gone to more
than one Project, because "always" would be a lie. Routes live in config.json
and are visible and deletable, since a rule written by a keystroke you may not
remember making has to be inspectable.
An existing session always wins outright, before any of that. A session whose
stamp names a Project you've since deleted is reported as orphaned — never
silently re-routed, because moving work that already has a worktree is the one
thing this exists to prevent.
Sessions carry their Project durably (@jmux-project, and in the durable-session
snapshot), because two Projects may share a directory and ctl has no IPC into
the running TUI. ctl status and ctl workflow board both report it, the
sidebar groups and bands by it, and Ctrl-a n offers your Projects ahead of
scanned directories.
ctl issue start goes through the same resolver rather than its own copy —
which is what closes a live regression the migration would otherwise have
opened: with teamRepoMap deleted, the CLI's own lookup answered nothing for
every issue and refused work the sidebar would have started. --repo still
wins where you pass it.
Upgrading: the migration runs once at startup. It computes the whole new
document first, writes config.json.backup-<timestamp> beside your config
before touching it, and removes the legacy keys only once the new file is
durably on disk. It's idempotent — projects existing is what suppresses it, so
a Project you delete stays deleted.
Downgrading is not supported, deliberately. An older jmux carries projects
through intact, so nothing is destroyed and upgrading again restores everything
— but it won't understand them, so issue routing and per-repo overrides sit
inert until you do.
The Command Center derives its own membership
You don't pin panes to populate the grid any more. It shows whichever sessions
the active view's filter/group/sort would put in your sidebar, computed by the
same primitive the sidebar uses — so it fills and empties on its own as agents
start and finish, and a sidebar disclosure gesture can't change what it mirrors.
One tile per session. tmux ties the current window and zoom to the session,
not the client, so two tiles genuinely cannot show two panes of one session
full-bleed at once — by any arrangement of pins. A session running several
agents shows one at a time, elected by live urgency; Ctrl-a x cycles which,
and the focused tile's border says ⌃a x agent 2/3 so the others are a
visible fact rather than something you have to already know.
Tabs are replaced by views: named presets of the grid's own axes, as a strip
of chips along the top. Switching views adopts that view's axes outright; a ·
on the active chip means your live narrowing has drifted from what's saved, and
Save current axes as view… is how you keep it.
| Key | Action (in the grid) |
|---|---|
Ctrl-a C |
Toggle the Command Center, from anywhere |
Ctrl-a P |
Remove the focused session — or add the one you're in |
Ctrl-a ↵ |
Open the focused tile's session full-size |
Ctrl-a x |
Cycle which of the session's agent panes the tile shows |
Ctrl-a z |
Zoom the focused tile, or restore |
Ctrl-a D |
Density: Fit (all visible) / Focus (four big) |
Ctrl-a G / s / f |
Cycle the grid's own group / sort / filter |
Ctrl-a 1…9, [ / ] |
Switch view |
Two per-session exceptions layer on top, and neither is ever silent. Pin to
Command Center keeps a session on the grid when the view wouldn't have it, and
prefers that pane as its face. Ctrl-a P hides a session until you bring it
back — the palette's Show hidden sessions (N)… lists every one. Hiding
always beats a pin left on one of that session's panes: hide's subject is the
whole session, a pin's is one pane in it, so pinning can't quietly undo an
explicit "keep this off my grid". Sessions dropped by commandCenter.maxTiles
show as +N not shown, and an empty grid names the view it's on and the keys
that widen it.
Your existing commandCenterTabs and autoPinAgentPanes keys are left in place
rather than deleted, and every legacy @jmux-pinned value — tab ids included —
reads as a plain "keep this on the grid".
Settings you can edit without opening the JSON
◂ ▸changes the selected row's value. Booleans toggle, numbers step
their ladder, lists cycle and commit live.Enteris now only for values you
have to type or search for, and a row with no ordered ladder declines rather
than pretending — the footer names only the keys that row actually answers./searches every category at once, andj/kmove like everywhere else.- An explain line for the selected row, always given its own reserved row.
- A rejected value says why instead of vanishing.
- Settings that were source-only are on the screen, and three Diagnostics
rows that said↵ editwhile doing nothing are now honestly read-only. - Pick a naming command from a list instead of hand-writing argv — and
Test naming command runs it and shows you what came back. That row is what
makes the setting honest: an automatic naming failure is silent by design, so
a command that returns a preamble or nothing has no other way to announce
itself. - Every stepped row now debounces its write, so holding a key moves the sidebar
live and writes once. Ctrl-a I→ Projects… for per-Project settings, each with its health
stated.
A tracker change applies without a restart
This is the bug the release came from. adapters was built once at import time
and the config watcher never rebuilt it — so choosing Linear did nothing until
you restarted, and the workflow screen, starved of statuses, hid the one
affordance that would have built your workflow for you. A setting that looks
configured, is not in force, and has nothing on screen willing to say so.
Swapping adapters is now an app-wide transaction carrying an epoch: every async
consumer re-checks it after each await, before any write, so a late 401 or
429 from the adapter you just retired can no longer mark the current one as
broken.
Credentials are verified rather than checked for existence — GitHub and
GitLab get identity probes, and transient blips are retried instead of read as
a bad token. A rejected token no longer destroys the working one.
Config that survives a bad write
config.jsonis written atomically, and a failed write is reported rather
than swallowed.- jmux refuses to start on a corrupt config — before tmux — with a
diagnostic naming the file. - An unparseable config is no longer treated as an absent one, which is what
used to invite first-run setup to write over it. - The watcher watches the config's directory, not its inode, so an atomic
replace doesn't blind it. - Writes latch off when the file on disk goes bad, so a good in-memory
config isn't flushed over a file you're mid-edit on. - The document carries a schema version, and unknown keys survive a round trip.
Which tmux config jmux sources
Layer 3 protects only what jmux can't run without; everything else it ships is
presentation you're invited to override — which is how an elaborate tmux config
lands its own chrome on top of jmux's UI. userTmuxConfig is the way out:
Unset auto-detects — and the detection changed. jmux now resolves the two
locations tmux itself documents, in order: ~/.tmux.conf, then
$XDG_CONFIG_HOME/tmux/tmux.conf. jmux had only ever checked the first, so if
you keep yours at the second it has been silently ignored and now won't be.
Also on the settings screen under tmux. It's read only when the tmux server
starts, so the row says restart to apply until you exit every session and
run tmux kill-server.
Also fixed
- jmux could not start on tmux 3.3a.
- Closing a pane quit jmux when it had attached to a tmux server it didn't
start itself. - The info panel's tab strip owns its own row, so its tabs can be clicked —
the toolbar was swallowing every click while hover kept working, which is
what hid it. Escapeout of the Projects or workflow screen goes back where you came from.ctl statusreported no agent kind for sessions that plainly had one, and
took an inherited session-scoped state at face value: a shell is not an agent
just because tmux says it has state.- Command Center: density resized only the tiles on screen; truncation ate the
dirty marker; the strip could window away the chip for the view you were in;
repairing an out-of-bounds view name deleted the view instead. - The session index was rebuilt once per issue on every poll.