fix(immersion): await lexical rollup shutdown during app quit

- Rebuild lifetime completion after stream watch-state changes
- Update release packaging metadata and fast-uri
This commit is contained in:
2026-09-02 10:51:20 -07:00
parent b01f0bcb4f
commit 0d66747f2e
15 changed files with 213 additions and 50 deletions
@@ -6,7 +6,7 @@ import {
createShouldRestoreWindowsOnActivateHandler,
} from './app-lifecycle-actions';
test('on will quit cleanup handler runs all cleanup steps', () => {
test('on will quit cleanup handler runs all cleanup steps', async () => {
const calls: string[] = [];
const cleanup = createOnWillQuitCleanupHandler({
destroyTray: () => calls.push('destroy-tray'),
@@ -32,7 +32,9 @@ test('on will quit cleanup handler runs all cleanup steps', () => {
destroyMpvSocket: () => calls.push('destroy-socket'),
clearReconnectTimer: () => calls.push('clear-reconnect'),
destroySubtitleTimingTracker: () => calls.push('destroy-subtitle-tracker'),
destroyImmersionTracker: () => calls.push('destroy-immersion'),
destroyImmersionTracker: () => {
calls.push('destroy-immersion');
},
destroyAnkiIntegration: () => calls.push('destroy-anki'),
destroyAnilistSetupWindow: () => calls.push('destroy-anilist-window'),
clearAnilistSetupWindow: () => calls.push('clear-anilist-window'),
@@ -50,7 +52,7 @@ test('on will quit cleanup handler runs all cleanup steps', () => {
stopDiscordPresenceService: () => calls.push('stop-discord-presence'),
});
cleanup();
await cleanup();
assert.equal(calls.length, 35);
assert.equal(calls[0], 'destroy-tray');
assert.equal(calls[calls.length - 1], 'stop-discord-presence');
@@ -63,7 +65,7 @@ test('on will quit cleanup handler runs all cleanup steps', () => {
assert.ok(calls.indexOf('flush-mpv-log') < calls.indexOf('destroy-socket'));
});
test('on will quit cleanup handler cleans jellyfin subtitle cache when stopping remote session fails', () => {
test('on will quit cleanup handler cleans jellyfin subtitle cache when stopping remote session fails', async () => {
const calls: string[] = [];
const cleanup = createOnWillQuitCleanupHandler({
destroyTray: () => {},
@@ -106,7 +108,7 @@ test('on will quit cleanup handler cleans jellyfin subtitle cache when stopping
stopDiscordPresenceService: () => calls.push('stop-discord-presence'),
});
assert.throws(() => cleanup(), /stop failed/);
await assert.rejects(cleanup(), /stop failed/);
assert.deepEqual(calls, [
'stop-jellyfin-remote',
'cleanup-jellyfin-subtitles',
+4 -4
View File
@@ -18,7 +18,7 @@ export function createOnWillQuitCleanupHandler(deps: {
destroyMpvSocket: () => void;
clearReconnectTimer: () => void;
destroySubtitleTimingTracker: () => void;
destroyImmersionTracker: () => void;
destroyImmersionTracker: () => void | Promise<void>;
destroyAnkiIntegration: () => void;
destroyAnilistSetupWindow: () => void;
clearAnilistSetupWindow: () => void;
@@ -35,7 +35,7 @@ export function createOnWillQuitCleanupHandler(deps: {
cleanupJellyfinSubtitleCache: () => void;
stopDiscordPresenceService: () => void;
}) {
return (): Promise<void> => {
return async (): Promise<void> => {
deps.destroyTray();
deps.stopConfigHotReload();
deps.restorePreviousSecondarySubVisibility();
@@ -55,7 +55,7 @@ export function createOnWillQuitCleanupHandler(deps: {
deps.destroyMpvSocket();
deps.clearReconnectTimer();
deps.destroySubtitleTimingTracker();
deps.destroyImmersionTracker();
await deps.destroyImmersionTracker();
deps.destroyAnkiIntegration();
deps.destroyAnilistSetupWindow();
deps.clearAnilistSetupWindow();
@@ -77,7 +77,7 @@ export function createOnWillQuitCleanupHandler(deps: {
deps.cleanupYoutubeSubtitleTempDirs();
deps.cleanupYoutubeMediaCache();
deps.stopDiscordPresenceService();
return Promise.resolve(stopSyncAutoScheduler);
await stopSyncAutoScheduler;
};
}
@@ -3,7 +3,7 @@ import test from 'node:test';
import { createBuildOnWillQuitCleanupDepsHandler } from './app-lifecycle-main-cleanup';
import { createOnWillQuitCleanupHandler } from './app-lifecycle-actions';
test('cleanup deps builder returns handlers that guard optional runtime objects', () => {
test('cleanup deps builder returns handlers that guard optional runtime objects', async () => {
const calls: string[] = [];
let reconnectTimer: ReturnType<typeof setTimeout> | null = setTimeout(() => {}, 60_000);
let immersionTracker: { destroy: () => void } | null = {
@@ -80,7 +80,7 @@ test('cleanup deps builder returns handlers that guard optional runtime objects'
});
const cleanup = createOnWillQuitCleanupHandler(depsFactory());
cleanup();
await cleanup();
assert.ok(calls.includes('destroy-tray'));
assert.ok(calls.includes('destroy-main-overlay-window'));
@@ -119,10 +119,10 @@ export function createBuildOnWillQuitCleanupDepsHandler(deps: {
destroySubtitleTimingTracker: () => {
deps.getSubtitleTimingTracker()?.destroy();
},
destroyImmersionTracker: () => {
destroyImmersionTracker: async () => {
const tracker = deps.getImmersionTracker();
if (!tracker) return;
tracker.destroy();
await tracker.destroy();
deps.clearImmersionTracker();
},
destroyAnkiIntegration: () => {
@@ -32,7 +32,7 @@ export type StartupLifecycleComposerOptions = ComposerInputs<{
export type StartupLifecycleComposerResult = ComposerOutputs<{
registerProtocolUrlHandlers: () => void;
onWillQuitCleanup: () => void;
onWillQuitCleanup: () => Promise<void>;
shouldRestoreWindowsOnActivate: () => boolean;
restoreWindowsOnActivate: () => void;
}>;