Repository navigation
Commit 2b215fd
release: 1.38.0 — reliable session close, terminal drawing, Apply update, performance (#46)
* fix(link): close the gaps a paired or unpaired peer could walk through
- removeMachine no longer resurrects the machine: closing the link fired onClose -> scheduleRetry
before the entry was deleted, so it redialed, re-saved the known host and kept delivering
sessions. Entries are now marked removed and every callback checks it is still current.
- A notify may only run `send` methods (it was running invoke methods such as cockpit:open with no
rate limit); non-input notifies share a token bucket, async rejections are caught.
- Only the session channels a host is allowed to push reach the renderer; a host can no longer
forge `link:sessions` for another machine (or for 'local').
- Pre-auth limits: at most 8 unauthenticated connections, 64 KiB frames until ready, TLS handshake
timeout, attach/detach only from a paired device; the frame decoder no longer copies O(n^2).
- Audit rows from refusals and strangers are throttled so they cannot evict the history.
- A hello->ready deadline so a host that stalls after TLS cannot wedge the dial forever.
- An exited session is dropped from every connection's attached set (it kept hasRemoteViewers
true forever, blocking idle shutdown); attach announces only when something changed.
- Port-mapping renewals stop when the server they were started for is gone.
- An already-paired device sending `pair` keeps its permissions; evicting the oldest device past
the cap is audited.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTT8PjCsRNq19Rc53mSSta
(cherry picked from commit f924b93970bf80bd5b29c704714ede01d234778b)
* fix(main): stop double-counting usage, leaking agent processes and killing work on a crash
- Usage: two scans resuming the same grown transcript folded into the CACHED digest's objects,
so concurrent scans (deck + usage view, or a remote viewer) added the new lines twice, for good
(200,100 -> 400,100 tokens until restart). Folds go into copies and are serialized per file;
the same fix for the per-session meta reader.
- Windows: closing a session now kills its process tree (taskkill /T, then node-pty's kill).
Measured: node-pty's console-only kill left a hidden child (the shape of an MCP server or a
background dev server) running; the tree kill ends it.
- A renderer crash no longer kills every terminal: the reload re-adopts the running sessions, so
only DevDeck's own login shells are reaped. Measured: both sessions survive a forced renderer
crash and come back as the same two tiles.
- git runs with GIT_OPTIONAL_LOCKS=0 / GIT_TERMINAL_PROMPT=0, so the 30-45 s status polls stop
taking index.lock out from under the agent's own git commands.
- AI summaries: no shell:true (it corrupted `--mcp-config '{"mcpServers":{}}'` on Windows), a
timed-out summarizer's whole tree is killed, failures are reported.
- A resume id that fails validation is reported as null instead of naming a conversation the
`-c` fallback never opened.
- state.json rename retries on EPERM/EBUSY/EACCES; a failed shutdown.exe returns the scheduler to
disarmed instead of counting down to nothing; at most 32 project terminals at once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTT8PjCsRNq19Rc53mSSta
(cherry picked from commit d5c47a164d90ad42c62c42d07559c49124470ef6)
* fix(cockpit): a closed session stays closed, everywhere; draw terminals correctly; apply agent updates
Close, measured on two paired instances before and after (qa/close.mjs, 22 checks):
- Closing at one machine left an "exited" tile on the OTHER one, which was saved, came back as a
previous session, and was re-run by an update restart. The exit now says it was a close (and
from where), and every deck showing the session follows it.
- A bulk close brought all but one of the closed sessions back as dead tiles: each close makes the
machine announce its sessions, and the first announcement still listed the rest, which adoption
read as "started elsewhere". Closed ids are tombstoned until the machine stops listing them.
- A remote close was a fire-and-forget notify. Offline, it vanished and the tile was adopted back on
reconnect with the agent still running; without the `control` permission it looked like it
worked. A new `cockpit:closeSession` answers: offline closes are kept and delivered on reconnect,
refusals put the tile back and say why. Older hosts get the old notify.
- Update restarts and the restart counter skip tiles whose terminal already ended.
- Sessions another machine starts no longer take the selection and the keyboard; "+ New session"
on a remote tile opens on that machine; reopening a project only dedupes against tiles on the
machine being opened; Enter on a focused Cancel cancels; a failed restart keeps the tile as a
saved entry; a restart waits for the old agent to exit and starts a never-written conversation
fresh under the same id instead of failing to resume it.
Rendering: tiles draw with xterm's DOM renderer again. Under the WebGL addon Claude Code's screen
built up stale cells - old status lines stacked down the screen, earlier text showing through
where new text had spaces - while the buffer was right: minimize/restore, which only makes xterm
redraw every row (measured: no pty resize at all), cleared it every time. WebGL had left the GPU
process's cost unchanged when it was measured.
Agent updates: Claude Code shows "Update installed · Restart to apply", and its in-session
/restart exists only on macOS and Linux (gated on platform), so on Windows the update waited for a
manual /exit. The tile header now offers "Apply update": the restart keeps the conversation, and
while a turn is running it waits for the turn to end.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTT8PjCsRNq19Rc53mSSta
(cherry picked from commit d0162e01cf381057033255e66b97237a4ecf5c08)
* chore(deps): Electron 43.8, drop the WebGL addon, clear the audit gate; add qa:close
`npm audit --audit-level=moderate` (the CI gate) failed at HEAD on advisories published since the
last release: Electron 43.3 (sandbox inheritance, protocol-handler CORS, preload cache poisoning;
fixed in 43.4.1/43.5.0) and, through electron-builder's @electron/get, global-agent 3 -> roarr ->
sprintf-js (no fixed sprintf-js exists; global-agent 4 no longer depends on it), pinned by override.
Now 0 vulnerabilities. Verified on 43.8: unit tests, qa, qa:close, qa:link, and a packaged
win-unpacked build whose embedded terminal opens, streams and closes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTT8PjCsRNq19Rc53mSSta
(cherry picked from commit 340d2bbb06506caef6e9bf34ccf66af532dbb7d6)
* perf+fix(renderer): spinners cost a frame stream, not a spinner; machine-scoped project state
- Measured (12 sessions, 6 working off screen): any running spinner makes Chromium redraw the window
every step, ~6% of a core whether one spinner turns or six. Spinners now tick 4x/s instead of 10x
and pause while the window is unfocused (all-frozen measured 6.8% vs 13.1%).
- qa/perf.mjs: its own rAF frame counter ran through every process-CPU window (idle read 6.8%, real
0.5%); it now runs only while a JS profile is taken.
- Task board / project memory Open went to THIS machine for a remote project; project activity,
badges, providers and sidebar project signals are now per machine.
- A size change's ConPTY repaint no longer counts as agent activity (it flipped idle sessions to
working); background refreshes no longer rebuild the header rename editor mid-typing; find bar
follows a language switch; view mode is not reverted by a slow reload; tray overdue count stays
this machine's; due dates save on blur/Enter; adding a single-project folder as a location no
longer turns it into a scan root; the login terminal is not shadowed by the usage modal's keys.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTT8PjCsRNq19Rc53mSSta
(cherry picked from commit 39354df1770c5a0f100be2d6c906759693da7287)
* perf+fix(main): stop blocking the main thread on scans; close remote-write and launch gaps
Performance (measured on synthetic stores at the user's scale: 100 projects, ~520k transcript lines,
5,000 Codex rollouts; "block" = longest main-thread stall):
- Codex session re-resolution walked all 5,000 rollouts per project every TTL: 18.8 s -> 277 ms for
100 projects (the deck pass's head cache is reused). Per-tile drift stat pass ~230 ms -> 32 ms.
- codexUsageScan re-read, deep-copied and rewrote its 4.5 MB index synchronously on every scan:
block 84-99 ms -> 6-11 ms (kept in memory, written async only when it changed).
- Tail search for the last user message: an O(n^2) 8 MB sync read, 43.6 ms block -> 1 ms cached,
6 ms on a miss (async, incremental, keyed by file size+mtime).
- Active time is an incremental tally instead of copy+sort of every timestamp on every scan:
warm usage scan 57-90 ms -> 23-30 ms, heap 40 MB -> 21 MB; session meta tick 3-5 ms -> ~1 ms.
- `where` for the Codex CLI cached (44-56 ms sync -> 0.1 ms); session file listing async;
`projects:list` shares the build already running (git once for N concurrent callers).
Fixes:
- `cockpit:liveSessionId` (observe permission) could rewrite which conversation any terminal here
announces; only this machine's window may record, on a terminal of the asked project/provider.
- Notes: versioned `project:saveNote` (old `setNote` kept for older peers, and it bumps the
revision) so a note saved on another device is no longer silently overwritten.
- Paired machines may write only to projects this machine actually scanned (the allowlist is a string
check); pasted images from them are capped per hour; their usage scans run one at a time.
- Session counts were the length of a list capped at 5; counted from the store now.
- Open in VS Code expanded %VAR% inside the folder name (shell:true) - escaped and verbatim now,
verified byte-exact through a real .cmd shim; wt args escape `;`; openFolder opens folders only;
`git init` is bounded; the in-progress task is read from the newest 50 task files, not the oldest.
- Tray menu and tooltip take their words from the window, in the app's language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTT8PjCsRNq19Rc53mSSta
(cherry picked from commit 8826a51a4da6f418f2519bdab11ca1769f3ebc10)
* perf+fix(renderer): spinners on a tick, not a CSS animation; versioned notes; i18n and a11y
- A running CSS animation keeps Chromium producing frames whether or not a step is due. A/B in one
app run, 12 sessions with 6 working: CSS spinner at 10 steps/s 16.0% of a core, 4 steps/s 12.3%,
1 step/s 9.0%; a 250 ms JS tick setting a custom property 5.5%, nothing turning 4.6%. The spinner
now turns on that tick (paused when unfocused, hidden or reduced-motion); the shipped build measured
3.6% in the same scenario.
- Note editor saves against the revision it read; on a conflict both notes are shown to reconcile,
never one erased. Max length shown on the box.
- Owed remote closes survive an app restart.
- Hard-coded English (project menu, checkboxes, chart title) localized; shutdown history dates use the
app language; per-project checkbox names, aria-pressed toggles, calendar day labels, keyboard edit
of task text.
- qa: the update-button scenario retries Enter on the trust prompt; the spinner check verifies the
spinner actually turns rather than that a CSS animation exists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTT8PjCsRNq19Rc53mSSta
(cherry picked from commit 5c45a1b1ee0ff71273260052329f6ae349c37e96)
* release: 1.38.0 notes, docs and version
Release notes, README highlight and compatibility, state-consistency contract for notes and session
close, backlog (note concurrency done), qa:close documented as a supervised harness.
Gates on this commit's tree: tsc 0, vitest 1420 passed, check:docs, qa:maintenance, qa:resilience,
qa:session-sync, qa:audit (0 violations), qa (0 console/page errors); supervised qa:close (22/22)
and qa:link (0 failing checks).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTT8PjCsRNq19Rc53mSSta
---------
Co-authored-by: Si Hyeong Lee <writingdeveloper@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>1 parent cb714bc commit 2b215fd
94 files changed
Lines changed: 4238 additions & 705 deletions
File tree
- docs
- releases
- qa
- src
- main
- api
- link
- preload
- renderer
- features/cockpit
- locales
- shell
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
| 19 | + | |
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| |||
83 | 83 | | |
84 | 84 | | |
85 | 85 | | |
86 | | - | |
| 86 | + | |
87 | 87 | | |
88 | 88 | | |
89 | 89 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
16 | 15 | | |
17 | 16 | | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
18 | 21 | | |
19 | 22 | | |
20 | 23 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
31 | | - | |
| 31 | + | |
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
19 | 21 | | |
20 | 22 | | |
21 | 23 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| 37 | + | |
37 | 38 | | |
38 | 39 | | |
39 | 40 | | |
| |||
119 | 120 | | |
120 | 121 | | |
121 | 122 | | |
122 | | - | |
123 | 123 | | |
124 | 124 | | |
125 | 125 | | |
126 | 126 | | |
127 | | - | |
| 127 | + | |
| 128 | + | |
128 | 129 | | |
129 | 130 | | |
0 commit comments