Commit Graph
17 Commits
Author SHA1 Message Date
Bill 6e7c9d5376 feat(sftp): 文件面板列头/排序、字体缩放与空白处右键菜单
- 列表头改为 sticky 并支持点击排序(名称/大小/修改时间/权限/用户/组,目录恒在前)
- 时间显示改为 YYYY/M/D HH:MM,大小显示改为 30.7 KB 风格
- 新增字体缩放 0.8–1.8(A-/A+ 与 Ctrl+滚轮),行高字号联动并记忆到 localStorage
- 行高/字号统一由 FilePanel.tsx 计算,经 CSS 变量下发,sftp.css 只消费变量
- 主进程新增 readdirDetailed + parseLongnameOwner,从 OpenSSH longname 解析真实的用户/组
- 列表空白处右键弹出菜单(行内右键仍走原菜单,两者不叠加)
- 修复:antd 把 ant-dropdown-trigger 克隆到行元素上,旧规则以更高特异性顶掉了 .sftp-row 的 display:grid,导致列此前并未按网格对齐
- 4 语言词典补充列名与缩放键
2026-10-09 08:33:06 +08:00
Bill a0be827650 test(main): cover updater fallback, ipc sender guard, log sanitizer, sftp timeouts
- 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.
2026-10-07 22:57:16 +08:00
Bill 5ff311ea0f docs+chore: refresh README/STATE, cache electron in CI, lock chord by keycode, misc P3
- README: add lock-screen section, refresh test list (13 offline tests),
  updater fallback order, data locations, architecture tree
- STATE.md: v1.0.20, current release.cjs flow (no --skip-github), M11-M13
- ci.yml: actions/cache for the ~100MB Electron binary keyed on lockfile
- .gitignore: ignore .env.* but keep committed templates
- scripts/archive-releases.cjs moved in from tmp-test; KEEP_TAG is now a
  required CLI arg (a hardcoded default is how the wrong version gets wiped)
- lockShortcuts: isPanicLockChord matches input.code ('KeyL') so Dvorak and
  non-Latin layouts trigger Ctrl+L lock correctly
- settings: drop the dead single-option update-channel Select from About tab
- Workspace/App: memo(IconRail/LayoutFlyout), useMemo layoutMenu keyed on
  language, useCallback onOpenSettings; drop unused IconRail onSplit prop
2026-10-07 22:06:58 +08:00
Bill d22923aedd security(main): dialog-grant admission for local paths, explicit webPreferences
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.
2026-10-07 22:06:45 +08:00
Bill 9c75cdc7d4 perf(renderer): field-level settings subscriptions, file list virtualization, per-panel error boundary
- TerminalView: replace whole-settings subscription with five useShallow
  field groups; theme writes no longer refit every pane, and unrelated
  settings writes no longer touch xterm options at all. useResolvedTheme
  is now a shallow subscription + memoized lookup. TerminalHandle was dead
  (no caller ever passed a ref) — dropped forwardRef/useImperativeHandle
- FilePanel: fixed-row-height (24px) windowing above 200 entries, no new
  dependency (antd 6 ships @rc-component/virtual-list only transitively);
  per-row Dropdown kept. NOTE: .sftp-row is now box-sizing:border-box
  height:24px, keep in sync with ROW_HEIGHT in FilePanel.tsx
- PanelErrorBoundary wraps each dockview panel so a pane crash no longer
  unmounts the whole workspace (i18n: workspace.error.panelTitle/panelRetry)
- SshBottomPanel: memo(FilePanel) — sessionId is the only prop and constant
- TransferPanel: ref-held Map + version counter replaces per-event Map
  copies; memo rows skip unchanged entries
- MonitorPanel: ResizeObserver/canvas setup runs once, data updates only
  redraw
2026-10-07 22:06:31 +08:00
Bill 6c04f0e8c1 feat(theme): add background image dim scrim slider
New terminal.backgroundImageDim setting (0-90%, default 0 = off). A black
scrim layer sits between the background image and the xterm screen, so
bright wallpapers stay readable at any opacity: unlike the opacity slider
(which blends the image toward the dark dock base), the scrim darkens the
image while preserving its saturation, and the raised minimumContrastRatio
(4.5) keeps lifted text legible on top.

- settings: backgroundImageDim with deepMerge type-fallback sanitize
- TerminalView: render .term-bg-dim only when image set and dim > 0
- ThemeSettingsTab: slider follows the existing draft + onChangeComplete
  pattern (no settings writes while dragging)
- i18n: settings.theme.backgroundImageDim(+Desc) in zh-CN/zh-TW/en/ja
- AGENTS.md: document the third image-mode invariant
2026-10-07 21:24:27 +08:00
Bill e16c860c7a fix(theme): dim background image toward dark and lift text contrast in image mode
On light themes the pane base is near-white (--chrome-bg), so lowering the
background image opacity washed the wallpaper out to white instead of
darkening it, and the theme's dark foreground text became unreadable.

- terminal.css: in has-bg-image mode the dock gets a fixed dark base
  (#0d1117), so the opacity slider always dims toward dark regardless of
  theme
- TerminalView: raise xterm minimumContrastRatio from 1 to 4.5 (WCAG AA,
  same as VS Code's terminal default) while an image is set; xterm treats
  the transparent background as black luminance, so dark foreground colors
  are lifted to stay readable over the wallpaper. Also fix the option name
  (minContrastRatio is silently ignored; the real key is
  minimumContrastRatio)
- i18n: update backgroundImageDesc in zh-CN/zh-TW/en/ja
- AGENTS.md: document the two image-mode invariants
2026-10-07 21:02:14 +08:00
Bill 36627ee4b7 i18n: add keys for zmodem/startup/updater/sftp/template messages 2026-10-07 20:44:45 +08:00
Bill b7938de464 fix(ui): theme-derived sftp/monitor surfaces, transfer cancel button, dialog cleanup; drop dead autoWrap; gentler bg-image default 2026-10-07 20:44:31 +08:00
Bill b7c7c5cd76 fix(highlight): settings UI fixes from review — grouped view, import guard, basic flag
CI / typecheck + test + build (windows) (push) Canceled after 0s
- grouped view actually clusters: drop the priority column's defaultSortOrder
  that made antd re-sort the dataSource and undo the category clustering
- replace import is Popconfirm-guarded and the import mode resets to append
  each time the dialog opens (it stayed on the destructive choice)
- rule editor exposes the basic flag (basic-mode membership) with a switch;
  clearing it on edit now actually removes the flag
- rule/profile writes read the store at call time instead of the render-scoped
  array, so two writes in one React batch no longer drop the first
- profile editor lists the rules a non-empty profile leaves out (uses the
  previously dead excludedByProfile helper)
- bands editor keeps rows sorted by min; band preview keys by index (duplicate
  min no longer collides); clear-background also closes the bg picker
- TerminalView pushes compiled rules into the HighlightStream from an effect
  instead of during render
2026-09-28 13:06:40 +08:00
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 c0342db1e8 fix(shared): i18n coverage and contract updates
- keyPath field no longer tells users to enter a server-side path
- settings.highlight.builtin.* notes, timeout messages in four languages
- prototype-safe dictionary lookup; language as a render-time dependency
- Partial<AppSettings> saveSettings contract, update:stateGet channel
2026-09-20 20:27:03 +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