fix(renderer): L138/L145 clipboard privacy+dedup, M14 state-driven settings search

- L138: gate the clipboard check on the settings `loaded` flag (not just
  clipboardWatch), so it never reads using the pre-load fallback; the effect
  re-runs when loaded flips true so a pre-launch copied link is still offered.
- L145: lastSeen is module-level, so a dismissed clipboard link stays suppressed
  across the hook's remount on a Downloads<->Library tab switch.
- M14: SettingsView renders each card in a React-owned wrapper and drives its
  visibility from a `hidden` state array — the search no longer writes display:none
  onto a card's own DOM node. Matching still reads textContent (full label coverage,
  no per-card keyword upkeep).

typecheck + 268 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 11:17:23 -04:00
parent 63f0a40f0e
commit 809c99d5bb
3 changed files with 82 additions and 49 deletions
+16 -4
View File
@@ -363,9 +363,14 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
those to the shared hook incrementally.*
- [x] **M13 — Inconsistent secret-field masking.** `updateToken` uses `type="password"`; `proxy`
(may carry `user:pass@`) and `youtubePoToken` (a token) are plain-text Inputs.
- [ ] **M14 — Settings search mutates React-owned DOM.** The search toggles each card's
- [x] **M14 — Settings search mutates React-owned DOM.** The search toggles each card's
`el.style.display` directly; fragile (breaks if a card ever gets a conditional `style`) and
matches on `textContent` incl. hidden text. Prefer state-driven filtering.
matches on `textContent` incl. hidden text. Prefer state-driven filtering. *Fixed: each card renders
inside a React-owned wrapper whose visibility is driven by a `hidden` state array in
[SettingsView.tsx](src/renderer/src/components/SettingsView.tsx) — the filter no longer writes
`display` onto a card's own node, so a card setting its own inline style can't break it. Matching still
reads the wrapper's `textContent` (so search covers every visible label without hand-maintained
per-card keywords); the minor "matches hidden descendant text" point is unchanged and left as-is.*
- [x] **M15 — Nested interactive controls in `role="button"` (a11y).** LibraryView's group header
is a `role="button"` div containing `<Button>`s (All / Download) — invalid ARIA / keyboard
semantics. Make the header a real element with sibling buttons.
@@ -789,9 +794,13 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
instead of an indeterminate state. *Fixed: `parseProgress` now sets `sizeUnknown: !totalBytes` on
`DownloadProgress`; the store threads it onto the item and `QueueItem` renders an indeterminate bar +
"Downloading…" (instead of a frozen 0%) when it's set, reusing the existing SR7 indeterminate path.*
- [ ] **L138 — Clipboard read on every window focus.** [useClipboardLink.ts](src/renderer/src/useClipboardLink.ts)
- [x] **L138 — Clipboard read on every window focus.** [useClipboardLink.ts](src/renderer/src/useClipboardLink.ts)
reads the full clipboard on each `focus` (and once on mount, possibly before `clipboardWatch` has
loaded — FALLBACK is `true`). Reading all clipboard text whenever focused may surprise privacy-conscious users.
*Fixed: the check now gates on the settings store's `loaded` flag as well as `clipboardWatch`, so the
clipboard is never read using the pre-load fallback (the mount-before-settings-load window); the effect
re-runs when `loaded` flips true, so a link copied before launch is still offered once the real setting
is known. (Per-focus reads were already gated on `clipboardWatch`; the privacy default itself is SR4.)*
- [ ] **L139 — Auto-update vs concurrent spawn race.** `runStartupYtdlpAutoUpdate` can overwrite
`yt-dlp.exe` on launch while a deep-link/queued download spawns the same binary — a Windows
file-in-use/corrupt-spawn window.
@@ -816,8 +825,11 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
(mitigated today by the embedded fallback tray icon).
- [ ] **L144 — Duplicate guard checks the queue only, not history.** Re-adding a URL downloaded earlier
(already cleared from the queue) gives no "already downloaded" hint (roadmap notes this as a possible extension).
- [ ] **L145 — `useClipboardLink` re-offers a dismissed link after a tab switch.** `lastSeen` is
- [x] **L145 — `useClipboardLink` re-offers a dismissed link after a tab switch.** `lastSeen` is
per-hook-instance; switching Downloads↔Library remounts it, so a previously-dismissed clipboard link is offered again.
*Fixed: `lastSeen` is now a module-level variable (not a per-instance `useRef`), so a dismissed/accepted
clipboard link stays suppressed across hook remounts. Only one field is mounted at a time (tab switch), so
there's no cross-field contention.*
- [x] **L146 — `parseTrimSections` accepts malformed multi-colon times.** The `\d+(?::\d{1,2})*` pattern
passes tokens like `1:2:3:4-5:6:7:8`, which then reach yt-dlp's `--download-sections` and fail there
rather than being rejected up front.