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)
Rules & engine:
- 22 presets (was 11): split status into okstate/warnstate/badstate, add delop,
createop, danger, secret, level, exitcode, percent, http; status words are
case-insensitive and cover the ✓ ✔ ✅ ✗ ✘ ✖ ❌ ⚠ symbol set
- value bands: the first number in a match picks the colour
(percent: <20% red / 20-50% yellow / 50-80% light green / >=80% green)
- optional per-rule `caseInsensitive`, `category`, `bands`; load-time
`refreshBuiltinRules` upgrades untouched built-in patterns in place
Settings page:
- three-way master switch `highlightMode` (all / basic / off); `basic` runs only
the five safety+status rules and `off` empties the rule set rather than
bypassing HighlightStream (which would drop the held tail)
- category column + grouping (`highlightGroupByCategory`), per-rule hit/duration
stats (`highlightStats`, opt-in sink, snapshot once a second), theme-following
colours (`highlightThemeColors`, hue-bucket mapping onto the ANSI palette),
import/export JSON envelope, live preview through the real engine
- named rule subsets bound per host (`highlightPerHost` + `highlightProfiles`
+ `SshConnection.highlightProfileId`); empty ruleIds = every rule
Tests: new tests/hl-rules.mjs (word boundaries, case flag, negative words,
bands, import/export, preview, basic mode, categories, stats, theme colours,
profiles) + profile round-trip in tests/settings-store.mjs
- esbuild alias fixed in the session/sysinfo/sftp test commands (they
could not build at all), .sftp-svc.mjs build documented, real
connection-stability assertions
- tests/build-bundles.cjs builds every bundle fresh; npm test runs the
seven offline suites; esbuild pinned in devDependencies
- remove the assertion-less .exact-inline.mjs; ignore/clean test temp dirs
i18n
- shared/i18n: dependency-free t() with flat per-namespace dictionaries
(common/settings/workspace/terminal/ssh/panels/main), zh-CN fallback
- language picker in Settings -> System; antd ConfigProvider locale follows it
- main process tracks the language too: tray menu, close prompt, ssh/sftp/
zmodem errors and the log TUI marker are translated; tray rebuilds on change
changelog
- CHANGELOG.md (zh-CN canonical) + .zh-TW/.en/.ja, bundled via ?raw and read
per interface language with per-version fallback to zh-CN (no network)
- sync-changelog.cjs merges RELEASE_NOTES[.<lang>].md per release; release.cjs
refuses to publish without a zh-CN entry for the version
settings robustness
- closeAction 'ask' survives the sanitizer (was silently coerced to 'tray',
which made the 'ask every time' option dead)
- highlight rules are repaired instead of dropped: string priority, 0/1
enabled, missing fg colour; unknown fields preserved
- load-time warnings are written to settings-warnings.log (deduped, capped)
terminal/UI
- configurable terminal toolbar: open working directory (default on), session
log recording (off), open logs folder (off)
- global shortcut field records key combos (modifier or F-key required)
- input suggestions + command history default to off, with a one-time reset
migration for existing installs
- settings dialog scrolling fixed (antd v6 renamed the tabs container), theme
gallery nested scrollbar removed, joined segmented pickers with readable
selected-state text
tests: settings-store.mjs (15 checks) added; commands-store.mjs updated for
the new off-by-default history setting
- 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