diff --git a/CODE-AUDIT.md b/CODE-AUDIT.md
index 1a8d966..eab18b7 100644
--- a/CODE-AUDIT.md
+++ b/CODE-AUDIT.md
@@ -436,7 +436,7 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
runs its primary action). Inconsistent.
- [x] **L28 — Default kind/quality applied once.** DownloadBar seeds from settings via a one-shot
ref; changing the default in Settings while on the Downloads tab isn't reflected until reload.
-- [ ] **L29 — UI constant in the store.** `QUALITY_OPTIONS` lives in `store/downloads.ts` yet is a
+- [x] **L29 — UI constant in the store.** `QUALITY_OPTIONS` lives in `store/downloads.ts` yet is a
presentational constant imported by views — couples UI to the store module.
- [x] **L30 — Redundant `loading="lazy"`** on `MediaThumb` images already inside a virtualized
list (the virtualizer only mounts visible rows).
@@ -480,7 +480,7 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
- [x] **L50 — "Cookies saved" shown for 0 cookies.** Closing the sign-in window without logging in
still writes the file and reports `ok` with `cookieCount: 0`.
- [ ] **L51 — Scheduled daily sync time hardcoded to 09:00** (schedule.ts) — on/off toggle only, no time picker.
-- [ ] **L52 — Installer handoff is a fixed `setTimeout(app.quit, 1500)`** (updater.ts) — a race on slow machines.
+- [x] **L52 — Installer handoff is a fixed `setTimeout(app.quit, 1500)`** (updater.ts) — a race on slow machines.
- [x] **L53 — `dialog.show*Dialog(win!, …)` non-null assertions** on a possibly-null window
(chooseFolder, backup export/import) — can throw if the sender window is gone.
- [x] **L54 — DownloadBar preview `
` has no `onError` fallback** (inconsistent with `MediaThumb`'s retry).
diff --git a/src/main/updater.ts b/src/main/updater.ts
index 6c068aa..79d1bb2 100644
--- a/src/main/updater.ts
+++ b/src/main/updater.ts
@@ -1,4 +1,5 @@
-import { app, net, shell, type WebContents } from 'electron'
+import { app, net, type WebContents } from 'electron'
+import { spawn } from 'child_process'
import { createWriteStream, type WriteStream } from 'fs'
import { stat, unlink } from 'fs/promises'
import { join, normalize, dirname } from 'path'
@@ -413,9 +414,6 @@ export async function downloadAppUpdate(url: string, wc: WebContents): Promise {
try {
@@ -428,10 +426,13 @@ export async function runAppUpdate(filePath: string): Promise<{ ok: boolean; err
return { ok: false, error: 'Refused to run an unexpected file.' }
}
await stat(target) // throws if the file is missing
- const err = await shell.openPath(target) // hand the installer to the OS
- if (err) return { ok: false, error: err }
- // Give the installer a beat to spawn, then quit so it can overwrite our files.
- setTimeout(() => app.quit(), INSTALLER_HANDOFF_MS)
+ // Spawn the installer detached + unref'd so it outlives our process even on a
+ // slow machine. shell.openPath goes through ShellExecute but gives us no child
+ // PID to wait on; spawning directly lets us call unref() before we quit.
+ const child = spawn(target, [], { detached: true, stdio: 'ignore', shell: false })
+ child.unref()
+ // Let the IPC response reach the renderer before we exit.
+ setImmediate(() => app.quit())
return { ok: true }
} catch (e) {
return { ok: false, error: e instanceof Error ? e.message : String(e) }
diff --git a/src/renderer/src/components/DownloadBar.tsx b/src/renderer/src/components/DownloadBar.tsx
index 4fb1374..6f8e259 100644
--- a/src/renderer/src/components/DownloadBar.tsx
+++ b/src/renderer/src/components/DownloadBar.tsx
@@ -28,7 +28,8 @@ import {
WarningRegular
} from '@fluentui/react-icons'
import type { MediaInfo, FormatOption, PlaylistInfo } from '@shared/ipc'
-import { useDownloads, QUALITY_OPTIONS, type MediaKind } from '../store/downloads'
+import { useDownloads, type MediaKind } from '../store/downloads'
+import { QUALITY_OPTIONS } from '../qualityOptions'
import { sameVideo } from '../store/queueStats'
import { useSettings } from '../store/settings'
import { Select } from './Select'
diff --git a/src/renderer/src/components/SettingsView.tsx b/src/renderer/src/components/SettingsView.tsx
index 8a793cd..45ecac2 100644
--- a/src/renderer/src/components/SettingsView.tsx
+++ b/src/renderer/src/components/SettingsView.tsx
@@ -55,7 +55,8 @@ import {
} from '@shared/ipc'
import { useSettings } from '../store/settings'
import { useSystemTheme } from '../store/systemTheme'
-import { QUALITY_OPTIONS, useDownloads } from '../store/downloads'
+import { useDownloads } from '../store/downloads'
+import { QUALITY_OPTIONS } from '../qualityOptions'
import { useTemplates } from '../store/templates'
import { useErrorLog } from '../store/errorlog'
import { Select } from './Select'
diff --git a/src/renderer/src/qualityOptions.ts b/src/renderer/src/qualityOptions.ts
new file mode 100644
index 0000000..c4cdd85
--- /dev/null
+++ b/src/renderer/src/qualityOptions.ts
@@ -0,0 +1,6 @@
+import { VIDEO_QUALITY_OPTIONS, AUDIO_QUALITY_OPTIONS } from '@shared/ipc'
+
+export const QUALITY_OPTIONS = {
+ video: VIDEO_QUALITY_OPTIONS,
+ audio: AUDIO_QUALITY_OPTIONS
+} as const
diff --git a/src/renderer/src/store/downloads.ts b/src/renderer/src/store/downloads.ts
index 4c34412..0085744 100644
--- a/src/renderer/src/store/downloads.ts
+++ b/src/renderer/src/store/downloads.ts
@@ -1,7 +1,5 @@
import { create } from 'zustand'
import {
- VIDEO_QUALITY_OPTIONS,
- AUDIO_QUALITY_OPTIONS,
type DownloadEvent,
type DownloadMeta,
type DownloadOptions,
@@ -71,13 +69,6 @@ export interface DownloadItem {
mediaItemId?: string
}
-// `satisfies` (not an annotation) so the tuple element types survive: under
-// noUncheckedIndexedAccess a `readonly string[]` index yields `string | undefined`,
-// but the const tuples keep `QUALITY_OPTIONS[kind][0]` known-defined (L168).
-export const QUALITY_OPTIONS = {
- video: VIDEO_QUALITY_OPTIONS,
- audio: AUDIO_QUALITY_OPTIONS
-} satisfies Record
// Placeholder channel shown while metadata is still being fetched. Tracked as a
// constant so the meta/error/done handlers can recognise and clear it instead of