Commit Graph
23 Commits
Author SHA1 Message Date
Bill 4e5377f49c fix: hardening round from code review (4 P1 + 15 P2)
CI / typecheck + test + build (windows) (push) Canceled after 0s
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
2026-09-27 22:25:03 +08:00
Bill 83dc3f15cc fix(lock): lock screen swallowed every keystroke; add Ctrl+L to lock
The renderer-side guard added in v1.0.17 called preventDefault on every
keydown while locked. A keydown's default action IS inserting the
character into the focused field, so the lock screen's password box
received nothing and a locked app could never be unlocked. Menu
accelerators are already stopped in main (before-input-event); the
renderer guard now just skips its own logic.

Also: Ctrl+L locks the screen from anywhere in the app, terminals
included. The chord is only taken when a lock actually engages, so an
unconfigured app keeps Ctrl+L for the shell's clear-screen.
2026-09-24 22:53:27 +08:00
Bill 35583b2c15 feat(lock): main-window lock screen; harden env gating, sandbox and ssh teardown
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)
2026-09-24 22:16:43 +08:00
Bill e08e1cfec4 feat(highlight): preset overhaul, categories, stats, theme colours, per-host profiles
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
2026-09-23 11:14:31 +08:00
Bill 481f54ed59 feat: custom terminal background image + theme editor hardening
- settings: backgroundImage/backgroundImageOpacity (10..100, default 60)
- main: otimg:// protocol serves only the configured background file
  (path-allowlisted, 403 otherwise); dev http origin cannot load file:
- TerminalView: allowTransparency + transparent theme background while an
  image is set; .term-bg-image layer behind the xterm surface
- terminal.css: .has-bg-image keeps .xterm-viewport transparent — the old
  chrome-bg pin covered the image layer (root cause of image not showing)
- ThemeSettingsTab: image picker + opacity slider
- ThemeEditor: duplicate-name hint now covers builtin names too; seed
  colors normalized to #rrggbb
- settingsStore: sanitize theme colors, reject non-hex values
2026-09-20 23:24:43 +08:00
Bill f128c3e8be chore: event-loop stall detectors for hang diagnosis
WER records AppHangTransient with no stack, so measure lag in both
processes and log it on recovery: [main] event loop stalled / [renderer]
main thread stalled. Tells a main-process block from a renderer freeze
the next time the app 'freezes then recovers'.
2026-09-20 22:25:23 +08:00
Bill e04f4f0ac1 fix(main): SFTP data integrity, session lifecycle, security hardening
- 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
2026-09-20 20:26:34 +08:00
Bill 4c6a29ef28 feat: multi-language interface (zh-CN/zh-TW/en/ja), multilingual offline changelog, settings robustness
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
2026-09-20 14:22:29 +08:00
Bill 34094d607b feat: shortcut recorder, configurable terminal toolbar, offline changelog
- 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
2026-09-20 11:25:59 +08:00
Bill b3744b4705 fix: luminance-aware chrome theming for light themes; serialized settings writes; buffered log flush
- 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
2026-09-17 09:11:05 +08:00
Bill 77fd241b43 chore(dev): give dev builds their own userData and single-instance lock
A dev instance shared the installed build's userData directory and lock, so
starting it demanded killing the real app and it wrote test settings and
session snapshots into the live profile. Dev now uses OpenTerminal-dev and
tags its window title '(dev)'; both instances run side by side.
2026-09-15 01:02:10 +08:00
Bill 458dd9c88b fix: write session logs as plain text instead of raw PTY soup
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.
2026-09-14 21:17:45 +08:00
Bill 01827bf70b fix: resolve drive-relative cd (cd d:) in cwd memory
path.isAbsolute('d:') is false on Windows, so a drive-relative cd resolved
against the current base, produced a nonexistent path, and the pane kept
its creation directory in the snapshot. Map a bare drive-letter argument
to the drive root (the fresh-shell answer; we cannot know the drive's
remembered directory).
2026-09-14 19:52:32 +08:00
Bill 277d8bb117 feat(commands): whole-history dedupe + switchable, capped history
历史命令去重从「只跟最新一条比」改为「全历史去重」:重敲任意一条已有命令
时把原条目提到最前(刷新 lastUsedAt)而不是新插一条,历史里每个命令只出现一次。

