Three related gaps closed so one full byse account stops wasting attempts on every subsequent job and later-added accounts get picked up without an app restart. 1. Pre-job-swap moved BEHIND the semaphore acquire. At scale (500 jobs / 1 slot) every worker was checking _failedAccounts at spawn time before the first upload had even tried — so none of them saw the failed state. Now each worker re-checks right before its first upload attempt. 2. save-config IPC handler re-resolves fallbacks for any account that is already in _failedAccounts but has no override set. Previously account-failed only fired once per account, so a config change after the first mark-failed was silently ignored and the batch stayed stuck on the dead account until the app restarted. 3. UploadManager exposes getFailedAccountKeys() and getOverride(hoster) so main.js can drive the late re-resolve without poking private fields. 4 new tests: pre-job-swap after semaphore, getters contract, fresh manager resets learned state, late-added fallback is honored by subsequent jobs. 80/80 green.
32 lines
3.6 KiB
Markdown
32 lines
3.6 KiB
Markdown
# 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.
|