Skip to content

Releases: theyashgupta/dispatch

v3.6.2 - Parked, and an undo for groups

Choose a tag to compare

@theyashgupta theyashgupta released this 10 Sep 20:41
v3.6.2
10d05c0

v3.6.2 - Parked, and an undo for groups

A ticket had work in it, but nobody was on it. It was not To Do, because a worktree and a Claude session existed. It was not In Progress, because the agent was idle and you had moved on. So it sat in In Progress and cluttered the board. A group had a similar problem. Once you started a group, you could not take it apart. A wrong grouping had no path back and no way to recover a mistake. This release adds a Parked column for the first case and a reversible Unwind for the second.

See it

Dispatch v3.6.2: drag a started ticket into the new Parked column, unwind a started group from its panel, undo from the toast, then find the group in the Archive under Settings

Higher-quality video: dispatch-v3.6.2-parked-unwind.mp4

A column for work you set aside

Parked sits between In Review and Done. You move a ticket there by hand, with a drag or the Move to picker. Nothing automatic moves a ticket into Parked. A Parked ticket keeps its worktree and its Claude session. The terminal stays live, and the card keeps its PR badge. When the agent finishes while the ticket is Parked, the ticket stays Parked. Dispatch records the status marker and does not move the card. When you send the ticket a prompt, it moves back to In Progress on its own. Dispatch keeps the recorded marker, so the text that is still on the pane cannot bounce the card straight back to Agent Done. If the session dies while the ticket is Parked, the card offers Resume as before, and the ticket stays in Parked.

Unwind a group, and undo it

A group card and each of its members now show an Unwind button in the detail panel. Click it and pick To Do or Inbox. Dispatch kills the group's terminal and tmux session, moves the group card into an Archive, removes the group link from each member, and sends the members to the column you picked. Nothing on disk changes. The worktrees, the branches and the workspace folder stay where they are. A toast appears with an Undo button. Undo puts the group back in one step: the same card, the same column, the same session record, and the members re-linked. The restored session shows Resume, and one click brings Claude back with no re-setup. Undo refuses when a member moved on, for example when you dragged it to another column, grouped it again or started it. The toast then shows which member blocks the restore, and the archive row stays.

The Archive in Settings

Settings, Cleanup gains an Archived groups section. Each row shows the group, its members, and where the members went. Restore does the same thing as Undo, at any time. Delete removes the worktrees and the workspace folder. It never deletes a branch. Delete refuses when a worktree has uncommitted changes, or when Dispatch cannot check a worktree. The row then shows the reason and a Delete anyway button. A retention setting on the same page deletes archived groups on its own after a number of days. The default is 30. Zero means never. The automatic delete never forces, so uncommitted work stays until you delete it by hand.

What changed underneath

The board database gains an archive table. An unwind writes the whole group card into it, with every session marked lost, before the card set persists without the group. A crash between the two leaves a restorable row, not a vanished group. A restore re-inserts the snapshot as a whole object, so the session projection keeps its single writer. The cleanup saga's kill, preflight and removal steps moved into named helpers. The Done cleanup and the archive delete now run the same code in the same order.

Unwind holds the card-scoped cleanup guard while it kills processes, so a resume, a cleanup or a second unwind cannot interleave with it. Delete reads the live card map before any destructive step. A delete that lands while a restore persists refuses instead of removing a live group's worktrees. The retention sweep skips a row whose group is live again. The Undo toast ignores a slow restore result that belongs to an earlier group, so it cannot dismiss or mislabel a newer toast.

Known limits

Unwind works on groups only. A single started ticket keeps its identifier as its workspace path, branch and tmux name, so a restart after an unwind would adopt the old worktree in place. The pane-scan status channel does not move a Parked card back to In Progress. Only the hooks channel does, the same rule that already applies to Agent Done and In Review. There are no keyboard shortcuts for Park or Unwind yet.

Upgrade notes

There is no configuration step. The archive table is created on the first boot. archiveRetentionDays is optional in config.json and defaults to 30. Restart the running Dispatch after you upgrade.

Full changelog: v3.6.1...v3.6.2

Requires Node ≥ 22.22, tmux, ttyd, git, and the Claude Code CLI. Setup is in the README.

v3.6.1 - The reply stays in view

Choose a tag to compare

@theyashgupta theyashgupta released this 10 Sep 14:47
c8643e2

v3.6.1 - The reply stays in view

