diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2c80b8b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,217 @@ +# 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)