Self-review M5/M6/UX1: accurate preview, invalidate on change, reset overrides

Fixes found reviewing the previous commit:

- M5 preview was inaccurate: loadCommandPreview omitted formatId/formatHasAudio
  and trim, so the "exact command" differed from what download() actually spawns
  whenever a probe-selected format or a trim range was set. toggleCommandPreview
  now mirrors download()'s StartDownloadOptions (format + trim included).
- Stale preview: a shown command only cleared on URL change. Now it also clears
  on kind, quality, format, trim, download-options change, and on probe — so the
  displayed command is never out of sync with the form.
- Command button is now a real toggle (Show command / Hide command) instead of a
  no-op "Command · shown" label.
- Per-download overrides are now one-shot: incognito + downloadOptions reset to
  defaults after a download, matching trim/schedule and preventing incognito from
  silently carrying into the next download.
- Style: replaced two inline style={{}} usages with makeStyles classes
  (commandError), hardcoded 'monospace' -> tokens.fontFamilyMonospace, dropped an
  unnecessary fragment, and let long command lines wrap (pre-wrap/break-all).

typecheck + 243 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 20:46:33 -04:00
parent cfa76314d7
commit e0465bc45e
+57 -26
View File
@@ -264,12 +264,20 @@ const useStyles = makeStyles({
backgroundColor: tokens.colorNeutralBackground2, backgroundColor: tokens.colorNeutralBackground2,
...shorthands.borderRadius(tokens.borderRadiusLarge), ...shorthands.borderRadius(tokens.borderRadiusLarge),
border: `1px solid ${tokens.colorNeutralStroke2}`, border: `1px solid ${tokens.colorNeutralStroke2}`,
fontFamily: 'monospace', fontFamily: tokens.fontFamilyMonospace,
fontSize: tokens.fontSizeBase200, fontSize: tokens.fontSizeBase200,
overflowX: 'auto', overflowX: 'auto',
whiteSpace: 'pre-wrap',
wordBreak: 'break-all',
color: tokens.colorNeutralForeground1 color: tokens.colorNeutralForeground1
}, },
commandError: {
color: tokens.colorStatusDangerForeground1
},
optionsPanel: { optionsPanel: {
display: 'flex',
flexDirection: 'column',
gap: '12px',
padding: '12px', padding: '12px',
backgroundColor: tokens.colorNeutralBackground2, backgroundColor: tokens.colorNeutralBackground2,
...shorthands.borderRadius(tokens.borderRadiusLarge), ...shorthands.borderRadius(tokens.borderRadiusLarge),
@@ -403,20 +411,28 @@ export function DownloadBar(): React.JSX.Element {
setItemKinds({}) setItemKinds({})
} }
async function loadCommandPreview(): Promise<void> { // Fetch (or toggle off) the exact yt-dlp command line for the current form
const trimmed = url.trim() // state. The options mirror what download() sends so the preview matches the
if (!trimmed) { // command that will actually run -- including a probe-selected format and any
// trim spec (M5).
async function toggleCommandPreview(): Promise<void> {
if (commandPreview) {
setCommandPreview(null) setCommandPreview(null)
return return
} }
const trimmed = url.trim()
if (!trimmed) return
setPreviewLoading(true) setPreviewLoading(true)
try { try {
const result = await window.api.previewCommand({ const result = await window.api.previewCommand({
id: 'preview', id: 'preview',
url: trimmed, url: trimmed,
kind, kind: usingFormats ? 'video' : kind,
quality, quality: usingFormats && selectedFormat ? selectedFormat.label : quality,
options: downloadOptions formatId: usingFormats ? selectedFormat?.id : undefined,
formatHasAudio: usingFormats ? selectedFormat?.hasAudio : undefined,
options: downloadOptions,
trim: trim.trim() || undefined
}) })
setCommandPreview(result) setCommandPreview(result)
} catch (e) { } catch (e) {
@@ -431,13 +447,15 @@ export function DownloadBar(): React.JSX.Element {
// Any probed info -- or a duplicate warning -- is stale once the URL changes. // Any probed info -- or a duplicate warning -- is stale once the URL changes.
if (info || probeError || playlist) clearProbe() if (info || probeError || playlist) clearProbe()
if (dup) setDup(null) if (dup) setDup(null)
if (commandPreview) setCommandPreview(null) setCommandPreview(null)
confirmDup.current = false confirmDup.current = false
} }
function onKindChange(next: MediaKind): void { function onKindChange(next: MediaKind): void {
setKind(next) setKind(next)
setQuality(QUALITY_OPTIONS[next][0]) setQuality(QUALITY_OPTIONS[next][0])
// The command changes with kind/quality, so any shown preview is now stale.
setCommandPreview(null)
} }
async function fetchFormats(): Promise<void> { async function fetchFormats(): Promise<void> {
@@ -447,6 +465,8 @@ export function DownloadBar(): React.JSX.Element {
setProbeError(null) setProbeError(null)
setInfo(null) setInfo(null)
setPlaylist(null) setPlaylist(null)
// Probing may switch the URL to the format-picker path, changing the command.
setCommandPreview(null)
try { try {
const res = await window.api.probe(trimmed) const res = await window.api.probe(trimmed)
if (res.ok && res.kind === 'playlist' && res.playlist) { if (res.ok && res.kind === 'playlist' && res.playlist) {
@@ -573,6 +593,11 @@ export function DownloadBar(): React.JSX.Element {
setShowSchedule(false) setShowSchedule(false)
setShowAdvanced(false) setShowAdvanced(false)
setCommandPreview(null) setCommandPreview(null)
// Per-download overrides are one-shot (like trim/schedule): reset so the next
// download starts from the global defaults, and incognito never silently
// carries over to a following download.
setIncognito(false)
setDownloadOptions(DEFAULT_DOWNLOAD_OPTIONS)
clearProbe() clearProbe()
} }
@@ -814,7 +839,10 @@ export function DownloadBar(): React.JSX.Element {
> >
<Textarea <Textarea
value={trim} value={trim}
onChange={(_, d) => setTrim(d.value)} onChange={(_, d) => {
setTrim(d.value)
setCommandPreview(null)
}}
placeholder={'0:30-1:45\n3:00-3:30'} placeholder={'0:30-1:45\n3:00-3:30'}
resize="vertical" resize="vertical"
/> />
@@ -855,22 +883,18 @@ export function DownloadBar(): React.JSX.Element {
size="small" size="small"
appearance="subtle" appearance="subtle"
icon={previewLoading ? <Spinner size="tiny" /> : <WindowConsoleRegular />} icon={previewLoading ? <Spinner size="tiny" /> : <WindowConsoleRegular />}
onClick={loadCommandPreview} onClick={toggleCommandPreview}
disabled={!url.trim() || previewLoading} disabled={!url.trim() || previewLoading}
> >
{commandPreview ? 'Command · shown' : 'Show command'} {commandPreview ? 'Hide command' : 'Show command'}
</Button> </Button>
</div> </div>
{commandPreview && ( {commandPreview && (
<div className={styles.commandPreviewPanel}> <div className={styles.commandPreviewPanel}>
{commandPreview.ok ? ( {commandPreview.ok ? (
<> commandPreview.command
<div>{commandPreview.command}</div>
</>
) : ( ) : (
<div style={{ color: tokens.colorStatusDangerForeground1 }}> <span className={styles.commandError}>Error: {commandPreview.error}</span>
Error: {commandPreview.error}
</div>
)} )}
</div> </div>
)} )}
@@ -881,13 +905,14 @@ export function DownloadBar(): React.JSX.Element {
onChange={(_, d) => setIncognito(!!d.checked)} onChange={(_, d) => setIncognito(!!d.checked)}
label="Incognito mode (no logging, no history, no cookies)" label="Incognito mode (no logging, no history, no cookies)"
/> />
<div style={{ marginTop: '12px' }}> <DownloadOptionsForm
<DownloadOptionsForm value={downloadOptions}
value={downloadOptions} onChange={(next) => {
onChange={setDownloadOptions} setDownloadOptions(next)
kind={kind} setCommandPreview(null)
/> }}
</div> kind={kind}
/>
</div> </div>
)} )}
</div> </div>
@@ -916,7 +941,10 @@ export function DownloadBar(): React.JSX.Element {
size="large" size="large"
value={selectedFormat?.id ?? ''} value={selectedFormat?.id ?? ''}
options={info.formats.map((f) => ({ value: f.id, label: f.label }))} options={info.formats.map((f) => ({ value: f.id, label: f.label }))}
onChange={setFormatId} onChange={(v) => {
setFormatId(v)
setCommandPreview(null)
}}
/> />
) : ( ) : (
<Select <Select
@@ -925,7 +953,10 @@ export function DownloadBar(): React.JSX.Element {
size="large" size="large"
value={quality} value={quality}
options={QUALITY_OPTIONS[kind].map((q) => ({ value: q, label: q }))} options={QUALITY_OPTIONS[kind].map((q) => ({ value: q, label: q }))}
onChange={setQuality} onChange={(v) => {
setQuality(v)
setCommandPreview(null)
}}
/> />
)} )}
</div> </div>