You asked Claude a question in a long session. The reply started to stream, then it slid below a green line and out of sight. The line stayed where it was. You scrolled to find the new text, and the view did not follow it. This release removes the line and makes the terminal follow the reply again. It also lets you cmd-click any markdown path that Claude prints.

See it

Dispatch v3.6.1: a long Claude reply streams in the session terminal, the view follows it, a scroll up holds the position, and a scroll back to the bottom resumes the follow

Higher-quality video: dispatch-v3.6.1-scroll-follow.mp4

The reply stays in view

The green line was a tmux pane border. It reads 0 "<pane title>" in the default tmux format. Claude Code draws it when it starts its first tmux teammate: it enables pane-border-status for the window, and it never disables it. After the team ends, the border row stays above the pane. The pane then starts on row 1. tmux scrolls a pane in that position through a partial scroll region. xterm.js only adds rows to its local scrollback when the region is the whole screen. So the scrollback stops, the border row stays fixed, and the live pane moves under it. The newest rows sit below the view.

Dispatch now installs a tmux hook on every session. When the window returns to one pane, the hook disables the border, and the pane goes back to row 0. The same reset runs when you open a terminal, so a session that was already stuck heals on the next reattach. While a team runs, the border stays on, because it labels the teammate panes. A multi-pane window cannot scroll the client in any case.

The result is the behaviour you expect from a terminal. A streaming reply stays in view. A scroll up holds the position you chose. A scroll back to the bottom resumes the follow.

Cmd-click any markdown path

Claude links only the Write(notes.md) header with an escape sequence. The result line Wrote 12 lines to notes.md and every path in prose were plain text, and a cmd-click on them did nothing. The terminal now finds plain .md and .markdown paths on a line and opens them in the Markdown viewer on cmd-click. A relative path resolves against the session workspace and its repo folders. A path that Claude prints from inside a repo therefore still opens.

What changed underneath

A new tmux adapter function, pinPaneBorderOff, sets a session-scoped window-layout-changed hook and runs the reset once. newSession and ensureTerminal both call it, next to the existing mouse off and status off pins. A regression test runs against a real tmux server. It splits the window, enables the border, and kills the split pane. It then asserts that the pane returns to row 0 and fills the window. The test fails when the fix is removed.

The markdown resolver is a new endpoint, GET /sessions/:id/terminal/markdown?path=. It accepts only .md and .markdown names, resolves each candidate with path.resolve, and rejects any result outside the workspace folder. The client opens the blank tab inside the click gesture and navigates it after the resolve returns, so the popup blocker does not fire.

Known limits

A burst of output larger than the screen still gets a full tmux redraw instead of line feeds, so that burst adds no scrollback. This is tmux behaviour and is unchanged. The terminal does not link a markdown path with spaces as plain text. The escape-sequence header link still covers it. The terminal does not link a path that wraps across two terminal rows.

Upgrade notes

There is no configuration step. Restart the running Dispatch after you upgrade. A session that is already stuck heals the next time you open its terminal. There is no need to restart Claude.

Full changelog: v3.6.0...v3.6.1

Requires Node ≥ 22.22, tmux, ttyd, git, and the Claude Code CLI. Setup is in the README.

v3.6.0 - Usage pacing: does the budget last?

Choose a tag to compare

@theyashgupta theyashgupta released this 09 Sep 13:38
fa0f1b6

v3.6.0 - Usage pacing: does the budget last?

You opened the account popover and read 26% used. Was that fine? On day 8 of the month it is. On day 3 it is a problem. The number gave you no reference point, so you did the division in your head, or you did not do it at all. This release does the division for you on every usage window. One line tells you whether the current rate holds until the reset.

See it

Dispatch v3.6.0: open the account popover and read the pace badge, the elapsed marker and the projection line on each usage window

Higher-quality video: dispatch-v3.6.0-usage-pacing.mp4

Every usage window shows its pace

Click the usage chip in the strip. Each window row in the popover keeps its bar and its percent, and gains three things. A thin marker on the bar shows how much of the period is over. You see the gap between usage and time at a glance. A badge reads On track, Ahead of budget, or Will run out. One line below the bar names the projection. A monthly credit budget reads "At this rate, credit runs out on 15 Sept". A session window reads "At this rate, limit hits in 40m". When the rate is fine, the line says so: "credit lasts the month" or "limit holds until reset". Hover the row or the badge to read the raw numbers: used percent, elapsed percent, and the burn rate. The rate is per hour for a session and per day for every other window.

