Fix L49/L53/L59: error string newlines, dialog null-safety, shared theme constant

L49: Remove newline from missing-binary error message in download.ts; inline
     error spans render it as literal newline, now one sentence with period.
L53: backup.ts exportBackup/importBackup/showMessageBox no longer use win!
     non-null assertions -- each uses a guarded ternary (same pattern index.ts
     already used for chooseFolder).
L59: Extract PAGE_BACKGROUND to src/shared/theme.ts; main/index.ts and
     renderer/src/theme.ts both import from it -- no more "keep in sync" comment.

typecheck + 242 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 13:42:22 -04:00
parent 72462cab1e
commit 9d816f0c86
6 changed files with 35 additions and 32 deletions
+13 -8
View File
@@ -12,10 +12,11 @@ interface BackupFile {
}
export async function exportBackup(win: BrowserWindow | undefined): Promise<BackupExportResult> {
const res = await dialog.showSaveDialog(win!, {
const opts = {
defaultPath: 'aerofetch-backup.json',
filters: [{ name: 'JSON', extensions: ['json'] }]
})
}
const res = await (win ? dialog.showSaveDialog(win, opts) : dialog.showSaveDialog(opts))
if (res.canceled || !res.filePath) return { ok: false }
// Strip credentials (proxy creds, API tokens) so a backup file shared or synced
// to the cloud doesn't leak secrets in plaintext (M22). The user re-enters them
@@ -32,10 +33,11 @@ export async function exportBackup(win: BrowserWindow | undefined): Promise<Back
}
export async function importBackup(win: BrowserWindow | undefined): Promise<BackupImportResult> {
const res = await dialog.showOpenDialog(win!, {
properties: ['openFile'],
const openOpts = {
properties: ['openFile' as const],
filters: [{ name: 'JSON', extensions: ['json'] }]
})
}
const res = await (win ? dialog.showOpenDialog(win, openOpts) : dialog.showOpenDialog(openOpts))
if (res.canceled || !res.filePaths[0]) return { ok: false }
let parsed: unknown
@@ -86,8 +88,8 @@ export async function importBackup(win: BrowserWindow | undefined): Promise<Back
})
.join('\n')
const more = commandTemplates.length > 10 ? `\n…and ${commandTemplates.length - 10} more.` : ''
const choice = await dialog.showMessageBox(win!, {
type: 'warning',
const msgOpts = {
type: 'warning' as const,
buttons: ['Enable custom commands', 'Import without enabling', 'Cancel'],
defaultId: 1,
cancelId: 2,
@@ -99,7 +101,10 @@ export async function importBackup(win: BrowserWindow | undefined): Promise<Back
'Custom commands run extra yt-dlp flags on every download. Only enable them if you trust this backup file.\n\n' +
preview +
more
})
}
const choice = await (win
? dialog.showMessageBox(win, msgOpts)
: dialog.showMessageBox(msgOpts))
if (choice.response === 2) return { ok: false } // Cancel — change nothing
applyCustomCommands = choice.response === 0
}
+1 -3
View File
@@ -239,9 +239,7 @@ export function startDownload(wc: WebContents, opts: StartDownloadOptions): Star
if (missingBins.length > 0) {
return {
ok: false,
error:
`${missingBins.join(' and ')} not found in ${getBinDir()}\n` +
`Add the ffmpeg build's binaries to resources/bin/ (see the README there).`
error: `${missingBins.join(' and ')} not found in ${getBinDir()}. Add the ffmpeg build's binaries to resources/bin/ (see the README there).`
}
}
// Reject anything that isn't an http(s) URL before it reaches yt-dlp's argv,
+5 -10
View File
@@ -22,6 +22,7 @@ import {
type SystemThemeInfo,
type TaskbarProgress
} from '@shared/ipc'
import { PAGE_BACKGROUND } from '@shared/theme'
import { getYtdlpVersion, updateYtdlp, runStartupYtdlpAutoUpdate } from './ytdlp'
import { getFfmpegVersions } from './ffmpeg'
import { checkForAppUpdate, downloadAppUpdate, runAppUpdate } from './updater'
@@ -109,12 +110,6 @@ if (is.dev && devScript) {
let mainWindow: BrowserWindow | null = null
// Page background per theme — keep in sync with `pageBackground` in
// src/renderer/src/theme.ts. Set as the window's NATIVE background so the
// one-frame compositor repaint (when a tooltip/dropdown overlay first paints on
// Windows) shows the app's current color instead of a mismatched white flash.
const THEME_BACKGROUND = { light: '#f7f7f8', dark: '#161618' } as const
// Resolve 'system' against the OS preference so callers always get a concrete color.
function resolveBackgroundMode(theme: Settings['theme']): 'light' | 'dark' {
return theme === 'system' ? (nativeTheme.shouldUseDarkColors ? 'dark' : 'light') : theme
@@ -177,7 +172,7 @@ function createWindow(): void {
minWidth: 640,
minHeight: 480,
show: false,
backgroundColor: THEME_BACKGROUND[resolveBackgroundMode(getSettings().theme)],
backgroundColor: PAGE_BACKGROUND[resolveBackgroundMode(getSettings().theme)],
autoHideMenuBar: true,
webPreferences: {
preload: join(__dirname, '../preload/index.cjs'),
@@ -342,7 +337,7 @@ function registerIpcHandlers(): void {
// caption always matches the in-app theme (W3). Use the validated result.
if (partial.theme) {
BrowserWindow.fromWebContents(e.sender)?.setBackgroundColor(
THEME_BACKGROUND[resolveBackgroundMode(result.theme)]
PAGE_BACKGROUND[resolveBackgroundMode(result.theme)]
)
applyNativeTheme(result.theme)
}
@@ -394,7 +389,7 @@ function registerIpcHandlers(): void {
// background and title bar in sync the same way settingsSet does (W3).
if (result.ok) {
const theme = getSettings().theme
win?.setBackgroundColor(THEME_BACKGROUND[resolveBackgroundMode(theme)])
win?.setBackgroundColor(PAGE_BACKGROUND[resolveBackgroundMode(theme)])
applyNativeTheme(theme)
}
return result
@@ -461,7 +456,7 @@ function registerIpcHandlers(): void {
function registerSystemThemeBridge(): void {
nativeTheme.on('updated', () => {
const info = getSystemThemeInfo()
const bg = THEME_BACKGROUND[resolveBackgroundMode(getSettings().theme)]
const bg = PAGE_BACKGROUND[resolveBackgroundMode(getSettings().theme)]
for (const win of BrowserWindow.getAllWindows()) {
win.webContents.send(IpcChannels.systemThemeUpdate, info)
if (getSettings().theme === 'system') win.setBackgroundColor(bg)
+2 -8
View File
@@ -1,3 +1,4 @@
import { PAGE_BACKGROUND } from '@shared/theme'
import {
createLightTheme,
createDarkTheme,
@@ -168,14 +169,7 @@ export function getTheme(accent: AccentColor, mode: 'light' | 'dark'): Theme {
return mode === 'dark' ? preset.dark : preset.light
}
// Page background behind the app shell. Light: a clean, barely-cool off-white.
// Dark: neutral charcoal. Intentionally accent-INDEPENDENT — the accent shows up
// only on buttons/links, so this stays neutral whichever preset is chosen.
// Keep light/dark in sync with THEME_BACKGROUND in src/main/index.ts.
export const pageBackground = {
light: '#f7f7f8',
dark: '#161618'
}
export const pageBackground = PAGE_BACKGROUND
// Per-kind thumbnail tints. Video vs audio differ by ramp DEPTH rather than a
// competing hue, keeping a monochrome look. Theme-aware (light/dark) but
+11
View File
@@ -0,0 +1,11 @@
/**
* Native window background colors per theme mode. Light: barely-cool off-white.
* Dark: neutral charcoal. These match the renderer's page background so there's
* no flash when a compositor overlay (tooltip, dropdown) first renders on Windows.
* Intentionally accent-independent — the accent shows on buttons/links only.
*
* Shared between main/index.ts (BrowserWindow backgroundColor) and
* renderer/src/theme.ts (CSS backgroundColor on the root div) so the two never
* drift apart.
*/
export const PAGE_BACKGROUND = { light: '#f7f7f8', dark: '#161618' } as const