fix: hardening round from code review (4 P1 + 15 P2)
CI / typecheck + test + build (windows) (push) Canceled after 0s
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
This commit is contained in:
1 parent
33efbe921e
commit
4e5377f49c
27 files changed
+356
-83
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { app, ipcMain, powerSaveBlocker } from 'electron'
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
||||
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
import { Ipc } from '../shared/ipc'
|
||||
import {
|
||||
@@ -435,13 +435,36 @@ function reportWarnings(warnings: string[]): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A file that exists but cannot be parsed must never be silently destroyed:
|
||||
* loadSettings falls back to defaults, and the next mutation (any settings save,
|
||||
* even the tray persisting its close action) would rewrite settings.json from
|
||||
* those defaults — taking the user's custom themes and highlight rules with it.
|
||||
* Keep a one-time copy the user can recover from before that can happen.
|
||||
*/
|
||||
function backupUnparseableSettings(err: unknown): void {
|
||||
try {
|
||||
const file = settingsPath()
|
||||
if (!existsSync(file)) return
|
||||
const bak = `${file}.bak`
|
||||
if (existsSync(bak)) return // keep the first backup; later loads must not clobber it
|
||||
copyFileSync(file, bak)
|
||||
reportWarnings([
|
||||
`settings.json unparseable (${err instanceof Error ? err.message : String(err)}) — original kept at settings.json.bak`
|
||||
])
|
||||
} catch {
|
||||
// best effort: the backup must never break settings loading
|
||||
}
|
||||
}
|
||||
|
||||
export function loadSettings(): AppSettings {
|
||||
try {
|
||||
const raw: unknown = JSON.parse(readFileSync(settingsPath(), 'utf8'))
|
||||
const { settings, errors } = deepMerge(raw)
|
||||
reportWarnings(errors)
|
||||
return settings
|
||||
} catch {
|
||||
} catch (err) {
|
||||
backupUnparseableSettings(err)
|
||||
return {
|
||||
terminal: { ...DEFAULT_SETTINGS.terminal },
|
||||
customThemes: [...DEFAULT_SETTINGS.customThemes],
|
||||
|
||||
Reference in new issue
Block a user