P1:
- settingsStore: back up an unparseable settings.json to .bak before
falling back to defaults, so the next mutation can no longer silently
wipe custom themes/highlight rules
- ptyDispatcher: fan out per-session data/exit handlers (Set instead of
a single slot) so SSH split panes stop stealing each other's stream
- FilePanel: monotonic refresh token keeps stale listings from painting
over a newer navigation; upload finish no longer yanks the panel back
- settings.css: active settings-tab label derives from --chrome-fg so it
stays visible on the shipped light themes
P2 (main/renderer):
- paste guard: a paste ending in a newline always confirms
- Workspace: closing an SSH pane no longer seeds the local cwd with a
remote path
- tray: skip close-dialog continuation on a destroyed window
- commands: close zombie 'in-progress' session logs at hydrate
- zmodem: clear the stale offer timer before arming a new one
- connectionsStore: coerce/validate renderer input before persisting
- sftp: OperationError marker class keeps translated errors out of the
transport-retry classifier
- CommandsPanel: surface save failures inside the dialog
- ConnectionSidebar: drop a tautological tooltip condition
P2 (i18n/tooling/tests):
- localize the 16 ANSI color labels and the highlight sample text
(21 new keys across zh-CN/zh-TW/en/ja)
- sync-changelog: keep ### subheadings, normalize CRLF notes
- release.cjs: GitHub release reuse-by-tag (idempotent re-runs); fail
loudly on a failed Gitea asset listing
- commands-store test: absent historyEnabled now truly tests absence
Lock screen (main-window overlay, no second window):
- scrypt password verifier in <userData>/lock.json (per-write salt,
timingSafeEqual); salt/hash/password never leave the main process
- lock now / idle auto-lock / lock at startup, growing failure cooldown,
lock flags persisted so a quit-and-relaunch cannot bypass the lock
- locked shell and body portals go inert while sessions keep running;
menu accelerators (reload, DevTools, zoom) are swallowed while locked
- settings gains a Lock tab; all copy in zh-CN/zh-TW/en/ja
Security and stability:
- packaged builds ignore ELECTRON_RENDERER_URL / OT_UPDATE_URL (devEnv)
- renderer preload runs with sandbox: true
- unreadable known_hosts store fails closed instead of being overwritten
- connect-time secrets gated by the bookmark's auth method (connectPromptFor)
- ssh stream teardown is idempotent: PTY_EXIT broadcasts exactly once
- sysinfo polling is refcounted for split panes (forceStopPolling on close)
- session-log index entries are path-contained; settings store writes
atomically with EPERM/EBUSY retry
- sync-changelog tolerates CRLF checkouts (was a silent no-op)
- retry ssh2 host-key generation (flaky malformed key, ~1/500)
Tests: lock-store + lock-controller suites; transport-death PTY_EXIT e2e;
GitHub Actions CI (typecheck + 10 offline tests + build)
- upload: per-chunk buffer (ssh2 re-reads the overflow tail after the ACK;
a reused buffer silently corrupted every file >= ~254KB)
- close the cached SFTP channel on eviction, attach an 'error' handler,
close the download handle, time out execQuiet, fail partial deletes
- per-session StringDecoder for the ssh data plane (CJK mojibake), real
exit codes, safe replay truncation, zmodem abort/counter/timer fixes
- sysinfo: idempotent poll end, error routing, per-poll watchdog, proc(5)
CPU total; expand cd ~/$HOME/%USERPROFILE% paths; log sanitizer fixes
- security: will-navigate guard, central IPC sender check, scheme
allowlist for openExternal, single-instance else branch, layout id and
log-name whitelists, custom theme sanitizing, atomic JSON writes with
EPERM retry in store.writeJson
- updater: per-attempt feed choice, quitAndInstall relaunch, dev guard,
update-state getter
- Global shortcut setting: press-to-record input (Esc cancels, Backspace
clears); requires a modifier or F-key so plain typing can't be hijacked
- Terminal toolbar: three settings-gated buttons — session-log record
(default off), open logs dir (default off), open working directory
(default on, new; local sessions only, cwd tracked via cd/OSC 7)
- Input suggestions + command history now default off, with a one-time
migration that resets persisted true values for existing installs
- Settings dialog: fix broken scrolling — antd v6 renamed the Tabs scroll
container to .ant-tabs-body-holder; theme gallery drops its nested
scroll (single outer scrollbar)
- Offline changelog: CHANGELOG.md at repo root bundled via ?raw; About tab
reads it first, network sources stay as fallback; scripts/sync-changelog.cjs
merges RELEASE_NOTES.md per release (release.cjs fails without an entry)
- commands.ts history prefs default aligned with new off-by-default
- Derive tab-bar/chrome palette by background luminance: light themes keep
a near-background bar with black-tinted tab overlays instead of a muddy
gray strip; dark themes unchanged
- Theme dockview tabs via the group-scoped --dv-*-tab-* vars its own rules
consume (they outspecify our .dv-tab rules and leaked abyss navy onto
light tabs); bump inactive-tab hover specificity to match
- Convert settings dialog + highlight editor hardcoded white text/border
tints to color-mix over --chrome-fg so panes stay readable on light themes
- commands.ts: per-file log write buffer with a single drain loop per file
(burst output coalesces into one appendFile per IO tick, chain no longer
grows per logWrite); stop-tail rides the same buffer
- settingsStore: serialize all writers through mutateSettings() queue that
re-reads latest state per mutation (tray close-action vs settings UI full
saves no longer clobber each other)
- tests: burst ordering + stop-tail case for the log buffer
Recorded logs carried the raw stream: SGR colors, cursor moves,
synchronized-output markers, and every TUI redraw frame — unreadable in an
editor and far larger than the visible output (a short kimi session logged
21.5KB of which 4.4KB is text). A per-session sanitizer now strips ANSI
statefully across chunk boundaries, collapses carriage-return overwrites
(progress bars keep only their final text), and suppresses alternate-screen
frames with a marker line. Ink-style inline TUIs redraw in the normal
buffer and cannot be frame-collapsed without screen emulation; their
committed lines are preserved.