fix(review): resolve deep-review findings across quickfix batch #2

- Button: `loading` always disables (|| not ??) — stops a double-submit
  when a call-site passes an explicit boolean `disabled` (VideoEditor).
- playlists: track un-ingestable-but-live YouTube ids in the sync baseline
  so a linked playlist containing a private/deleted video converges instead
  of re-classifying it as a pending pull forever; re-attempt ingestion each
  sync so a restored (private->public) video heals, and prune ids that
  leave YouTube.
- SettingsPanel: download-auth section renders an honest error/loading
  state (StateMessage + retry) instead of a bare empty section on a failed
  credentials fetch; HU+EN loadError string.
- tests: reorder-order, partial-failure baseline hold, un-ingestable
  converge/heal/prune, Button loading-disables, download-auth error/retry.
This commit is contained in:
2026-08-29 03:52:13 +02:00
parent 9af4e8f4ff
commit 3369587412
8 changed files with 352 additions and 97 deletions
+92 -75
View File
@@ -14,6 +14,7 @@ import { inputCls, Section, SettingRow, Switch } from "./ui/form";
import { Button } from "./ui/Button";
import { type SaveState } from "./ui/DraftSaveBar";
import { useConfirm } from "./ConfirmProvider";
import { StateMessage } from "./QueryState";
import { useWizard } from "./WizardProvider";
import { usePrefs } from "./PrefsProvider";
import Tabs from "./Tabs";
@@ -633,91 +634,107 @@ export function DownloadAuth() {
return (
<Section title={t("settings.downloadAuth.title")}>
<p className="text-xs text-muted leading-relaxed mb-2">{t("settings.downloadAuth.intro")}</p>
<div className="divide-y divide-border">
{hosts.map((h) => {
const when = h.updated_at ? new Date(h.updated_at).toLocaleDateString(i18n.language) : "";
const open = openHost === h.host_key;
return (
<div key={h.host_key} className="py-2" data-testid={`cookie-host-${h.host_key}`}>
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<div className="text-sm font-medium">{h.label}</div>
<p className="text-xs text-muted mt-0.5" data-testid={`cookie-status-${h.host_key}`}>
{h.configured
? t("settings.downloadAuth.configured", { when })
: t("settings.downloadAuth.notSet")}
</p>
</div>
<div className="flex items-center gap-2 shrink-0">
{h.configured && (
<Button
variant="danger-outline"
size="sm"
onClick={() => onRemove(h.host_key)}
disabled={remove.isPending}
{/* The host list is server-provided (always non-empty on success), so an empty `hosts` means
the fetch failed or is still loading — render an honest state, never a bare empty section
that reads as "nothing to configure here". */}
{q.isError && hosts.length === 0 ? (
<StateMessage tone="error" onRetry={() => q.refetch()}>
{t("settings.downloadAuth.loadError")}
</StateMessage>
) : q.isLoading ? (
<StateMessage>{t("common.loading")}</StateMessage>
) : (
<div className="divide-y divide-border">
{hosts.map((h) => {
const when = h.updated_at
? new Date(h.updated_at).toLocaleDateString(i18n.language)
: "";
const open = openHost === h.host_key;
return (
<div key={h.host_key} className="py-2" data-testid={`cookie-host-${h.host_key}`}>
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<div className="text-sm font-medium">{h.label}</div>
<p
className="text-xs text-muted mt-0.5"
data-testid={`cookie-status-${h.host_key}`}
>
{t("settings.downloadAuth.remove")}
</Button>
)}
<Button
variant="outline"
size="sm"
onClick={() => {
setOpenHost(open ? null : h.host_key);
setDraft("");
}}
>
{h.configured
? t("settings.downloadAuth.update")
: t("settings.downloadAuth.set")}
</Button>
</div>
</div>
{open && (
<div className="mt-2 space-y-2">
{/* Write-only: the field is ALWAYS empty on open — the stored blob is never
fetched or prefilled. */}
<textarea
value={draft}
onChange={(e) => setDraft(e.target.value)}
rows={5}
placeholder={t("settings.downloadAuth.placeholder")}
className={`${inputCls} font-mono text-xs resize-y`}
data-testid={`cookie-input-${h.host_key}`}
spellCheck={false}
autoComplete="off"
/>
<p className="text-2xs text-muted leading-relaxed">
{t("settings.downloadAuth.help")}
</p>
<div className="flex items-center gap-2">
{h.configured
? t("settings.downloadAuth.configured", { when })
: t("settings.downloadAuth.notSet")}
</p>
</div>
<div className="flex items-center gap-2 shrink-0">
{h.configured && (
<Button
variant="danger-outline"
size="sm"
onClick={() => onRemove(h.host_key)}
disabled={remove.isPending}
>
{t("settings.downloadAuth.remove")}
</Button>
)}
<Button
variant="primary"
size="sm"
disabled={!draft.trim() || save.isPending}
onClick={() => save.mutate({ host: h.host_key, cookies: draft })}
>
{save.isPending
? t("settings.downloadAuth.saving")
: t("settings.downloadAuth.save")}
</Button>
<Button
variant="ghost"
variant="outline"
size="sm"
onClick={() => {
setOpenHost(null);
setOpenHost(open ? null : h.host_key);
setDraft("");
}}
>
{t("settings.downloadAuth.cancel")}
{h.configured
? t("settings.downloadAuth.update")
: t("settings.downloadAuth.set")}
</Button>
</div>
</div>
)}
</div>
);
})}
</div>
{open && (
<div className="mt-2 space-y-2">
{/* Write-only: the field is ALWAYS empty on open — the stored blob is never
fetched or prefilled. */}
<textarea
value={draft}
onChange={(e) => setDraft(e.target.value)}
rows={5}
placeholder={t("settings.downloadAuth.placeholder")}
className={`${inputCls} font-mono text-xs resize-y`}
data-testid={`cookie-input-${h.host_key}`}
spellCheck={false}
autoComplete="off"
/>
<p className="text-2xs text-muted leading-relaxed">
{t("settings.downloadAuth.help")}
</p>
<div className="flex items-center gap-2">
<Button
variant="primary"
size="sm"
disabled={!draft.trim() || save.isPending}
onClick={() => save.mutate({ host: h.host_key, cookies: draft })}
>
{save.isPending
? t("settings.downloadAuth.saving")
: t("settings.downloadAuth.save")}
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => {
setOpenHost(null);
setDraft("");
}}
>
{t("settings.downloadAuth.cancel")}
</Button>
</div>
</div>
)}
</div>
);
})}
</div>
)}
</Section>
);
}