Skip to content

release: 1.38.0 — reliable session close, terminal drawing, Apply update, performance - #46

Merged
writingdeveloper merged 8 commits into
mainfrom
release/1.38.0
Oct 16, 2026
Merged

writingdeveloper merged 8 commits into
mainfrom
release/1.38.0

Conversation

@writingdeveloper

Copy link
Copy Markdown
Owner

User-visible change

See docs/releases/1.38.0.md. In short:

  • Closing sessions is reliable across paired PCs.
    • Measured before the fix on two paired instances:
      • Closing on one PC left an exited tile on the other. That tile was persisted and re-run by update auto-restore.
      • A bulk close of 3 brought 2 back as zombie tiles.
      • A remote close while offline or without the control permission was silently dropped, and the session was re-adopted on reconnect.
    • Now: closes are answered (cockpit:closeSession), owed closes are delivered on reconnect (and survive restart), closed ids are tombstoned against re-adoption, and exits carry closed/closedBy.
  • Overlapping terminal screen.
    • Minimize→restore cleared it. Measured: that triggers no pty resize, only a full xterm redraw. So the buffer was correct and the renderer was stale.
    • The WebGL addon is removed; terminals use the DOM renderer.
  • Apply update for Claude Code. Claude Code's /restart is macOS/Linux-only. DevDeck now detects "Update installed · Restart to apply" and restarts the session on the new version with the same conversation.
  • Data safety:
    • Versioned notes. A conflict shows both versions instead of overwriting.
    • Usage double-count fixed.
    • Real session counts on cards.
    • Remote Open from the task board and memory modal goes to the right machine.
  • Performance:
    • Spinners run on a JS tick instead of a CSS animation: 16.0% → 3.6% of a core (12 sessions, 6 working; A/B in one app run).
    • Codex lookups: 18.8 s → 0.28 s per 100 projects.
    • Codex usage scan stalls: 84–99 ms → 6–11 ms.
  • Security/robustness:
    • Link: pre-auth limits, notify channel/rate limits, event-channel allowlist, audit throttling, handshake deadline, removed-machine resurrection.
    • Observe-only callers can no longer rewrite pty metadata.
    • Remote writes are limited to scanned projects.
    • Windows process-tree kill: measured that a hidden child previously survived.
    • %VAR%/; safe launches: verified byte-exact through a real .cmd shim.
    • Electron 43.7.11; npm audit reports 0.

Verification

Windows 11, Node 22.16 (local), Electron 43.7.11, on this branch's tree:

  • tsc --noEmit: 0 errors. vitest: 1420 passed, 1 skipped.
  • check:docs, qa:maintenance, qa:resilience, qa:session-sync, qa:audit (0 violations), qa (0 console/page errors): all pass.
  • Supervised runs:
    • qa:close (new, two apps, 22 checks). Failed on the baseline for every close scenario above.
    • qa:link: 0 failing checks.
    • qa:perf -- 4 --allow-live-agents: PASS.
    • A packaged win-unpacked build opened, streamed and closed a real PTY.
  • Not covered: macOS/Linux packaging (left to CI), physical two-PC field use, and the WebGL→DOM change against the user's original screen. The stale-cell symptom was not reproduced synthetically; the attribution rests on the minimize/restore measurement.

Compatibility and privacy

  • New methods only; existing Link shapes unchanged:
    • cockpit:closeSession: an older host answers "no such method" and the viewer falls back to the old notify.
    • project:saveNote: an older host falls back to setNote, which also bumps the revision on a new host.
  • Persisted state: a noteRevision field per project, and owed remote closes in renderer localStorage.
  • No new network destinations.

Documentation and remaining work

  • Updated: README, docs/state-consistency.md, docs/backlog.md (note concurrency done), and docs/quality.md (qa:close).
  • Candidate for the 1.38.0 published release after CI.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YTT8PjCsRNq19Rc53mSSta

writingdeveloper and others added 8 commits October 16, 2026 16:23
- 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)
…lling 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)
…ls 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)
…e; 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)
…ine-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)
…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)
…d 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 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
@writingdeveloper
writingdeveloper merged commit 2b215fd into main Oct 16, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant