feat(notifications): add notification history panel and overlay UX fixes

- New toggleNotificationHistory (Ctrl+N) session-scoped history panel; slides in from same edge as notification stack
- Overlay error/recovery toast follows notifications.overlayPosition; stack and history side seeded at startup
- Cold managed background startup initializes tray and visible overlay shell before tokenization warmups finish
- Add Update button to overlay update-available notifications
- Fix Ctrl+S sentence-card flow: only Anki progress notification, no duplicate status toast
- Fix overlay notification close/actions clickability above subtitle bars on Linux
- Increase pause-until-ready default timeout from 15s to 30s
This commit is contained in:
2026-06-06 15:29:14 -07:00
parent a34ec049a2
commit 8111deac44
68 changed files with 1408 additions and 69 deletions
+7
View File
@@ -60,6 +60,7 @@ import type {
YoutubePickerResolveRequest,
YoutubePickerResolveResult,
OverlayNotificationEventPayload,
OverlayNotificationPosition,
} from './types';
import { IPC_CHANNELS } from './shared/ipc/contracts';
@@ -212,6 +213,9 @@ const onOverlayNotificationEvent =
IPC_CHANNELS.event.overlayNotification,
(payload) => payload as OverlayNotificationEventPayload,
);
const onNotificationHistoryToggleEvent = createQueuedIpcListener(
IPC_CHANNELS.event.notificationHistoryToggle,
);
const onSubtitleVisibilityEvent = createLatestValueIpcListenerWithPayload<boolean>(
IPC_CHANNELS.event.subtitleVisibility,
(payload) => payload === true,
@@ -239,6 +243,7 @@ const electronAPI: ElectronAPI = {
sendOverlayNotificationAction: (notificationId: string, actionId: string) => {
ipcRenderer.send(IPC_CHANNELS.command.overlayNotificationAction, { notificationId, actionId });
},
onNotificationHistoryToggle: onNotificationHistoryToggleEvent,
onVisibility: (callback: (visible: boolean) => void) => {
onSubtitleVisibilityEvent(callback);
@@ -312,6 +317,8 @@ const electronAPI: ElectronAPI = {
ipcRenderer.invoke(IPC_CHANNELS.command.dispatchSessionAction, { actionId, payload }),
getStatsToggleKey: (): Promise<string> =>
ipcRenderer.invoke(IPC_CHANNELS.request.getStatsToggleKey),
getOverlayNotificationPosition: (): Promise<OverlayNotificationPosition> =>
ipcRenderer.invoke(IPC_CHANNELS.request.getOverlayNotificationPosition),
getMarkWatchedKey: (): Promise<string> =>
ipcRenderer.invoke(IPC_CHANNELS.request.getMarkWatchedKey),
markActiveVideoWatched: (): Promise<boolean> =>