mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-06-10 03:13:32 -07:00
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:
@@ -36,6 +36,28 @@ test('notifyUpdateAvailable routes notification surfaces from config', async ()
|
||||
]);
|
||||
});
|
||||
|
||||
test('notifyUpdateAvailable adds an install action to overlay update notifications', async () => {
|
||||
const payloads: OverlayNotificationPayload[] = [];
|
||||
|
||||
await notifyUpdateAvailable(
|
||||
{ notificationType: 'overlay', version: '0.15.0' },
|
||||
{
|
||||
showSystemNotification: () => {},
|
||||
showOsdNotification: async () => {},
|
||||
showOverlayNotification: (nextPayload) => {
|
||||
payloads.push(nextPayload);
|
||||
},
|
||||
log: () => {},
|
||||
},
|
||||
);
|
||||
|
||||
const payload = payloads[0];
|
||||
assert.ok(payload);
|
||||
assert.deepEqual(payload.actions, [{ id: 'install-update', label: 'Update' }]);
|
||||
assert.equal(payload.id, 'subminer-update-available');
|
||||
assert.equal(payload.persistent, true);
|
||||
});
|
||||
|
||||
test('notifyUpdateAvailable logs osd fallback when overlay notification fails', async () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { UpdateNotificationType } from '../../../types/config';
|
||||
import type { OverlayNotificationPayload } from '../../../types/notification';
|
||||
|
||||
export const UPDATE_AVAILABLE_NOTIFICATION_ID = 'subminer-update-available';
|
||||
export const INSTALL_UPDATE_ACTION_ID = 'install-update';
|
||||
|
||||
export interface UpdateNotificationDeps {
|
||||
showSystemNotification: (title: string, body: string) => void;
|
||||
showOverlayNotification: (payload: OverlayNotificationPayload) => void;
|
||||
@@ -17,9 +20,12 @@ export async function notifyUpdateAvailable(
|
||||
const message = `SubMiner v${options.version} is available`;
|
||||
if (options.notificationType === 'overlay' || options.notificationType === 'both') {
|
||||
deps.showOverlayNotification({
|
||||
id: UPDATE_AVAILABLE_NOTIFICATION_ID,
|
||||
title: 'SubMiner update available',
|
||||
body: message,
|
||||
variant: 'info',
|
||||
persistent: true,
|
||||
actions: [{ id: INSTALL_UPDATE_ACTION_ID, label: 'Update' }],
|
||||
});
|
||||
}
|
||||
if (options.notificationType === 'osd' || options.notificationType === 'osd-system') {
|
||||
|
||||
@@ -96,6 +96,28 @@ test('manual update check falls back to GitHub release when app metadata is unav
|
||||
assert.deepEqual(calls, ['available-dialog:0.15.0']);
|
||||
});
|
||||
|
||||
test('manual update install request skips available dialog and updates app', async () => {
|
||||
const { deps, calls } = createDeps({
|
||||
checkAppUpdate: async () => ({ available: true, version: '0.15.0' }),
|
||||
showUpdateAvailableDialog: async () => {
|
||||
throw new Error('unexpected update confirmation');
|
||||
},
|
||||
updateLauncher: async (_launcherPath, channel) => {
|
||||
calls.push(`launcher:${channel}`);
|
||||
return { status: 'skipped' };
|
||||
},
|
||||
});
|
||||
const service = createUpdateService(deps);
|
||||
|
||||
const result = await service.checkForUpdates({
|
||||
source: 'manual',
|
||||
installWhenAvailable: true,
|
||||
});
|
||||
|
||||
assert.equal(result.status, 'updated');
|
||||
assert.deepEqual(calls, ['download', 'launcher:stable', 'restart-dialog']);
|
||||
});
|
||||
|
||||
test('manual update check reports available when no update asset was applied', async () => {
|
||||
const { deps, calls } = createDeps({
|
||||
checkAppUpdate: async () => ({ available: false, version: '0.14.0', canUpdate: false }),
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface UpdateCheckRequest {
|
||||
source: UpdateCheckSource;
|
||||
force?: boolean;
|
||||
launcherPath?: string;
|
||||
installWhenAvailable?: boolean;
|
||||
}
|
||||
|
||||
export type UpdateCheckStatus =
|
||||
@@ -164,9 +165,11 @@ export function createUpdateService(deps: UpdateServiceDeps) {
|
||||
return { status: 'update-available', version: latest.version };
|
||||
}
|
||||
|
||||
const choice = await deps.showUpdateAvailableDialog(latest.version);
|
||||
if (choice === 'close') {
|
||||
return { status: 'update-available', version: latest.version };
|
||||
if (!request.installWhenAvailable) {
|
||||
const choice = await deps.showUpdateAvailableDialog(latest.version);
|
||||
if (choice === 'close') {
|
||||
return { status: 'update-available', version: latest.version };
|
||||
}
|
||||
}
|
||||
|
||||
const canInstallAppUpdate = appUpdate.available && appUpdate.canUpdate !== false;
|
||||
|
||||
Reference in New Issue
Block a user