cowork-deck

Release ·

cowork-deck v0.3.0

A scenario used to leave nothing behind. sessions.json dropped the entry when the tile closed, so a schedule that fired at 03:00 into a session nobody was there to watch was unrecoverable by morning — the only surviving trace anywhere was one row per scenario in schedule_state.json, written by the scheduler and only for scheduled fires. This release gives scenario runs a journal and a screen to read it on.

What people get

Scenario run history (#217, in three parts: #218, #219, #220). Every run is journalled — how it started, how long it took, and the final assistant message — and a History screen beside Terminals / Board / Pull requests lists them newest first, filterable by scenario and by trigger.

  • The journal is runs.jsonl in the app data dir, one appended line per event. Append-only, because a store that truncates before it writes cannot be trusted with a file written on every launch; versioned per line, because after an upgrade the file physically holds lines from several app versions and a header would lie about the ones below it.
  • Rows carry snapshots, not lookups: the name, icon, expanded prompt and parameter values as they were. Renaming a scenario, deleting it, or deleting its workspace cannot rewrite the past.
  • A row that produced nothing says which nothing it was — still running, no session started (with the scheduler’s reason), no transcript ever reported, or the transcript is gone. Never an empty box: the run happening and the run producing nothing are different facts. A conversation that was /cleared since says so out loud rather than presenting a stranger’s tail as its result.
  • Three actions on a row, and deliberately no fourth. Go to the session while it is still live, switching workspace first if it is running in another one. Re-run, through the usual “Launch parameters” form with the recorded values pre-filled and selected — never silent, because a scenario’s parameters may name a branch, a target or a person — and reconciled against the scenario as it stands now, so a placeholder the prompt has since lost is dropped and one it has gained comes up empty. Reveal the transcript in the file manager, reveal only, never open: a .jsonl is not handed to whatever is registered for it.
  • Where an action cannot work the control says why, on itself and in its accessible name — a scenario that was deleted, a workspace that was, a transcript that was never written. aria-disabled rather than disabled, so the reason is reachable without a mouse.
  • History is immutable. No row can be edited or deleted; a journal whose rows can be revised answers nothing. Erasing exists at one granularity — a scenario’s history wholesale, from the head of the screen while it is narrowed to that scenario, so the rows it erases are the ones on screen.
  • Recording can be switched off, and the screen says so rather than looking empty. Retention is the last 100 runs per scenario, pruned at app start — a time window would keep three records for a monthly job and two thousand for an hourly one.
  • The scenario’s sidebar row grows a state dot for how its last run went, which opens the history narrowed to that scenario. An indicator and nothing else — that row has learned this lesson once already, when one ⏰ was both a status badge and a real launch, and reaching for information started a session.

Shift+Enter writes a second line (#234). It used to submit the half-written message. xterm maps Enter to a bare CR whatever modifiers are held, so Shift+Enter reached claude byte-identical to Enter and claude did the only thing a bare CR allows. The app is the terminal here, so it now makes the binding every real terminal makes — the one claude’s own /terminal-setup writes into iTerm2 and VS Code: ESC + CR. Alt/Option+Enter and Ctrl+Enter map to the same newline, since neither can mean anything else and they are what a person tries next. Plain Enter still submits, and zoom keeps its binding — Cmd+Enter on macOS, Ctrl+Shift+Enter on Windows and Linux.

Two defects fixed

  • The status pill took the keyboard every five seconds (#203) — and did it precisely while a session sat waiting for an answer, which is the state the pill exists to report. On macOS show() is makeKeyAndOrderFront:, and a focusable window duly became the key window on every poll tick. The pill is now built non-focusable, and the count is only emitted when it actually changes. Typing into a terminal is no longer interrupted.
  • Open in browser did nothing (#252) — no browser, no navigation, no feedback. A Tauri v2 webview has nowhere to open a target="_blank" link, so the navigation was simply dropped. Five other links were dead the same way: the pull request row, the GitHub screen, links and images in rendered markdown, the diff drawer, and a card. All six now go through one gate that opens http/https in the system browser and refuses everything else. The capability grants URL-opening scoped to those two schemes and nothing more — a URL out of a pull request description must never be able to name a file to run.

Housekeeping, no behaviour attached

  • 44 spec and plan documents retired (#253) — the record of a workflow this project no longer uses. Work is scoped in GitHub issues and decisions are recorded in docs/adr/; a standalone spec was a third, unmaintained account of intent. Four code comments that cited one for a measurement now state the measurement inline.
  • The first two ADRs (#216) — how connected social accounts will hold credentials, and who uses them over what. Both are inputs to #205; the “the app stores no tokens” invariant is narrowed rather than deleted.
  • Where agents look things up (#202) — the issue tracker, the five canonical triage labels, and the domain-doc layout, written down so a skill does not guess and open a pull request against main.
  • The README gains the Features bullet the history screen shipped without.

Known, and tracked

Found while building this, left out on purpose:

  • #235 — the history screen reads an unreadable journal as an empty one.
  • #236 — no test covers the history screen’s boot-level wiring.
  • #249 — an unpinned scheduled scenario runs in whichever workspace happened to be active. The form says so; saying so is not the fix.
  • #255 — every modifier+key pair other than the three now translated still collapses. The real answer is negotiating the kitty keyboard protocol.

Still open from v0.2.0: #199 (after /clear, restart and auto-restore resume the pre-clear conversation), #195 (a non-UTF-8 transcript reports zero tokens and no name, silently), #194 (a renamed card tile loses the ☑ that says which card it is on).


Install

macOS — download the .dmg for your architecture (aarch64 for Apple Silicon, x64 for Intel), drag the app to Applications, then clear the quarantine flag once:

xattr -cr /Applications/cowork-deck.app

Linux — download the .AppImage, chmod +x it and run, or install the .deb.

The app checks this page for updates on launch and installs them in place; the xattr step is first-install only.