fix(update): separate in-flight dedup keys for manual check vs install

- manual:check and manual:install now tracked independently; install no longer reuses a check's in-flight promise
- add toggleNotificationHistory (Ctrl+N) shortcut to config example and docs
This commit is contained in:
2026-06-07 23:18:00 -07:00
parent 9d77907877
commit 14cd37d8d7
4 changed files with 37 additions and 5 deletions
+2 -1
View File
@@ -208,7 +208,8 @@
"openSessionHelp": "CommandOrControl+Slash", // Accelerator that opens the session help / keybinding cheatsheet.
"openControllerSelect": "Alt+C", // Accelerator that opens the controller selection and learn-mode modal.
"openControllerDebug": "Alt+Shift+C", // Accelerator that opens the controller debug modal with live axis/button readouts.
"toggleSubtitleSidebar": "Backslash" // Accelerator that toggles the subtitle sidebar visibility.
"toggleSubtitleSidebar": "Backslash", // Accelerator that toggles the subtitle sidebar visibility.
"toggleNotificationHistory": "Ctrl+N" // Accelerator that toggles the overlay notification history panel.
}, // Overlay keyboard shortcuts. Set a shortcut to null to disable.
// ==========================================
+1
View File
@@ -82,6 +82,7 @@ Mouse-hover playback behavior is configured separately from shortcuts: `subtitle
| `Ctrl/Cmd+Shift+O` | Open runtime options palette | `shortcuts.openRuntimeOptions` |
| `Ctrl/Cmd+/` | Open session help modal | `shortcuts.openSessionHelp` |
| `Ctrl+Shift+J` | Open Jimaku subtitle search modal | `shortcuts.openJimaku` |
| `Ctrl+N` | Toggle overlay notification history panel | `shortcuts.toggleNotificationHistory` |
| `Ctrl+Alt+C` | Open the manual YouTube subtitle picker | `keybindings` |
| `Ctrl+Alt+S` | Open subtitle sync (subsync) modal | `shortcuts.triggerSubsync` |
| `\` | Toggle subtitle sidebar | `subtitleSidebar.toggleKey` |
@@ -293,6 +293,28 @@ test('concurrent update checks share one in-flight check', async () => {
assert.equal(checkCount, 1);
});
test('manual install request does not reuse in-flight manual check', async () => {
let checkCount = 0;
const resolveChecks: Array<(value: { available: boolean; version: string }) => void> = [];
const { deps } = createDeps({
checkAppUpdate: () =>
new Promise((resolve) => {
checkCount += 1;
resolveChecks.push(resolve);
}),
});
const service = createUpdateService(deps);
const manualCheck = service.checkForUpdates({ source: 'manual' });
const manualInstall = service.checkForUpdates({ source: 'manual', installWhenAvailable: true });
await Promise.resolve();
assert.equal(checkCount, 2);
for (const resolve of resolveChecks) {
resolve({ available: false, version: '0.14.0' });
}
await Promise.all([manualCheck, manualInstall]);
});
test('manual update check does not reuse in-flight automatic check', async () => {
let checkCount = 0;
const resolveChecks: Array<(value: { available: boolean; version: string }) => void> = [];
+12 -4
View File
@@ -108,7 +108,14 @@ function summarizeError(error: unknown): string {
}
export function createUpdateService(deps: UpdateServiceDeps) {
const inFlightBySource = new Map<UpdateCheckSource, Promise<UpdateCheckResult>>();
const inFlightBySource = new Map<string, Promise<UpdateCheckResult>>();
function getInFlightKey(request: UpdateCheckRequest): string {
if (request.source === 'manual') {
return request.installWhenAvailable ? 'manual:install' : 'manual:check';
}
return request.source;
}
async function runCheck(request: UpdateCheckRequest): Promise<UpdateCheckResult> {
const now = deps.now();
@@ -206,12 +213,13 @@ export function createUpdateService(deps: UpdateServiceDeps) {
return {
checkForUpdates(request: UpdateCheckRequest): Promise<UpdateCheckResult> {
const inFlight = inFlightBySource.get(request.source);
const key = getInFlightKey(request);
const inFlight = inFlightBySource.get(key);
if (inFlight) return inFlight;
const nextInFlight = runCheck(request).finally(() => {
inFlightBySource.delete(request.source);
inFlightBySource.delete(key);
});
inFlightBySource.set(request.source, nextInFlight);
inFlightBySource.set(key, nextInFlight);
return nextInFlight;
},
startAutomaticChecks(options: { startupDelayMs?: number; pollIntervalMs?: number } = {}): void {