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
This commit is contained in:
Bill committed 2026-09-20 11:25:59 +08:00
1 parent c1744184a3
commit 34094d607b
17 files changed
+319 -41

No files matched your search

+1 -1
View File
@@ -376,7 +376,7 @@ function loadHistoryPrefs(): { historyEnabled: boolean; historyLimit: number } {
try {
const terminal = loadSettings().terminal
return {
historyEnabled: terminal.historyEnabled !== false,
historyEnabled: terminal.historyEnabled === true,
historyLimit: normalizeLimit(terminal.historyLimit)
}
} catch {
+15 -1
View File
@@ -1,6 +1,7 @@
import { app, ipcMain } from 'electron'
import { app, ipcMain, shell } from 'electron'
import fontList from 'font-list'
import { homedir } from 'os'
import { statSync } from 'fs'
import { Ipc, type AppInfo, type LayoutMeta, type PtyCreateOptions } from '../shared/ipc'
import type { HostKeyAction, SessionOpenOptions, SshConnection, SshConnectionInput } from '../shared/connections'
import { getLayout, listLayouts, saveLayout, deleteLayout } from './layouts'
@@ -181,4 +182,17 @@ export function registerIpc(): void {
resolveCwd(current, arg)
)
ipcMain.handle(Ipc.CWD_REPORT, (_event, payload: string) => normalizeReportedCwd(payload))
// Open a local directory in the file manager. Validates the path is an
// existing directory so renderer-supplied values can't open arbitrary files.
ipcMain.handle(Ipc.CWD_OPEN, (_event, dir: unknown): boolean => {
if (typeof dir !== 'string' || dir === '') return false
try {
if (!statSync(dir).isDirectory()) return false
} catch {
return false
}
void shell.openPath(dir)
return true
})
}
+28 -1
View File
@@ -1,5 +1,5 @@
import { app, ipcMain, powerSaveBlocker } from 'electron'
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'fs'
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'fs'
import { join } from 'path'
import { Ipc } from '../shared/ipc'
import {
@@ -161,6 +161,32 @@ export function loadSettings(): AppSettings {
let settingsQueue: Promise<unknown> = Promise.resolve()
/**
* One-time reset for existing installs: input suggestions and command history
* are now off by default, but installs that upgraded from earlier versions
* already have `true` persisted — flip those off exactly once, then leave a
* marker so the user's later choices are never touched again.
*/
function migrateDefaultsOnce(): void {
try {
const flag = join(app.getPath('userData'), '.migrate-defaults-v2')
if (existsSync(flag)) return
const current = loadSettings()
if (current.terminal.suggestEnabled || current.terminal.historyEnabled) {
current.terminal.suggestEnabled = false
current.terminal.historyEnabled = false
mkdirSync(app.getPath('userData'), { recursive: true })
const path = settingsPath()
const tmp = `${path}.tmp`
writeFileSync(tmp, JSON.stringify(current, null, 2), 'utf8')
renameSync(tmp, path)
}
writeFileSync(flag, '', 'utf8')
} catch {
// best effort: a failed migration just keeps the old values
}
}
/**
* Serialize a settings mutation against every other writer. Each queued step
* re-reads the file so the mutation applies on top of the latest state — a
@@ -196,6 +222,7 @@ export function saveSettings(next: AppSettings): Promise<AppSettings> {
}
export function registerSettingsIpc(): void {
migrateDefaultsOnce()
ipcMain.handle(Ipc.SETTINGS_GET, () => loadSettings())
ipcMain.handle(Ipc.SETTINGS_SET, (_event, next: AppSettings) => saveSettings(next))
}