mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-09-21 00:10:49 +02:00
Pause AlertToast auto-dismiss while hovered (#235)
Each alert toast had a 5-second auto-dismiss timer that fired even while the user was reading the card. This adds pause-on-hover: the dismiss timer stops while the mouse is over a toast and restarts (full lifetime) on mouse leave. The progress bar animation pauses with it, so the visual matches the actual remaining time. All other behavior is preserved: same cyber/mono styling, same spring slide-in, same risk-color border + glow, same warning icon, same LVL X/10 readout, same title/source layout, same click-to-fly + dismiss on body click, same × dismiss button. Implementation notes: - Extract a ToastCard sub-component so each card can own its own paused state (useState can't be array-indexed in the parent). - Move the auto-dismiss timer out of useAlertToasts.ts and into ToastCard. The hook previously scheduled the dismiss itself, which meant the UI couldn't pause it — only the component knows whether the user is interacting. - Add tests covering: title/source/severity render, auto-dismiss fires at 5s, hover pauses indefinitely, mouse-leave restarts the full lifetime, × dismisses without flying, body-click flies + dismisses. This implements the genuine UX improvement that PR #234 was reaching for, without #234's broken syntax, missing-field bug, duplicate timer logic, or design regression. Refs: #234 Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
40734e310b
commit
421682c447
@@ -2,7 +2,13 @@
|
||||
* useAlertToasts — watches for new high-severity news items and surfaces toast notifications.
|
||||
*
|
||||
* Monitors the `news` data key for articles with risk_score >= 8.
|
||||
* Maintains a seen-set to avoid duplicate toasts. Auto-dismisses after 5 seconds.
|
||||
* Maintains a seen-set to avoid duplicate toasts.
|
||||
*
|
||||
* NOTE: auto-dismissal is owned by the `AlertToast` component (per-card
|
||||
* timer with pause-on-hover) — this hook used to schedule its own
|
||||
* dismiss timer, but that prevented the UI from pausing it. The hook
|
||||
* now only manages the toast queue + dedup; the component decides when
|
||||
* a toast goes away.
|
||||
*/
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useDataKey } from './useDataStore';
|
||||
@@ -20,30 +26,14 @@ export interface ToastItem {
|
||||
|
||||
const TOAST_THRESHOLD = 8; // minimum risk_score to trigger a toast
|
||||
const MAX_VISIBLE = 3;
|
||||
const AUTO_DISMISS_MS = 5_000;
|
||||
|
||||
export function useAlertToasts() {
|
||||
const news = useDataKey('news') as NewsArticle[] | undefined;
|
||||
const seenKeys = useRef(new Set<string>());
|
||||
const [toasts, setToasts] = useState<ToastItem[]>([]);
|
||||
const timersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
|
||||
|
||||
// Auto-dismiss scheduled toasts
|
||||
const scheduleDismiss = useCallback((id: string) => {
|
||||
const timer = setTimeout(() => {
|
||||
setToasts((prev) => prev.filter((t) => t.id !== id));
|
||||
timersRef.current.delete(id);
|
||||
}, AUTO_DISMISS_MS);
|
||||
timersRef.current.set(id, timer);
|
||||
}, []);
|
||||
|
||||
const dismiss = useCallback((id: string) => {
|
||||
setToasts((prev) => prev.filter((t) => t.id !== id));
|
||||
const timer = timersRef.current.get(id);
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timersRef.current.delete(id);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Watch for new high-severity articles
|
||||
@@ -76,22 +66,8 @@ export function useAlertToasts() {
|
||||
const merged = [...newToasts, ...prev].slice(0, MAX_VISIBLE);
|
||||
return merged;
|
||||
});
|
||||
|
||||
// Schedule auto-dismiss for each new toast
|
||||
for (const t of newToasts) {
|
||||
scheduleDismiss(t.id);
|
||||
}
|
||||
}
|
||||
}, [news, scheduleDismiss]);
|
||||
|
||||
// Cleanup timers on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
for (const timer of timersRef.current.values()) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
}, [news]);
|
||||
|
||||
return { toasts, dismiss };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user