⚡ Claude Terminal v1.3.2
The remote you can actually walk away with — and guards that catch what only a human was catching.
The headline
📱 The phone stops dropping the conversation1.3.1 put your chats on your phone. Twelve commits here make them stay there: a lock screen no longer costs you the PIN, a dead link is now noticed, and a reconnect replays something readable instead of freezing for seconds. |
🗓️ A project timelineThe dashboard said what a project is. Nothing said what happened to it — the app records that in six separate screens. A third sub-view merges commits, sessions, hours, workflow runs, parallel runs and artifacts onto one line. |
✨ A "What's new" panel after a restartThe update banner offered release notes before the install, so clicking Restart to update told you nothing. Now every version between the one you last saw and the one you are running is surfaced — each move with a button that takes you there. |
💾 Kanban cards stop disappearing
|
📱 The remote, made to hold
Every one of these was the difference between a phone that looks connected and a phone that is.
| What was wrong | |
|---|---|
| Staying connected | The 24h session token was revoked by the socket's own close handler, so every ordinary mobile disconnect — screen lock, Wi-Fi to cellular, app backgrounded — forced a PIN re-entry. And reconnecting unregistered the live socket, so the phone got its init burst and then went permanently silent
|
| Noticing a dead link | Neither end sent a keepalive. A mobile connection rarely closes cleanly — the NAT entry expires and nobody is told — so readyState stayed OPEN, the dot stayed green, and every action fell on the floor. A 25s ping now proves the round trip
|
| Coming back from the background | iOS freezes timers, so a reconnect backoff of up to 30s did not even start while the app was away. Foregrounding and online now reconnect immediately, or probe a socket that is likely half-open
|
| Replaying a transcript | Each text fragment took its own buffer slot, so the 500-entry cap held less than one answer and the replay began mid-message. Deltas are folded before buffering, and the replay draws once at the end instead of a few hundred times |
| Staying where you were | Opening a tab on the desktop threw the phone out of whatever it was reading — and since a reconnect replays one of those per session, every reconnect dumped you on a random conversation |
| The composer | The spinner and the send/interrupt pair were single DOM elements shared by every conversation. A background session finishing flipped the on-screen one back to "send" mid-turn; one starting showed an interrupt button that would have interrupted somebody else |
| Model and effort | The PWA held one global pair, so the menu could show Opus for a conversation still running Sonnet. They now belong to the conversation, exactly as on the desktop |
| Security | The desktop has run under a strict CSP for a long time — the PWA renders the same model-authored markdown and shipped with no policy at all. It now carries the equivalent, plus nosniff, referrer and framing headers, with a test asserting the meta tag and the header agree |
| Accessibility | Not one of the 29 icon buttons had a label, so a screen reader announced fourteen adjacent controls as "button", and streamed text was never announced. maximum-scale=1 also blocked pinch-zoom (WCAG 1.4.4) for nothing
|
| Language | Nine SDK and turn-failure messages stayed English whatever language the desktop pushed — the strings you meet when something goes wrong, which is the worst moment to switch language on someone |
Everything else
- Timeline export — the new dashboard view offers 7/14/30 days, per-source filter chips, and a Markdown export for pasting into a standup note
- Sources fail independently — a project with no remote, no workflows and no artifacts is the normal case, so one dead source can never blank the view; it says which one failed instead of quietly showing less than the truth
- Push-to-talk froze the entire keyboard on Linux — F13 is absent from the default X11 symbols map, so the grab resolved to keycode 0, which is
AnyKey: it grabbed every keystroke on the machine, in every application, until the app quit (#166) - 32 of 115 conversations were unreachable on the repository this was found on — a session that enters a worktree is re-filed under the worktree's directory, and the app only ever read one directory per project
- A usage limit mid-turn took four steps and two hand-typed prompts to get the tab onto another account;
rate_limitis the same 429 for a five-hour limit, a weekly limit and a spend cap, so the generic "wait a moment" advice had replaced the one line that said which limit was hit - One unanswered Keychain dialog wedged the usage bar for the rest of the session — the
keytarawait was unbounded, and the macOS prompt opens behind the window - "Refresh usage" could not see a fresh login — it bypassed the cache of the figures but not the token cache underneath, which is the one case the gesture exists for
- Enter and Tab on an
@-mention threw — four of the seven picker modes called functions that are declared nowhere in the repository - Five external-link buttons had never worked — they called
require('electron')from renderer code, which throws undercontextIsolation - The expanded sidebar overflowed the window — with every tab pinned the list grew past the viewport and pushed the collapse button off screen, so it could no longer be folded back
- 144k lines across 373 files had no linter. ESLint now enforces the architectural boundaries that were previously described in prose and enforced by nobody: no
child_process/fs/electronin the renderer, nosrc/main/from the renderer, nosrc/renderer/from main, nodocumentin main. No formatting rules and no Prettier — a reformatting pass would bury everygit blamethat explains why something is the way it is - Nothing asserted that the app starts. All Jest suites run in jsdom against a mocked bridge, so every "the window opens but panel X throws" regression had to be found by a human. A Playwright smoke test now launches the real Electron app, opens every sidebar tab, and fails on a renderer console error or a
criticalmain-process entry — blocking, because a test that cannot fail the build reports nothing CLAUDE.mdis checked against the tree in CI, so a claim that has become false fails the build instead of misleading the next reader- The Windows test teardown retries every recursive removal, so a file lock can no longer fail an otherwise green suite
🙏 Thanks
| @bleleve | The X11 keyboard grab, the unreachable worktree sessions, the wedged Keychain read, the stale usage refresh, the mid-turn account switch and the Windows teardown — six deep, well-diagnosed fixes (#168, #169, #171, #173, #175, #179). Once again, exceptional work |
📜 Full changelog — 31 commits
feat(updates) show a What's new panel after a restart
feat(dashboard) a project timeline merging every record the app already keeps
fix(projects) stop losing projects.json data written by other processes
fix(accounts) carry the conversation through a switch after a usage limit
fix(accounts) make "refresh usage" re-read every account's credential store
fix(usage) stop the usage bar freezing on a pending Keychain prompt
fix(sessions) keep a session that entered a worktree in its project
fix(shortcuts) stop the default push-to-talk bind freezing the keyboard on Linux
fix(chat) make Enter and Tab select a mention instead of throwing
fix(renderer) open external links through the bridge, not require('electron')
fix(sidebar) stop the expanded rail overflowing the window
fix(remote) stop dropping mobile clients on reconnect
fix(remote) detect the half-open socket a phone actually dies with
fix(remote) make the replayed transcript survive more than one turn
fix(remote) stop the desktop yanking the phone between conversations
fix(remote) scope the chat composer and spinner to the open conversation
fix(remote) make model and effort belong to the conversation
fix(remote) ship the PWA with a content-security-policy
fix(remote) give the PWA's icon controls an accessible name
fix(remote) translate the strings the PWA still hardcoded in English
fix(remote) close the remaining gaps found in the PWA audit
perf(remote) stop rebuilding the whole transcript on every SDK event
build(lint) add ESLint, and make it enforce the main/renderer boundary
test(e2e) add a Playwright smoke test that starts the real app
fix(e2e) make the smoke test actually start the app on CI
ci(e2e) let the smoke test fail the build
test(remote) stop the new suites leaking timers into their jest worker
test(setup) retry every recursive teardown so Windows cannot fail a green suite
docs resync CLAUDE.md with the tree, and guard it in CI
docs repair two defects in CLAUDE.md, and close the blind spot that hid one
chore bump version to 1.3.2
Note
macOS users: if the app is reported as damaged on first launch, it is unsigned rather than broken. Run xattr -cr "/Applications/Claude Terminal.app", or right-click the app and choose Open.