The badge thresholds are simple. Pace is the used percent divided by the elapsed percent. At most 1.0 is On track. Above 1.0 and at most 1.25 is Ahead of budget. Above 1.25 is Will run out. A window at 100% used is always Will run out, and its line reads "Limit reached" or "Credit used up".

The same view applies to every plan. Enterprise accounts see it on the monthly credit budget. Teams, Max and Pro accounts see it on the session and weekly windows. The strip chip and the Settings Accounts tab do not change.

Resume opens the newest conversation

A Dispatch session used to remember one Claude conversation id, captured from the first hook event and never changed. After a context handoff you cleared the pane and started a new conversation. Dispatch logged the new id as a mismatch, and Resume reopened the old, full conversation.

Each session record now keeps a history of every conversation the hooks report. Resume opens the most recent one. When claude --resume answers "No conversation found" for that id, Dispatch marks that conversation missing. The next Resume opens the previous one. A Restart no longer resets the id, so the history survives it. On the first boot after the upgrade, a migration seeds one history entry for every record that already names a conversation. Existing tickets therefore resume the same conversation as before.

What changed underneath

Every usage window on the wire now carries a derived period start and end. The usage endpoint reports only a reset time for session and weekly limits. The start is therefore the known window length back from the reset: 5 hours for a session, 7 days for a weekly limit. The endpoint reports no billing period at all for the enterprise credit budget, so Dispatch uses the local calendar month. A window kind Dispatch does not know gets no period and shows the plain bar.

The pace math runs in the browser, anchored to the instant Dispatch fetched the usage snapshot. The used percent dates from that instant, so the elapsed time does too. A refetch under an open popover cannot skew the ratio. Under 1% elapsed there is no projection, so a fresh period never reads as a problem. A stale snapshot, for example after a rejected token, keeps its bars but hides the pace row. A rate that improves as the data ages would mislead you.

A new environment variable, DISPATCH_USAGE_URL, points the usage fetch at a local stub. It exists for testing recorded Enterprise and Teams payloads. The bearer token goes to whatever host you name, so treat it like any other value in your shell environment.

Known limits

The credit budget's period is a calendar month by assumption. The endpoint returns no billing boundaries, so a plan that bills on another day shows a month that starts on the 1st. The projection is linear. A burst of usage early in a window reads as Will run out until the rate settles.

Upgrade notes

There is no configuration step. Restart the running Dispatch after you upgrade. The first boot runs the session history migration once, with no operator action.

Full changelog: v3.5.1...v3.6.0

Requires Node ≥ 22.22, tmux, ttyd, git, and the Claude Code CLI. Setup is in the README.

v3.5.1 - Rotate shows the value you replace

Choose a tag to compare

@theyashgupta theyashgupta released this 08 Sep 20:23
5229ae1

v3.5.1 - Rotate shows the value you replace

You clicked Rotate on a filled key and got an empty box. The value you were about to replace was nowhere on the screen. You could not copy it to revoke it upstream, and you could not compare it with the new one. The same page had two smaller faults from v3.5.0. Each key card lost its inner padding. The search box's focus ring was clipped at the edge of the column.

This release fixes all three.

See it

Dispatch v3.5.1: click Rotate on a filled vault key, read the current value, enter a new one, save

Higher-quality video: dispatch-v3.5.1-rotate-current-value.mp4

Rotate shows the current value

Click Rotate on a filled key. The editor now opens with a "Current value" line above the "New value" box. The line shows the stored value in full, and you can select and copy it. It stays on the screen until you click Save value or Cancel edit. Cancel leaves the stored value as it was. Save replaces it, and the "Previous" line from v3.5.0 then holds the value you just replaced.

Set value on an empty key is unchanged. It has no current value, so it shows no line and makes no request.

The key list looks right again

The v3.5.0 key cards used a spacing token that does not exist. Their padding collapsed to zero, and the key name sat on the border. Each card now has its 8px padding on every side. The search box's focus ring no longer has an offset, so it is not clipped at the column edge.

What changed underneath

The vault gains one route, GET /api/vault/:name/value. It returns the current value of one named key and nothing else. The rotate editor calls it when it opens for a filled key, and only then. The list route still carries no value and no length hint. The route sits behind the same gate as every other vault route. A loopback caller or a valid remote session can therefore read one key's current value on request. This is a deliberate change: before this release no route returned a current value.

Upgrade notes

