# Changelog Major-Bump 4.6.155 -> 5.0.0 (GA) + 5.1.0-alpha.1 (kontinuierliche post-GA Arbeit). 43 Commits, 20 neue Module in `src/main/`, 219 Unit-Tests (vorher 0). --- ## 5.1.0-alpha.1 (2026-05-12) ### Pillar 5 — UI Power (erste sichtbare Komponente) - **Command Palette** (`Ctrl+K`): Modal mit 6 Tab-Wechsel-Befehlen (VODs, Queue, Streamers, Stats, Archive, Settings). - ArrowUp/Down navigiert, Enter fuehrt aus, Esc/Overlay-Click schliesst. - Prefix-Match auf Label + Synonyme (DE/EN). - Registriert in `closeTopmostOpenModal` damit globaler Esc-Handler greift. - Dateien: `src/index.html` (Modal-Markup), `src/styles.css` (.cp-* Klassen), `src/renderer-command-palette.ts`. ### Pillar 7 — Auto-Discovery (Scaffold) - **`top-clips-crawler` Modul**: Helix `/clips` API + Pagination + Sortierung nach view_count desc. - Konfigurierbar: `broadcaster_id`, `first` (1-100 geclamt), `started_at`/`ended_at`. - `rangeLastDays(N)` Helper fuer ISO-Range "letzte N Tage". - `fetchImpl` injizierbar fuer Tests (kein echter HTTP-Call in CI). - 9 Tests (sortierung, snake-case→camelCase Mapping, query-string, clamping, error paths). ## 5.1.0-alpha.0 (2026-05-11) ### Pillar 1 — Live Recording (Storage-Layer) - **`integrity-check` Modul**: ffprobe-JSON-Parser + Verdict-Assessor. - `parseFfprobeJson(rawJson)` -> `ProbeResult` (streams, duration, size). - `assessIntegrity(probe, opts)` -> `IntegrityVerdict` (ok, reasons[], hasVideo, hasAudio). - Reasons: `no-video-stream`, `duration-too-short`, `duration-mismatch:actual=Xs,expected=Ys`. - Spawn-frei (Caller liefert JSON-String) -> 12 Tests ohne ffprobe-Dependency. ### Pillar 6 — Smart-Resume (Archive-Index) - **`archive-files-store` Modul**: CRUD auf `archive_files` Tabelle. - upsert / get / list (filter by streamer, ordered by createdAt DESC NULLS LAST). - setVerified, delete. - `summaryByStreamer()` (Datei-Count + total bytes pro Streamer, sortiert). - `totalBytes()` (sum aller archive_files). - normalizeLogin auf streamer_login bei Write+Filter -> '@Alice'/'Alice'/'alice' kollabieren zu 'alice'. - 10 Tests. ### Pillar 4 — Architecture Split (4 weitere Helpers extrahiert) - **`src/main/infra/format-helpers.ts`** mit 4 pure Funktionen aus main.ts: - `sanitizeFilenamePart` (Windows-FS-verbotene Chars + Path-Separators -> '_'). - `formatTwitchDurationFromSeconds` ('1h2m3s'-Style, NaN/negative geclamt auf 0). - `formatDateWithPattern` (yyyy/yy/MM/M/dd/d/HH/H/hh/h/mm/m/ss/s Tokens, Backslash-strip). - `getMergeGroupPhaseText` (DE/EN, mit Language als Parameter — main.ts hat 1-Arg-Adapter). - 24 Tests. ## 5.0.0 (GA, 2026-05-11) Major-Bump. 4.6.155-User sehen das ueber Auto-Updater (sobald Gitea-Release publiziert ist). ### Pillar 3 — SQLite-Migration (DONE, Breaking) Source-of-Truth bleibt JSON (`C:\ProgramData\Twitch_VOD_Manager\config.json` + `download_queue.json`) — SQLite ist Shadow-Schreibziel. Cutover (SQLite wird Master) erfolgt in spaeterem Release. - **Schema v5** in `src/main/infra/schema-v5.ts` (inline SQL-Konstante, kein non-TS Asset im Build): - `schema_meta(key PK, value)` — version-tracking. - `config_kv(key PK, value, updated_at)` — KV-Mirror der config.json fuer alle ungebundenen Keys. - `queue_items(id PK, streamer_login, vod_id, clip_id, title, output_path, status, progress_pct, error_message, created_at, updated_at, completed_at, payload_json)` — Mirror der Queue, mit Index auf status / streamer_login / created_at. - `downloaded_vods(vod_id PK, downloaded_at)` — bounded list von schon-runtergeladenen VODs. - `streamers(login PK, auto_record, auto_vod_download, added_at)` — Streamer-Watchlist, Indices auf beiden auto_* Flags. - `archive_files(path PK, streamer_login, size_bytes, duration_seconds, created_at, verified)` — Archiv-Index, Index auf streamer_login. - `migrations_applied(name PK, applied_at, payload)` — Migrator-Marker (Idempotenz). - `oauth_accounts(id PK AUTOINC, provider, twitch_user_id, login, display_name, encrypted_access_token, encrypted_refresh_token, expires_at, scopes_json, is_default, created_at, updated_at)` — UNIQUE(provider, twitch_user_id), Indices auf provider + is_default. - `chunk_index(id PK AUTOINC, item_id, chunk_seq, sha1_hex, bytes, created_at)` — UNIQUE(item_id, chunk_seq), Indices auf item_id + sha1_hex. - **`db.ts` Wrapper** (`src/main/infra/`): - better-sqlite3 unter der Haube, WAL-Journal + busy_timeout 5000ms + foreign_keys ON. - Public `DbHandle` Interface: run / get / all / transaction / runBatch / close / raw. - 10 Tests (creates file, schema_meta=5, WAL aktiv, idempotenter open, roundtrip insert/select, transaction commit + rollback, oauth_accounts INSERT + UNIQUE, chunk_index INSERT + UNIQUE). - **Migrator** (`src/main/domain/migrator.ts`): - Idempotent (marker in migrations_applied, zweimaliger Aufruf = identischer Endzustand). - Fail-soft (malformed JSON wird ins `errors[]` Array geschrieben, kein Crash). - Per-source `.v4-backup` Copy nach erfolgreichem Migrations-Run. - 31 whitelisted config_kv keys (language, performance_mode, alle filename_template_*, alle discord_*, alle auto_*, etc.). - downloaded_vod_ids als Set-Insert in `downloaded_vods`. - auto_record_streamers + auto_vod_download_streamers normalisiert via `normalizeLogin` (lowercase + @-Stripping), upserted in `streamers` mit dem entsprechenden Flag. - download_queue.json -> queue_items, mit payload_json als kompletter JSON-Dump des Original-Items (so dass auch unbekannte Felder erhalten bleiben). - 8 Tests (leeres AppData / config-keys / vod-ids / streamers / queue / idempotent / backup-files / malformed-json). - **DB-Handle-Singleton** in main.ts: - Long-lived `appDb: DbHandle | null` Modul-scope, geoeffnet im `app.whenReady` Block. - `getAppDb()` Getter exportiert (Voraussetzung fuer kommende Recorder-Integration). - `shutdownCleanup` schliesst die DB vor dem Debug-Log-Flush -> WAL-Checkpoint sauber. - Fail-soft: bei better-sqlite3 Native-Build-Fehler bleibt `appDb = null` und die App startet trotzdem (JSON-Pfad ist weiterhin der Master). - Logger-Output: JSON-serialisiertes MigrationResult landet in `debug.log` als `[ts] sqlite-migrator | {...}`. ### Pillar 2 — Twitch OAuth (Scaffold) Twitch unterstuetzt **kein** Device Code Flow (im goal.md falsch angenommen). Korrektur: Authorization Code Flow + PKCE via System-Browser + Loopback-Redirect (RFC 8252). IPC-Wiring + Renderer-Login-Button + Twitch Dev App Registrierung folgen post-5.0. - **`secure-storage` Modul** (`src/main/infra/`): - `SecureStorage` Interface (isEncryptionAvailable, encrypt, decrypt). - `MemorySecureStorage` (base64 ohne echte Crypto, fuer Tests / Headless-Envs — meldet `isEncryptionAvailable()=false`). - `createElectronSecureStorage()` wrappt `electron.safeStorage` (Win Credential Manager / macOS Keychain / Linux libsecret). - 7 Tests. - **`token-store` Modul** (`src/main/domain/`): - CRUD auf `oauth_accounts` mit Encryption-on-Write / Decryption-on-Read. - upsert mit ON CONFLICT(provider, twitch_user_id) DO UPDATE — neue Token ueberschreiben alte. - getDefault / setDefault sind provider-scoped (genau ein Default pro Provider). - Scopes serialisiert als JSON-Array. - 11 Tests (insert / update-not-duplicate / list / filter / default-toggle / getAccessToken decrypt / scopes / delete / expiresAt / list-by-id). - **PKCE-Pair** (`src/main/domain/pkce.ts`): - 32-Byte verifier (43-char base64url), S256 challenge. - `generateState()` fuer CSRF-Schutz. - 7 Tests (S256 marker, charset, sha256-derived, entropy). - **Loopback-Server** (`src/main/infra/loopback-server.ts`): - Ephemerer HTTP-Server auf 127.0.0.1:PORT (port=0 = OS waehlt, kein Firewall-Prompt). - Path-Prefix-Filter (default `/oauth/callback`). - HTML-Success / Error Antwort fuer den User-Browser. - `awaitParams({timeoutMs})` returnt `URLSearchParams` oder rejected. - 5 Tests. - **Twitch-OAuth Flow** (`src/main/domain/twitch-oauth.ts`): - `startLoginFlow({clientId, scopes})` -> Auth-URL + Loopback-Server. - `awaitAuthorizationCode(flow)` validiert state (CSRF), wirft auf error= oder missing code. - `exchangeCodeForToken(...)` POST an `id.twitch.tv/oauth2/token` mit PKCE verifier. - `fetchTwitchUserInfo(accessToken, clientId)` -> Helix `/users` -> twitch_user_id + login + display_name. - Alle Helix-Calls nehmen `fetchImpl` als optionalen Parameter (Testbarkeit ohne echten HTTP-Call). - 9 Tests. ### Pillar 6 — Smart-Resume (Foundation) - **`chunk-hash` Modul** (`src/main/infra/`): - `hashBuffer(Buffer)` -> sha1 hex (sync). - `hashFile(path)` -> Promise (streaming, blockiert keinen Event-Loop bei grossen Recorded-Segments). - 8 Tests (known-vectors fuer 'hello' und empty, large-buffer determinism, file=buffer hash, missing-file rejects). - **`chunk-index-store` Modul** (`src/main/domain/`): - CRUD auf `chunk_index`. - record / listForItem (sorted by chunk_seq) / countForItem / lookupBySha1 (dedupe candidates) / deleteForItem (returns count). - ON CONFLICT(item_id, chunk_seq) DO UPDATE -> re-recorded segments ueberschreiben den alten Hash. - 8 Tests. ### Pillar 4 — Architecture Split (Start) main.ts ging von 7485 LoC auf 7276 LoC (-209). 20 Module in `src/main/{infra,domain}/`. 18 Test-Files. **Foundation (5.0.0-alpha.0):** - `vitest` als Test-Runner (replacements/erweiterung der bisherigen Playwright-only Suite). - `test:unit` + `test:unit:watch` Scripts; `test:e2e:release` kettet jetzt build + unit + update-logic + 3 Playwright-Stages. - 5 Initial-Extraktionen (alle pure, alle mit Tests): - `src/main/infra/fs-atomic.ts` — `writeFileAtomicSync` (tmp+rename, EPERM-Retry-Pattern, Windows-Fallback copy+unlink). 6 Tests. - `src/main/infra/duration.ts` — `parseDuration`, `formatDuration`, `formatDurationDashed`. 18 Tests. - `src/main/domain/update-version-utils.ts` — verschoben von `src/`, `normalizeUpdateVersion`/`compareUpdateVersions`/`isNewerUpdateVersion`. 16 Tests. - `src/main/domain/i18n-backend.ts` — `BACKEND_MESSAGES` (DE/EN, 38 keys) + pure `tBackend(key, params, lang)`. main.ts hat 2-arg Adapter der `config.language` injiziert. 8 Tests. - `src/main/domain/config-normalize.ts` — 8 pure Normalizer (`normalizeAutoRecordPollSeconds`, `normalizeAutoRecordList`, `normalizeStreamlinkQuality`, `normalizeFilenameTemplate`, `normalizeMetadataCacheMinutes`, `normalizePerformanceMode`, `isPlainObject`, `normalizeLogin`) + `VALID_STREAMLINK_QUALITIES` + `PerformanceMode` type. 47 Tests. **Post-Foundation (5.1.0-alpha.0):** - `src/main/infra/format-helpers.ts` (siehe oben). --- ## Breaking Changes | # | Was bricht | Migration | User-Impact | |---|---|---|---| | BC-1 | Config-Speicherort additiv erweitert | Migrator on first 5.0 start, `.v4-backup` der JSONs bleibt liegen | Einmalig sek-30s Migrations-Dialog (silent, ins debug.log geloggt) | | BC-2 | Auto-Updater Channel | 4.6-User auf `stable` erkennen 5.0.0 sobald Gitea-Release publiziert ist | Auto-Update wie immer (Klick Install bei Notification) | Kein hard-break fuer existing State. Migrator ist additiv + idempotent. Wenn er fehlschlaegt, bleibt JSON der Master. --- ## Vergleich: 4.6.155 vs 5.1.0-alpha.1 | Metric | 4.6.155 | 5.1.0-alpha.1 | |---|---|---| | Total LoC (src/) | 16016 | ~16700 (mit Tests) | | main.ts | 7485 | 7276 | | Module in src/main/ | 0 | 20 | | Unit Tests (vitest) | 0 | 219 | | E2E Tests (Playwright) | 4 stages | 4 stages (gleich) | | Dependencies | axios, electron-updater | + better-sqlite3 | | DevDeps | playwright, typescript, eslint | + vitest, @types/better-sqlite3 | | SQLite-Schema | n/a | v5 (9 tables) | | OAuth-Support | n/a | scaffold (AuthCode+PKCE) | | Smart-Resume | n/a | chunk-hash + index store | | Command Palette | n/a | Ctrl+K | | Themes | 5 (Twitch, Discord, YouTube, Apple, Light) | gleich | | Locales | DE, EN | gleich | --- ## Was in 5.1.x noch kommt (Roadmap, post diesem Release) - **Pillar 2 OAuth-Vervollstaendigung**: IPC-Handler `oauth-twitch-login`/`-logout`/`-whoami`, Renderer-Login-Button im Settings-Tab, Client-ID-Config-Feld (Twitch Dev App Registration ist User-seitig). - **Pillar 4 Architektur-Split**: state-coupled Module aus main.ts (twitch-api, queue, download, record, cutter, stats, updater). Voraussetzung: State-Container-Design entscheiden. - **Pillar 5 UI Power-Restpaket**: Virtual List fuer Queue/Archive (10k+ Eintraege), Mini-Player (HLS-Stream Vorschau via hls.js), Drag-Reorder, mehr Command-Palette-Commands (Streamer-Suche). - **Pillar 6 Smart-Resume Integration**: chunk-hash-Producer in den live Recorder einklinken, Resume-on-startup Dialog. - **Pillar 7 Auto-Discovery Erweiterung**: Top-Clip-Crawler Scheduler (alle X Tage), Follow-Sync (importiert die Follow-Liste des eingeloggten Twitch-Users), Top-Clip-Modal im Streamer-Tab. --- ## Migration Checklist fuer Endnutzer Beim ersten Start von 5.0 (oder 5.1) auf einer bestehenden 4.6.x-Installation: 1. App starten — Migrator laeuft automatisch, fail-soft. 2. Pruefen: `%PROGRAMDATA%\Twitch_VOD_Manager\app.db` existiert. 3. Pruefen: `config.json.v4-backup` und `download_queue.json.v4-backup` liegen daneben. 4. debug.log nach `sqlite-migrator | ` Zeile suchen — sollte JSON mit `configMigrated:true, queueMigrated:true, downloadedVodsCount:N, streamersCount:N` enthalten. 5. Bei Problemen: einfach 4.6.155 reinstallieren — JSON ist unangetastet, SQLite war nur Shadow-Schreibziel. --- ## Build / Release Setup.exe gebaut mit `npm run dist:win`. Auto-Updater pickt das via `latest.yml` (sha512 + size + releaseDate). Co-Authored-By: Claude Opus 4.7 (1M context)