# Queue-Persistenz Bug: fertige Dateien tauchen nach Neustart wieder auf ## Symptom User: 300 Dateien, 100 übrig, Programm schließen + öffnen → manchmal sind bereits fertig hochgeladene Dateien wieder in der Liste. ## Root Cause (verifiziert im Code) - **RC-1 (Persist-Starvation, code-confirmed):** `persistQueueStateSoon()` setzt bei jedem Progress-Event den Timer per `clearTimeout` zurück; Delay während Upload war 10000ms. Progress-Events feuern öfter als alle 10s → Timer feuert NIE während eines aktiven Uploads. Der Disk-Snapshot bleibt auf dem Stand VOR Upload-Start stehen (alle Jobs `preview`). - **RC-2 (unzuverlässiger Close-Flush):** beforeunload-Sync-Flush existiert (app.js:4605) und fängt den sauberen Close ab. Bei hartem Kill / Crash / OS-Kill läuft er nicht → der stale Snapshot bleibt liegen. - **RC-3 (Dedup-Asymmetrie):** `_autoDeduplicateFromLog` droppt beim Start nur Jobs mit Status `done`. Die Ghosts aus dem stale Snapshot stehen aber als `preview` da → werden NICHT gedroppt → fertige Dateien erscheinen erneut. ## Fix (mechanismus-unabhängig, vom Kern auf) - [x] **FIX A — Timestamp-gated Dedup (Kern-Fix, durable):** Beim Start jeden restored Job droppen, dessen file+hoster im Log mit `ts >= floor(savedAt)` steht — egal ob `preview` oder `done`. Fängt Ghosts auch nach hartem Kill (hängt vom Log ab, nicht vom Snapshot). `lib/queue-dedup.js` additiver 3. Param `savedAt`; `buildPersistedQueueState` stempelt `savedAt`; `restoreQueueStateFromConfig` merkt `_restoredSnapshotSavedAt`; Log-Zeile → `ts` geparst; `_autoDeduplicateFromLog` reicht savedAt durch. - [x] **FIX B — Throttle mit max-wait:** `lib/throttle-timer.js` (neu). Upload: delay 500 + maxWait 20000 → Snapshot alle ~20s statt nie. Idle: reine Debounce. Fallback-Shim honoriert maxWait (kein stilles Starvation-Reintro). - [x] **FIX C — Close-Write-Härtung:** `save-global-settings-sync` renameSync-Retry bei EBUSY/EPERM/EACCES + pid-unique tmp + tmp-cleanup. Startup-Sweep `_sweepOrphanConfigTmps` räumt verwaiste `..tmp` toter PIDs (gegen Orphan-Akkumulation). - [x] **Seam-Extraktion (Advisor #2):** `lib/upload-log.js` (neu) — `formatUploadLogLine` + `parseUploadLogLine` aus main.js gezogen; Test fährt den ECHTEN Writer→Reader→Gate- Vertrag (kein Mirror) → fängt künftige Format-/Epoch-Brüche. ## Tests - [x] `tests/throttle-timer.test.js`: Starvation ohne maxWait → 0 Fires; mit maxWait → periodische Fires; last-write-wins (distinct fn); flushSync/cancel. - [x] `tests/queue-dedup.test.js`: ts>=savedAt→DROP; ts