There is no migration and no configuration step. Restart the running Dispatch after you upgrade so the server serves the new route.

Full changelog: v3.5.0...v3.5.1

Requires Node ≥ 22.22, tmux, ttyd, git, and the Claude Code CLI. Setup is in the README.

v3.5.0 - The terminal is a shell now

Choose a tag to compare

@theyashgupta theyashgupta released this 08 Sep 13:47
5b6bb09

v3.5.0 - The terminal is a shell now

A ticket's terminal used to be Claude and nothing else. Press Ctrl-C twice, and the tmux session died with it. The card flipped to "Session lost". The only way back was Resume, which created a new session from nothing. You could not run git status. You could not run anything.

This release changes what the terminal is. The tmux session now runs your login shell, and Claude is a program that Dispatch starts inside it. Ctrl-C leaves you at a prompt in the workspace, the session stays alive, and a "Run Claude" button restores Claude with the same conversation.

See it

Dispatch v3.5.0: exit Claude with Ctrl-C, run git status at the shell prompt, click Run Claude

Higher-quality video: dispatch-v3.5.0-shell-session.mp4

Ctrl-C returns a prompt, not a dead card

When you exit Claude, the pane shows your shell in the ticket's workspace directory. The tmux session, its scrollback, and the browser terminal all survive. The card keeps its session, and no recovery notice appears. Type any command there. git -C <repo> status shows the ticket branch, because the workspace directory holds one worktree folder per repository.

The "Session lost" notice and Resume still exist. They now mean one thing only: the tmux session itself is gone, for example after a reboot.

Run Claude, with the right flags

The terminal panel gains a toolbar with one button, "Run Claude". Dispatch types the same launch line a fresh start uses, with the hooks that route status back to the card. When Dispatch recorded the conversation id, the relaunch resumes it. When it did not, the relaunch starts a new conversation.

The typed line goes into your shell history, so Up-arrow and Enter relaunch Claude with the same flags. A bare claude typed by hand also works. It runs without the Dispatch status hooks, so the card's status stays where it was until the next full launch.

Run Claude refuses to type into a pane that is busy. It answers a clear error in three cases: Claude is already running, another launch is in flight, or an older Dispatch build created the session.

What changed underneath

Dispatch waits for your shell to reach its prompt before it types the launch line. It watches the pane's foreground process group, not process names. macOS /bin/sh re-executes as bash, so the names disagree even at an idle prompt. The wait exists because a real run showed a shell startup prompt swallowing the typed line.

Dispatch single-quotes every token of the launch line. A Settings argument such as --append-system-prompt 'be terse; $(id)' reaches Claude as one literal argument. Dispatch refuses a token that carries a control byte, because the shell's line editor acts on such bytes before the parser sees a quote.

A session created by an older build keeps working. Run Claude declines it with a clear message. It becomes a shell session when Claude exits and Resume relaunches it.

Vault: the previous value, and search

A rotated vault key now remembers the value it held before the rotate. The key shows a "Previous" line with an eye button. A click reveals the previous value. A second click hides it. The previous value stays in its own sealed file. No other route returns it.

A search box above the key list filters keys by name and purpose. Each key now sits in its own bordered card.

Known limits

A shell startup prompt that runs as a shell builtin, for example read in your rc file, is not detectable. The start then fails with that prompt visible on the card. Dispatch detects a prompt that runs as a separate process, such as an ssh-agent PIN request, and waits for it. Dispatch cannot answer that prompt for you. Guard such blocks in your rc file for Dispatch panes, for example by skipping them when DISPATCH_CARD_ID is set.

A card whose Claude has exited stays in its column with a live terminal and no status source until you launch Claude again. A "Claude exited" state on the card is a later change.

A Claude session that is killed within seconds of its first prompt may not have saved its transcript yet. Claude then refuses its own resume id. Dispatch drops that id, so the next Run Claude starts fresh.

Upgrade notes

There is no migration and no configuration step. Sessions that are live at upgrade time keep running with Claude as the pane process. Each of them becomes a shell session the first time Claude exits and Resume relaunches it.

Full changelog: v3.4.4...v3.5.0

Requires Node ≥ 22.22, tmux, ttyd, git, and the Claude Code CLI. Setup is in the README.

v3.4.4 - Fixes: terminal copy, status bar, usage, and appearance

Choose a tag to compare

@theyashgupta theyashgupta released this 04 Sep 10:02
6297e88

