# 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 === true` gewinnt immer gegen `fileRejected` — 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.