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:
@@ -45,6 +45,11 @@ interface ClipboardLink {
|
||||
* clipboard guess; links pushed via `offer()` bypass it (and the other guards),
|
||||
* since they're explicit requests rather than guesses.
|
||||
*/
|
||||
// Module-level (not a per-instance ref) so a dismissed/accepted link stays
|
||||
// suppressed across hook remounts — switching Downloads↔Library remounts the hook,
|
||||
// and the same clipboard link shouldn't be re-offered after being dismissed (L145).
|
||||
let lastSeen: string | null = null
|
||||
|
||||
export function useClipboardLink(
|
||||
currentValue: string,
|
||||
filter?: (url: string) => boolean
|
||||
@@ -57,18 +62,22 @@ export function useClipboardLink(
|
||||
// focus listener from re-subscribing every render.
|
||||
const filterRef = useRef(filter)
|
||||
filterRef.current = filter
|
||||
// The last link we offered or that the user dismissed — so re-focusing doesn't
|
||||
// keep re-offering the same one.
|
||||
const lastSeen = useRef<string | null>(null)
|
||||
// Mirror the live suggestion so accept/dismiss can read it without depending on
|
||||
// it — keeps their identity stable across renders.
|
||||
const suggestionRef = useRef<string | null>(null)
|
||||
suggestionRef.current = suggestion
|
||||
// Re-run the check once persisted settings arrive, so a link copied before launch
|
||||
// is still offered (the mount check is a no-op until `loaded`, L138).
|
||||
const loaded = useSettings((s) => s.loaded)
|
||||
|
||||
useEffect(() => {
|
||||
let active = true
|
||||
async function check(): Promise<void> {
|
||||
if (!useSettings.getState().clipboardWatch || valueRef.current.trim()) return
|
||||
const s = useSettings.getState()
|
||||
// Don't touch the clipboard until real settings have loaded (L138): before
|
||||
// that we'd be acting on the pre-load fallback for clipboardWatch instead of
|
||||
// the user's actual choice — and we never read it when the watcher is off.
|
||||
if (!s.loaded || !s.clipboardWatch || valueRef.current.trim()) return
|
||||
let text = ''
|
||||
try {
|
||||
text = (await window.api.readClipboard()) ?? ''
|
||||
@@ -78,7 +87,7 @@ export function useClipboardLink(
|
||||
if (!active) return
|
||||
text = text.trim()
|
||||
const wanted = looksLikeUrl(text) && (filterRef.current?.(text) ?? true)
|
||||
if (wanted && text !== lastSeen.current) {
|
||||
if (wanted && text !== lastSeen) {
|
||||
setSource('clipboard')
|
||||
setSuggestion(text)
|
||||
}
|
||||
@@ -89,18 +98,18 @@ export function useClipboardLink(
|
||||
active = false
|
||||
window.removeEventListener('focus', check)
|
||||
}
|
||||
}, [])
|
||||
}, [loaded])
|
||||
|
||||
const accept = useCallback((): string | null => {
|
||||
const link = suggestionRef.current
|
||||
if (!link) return null
|
||||
lastSeen.current = link
|
||||
lastSeen = link
|
||||
setSuggestion(null)
|
||||
return link
|
||||
}, [])
|
||||
|
||||
const dismiss = useCallback((): void => {
|
||||
lastSeen.current = suggestionRef.current
|
||||
lastSeen = suggestionRef.current
|
||||
setSuggestion(null)
|
||||
}, [])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user