Fix H3/M33/L25/L32: fix probe dependency, correct ipc comment, throttle taskbar, memoize palette
H3: probe.ts now imports fmtBytes from lib/formatters instead of download.ts,
breaking the circular probe->download dependency direction.
M33: ipc.ts comment corrected to reflect that no batch cap exists; notes the
known M33 issue for future implementation.
L25: App.tsx taskbar subscriber skips IPC when fraction/mode/badge unchanged,
eliminating redundant roundtrips on every progress tick.
L32: paletteActions wrapped in useMemo so CommandPalette sees a stable array
reference; only rebuilds when isDark changes.
typecheck + 242 tests + eslint + prettier green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { execFile } from 'child_process'
|
||||
import { existsSync } from 'fs'
|
||||
import { getYtdlpPath } from './binaries'
|
||||
import { fmtBytes } from './download'
|
||||
import { fmtBytes } from './lib/formatters'
|
||||
import { cleanError } from './log'
|
||||
import { assertHttpUrl } from './url'
|
||||
import { entryUrl, fmtDuration, type RawEntry } from './indexerCore'
|
||||
|
||||
+52
-28
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import { FluentProvider, makeStyles, tokens } from '@fluentui/react-components'
|
||||
import { Sidebar, type TabValue } from './components/Sidebar'
|
||||
import { DownloadsView } from './components/DownloadsView'
|
||||
@@ -56,44 +56,68 @@ function App(): React.JSX.Element {
|
||||
|
||||
// Mirror overall queue progress onto the Windows taskbar. Subscribe to the store
|
||||
// directly (not via a selector) so taskbar updates don't re-render the app.
|
||||
// Compare against the last IPC call and skip when nothing changed — the store
|
||||
// fires on every progress tick and this avoids one IPC roundtrip per tick (L25).
|
||||
useEffect(() => {
|
||||
let lastFraction = -1
|
||||
let lastMode = ''
|
||||
let lastBadge = -1
|
||||
function push(items: ReturnType<typeof useDownloads.getState>['items']): void {
|
||||
const s = summarizeQueue(items)
|
||||
const mode = s.active ? (s.failed > 0 ? 'error' : 'normal') : 'none'
|
||||
if (s.progress === lastFraction && mode === lastMode && s.downloading === lastBadge) return
|
||||
lastFraction = s.progress
|
||||
lastMode = mode
|
||||
lastBadge = s.downloading
|
||||
window.api?.setTaskbarProgress?.({ fraction: s.progress, mode, badgeCount: s.downloading })
|
||||
}
|
||||
push(useDownloads.getState().items)
|
||||
return useDownloads.subscribe((st) => push(st.items))
|
||||
}, [])
|
||||
|
||||
const paletteActions: PaletteAction[] = [
|
||||
{
|
||||
id: 'go-downloads',
|
||||
label: 'Go to Downloads',
|
||||
hint: 'Navigate',
|
||||
run: () => setTab('downloads')
|
||||
},
|
||||
{ id: 'go-library', label: 'Go to Library', hint: 'Navigate', run: () => setTab('library') },
|
||||
{ id: 'go-history', label: 'Go to History', hint: 'Navigate', run: () => setTab('history') },
|
||||
{ id: 'go-terminal', label: 'Go to Terminal', hint: 'Navigate', run: () => setTab('terminal') },
|
||||
{ id: 'go-settings', label: 'Go to Settings', hint: 'Navigate', run: () => setTab('settings') },
|
||||
{
|
||||
id: 'new-download',
|
||||
label: 'New download',
|
||||
hint: 'Focus URL',
|
||||
run: () => {
|
||||
setTab('downloads')
|
||||
// Wait for the download bar to mount after the tab switch, then focus.
|
||||
setTimeout(() => document.getElementById('aerofetch-url')?.focus(), 60)
|
||||
// Memoized so CommandPalette sees a stable array reference between renders —
|
||||
// only rebuilds when the theme label needs to change (L32).
|
||||
const paletteActions = useMemo<PaletteAction[]>(
|
||||
() => [
|
||||
{
|
||||
id: 'go-downloads',
|
||||
label: 'Go to Downloads',
|
||||
hint: 'Navigate',
|
||||
run: () => setTab('downloads')
|
||||
},
|
||||
{ id: 'go-library', label: 'Go to Library', hint: 'Navigate', run: () => setTab('library') },
|
||||
{ id: 'go-history', label: 'Go to History', hint: 'Navigate', run: () => setTab('history') },
|
||||
{
|
||||
id: 'go-terminal',
|
||||
label: 'Go to Terminal',
|
||||
hint: 'Navigate',
|
||||
run: () => setTab('terminal')
|
||||
},
|
||||
{
|
||||
id: 'go-settings',
|
||||
label: 'Go to Settings',
|
||||
hint: 'Navigate',
|
||||
run: () => setTab('settings')
|
||||
},
|
||||
{
|
||||
id: 'new-download',
|
||||
label: 'New download',
|
||||
hint: 'Focus URL',
|
||||
run: () => {
|
||||
setTab('downloads')
|
||||
// Wait for the download bar to mount after the tab switch, then focus.
|
||||
setTimeout(() => document.getElementById('aerofetch-url')?.focus(), 60)
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'toggle-theme',
|
||||
label: isDark ? 'Switch to light theme' : 'Switch to dark theme',
|
||||
hint: 'Appearance',
|
||||
run: () => updateSettings({ theme: isDark ? 'light' : 'dark' })
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'toggle-theme',
|
||||
label: isDark ? 'Switch to light theme' : 'Switch to dark theme',
|
||||
hint: 'Appearance',
|
||||
run: () => updateSettings({ theme: isDark ? 'light' : 'dark' })
|
||||
}
|
||||
]
|
||||
],
|
||||
[isDark, setTab, updateSettings]
|
||||
)
|
||||
|
||||
// AeroFetch's own version, shown in the sidebar. Loaded once over IPC.
|
||||
const [version, setVersion] = useState('')
|
||||
|
||||
+3
-3
@@ -659,9 +659,9 @@ export interface BackupImportResult {
|
||||
|
||||
// --- Sources & media-manager indexing (Pinchflat-style) ---------------------
|
||||
// See ROADMAP-PINCHFLAT.md. A Source (channel/playlist) is indexed once into a
|
||||
// persisted list of MediaItem records; the live download queue then pulls from
|
||||
// that list a batch at a time, so "download an entire channel" never has to hold
|
||||
// the whole collection in the queue at once.
|
||||
// persisted list of MediaItem records. When the user downloads pending items,
|
||||
// all selected entries are enqueued at once — add a batch cap here if large
|
||||
// channel queues (thousands of items) cause performance issues (M33).
|
||||
|
||||
/** Whether a Source is a whole channel or a single playlist. */
|
||||
export type SourceKind = 'channel' | 'playlist'
|
||||
|
||||
Reference in New Issue
Block a user