Fix L29/L52: QUALITY_OPTIONS moved to UI module; installer spawn via child_process
L29: QUALITY_OPTIONS was a presentational constant living in store/downloads.ts,
coupling views to the store. Moved to src/renderer/src/qualityOptions.ts,
which assembles it directly from @shared/ipc. store/downloads.ts no longer
imports VIDEO/AUDIO_QUALITY_OPTIONS. Both consumers (DownloadBar, SettingsView)
updated to import from the new module.
L52: Replaced the fixed `setTimeout(app.quit, 1500)` installer handoff with a
detached spawn. The installer is spawned with stdio:'ignore' and unref'd so
it outlives AeroFetch even on a slow machine. app.quit() fires on setImmediate
so the IPC response returns to the renderer before the process exits.
typecheck + 242 tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -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 `<img>` has no `onError` fallback** (inconsistent with `MediaThumb`'s retry).
|
||||
|
||||
+9
-8
@@ -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<A
|
||||
})
|
||||
}
|
||||
|
||||
/** Let the launched installer spawn before we quit to release our files (ms). */
|
||||
const INSTALLER_HANDOFF_MS = 1500
|
||||
|
||||
/** Launch a freshly-downloaded installer, then quit so it can replace the app. */
|
||||
export async function runAppUpdate(filePath: string): Promise<{ ok: boolean; error?: string }> {
|
||||
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) }
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
@@ -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<MediaKind, readonly string[]>
|
||||
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user