新增两个设置(设置 → 终端):
- 记录命令历史:关闭后不再记录新命令;已有历史保留,不清空
- 历史条数上限:默认 100,可配 1..500;写入按上限截断,读取也按当前
  上限截断(调低立即生效)

顺修一个暴露的既有 bug:logWrite 用 async appendFile,两次快速追加会
乱序落盘(line two 先于 line one)。改为按文件串行化追加。

测试:tests/commands-store.mjs 全历史去重 / 提到最前 / 上限生效 /
开关关闭不记录 / 关闭保留已有 / 恢复记录 / 越界上限夹紧,全部通过。
2026-09-14 12:01:23 +08:00
Bill beeb32a76b fix(session): keep panel ids distinct from session ids so restore rebinds panes
面板 id 曾直接复用 pty 会话 id(`addPanel({ id: sessionId })`),而
`updateParameters` 只能换会话、不能改面板 id。于是恢复时面板 id 指向一个
已不存在的会话:分屏少一个、剩下的 pane 背后没有 pty,界面看起来是空白,
也无法输入。

- 新增 `panelId()`,面板 id 与会话 id 彻底分离,并用于所有建面板处
  (新建终端、SSH 连接、分屏复制)
- 新增 `breakIdCoincidence()`:旧快照仍带 `panelId === sessionId`,
  在 `fromJSON` 之前重写 grid/panels/views/activeView 中的 id,
  返回 old→new 映射
- `rebindSessionPanels` 用该映射回查快照,恢复每个 pane 自己的目录
  (否则新 id 查不到记录,cwd 会静默退回 home)

验证(重启恢复 E2E,legacy 快照 + 2 分屏):
  2/2 pane 挂载、2 个独立存活会话、输入可回环、
  cwd 分别回到 D:/AIGC/OpenTerminal 与 C:/Windows、回写快照无 id 冲突;
  restoreSession=false 时正确忽略快照只建 1 个终端。
2026-09-14 11:29:56 +08:00
Bill 6c100542c5 fix: batch of review findings — dead install button, history pollution, TUI completion interference, ssh split session kill, scrollback live apply, zmodem second transfer, sftp shell quoting, replay buffer leak, release guards 2026-09-14 02:20:40 +08:00
Bill e6fc021903 fix: drop leaked NO_COLOR/FORCE_COLOR from the pty environment (Claude Code rendered uncolored) 2026-09-14 01:39:11 +08:00
Bill c4fb6fe7e2 polish: tray balloon title no longer repeats the app name 2026-09-14 01:19:14 +08:00
Bill 70c33a5572 fix: register update IPC handlers (dead check/download buttons since 1.0.1); bump 1.0.4 2026-09-08 21:40:25 +08:00
Bill 2b3fefafd0 feat: close-to-tray default + system settings entry; bump 1.0.3 2026-09-08 17:00:09 +08:00
Bill c3baac3f05 fix: updater proxy strategy (Gitea direct, GitHub system proxy) + changelog via channel release-notes.md 2026-09-08 09:44:34 +08:00
Bill f25a1627f3 feat: about tab with update check/download (Gitea feed first, GitHub fallback)
- updater: dual-feed state machine, manual check/download/install IPC,
  changelog from Gitea releases API with GitHub fallback
- settings: new About tab (version, channel, auto-check toggle, progress)
- system.autoCheckUpdate setting gates the startup check
2026-09-07 17:18:15 +08:00
Bill a9acdbe500 Initial commit: OpenTerminal v0.1.0
Open-source terminal app (local + SSH): split panes, themes, SFTP,
server monitoring, broadcast input, ZMODEM, system tray, single instance.
Electron + React + TypeScript. MIT License.
2026-09-07 16:15:50 +08:00