v3.4.4 - Fixes: terminal copy, status bar, usage, and appearance

A green line cut through long Claude output. Selected text vanished instead of reaching the clipboard. An enterprise account read "usage stale" while it was plainly active. A font pick did nothing.

This release adds no new product surface. It closes four terminal and account problems. Each fix ships behind a check that was proven able to fail.

The web terminal keeps your copy

A session created before the earlier selection fix, then resumed, still handed a mouse drag to tmux. The drag became a tmux copy-mode selection. The yellow highlight held the text in a tmux buffer, so a copy never reached the clipboard.

Dispatch now re-asserts the session-scope mouse off pin on every terminal attach. An old session heals the moment you reconnect to it. No relaunch is needed.

The tmux status bar stops cutting through output

Dispatch turns off the tmux alternate screen so the web terminal keeps local scrollback. That choice left the green tmux status bar on the primary screen bottom row. A scroll then froze that bar into the middle of long output and cut the text in half.

Dispatch now turns the tmux status bar off for its own web sessions. The setting is applied per session, so the shared tmux server's other clients keep their status bar. The app already shows the session identity in its own interface.

Enterprise usage reads its real number

An enterprise seat has no session or weekly rate-limit window, so the usage endpoint returns an empty list. The account chip read the orange "usage stale" state and showed nothing.

Enterprise usage lives in a monthly credit budget instead. Dispatch now maps that budget to a "Usage credits" window when no rate-limit window exists. The chip shows the real percentage in the normal color.

Terminal appearance settings tell the truth

The font menu offered families that a given machine may not have installed. A pick of one silently fell back to the bundled font, so the terminal looked unchanged. The menu now detects which fonts the browser can render and labels the rest "(not installed)".

The background opacity slider is removed. The web terminal is a panel over the app's own near-black background, so no opacity value was ever visible, and a browser panel has nothing behind it to show through. The terminal now always renders solid.

Upgrade notes

The change is safe to take as-is. There is no migration and no configuration step. A saved terminal appearance that still carries an opacity value stays valid, because the reader ignores the extra key. If you ran a Dispatch session before this release, reconnect to it once so the terminal picks up the mouse and status pins.

Full changelog: v3.4.3...v3.4.4

Requires Node ≥ 22.22, tmux, ttyd, git, and the Claude Code CLI. Setup is in the README.

v3.4.3 - Paste a screenshot into a ticket

Choose a tag to compare

@theyashgupta theyashgupta released this 03 Sep 17:50
de30592

A bug report usually starts as a screenshot, and Dispatch made you discard it. The New ticket box took text only. You described the picture in words, and every Claude session for that ticket started from the words. v3.4.3 lets you paste the screenshot into the box. The draft generator looks at the image before it writes the title and description. The card keeps the file, the detail view shows it, and each session that starts for the card reads the image before it does anything else. The same release repairs a quieter failure. A Done card whose tmux pane died while Dispatch was down showed "Terminal disconnected" forever, and a second Dispatch instance on the same machine could kill the first one's terminals.

See it

Dispatch v3.4.3 paste images into a ticket

Higher-quality video: dispatch-v3.4.3-paste-images.mp4

Paste a screenshot, get a ticket that saw it

Copy an image and paste it into the "What do you want to build or fix?" box. A thumbnail appears under the box with a remove control. Paste up to ten images. Plain text paste works exactly as before, and a paste with no image does nothing. Click Generate ticket and the draft run receives the pixels, not a file name. The title and description therefore describe what the screenshot shows. The thumbnails stay in the Review step, survive Regenerate, and go with Accept. Remove one before you accept and it never reaches the card.

The card stores each image under ~/.dispatch/attachments/<cardId>/, named by the hash of its bytes. Its description ends with a Screenshots section that links them. The detail view renders the images from the card's own attachment route. Only PNG, JPEG, GIF, and WebP are accepted. The server decides the type from the bytes, never from the clipboard's label, so a pasted TIFF or SVG never becomes a thumbnail.

Every session gets the picture

When a session starts for a card with attachments, the kickoff lists each image by absolute path. It tells Claude to read every file with the Read tool before it does anything else. The Read tool returns the image as visual content, so the session can describe the screenshot and act on it. Ask a fresh session what color the attached image is and it answers.

Dead sessions recover

