Self-review H1: give useDownloadBar an explicit return type

The extracted hook relied on inferred return typing, inconsistent with the
codebase convention (useClipboardLink: ClipboardLink, useResolvedDark: boolean).
Add a compiler-verified DownloadBarController interface and annotate the hook —
the returned object is checked against it, so the surface can't silently drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 21:41:06 -04:00
parent e0afaf0fdf
commit 9e0064aab2
@@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from 'react' import { useState, useEffect, useRef, type Dispatch, type SetStateAction } from 'react'
import { import {
parseUrlShortcutContent, parseUrlShortcutContent,
type MediaInfo, type MediaInfo,
@@ -12,7 +12,12 @@ import { useDownloads, type MediaKind } from '../../store/downloads'
import { QUALITY_OPTIONS } from '../../qualityOptions' import { QUALITY_OPTIONS } from '../../qualityOptions'
import { sameVideo } from '../../store/queueStats' import { sameVideo } from '../../store/queueStats'
import { useSettings } from '../../store/settings' import { useSettings } from '../../store/settings'
import { useClipboardLink, looksLikeUrl, firstUrlInText } from '../../useClipboardLink' import {
useClipboardLink,
looksLikeUrl,
firstUrlInText,
type SuggestionSource
} from '../../useClipboardLink'
import { logError } from '../../reportError' import { logError } from '../../reportError'
/** Pull the URL= target out of a dropped Windows .url Internet Shortcut file. */ /** Pull the URL= target out of a dropped Windows .url Internet Shortcut file. */
@@ -29,12 +34,78 @@ export function toLocalDatetimeValue(d: Date): string {
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}` return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`
} }
/** Everything the DownloadBar render needs — the hook's public surface. */
export interface DownloadBarController {
// form state
url: string
kind: MediaKind
quality: string
// trim / schedule
showTrim: boolean
setShowTrim: Dispatch<SetStateAction<boolean>>
trim: string
showSchedule: boolean
setShowSchedule: Dispatch<SetStateAction<boolean>>
scheduleAt: string
setScheduleAt: Dispatch<SetStateAction<string>>
// drag / dup
dragActive: boolean
setDragActive: Dispatch<SetStateAction<boolean>>
dup: string | null
setDup: Dispatch<SetStateAction<string | null>>
// advanced / preview
showAdvanced: boolean
setShowAdvanced: Dispatch<SetStateAction<boolean>>
downloadOptions: DownloadOptions
incognito: boolean
setIncognito: Dispatch<SetStateAction<boolean>>
commandPreview: CommandPreviewResult | null
previewLoading: boolean
// probe (single video)
info: MediaInfo | null
thumbFailed: string | null
setThumbFailed: Dispatch<SetStateAction<string | null>>
probing: boolean
probeError: string | null
usingFormats: boolean
selectedFormat: FormatOption | undefined
// playlist
playlist: PlaylistInfo | null
selected: Set<number>
allSelected: boolean
// suggestion banner
suggestion: string | null
suggestionSource: SuggestionSource
dismissSuggestion: () => void
// handlers
onDragOver: (e: React.DragEvent) => void
onDrop: (e: React.DragEvent) => void
onUrlChange: (next: string) => void
onKindChange: (next: MediaKind) => void
onQualityChange: (v: string) => void
onFormatIdChange: (v: string) => void
onTrimChange: (v: string) => void
onDownloadOptionsChange: (next: DownloadOptions) => void
toggleCommandPreview: () => Promise<void>
fetchFormats: () => Promise<void>
paste: () => Promise<void>
acceptSuggestion: () => void
download: () => void
downloadAnyway: () => void
addPlaylist: () => void
toggleEntry: (index: number, on: boolean) => void
toggleAll: () => void
effKind: (index: number) => MediaKind
toggleItemKind: (index: number) => void
setAllKinds: (k: MediaKind) => void
}
/** /**
* All state and behaviour for the DownloadBar. Extracted from the component so * All state and behaviour for the DownloadBar. Extracted from the component so
* the ~17 interdependent state hooks and their handlers live in one place and * the ~17 interdependent state hooks and their handlers live in one place and
* the component is render-only. * the component is render-only.
*/ */
export function useDownloadBar() { export function useDownloadBar(): DownloadBarController {
const addFromUrl = useDownloads((s) => s.addFromUrl) const addFromUrl = useDownloads((s) => s.addFromUrl)
const addMany = useDownloads((s) => s.addMany) const addMany = useDownloads((s) => s.addMany)
const settingsLoaded = useSettings((s) => s.loaded) const settingsLoaded = useSettings((s) => s.loaded)