fix(ime): actually ship @xterm/xterm 6.1.0-beta.304 (v1.0.21 built from stale
node_modules with 6.0.0); add predist dependency-consistency gate
(scripts/verify-deps.cjs) and rename-retry shim for the AV scan EPERM race
Titles are stored display text (layout templates, session snapshot,
broadcast registry), so a pane used to keep the wording of the language it
was opened in, and nextTerminalTitle could not even parse the number back
out of another language's pattern (numbering restarted, duplicates).
New pure module src/shared/terminalTitle.ts: resolution tries all four
languages' patterns, rendering uses the active one; i18n gains tFor(lang)
for off-language rendering. Workspace retitles auto-numbered local tabs in
place on language switch, snapshot restore and template apply; SSH panels
(user-typed connection names) are never touched. dockview's title-change
event propagates the new title to the broadcast registry and the snapshot
save with no extra wiring.
New test terminal-title.mjs (16 writer/reader language pairs, non-auto
title boundaries, gap filling); offline suite grows 17 -> 18.
Renderer code is fully bundled by Vite into out/renderer; externalizeDepsPlugin
only applies to main/preload, so renderer packages in 'dependencies' were
shipped twice. Moved to devDependencies: @xterm/*, antd, dockview-react,
react, react-dom, zustand. Kept in dependencies (imported by src/main):
@lydell/node-pty, ssh2, zmodem.js, font-list, electron-updater. undici is
now explicit (release.cjs/download-stats.cjs require it). Also dropped the
dead @xterm/addon-serialize (zero imports anywhere).
Verified: npm test 17/17 green, dist:dir builds, asar node_modules contains
only main-process deps, asarUnpack natives intact, win-unpacked smoke
(window, theme, pty spawn) passes.
- updater-fallback.mjs (82 assertions): GitHub probe fallback to Gitea,
timeout budgets, in-flight check never stuck in 'checking'; updater.ts
gains a setUpdateTimeouts test seam, electron-updater aliased to a stub
- ipc-guard.mjs (43): trusted-frame guard exercised through real
registerIpc handlers with forged senderFrames; electron-stub now records
registrations via globalThis so bundle and test share one instance
- log-sanitizer.mjs (71): CSI/OSC/charset state machine, alt-screen fold,
byte-split fuzz equal to whole-chunk output; fixes a wrong comment
- sftp-timeout.mjs (39): per-op timeouts (metadata 30s, transfer chunk 60s,
open 10s) evict half-dead channels with one retry, slow-but-progressing
transfers untouched, late rejections never unhandled
- reservedAccelerators.ts: single pure isReservedAccelerator shared by the
settings recorder and applyGlobalShortcut (the two tables had drifted —
main now also refuses Ctrl+=/-/0/PgUp/PgDn legacy values); 56 assertions
- ssh-loopback.mjs loads the real ssh.ts via a bundle (50 assertions):
TOFU pinning, fail-closed stores, auth gate, connect budget
Offline suite grows 13 -> 17. Renderer test framework evaluated: not
introducing vitest/jsdom; pure logic keeps being extracted and tested
through the existing bundle harness.
New localPathGrants.ts: an in-memory registry of paths the user picked in a
native dialog. Every check resolves realpath + stat at grant and use time;
Windows case folding; missing files, directories-as-files, devices and
symlinked parents can never pass. SFTP upload/download and zmodem send/
receive now refuse renderer-supplied local paths that were never granted,
returning the resolved path so callers never re-traverse a symlink. keyPath
is validated as a regular file <= 1MB before reading (a device node would
have blocked the UI thread forever). Tests inject a stub policy via
setLocalPathPolicy; production always defaults to the real registry.
Also: webPreferences now explicitly pins contextIsolation/nodeIntegration/
webSecurity instead of relying on defaults.
New offline test tests/local-path-grants.mjs (37 assertions incl. symlink
escape); offline suite grows to 13. i18n: 6 main.sftp/main.key error keys
in 4 languages.
- remove the application menu while locked (lockMenu.ts): Alt reveals the
default menu and its mouse-clickable Reload/DevTools/Zoom items bypass
before-input-event entirely; menu is rebuilt from the default template on
unlock, startup-restored locks covered from initLockController
- extract the keyboard classification into pure lockShortcuts.ts
(isLockBlockedShortcut/isPanicLockChord) with a table-driven test
(lock-shortcuts.mjs, 29 cases) — the v1.0.17 lockout escaped CI because
this decision lived inline in createWindow()
- reserve Ctrl+L in the global show/hide shortcut recorder: a global
registration intercepts the chord at OS level and silently disables the
panic lock
- skip auto-repeat keydowns in the panic-lock branch (held Ctrl+L on an
unconfigured app re-read lock.json + settings.json per repeat)
- LockScreen: re-sync the cooldown clock on visibilitychange/focus/pageshow
so a suspended renderer timer cannot leave the password input disabled
past the real deadline
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
- website/: zero-dependency static landing page (HTML+CSS), corporate
light theme, real app screenshots, download links pointing to the
git.codingplan.site release channel (exe/msi) with GitHub mirror
- .github/workflows/deploy-website.yml: deploy website/ to GitHub Pages
on push to main (paths: website/**)
- package.json: author -> CodingPlan.Site
- README: link to the site and its source folder
- 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
setGlobalDispatcher routed every request — including the Gitea release and
channel PUTs — through the local proxy, which reset mid-upload once and left
the channel half-written. The proxy agent is now passed explicitly on the
GitHub requests only.
- Sidebar/tab bars/dividers/settings dialog follow the terminal theme
- Tab accent color customizable in theme settings (color picker)
- Title bar shows the app icon; Material Dark is the default theme
- Slimmer rectangular tabs (28px), slate scrollbars, visible pane dividers
- Fix pure-black xterm viewport gaps after pane resize