A Done card whose tmux pane vanished now shows Session lost with a Resume button, in every column except To Do. Reconnect on a dead pane no longer loops on "tmux session no longer exists". It marks the session lost and runs the resume, so one click relaunches the recorded Claude session in the same worktree. The readiness poll answers Claude Code's new "Resume from summary" dialog with the recommended summary option. Before this release the poll timed out after 30 seconds and reported "Resume failed. The worktree may be gone."

A test instance stays isolated

A second Dispatch instance under a DISPATCH_DIR override hashed the same literal path for its ttyd instance key. Two instances therefore adopted and swept each other's terminals on every boot, and a shared tmux server let a test instance reach the real one's sessions. The instance key now hashes the resolved data directory. Every override instance runs on its own tmux server (tmux -L dsp-<instance id>). The home instance keeps tmux's default server, so an upgrade orphans nothing.

Underneath

  • Images travel as base64 in the existing JSON bodies of the create and draft routes. Only those two routes accept the larger body. Every other route keeps the 1 MB limit.
  • The draft run keeps zero tools. Images reach it as image blocks over stream-json stdin. The no-image draft invocation is unchanged.
  • The attachment route validates both path parameters and resolves the file under the card folder. It serves the file with nosniff, a sandbox Content-Security-Policy, and an immutable cache header.
  • Files are staged before the card row exists and moved into place after. A disk failure therefore never creates a card whose links point at missing files.
  • The subprocess helper gained a stdin option with an error listener. Without it, a child that exits before it drains a large input raises EPIPE and stops the server.
  • The repository now has a node:test suite (npm test) that runs inside npm run check. It covers attachment decoding, both routes, the exec helper, the kickoff builder, the paste helpers, the Markdown attachment branch, session-lost marking, the ttyd instance key, and the tmux server label.

Limits, stated plainly

Drag and drop of files is not supported. Images cannot be added to an existing card or to a Linear or group ticket. A local card that is later synced to Linear keeps its files, but its kickoff follows the Linear path and does not list them. There is no way to delete an attachment, because no card deletion path exists. The text-only draft can still fail when the model answers a code-flavored direction with tool markup instead of the two expected headers. That behavior predates this release. The resume-mode dialog is answered with the summary option only. Restart the running Dispatch after you upgrade. Each Done card with a dead pane then shows Resume, and a test instance shows its old sessions as lost once.

Full changelog: v3.4.2...v3.4.3

Requires Node ≥ 22.22, tmux, ttyd, git, and the Claude Code CLI. Setup is in the README.

v3.4.2 - Accounts and terminal appearance

Choose a tag to compare

@theyashgupta theyashgupta released this 02 Sep 14:49
6d4273f

You run more than one Claude subscription, and Dispatch only knew about one of them. Every session launched on whatever login sat in ~/.claude, and the only way to see how much of a plan you had left was to leave the board and ask the CLI. v3.4.2 puts the accounts and their usage where you already look: the header shows the active account's tightest window, one click switches accounts for the next session, and Settings gains an Accounts tab. The same release makes the embedded terminal yours to style, with a translucent near-black default that no longer depends on a terminal emulator you may not have.

See it

Dispatch v3.4.2 accounts and terminal appearance

Higher-quality video: dispatch-v3.4.2-accounts-terminal.mp4

Usage in the header, accounts one click away

A chip in the sync strip names the active account and shows the window that bites first: the percent used and when it resets. Click it and the popover lists every account with its session, weekly, and per-model windows, a refresh button, and a Switch button on the others. Below 1024 px the chip shrinks to the percent and the popover spans the viewport. When usage cannot be read, the chip says so in words: stale after a rejected token, unavailable for an account that has never signed in, rate limited when the endpoint pushes back.

Usage is polled every 15 minutes, because the endpoint rate-limits tighter polling, and a manual refresh is limited to one per 30 seconds per account. A rejected token shows the last known windows as stale. Dispatch never refreshes a token itself.

Add an account without leaving Dispatch

Settings, Accounts lists the Default account (your own ~/.claude login) and every added account with its organization, plan, last sign-in, and usage line. Add account runs the Claude CLI's own sign-in: the modal shows the real sign-in link, the page opens in your browser, and you paste the code back. A wrong code ends in a clear error with a retry. Re-login repairs an account in place. Remove signs the account out, deletes its config directory, and leaves sessions already running on it alone.

Each account keeps its own Claude config

