fix: invalidate stale startup reconcile generations

Bind deferred startup reconciliation to an explicit FolderMonitor lifecycle generation and invalidate pending work on stop, pause, resume, import replacement, shutdown, and every non-deferred restart. This prevents an old startup request from surviving into a later productive monitor lifecycle.

Challenge each renderer document with a monotonic generation before accepting readiness, reject stale or foreign acknowledgements, and preserve exactly-once release across reloads and renderer initialization recovery. Add dynamic hidden Electron and lifecycle harness coverage for stale readiness, restart, close, shutdown, and ARIA regressions.
This commit is contained in:
Sucukdeluxe
2026-08-26 17:26:47 +02:00
parent a0041e9cda
commit 70a9073bf0
4 changed files with 456 additions and 48 deletions
+19 -8
View File
@@ -1,7 +1,21 @@
const { contextBridge, ipcRenderer, webUtils } = require('electron');
let folderMonitorCandidateListenerReady = false;
let folderMonitorReadySent = false;
let folderMonitorRendererGeneration = null;
let folderMonitorReadySentGeneration = null;
function signalFolderMonitorReady() {
if (!folderMonitorCandidateListenerReady || !Number.isSafeInteger(folderMonitorRendererGeneration) || folderMonitorRendererGeneration <= 0 || folderMonitorReadySentGeneration === folderMonitorRendererGeneration) return false;
folderMonitorReadySentGeneration = folderMonitorRendererGeneration;
ipcRenderer.send('folder-monitor:renderer-ready', folderMonitorRendererGeneration);
return true;
}
ipcRenderer.on('folder-monitor:renderer-generation', (_event, generation) => {
if (!Number.isSafeInteger(generation) || generation <= 0 || (folderMonitorRendererGeneration !== null && generation <= folderMonitorRendererGeneration)) return;
folderMonitorRendererGeneration = generation;
signalFolderMonitorReady();
});
contextBridge.exposeInMainWorld('api', {
// Config
@@ -102,12 +116,7 @@ contextBridge.exposeInMainWorld('api', {
ipcRenderer.on('folder-monitor:new-files', (_event, data) => callback(data));
folderMonitorCandidateListenerReady = true;
},
signalFolderMonitorReady: () => {
if (!folderMonitorCandidateListenerReady || folderMonitorReadySent) return false;
folderMonitorReadySent = true;
ipcRenderer.send('folder-monitor:renderer-ready');
return true;
},
signalFolderMonitorReady,
onAutomationStatus: (callback) => {
ipcRenderer.on('automation:status', (_event, data) => callback(data));
},
@@ -187,7 +196,9 @@ contextBridge.exposeInMainWorld('api', {
getPathForFile: (file) => webUtils.getPathForFile(file),
removeAllListeners: () => {
folderMonitorCandidateListenerReady = false;
folderMonitorReadySent = false;
folderMonitorRendererGeneration = null;
folderMonitorReadySentGeneration = null;
ipcRenderer.removeAllListeners('folder-monitor:renderer-generation');
ipcRenderer.removeAllListeners('upload-progress');
ipcRenderer.removeAllListeners('upload-batch-done');
ipcRenderer.removeAllListeners('upload-stats');