Reproduced from a real saved config: pendingQueue held 4 'preview' jobs (one file across 4 hosters); the queue saved + restored correctly. But _autoDeduplicateFromLog (runs at init after restore) removed jobs whose fileName|hoster appeared ANYWHERE in the lifetime fileuploader.log, regardless of status — so all 4 pending previews were deleted and the queue showed the empty "Dateien hierhin ziehen" state. Looked update-specific only because the server restarts on update; a plain restart did the same. - New lib/queue-dedup.js (pure, dual CJS/window export like queue-prune.js): partitionRestoredJobsByLog drops ONLY 'done' jobs that match the log. Pending (preview/queued) and failed (error/aborted) jobs always survive — they're intentional queued work (often a deliberate re-upload of a previously uploaded file). Manual importUploadLog stays separate/explicit. - renderer wires it in; index.html loads the module before app.js. - Tests: 5 cases incl. the exact reproduced scenario (4 previews all in log -> 0 removed). Full suite 162/162. Verified against the user's real electron-config.json + fileuploader.log: old logic removed 4/4 (empty queue), new logic removes 0/4 (queue preserved). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
6.2 KiB
Lessons
2026-04-21 — DOM-Doppelrender bei Bulk-State-Changes
Symptom: User klickt auf "Erneut versuchen" mit 500+ Jobs → App hängt sekundenlang.
Root cause: retrySelectedJobs() ruft renderQueueTable + updateQueueActionButtons + updateStatusBar auf, startSelectedUpload() ruft direkt danach genau dieselben Funktionen nochmal auf.
Regel: Wenn ein Click-Handler await anotherHandler() aufruft und der innere Handler seinen eigenen kompletten Render-Zyklus hat, NIEMALS noch einen davor. Einmal ist genug — der folgende innere Render sieht die frischen State-Mutationen ohnehin.
Wie anwenden: Vor jeder await fn()-Folge in einem Handler prüfen: macht fn schon renderQueueTable()? Wenn ja, äußere Render-Calls löschen.
2026-04-21 — State-Checks MÜSSEN hinter die Semaphore-Queue
Symptom: Pre-Job-Swap prüfte _failedAccounts vor semaphore.acquire. Bei N parallelen Workers war der Check zum Start für ALLE leer — niemand hat geswapt. Erst nachdem alle im Semaphore ordentlich gewartet hatten und einer fehlschlug, wurde _failedAccounts befüllt, aber die anderen hatten ihren Check längst hinter sich.
Regel: State-basierte Entscheidungen (failed accounts, overrides, cached stats) gehören direkt vor die Aktion die sie betreffen — nach jeder async await die die Position in der Queue bestimmt. Nicht am Task-Start für später wichtigen State abfragen.
Wie anwenden: Bei Queue-basierten Pipelines prüfen: "Was kann sich zwischen Task-Start und dem tatsächlichen Execute ändern?" Alles was sich ändern kann, muss direkt vor dem Execute geprüft werden, nicht davor.
2026-04-21 — Reaktive Config-Updates für laufende State-Maschinen
Symptom: User fügt mid-batch einen neuen Account hinzu, aber der UploadManager merkt nicht dass die Config sich geändert hat. account-failed Event feuert nur einmal pro Account → keine zweite Re-Resolve-Chance.
Regel: Wenn ein State nur bei Events neu evaluiert wird und Events "nur einmal" feuern, muss jede externe Zustandsänderung (Config-Save, User-Action) den State explizit triggern.
Wie anwenden: Save-Handler müssen aktive State-Maschinen informieren. Lieber einen überflüssigen Re-Resolve-Call als einen verpassten. Für Upload-Manager: nach saveConfig → re-evaluate failed accounts ohne Override.
2026-04-21 — Error-Klassifikation: fileRejected vs accountError
Symptom: Voller Byse-Account wurde nicht rotiert — skip-rotation-file-rejected geloggt für jede Datei.
Root cause: Generisches Match auf Prefix-String ("lehnte Datei ab") klassifizierte ALLE Byse-Errors als file-level, inklusive Account-voll-Meldungen.
Regel: Hoster-Parser setzen den spezifischen Flag (fileRejected ODER accountError), nicht beide nie. Classifier matcht konkrete Phrasen (Duplicate, Not video format, …), niemals generische Wrapper-Strings die für mehrere Fehlerarten benutzt werden.
Wie anwenden:
- Bei neuen Hostern: per-status-Klassifikation bereits im Parser, nicht erst im Upload-Manager.
- Classifier-Regexes auf Rejection-Kernphrasen, nicht auf UI-Prefix.
- Defensive:
accountError === truegewinnt immer gegenfileRejected— Account-Rotation ist weniger schlimm als endlose Fails auf einem toten Account.
2026-04-21 — Keine fake Build-ETAs
Symptom: User wartet 5+ min auf Tauri-Build den ich mit "1-2min" angekündigt habe.
Regel: Tauri-Release-Builds brauchen real 3-6 min (Rust + NSIS + MSI). Keine Zeitangabe oder ehrlich "kann 3-6min dauern" schreiben.
Wie anwenden: Wenn User nach Status fragt: sofort tail des Logs + ls des Bundle-Ordners zitieren, nicht raten.
2026-05-24 — Packaged-Electron Log-Pfade: nie __dirname/.. zum Schreiben
Symptom: doodstream-debug.log hatte auf dem Server null aktuelle Einträge; nur alte Dev-Logs. Fehler "kein Filecode" war nicht diagnostizierbar.
Root cause: path.join(__dirname, '..', 'x.log') zeigt im gepackten Build in resources/app.asar (read-only). fs.appendFileSync wirft EACCES, der try/catch schluckt es → null Production-Logs.
Regel: Schreibbare Pfade IMMER über app.getPath('userData') (lazy require('electron'), Fallback __dirname/.. nur für Tests/plain-node). Gilt für jede Datei die der gepackte App schreibt.
Wie anwenden: Bei jedem neuen Log/Cache/State-File prüfen: wohin schreibt das im NSIS-Build? Nicht ins Install-Verzeichnis, nicht in asar.
2026-05-24 — Hoster-Fehler: echten Status surfacen, nicht generisch schlucken
Symptom: "upload_result Seite hat keinen filecode ()" — nichtssagend; User dachte doodstream-Format geändert.
Root cause: XFileSharing liefert den echten Grund im st-Feld (Error: duplicate / file too big / …). Code ignorierte st komplett und warf nur den leeren Body.
Regel: Bei Hoster-Parsefehlern immer die Server-Statusfelder (st/msg/code) + Kontext (welcher CDN-Node, war filecode da) in die Fehlermeldung packen. Format-Struktur unverändert + leerer Inhalt = Backend-Ablehnung, kein Parsing-Bug.
2026-05-25 — Queue leer nach Update: Auto-Dedup zu aggressiv (nicht Save/Restore)
Symptom: Queue gestoppt, App-Update -> nach Neustart Queue leer ("Dateien hierhin ziehen"). User dachte Save/Restore kaputt.
Root cause: Queue WIRD korrekt gespeichert (pendingQueue) + restored. ABER _autoDeduplicateFromLog (läuft bei init nach restore) entfernte Jobs per fileName|hoster-Match gegen das GESAMTE Lifetime-fileuploader.log — UNABHÄNGIG vom Status. Pending 'preview'-Jobs, deren Datei früher mal hochgeladen wurde, flogen alle raus -> komplette Queue weg. "Update-spezifisch" nur weil der Server-App nur beim Update neustartet (normaler Restart hätte dasselbe getan).
Verifiziert: Reale electron-config.json: 4 preview-Jobs, alle 4 Keys im Log -> alte Logik entfernt 4/4. Neue Logik (nur status==='done' droppen) entfernt 0/4.
Regel: Auto-Cleanup/Dedup darf NIE pending/actionable User-Arbeit löschen. Nur genuin abgeschlossene ('done') Jobs decluttern. Lifetime-Logs sind Historie, nicht Session-Fortschritt — nicht als "schon erledigt"-Quelle für pending Jobs missbrauchen.
Wie anwenden: Bei jeder Filter/Remove-Logik auf User-State: nach Status gaten, nicht nur nach Identitäts-Match gegen historische Daten.