Every added account gets its own config directory under Dispatch's data directory and launches with CLAUDE_CONFIG_DIR pointing at it. That directory links everything from ~/.claude that is safe to share (skills, projects, settings) and keeps credentials, sessions, history, and caches per account. Claude Code owns the tokens and their refresh. Dispatch never copies a credential between accounts, which is the mechanism behind the re-login and token-wipe bugs in the reference implementation this feature replaces.

Sessions record the account they launched on. Switching the active account changes the next session only. A resume relaunches on the recorded account, and if that account is gone the card says so instead of guessing.

A terminal that looks like the app

The embedded terminal opened with a hard bluish background borrowed from a Ghostty config that many machines never had. It now ships a translucent near-black default (#111111 at 0.93 opacity, #e8e9ea text) that shows the app panel through it. Settings, Terminal lets you set the background color and opacity, text and cursor color, font family from a short allowlist, and font size. Save applies to every open terminal at once, without a reload, and survives a restart. An invalid value never blocks boot: the app falls back to the defaults and the Save button names the field it refuses.

The Ghostty resolver is gone. Nothing reads an external terminal config at runtime, so the look is the same on every machine.

Underneath

  • DISPATCH_DIR now relocates every data path through one resolver, so a second instance boots fully isolated from the live one.
  • The config writer shares one atomic, mode 0600 helper across all flat settings.
  • Modal dialogs claim Escape in the capture phase, so pressing Escape inside a dialog no longer also closes the Settings screen behind it.
  • No credential value reaches a log, a file Dispatch writes, or an API response. Every account route was grepped for token substrings against a live instance, with zero hits.

Limits, stated plainly

A real second-account sign-in was never completed on the test machine, because the code paste needs a human. Everything up to that point is proven against the real CLI, and the keychain naming rule for added accounts comes from the reference implementation. If that rule ever differs, launches still work and the account's usage reads unavailable until it is corrected. The Settings sidebar still has no narrow layout, so at 390 px every Settings tab, the new Accounts tab included, is squeezed. This predates v3.4.2 and stays on the backlog. Automatic switching on rate limit and usage history are out of scope.


Full changelog: v3.4.1...v3.4.2

Requires Node ≥ 22.22, tmux, ttyd, git, and the Claude Code CLI. Setup is in the README.

v3.4.0 - Linear-like UI

Choose a tag to compare

@theyashgupta theyashgupta released this 01 Sep 20:10
e2bd146

You look at this board all day. It worked, but it never felt finished: padding a little loose here, a hover that did nothing there, a red label you could not quite read against a dark card. v3.4 is the milestone where every one of those judgments stopped being a vibe and became a measured number. The board and the detail panel now follow one written design contract, and every value in it was fixed against the real rendered app, at four breakpoints, before a single component changed.

See it

Dispatch v3.4.0 Linear-like UI

Higher-quality video: dispatch-v3.4.0-linear-ui.mp4

A denser board that earns it

Card padding, card gap, and column headers all tightened to contract values (6px vertical padding, 6px gap, 28px headers). On a mid-size window that buys back a full extra card row per column. Type sizes did not shrink. The density comes from spacing and line height, so nothing got harder to read. The candidates were tried live against a seeded 18-card board and the winner was measured, not guessed.

Every state is now deliberate

Hover, pressed, and keyboard focus have explicit values on every interactive element: cards, column headers, buttons, segment controls, resize handles. Along the way the milestone fixed real defects that had been invisible: the active view toggle swallowed its own hover, cards showed the browser's default focus ring instead of the app's, and an undocumented shadow appeared when you hovered a card. Pressed states did not exist anywhere before this release. Now one formula covers them all.

Motion with a budget

Cards now slide when they change columns (a 150ms FLIP transition, driven by real board events). Column counts pulse when they change. The detail panel opens in 200ms and closes in 150ms, with separate enter and exit easings. Everything animates through CSS only, on purpose: the prefers-reduced-motion setting kills all of it, and the test harness proved that with a positive control that the kill-switch cannot reach.

Red you can read

Destructive red used to pull double duty as both a fill and a text color, and as text it failed the contrast floor on several dark surfaces. The roles are now split. Text red is a lighter shade measured at 4.5:1 or better on every surface it renders on, including hover and pressed tiers. The danger button got a darker fill so its white label reads at 5.62:1. The contrast instrument now checks 75 token pairs on every npm run check, with zero failures and a permanently empty residual list.

Status colors are also locked down for good: a new repo invariant fails the build if any status-meaning color literal ever lands outside the token file.

Vault rows you can actually read

The Vault page in Settings had a row layout that truncated long key names to a few characters and hid the name entirely while editing. Rows now follow the standalone Envault tool's pattern: the full key name always renders and wraps, the purpose sits beneath it and is edited in place, and the value action is a plain text button that says "Set value" or "Rotate". The sealed-value guarantee is unchanged. There is still no way to read a stored secret back.

Audited, surface by surface

The milestone closed with a per-surface audit: 22 component surfaces and 57 contract keys, each with a recorded verdict from live measurement. It closed at zero open findings. The audit found and fixed real issues on the way, including two mono-size violations the previous milestone's census had missed.

Limits, stated plainly

The Settings dialog sidebar has no narrow-viewport layout. At 390px it pushes row controls off screen. This predates v3.4 and is on the backlog. The board's column resize handles do not render below 1024px, a pre-existing structural cut. The terminal viewport deliberately sits on the darkest background tier, recorded as an accepted exception, not an oversight.


Full changelog: v3.3.0...v3.4.0

Requires Node ≥ 22.22, tmux, ttyd, git, and the Claude Code CLI. Setup is in the README.

v3.3.0 - Push notifications + Markdown viewer

Choose a tag to compare

@theyashgupta theyashgupta released this 31 Aug 08:40
5ba5b97

v3.3.0 - Push notifications + Markdown viewer

An agent hits a question it cannot answer alone, and the session stops. The board knows. You, three rooms away with the laptop closed, do not. Dispatch now tells your devices. When a session flips to needs-input, every phone and laptop you subscribed gets a push, with the tab closed, and one tap lands you on the exact card that is waiting.

See it

Dispatch v3.3.0 push notifications and the markdown viewer

Higher-quality video: dispatch-v3.3.0-push-viewer.mp4

A push when a session needs you

You enable push from Settings, Notifications. The browser permission prompt fires only on that click, never on page load. Subscribe as many devices as you want, and every one of them is alerted. On iOS, install Dispatch to the Home Screen first, and Settings walks you through it.

Only needs-input sends a push. A finished agent does not buzz your phone. When the same card asks again, the new notification replaces the old one and still alerts, so a chatty session cannot stack up a shade full of stale alerts. If you have a tab open, the push and the in-tab notification collapse into one. Tapping a notification focuses an open Dispatch tab and opens the card, or opens a new tab cold on that card. Push also works over the remote tunnel, behind the same auth gate as everything else, and each device's notification links back through the origin it subscribed from.

Dead subscriptions clean themselves up. When a push service answers 404 or 410, the row is pruned on the spot.

The crypto is ours, and reality graded it

The Web Push payload encryption (RFC 8291) and the VAPID signature (RFC 8292) are hand-rolled on node:crypto, keeping the runtime dependency count at zero for the whole push track. The proof does not come from this repo agreeing with itself. The test harness subscribes a real headless Chrome to the real push service, drives a real needs-input transition, and the push service accepted the message (HTTP 201). Strip one parameter from the signature and the same service rejects it (403). A real phone over a real tunnel, with the tab fully closed, received the notification and landed on the right card.

The service worker that makes this possible has no fetch handler, on purpose. It cannot intercept a single app request, so it can never break loading or updates, and it is served no-cache so a new version is never stuck behind an old one.

Markdown, rendered where you click it

Agents write markdown constantly: plans, summaries, reports. The terminal links them, and until now those links went nowhere useful. Cmd-click a .md link in a session terminal and it opens rendered in a new tab, on a Dispatch route, never a raw file:// page.

The view is read-only and safe by construction. Raw HTML in the file never reaches the DOM, and images render as links, the same posture the board's markdown already uses. Fenced code blocks get syntax highlighting. Long files get a table-of-contents sidebar, headings get anchors, and relative links between .md files navigate inside the viewer with a working back button.

The backend route behind it serves only .md files inside your registered workspaces. Path traversal, symlink escapes, and sibling-prefix tricks were each tried for real against a live server, and each one gets the same flat 404.

Limits, stated plainly

iOS delivery requires the Home-Screen-installed app, which is Apple's constraint, not ours. After the tunnel hostname rotates, an already-delivered notification still deep-links to the old hostname, because pruning acts only on 404 and 410, by design. The viewer's table of contents hides below 1024px wide, a deliberate cut rather than a drawer.


Full changelog: v3.2.0...v3.3.0

Requires Node ≥ 22.22, tmux, ttyd, git, and the Claude Code CLI. Setup is in the README.