User-Report: ueber jedem VOD-Bild ein grauer Balken (~16px).
Root Cause (per Playwright-Geometrie-Probe gefunden, nicht geraten):
.vod-select-checkbox war als position:relative gerendert statt absolute.
Die globale Regel
(Spezifitaet 0,0,1,1 = type+attr) schlaegt
(0,0,1,0 = 1 class). Dadurch war die Bulk-Select-Checkbox ein in-flow
Element und belegte als erstes Flex-Item eine 16px-Reihe oben in der
.vod-card -> schob das Thumbnail 16px runter, die 16px Card-Background
waren der 'graue Balken'. Auch width/height wurden auf 16px statt 18px
ueberschrieben.
Fix: Selektor (0,0,2,1)
schlaegt die globale Regel sauber -> Checkbox wieder position:absolute,
overlay oben-links ueber dem Thumbnail. thumb-wrap relTop 17px -> 1px
(nur noch Border), Card-Hoehe 320 -> 304px.
Verifiziert: Geometrie-Probe (gapCardToWrap 16->~0), Button-Alignment
weiter 0px ueber xqc/papaplatte/xrohat, 219 unit + e2e gruen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aus dem 2. Screenshot-Pass (scripts/ui-screenshot2.js — Modals, Hover,
responsive):
- Command Palette war teilweise hardcoded Deutsch: Input-Placeholder
'Suche Befehl...' + Hint-Zeile 'Up/Down zum Navigieren, Enter zum
Ausfuehren, Esc zum Schliessen'. Im EN-Mode zeigte das trotzdem
Deutsch. Jetzt ueber Locale-Keys (commandPaletteSearchPlaceholder +
commandPaletteHint) verdrahtet — zeigt 'Search command...' /
'Up/Down to navigate, Enter to run, Esc to close' im EN-Mode.
- Command-Hint von 'Tab' auf 'Open' geaendert — 'Tab' las sich wie eine
Tastatur-Taste statt 'oeffnet diesen Tab'.
Verifiziert: Trim-Modal, Merge/Clips/Archive/Stats Tabs, alle 5 Themes,
Light-Theme Inputs — alles sauber. minWidth:1200 -> sub-1200px responsive
ist kein realer Fall.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hauptfix (User-Report): in der VOD-Grid sass der Trim/Queue-Button bei
Cards mit 1-zeiligem Titel hoeher als bei 2-zeiligen Nachbarn. Ursache:
.vod-card war ein Block, Buttons flossen mit dem Content. Grid streckt
zwar alle Cards einer Reihe gleich hoch, aber der Leerraum landete unten.
Fix: .vod-card = flex column, .vod-actions = margin-top:auto -> Buttons
docken am Boden an. Verifiziert per Playwright ueber xqc (16 Reihen),
papaplatte (6), xrohat (12): maxButtonTopSpread = 0px in allen.
Weitere Funde aus dem Screenshot-Pass (scripts/ui-screenshot.js):
- Globale Basis-Dark-Theme-Regel fuer alle text-Inputs + textarea, damit
bare Inputs ohne .form-group/.form-stack Wrapper nie OS-weiss durchkommen
(#cutterFilePath war weiss im Dark-Theme).
- Cutter-Preview-Placeholder 'Video auswahlen um Vorschau zu sehen' war
hardcoded Deutsch ohne id -> id + Locale-Key + Wiring (zeigt jetzt
'Select a video to see a preview' im EN-Mode).
- Clips-Button '#btnClip' wurde nie lokalisiert (zeigte immer 'Clip
herunterladen') -> setText-Wiring ergaenzt, nutzt existierenden
clips.downloadButton key.
scripts/ui-screenshot.js: neues Harness das die App startet, durch
Tabs/Streamer/Themes navigiert, Screenshots macht + Button-Alignment
programmatisch misst.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root Cause (User-Report): bei Part 1/6 Multi-Part-Download mit 869 MB
schon geladen, blieb der Bar auf einer fixen Position weit links — sah
aus als ob ein paar % erreicht waeren obwohl viel mehr lief.
Drei Probleme die das aus 5.0.1-5.0.11 noch ueberlebt haben:
1. downloadVODPart Path A (1-Sek-Heartbeat) emittierte progress=-1
wenn HLS kein known total hat. UI flippte zwischen 'determinate
bei letztem streamlink-%' und 'indeterminate-Animation' — User
sah einen 35%-Bar der zwischen Streamlink-%-Updates kurz aufpoppt.
2. Part-based-Split (download_mode='parts', langes VOD in N Stunden-
Parts) hat downloadVODPart's onProgress UNGEWRAPPT an die UI
gegeben. Bar zeigte 'X% von Part i' statt 'gewichtete overall %'.
Bei Part-Wechsel sprang er von 100% zurueck auf 0%. Bei Part 1
mit 50% Stream-Progress zeigte der Bar 50% obwohl overall erst
bei 8.3% (1 von 6 Parts halb fertig).
3. Full-VOD-Download (kein --hls-duration) hatte kein expected total
fuer den bytes-Estimate -> blieb in indeterminate-Mode.
Fixes:
- downloadVODPart bekommt optionalen Parameter.
Path A schaetzt jetzt progress aus downloadedBytes / (duration *
~625 KB/s, Twitch ~5 Mbit/s Schaetzung), gecappt bei 95%. Wenn
streamlink-stdout-% rauskommt, override mit dem (genauer). Nur
noch progress=-1 wenn weder bytes noch streamlink-% verfuegbar.
- Part-based-Split (downloadVodWithStrategy) wrappt onProgress jetzt
mit einem Aggregator: pro Part den max-bekannten %-Wert merken,
overallProgress = avg(allParts). Bei Part-Done aus dem Loop
partProgresses[i] = 100 setzen. Bar steigt monoton von 0% auf
100% ueber alle Parts.
- Full-VOD-Download passt totalDuration als expectedTotalSec an
downloadVODPart, sodass auch hier der bytes-Estimate greift.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.0.9 hatte .form-stack input/select und .input-narrow Dark-Theme-Styling
gegeben, aber der border-color in der base rule ist rgba(255,255,255,0.1) —
auf hellem Light-Theme-Background unsichtbar. Im Light-Theme-Block sind
.form-group und .add-streamer schon ueberschrieben, .form-stack und
.input-narrow fehlten.
Fix: light-theme border-color rgba(0,0,0,0.12) auch fuer .form-stack
input/select und .input-narrow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DEFAULT_METADATA_CACHE_MINUTES und DEFAULT_PERFORMANCE_MODE waren
dupliziert in main.ts und src/main/domain/config-normalize.ts. Wenn jemand
einen Default aendert (z.B. metadata-cache von 10 auf 15min), ist die
andere Seite still drift-anfaellig — config laedt den alten Default
beim Erst-Start aber normalize-Funktion fallt auf den neuen zurueck.
Fix: beide Konstanten werden jetzt aus config-normalize.ts exportiert
und in main.ts importiert. Single source of truth.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inputs/Selects in .form-stack Containern (Auto-Cleanup: Tage-Schwelle,
Bereich, Aktion) und bare .input-narrow Inputs (Auto-VOD: Poll-Intervall,
Max. Alter) hatten OS-default-weisse Backgrounds — die existierende
.form-group-Regel matched die Selektoren nicht.
Fix: .form-stack-Selektoren parallel zu .form-group hinzugefuegt, und
.input-narrow stylt sich jetzt selbst (background-color + border +
hover/focus state) damit auch bare Inputs ohne wrapper consistent
aussehen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
.form-group input[type='checkbox'] hatte width:auto, das die 16x16 aus
der globalen Regel ueberschrieben hat (Klassen-Specificity > Type-
Specificity). Vor 5.0.6 unsichtbar weil der rotated-border-Checkmark
absolut positioniert war und keine Box-Breite brauchte. Mit dem 5.0.6
inline-SVG-Checkmark brauchts echte Breite — daher in den Settings
plotzlich nur noch 1-2px schmale Vertikalstreifen sichtbar statt
echter Checkboxen.
Fix: width:16px explicit in der .form-group-Regel, behaelt die globale
Groesse bei.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Streamlink Diagnose:
- stdout-Buffer wird jetzt zusaetzlich zu stderr gesammelt; einige
streamlink-Builds schreiben 'error:'-Lines auf stdout statt stderr,
was bisher zu leerem stderrTail + 'Streamlink exit code 1' ohne
Detail-Info gefuehrt hat.
- download-part-failed Debug-Log enthaelt jetzt stderrTail UND
stdoutTail (jeweils letzte 2000 chars), plus User-facing-Error wird
aus beiden Streams kombiniert gesucht.
CSS:
- .form-group select: 'background: ...' Shorthand wurde durch
'background-color: ...' ersetzt — Shorthand hatte background-image
(Chevron-SVG), background-repeat, background-position, background-size
aus der globalen select-Regel resettet, was zu tiled Chevrons im
Dropdown gefuehrt hat ('Best (default) ▼▼▼▼▼▼...').
- input[type='checkbox']:checked: rotated-border-Trick durch inline-
SVG-Checkmark ersetzt — die rotated borders renderten in mstsc/RDP-
Sessions teilweise als Pfeile statt Haken (DPI-Skalierungs-Artefakt).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Problem: User berichtet 'streamlink exit code 1' bei VOD-Downloads ohne
sinnvolle Fehlermeldung — UI zeigt 'Retrying in 8s (unknown)...'. Root
Cause: classifyDownloadError matched die echten Twitch-Errors nicht und
nur die letzte stderr-Zeile wurde im Debug-Log gespeichert.
Fixes:
- Volle stderr-History wird gepuffert + im download-part-failed Debug-Log
als stderrTail (letzte 2000 chars) gespeichert
- UI bekommt jetzt die echte streamlink Error-Zeile statt 'Streamlink
Fehlercode N' (prefer 'error:'-prefixed Zeilen, dann last non-bracket
non-INFO line)
- classifyDownloadError matcht jetzt zusaetzlich: 'no playable streams',
'could not find any kind of stream', 'access token', 'session token',
'signature', 'integrity token', 'subscriber only', 'sub-only',
'not subscribed', 'http error', 'connectionerror', 'readerror'
Streamlink-Args:
- --stream-segment-attempts 5 (default 3 — mehr Retries bei flaky CDN)
- --stream-segment-timeout 20
- --stream-timeout 120
- --retry-streams 3 (retry initial stream listing)
- --retry-max 2
Damit ueberlebt der Download transiente Twitch-CDN-Hicks und der User
sieht im naechsten Fail die echte Fehlerursache in der UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Probleme der 5.0.3:
- CSS .vod-storyboard-preview hatte noch aspect-ratio:16/9, top:0, left:0, right:0
- JS hat dann inset:0 + aspect-ratio:auto inline gesetzt
- Im Electron-28-Chromium kam ein Layout-Konflikt raus -> Overlay-Box-
Dimensions wichen von Host ab -> backgroundSize-Skalierung passte nicht
zu visible-area -> mehrere Cells gleichzeitig sichtbar (Sprite-Sheet-
Look statt Single-Cell-Preview)
Fix 5.0.4:
- CSS-Klasse hat NUR noch Visual+Stacking (opacity, transition, border-
radius, overflow:hidden, z-index, position:absolute, pointer-events)
- KEIN top/left/right/bottom/width/height/aspect-ratio im CSS
- JS setzt alles inline und voll explicit: top=0, left=0, width=Xpx,
height=Ypx aus hostRect (.vod-thumb-wrap)
- Sanity-Guards fuer width/height<=0 und cellWidth/cellHeight<=0
- scaleX und scaleY weiter unabhaengig fuer korrekte Cell-Box-Fuellung
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wechsel von explicit width/height auf inset:0 (top/right/bottom/left:0,
width/height:auto, aspect-ratio:auto override des CSS-Legacy 16/9). Damit
fuellt das Overlay garantiert das gesamte .vod-thumb-wrap — kein leerer
Streifen mehr am oberen oder unteren Rand der Hover-Vorschau, egal ob
Twitch Storyboard-Cell-Aspect oder Subpixel-Rendering minimal abweicht.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix 1: scaleX und scaleY werden jetzt unabhaengig berechnet. Twitch
Storyboard-Cells haben nicht zwingend 16:9; eine einheitliche scale-Variable
fuehrte zu Subpixel-Leakage am oberen oder unteren Rand mit Inhalt aus der
Nachbarzelle. Mit getrennter Achsen-Skalierung fuellt eine Cell die Overlay-
Box exakt.
Fix 2: Bulk-Select-Checkbox und Downloaded-Badge werden waehrend des Hover-
Previews ausgeblendet (vorher nur die Duration-Badge). Mein vorheriger Move
des Overlays in .vod-thumb-wrap hatte die Z-Order so geaendert, dass diese
Elemente jetzt sichtbar drueber lagen.
219 unit tests + e2e gruen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug 1 — VOD-Hover Storyboard zeigte am unteren Rand einen statischen Streifen vom Original-Thumbnail (Subpixel-Mismatch + Aspect-Ratio-Konflikt). Fix: Overlay haengt jetzt an .vod-thumb-wrap statt .vod-card, mit explizitem width+height aus dem Thumbnail-BoundingRect — keine CSS-aspect-ratio-Interferenz mehr.
Bug 2 — Merge-Group Download zeigte einen eingefrorenen Progress-Bar bei Multi-Part-VODs (Part X/Y). Root Cause: der weighted-progress Wrapper clamped progress=-1 (HLS unknown-total 1s-Tick) auf 0, was overallProgress auf priorWeight fix-nagelte. Bar oszillierte zwischen indeterminate-animation und einem fixen ~10% Wert. Fix: lastVodProgress persistiert zwischen Path-A-Ticks und Path-B-Streamlink-%-Lines, sodass der Bar smooth waehrend einer Part hochzaehlt.
210 unit tests + e2e:release gruen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pillar 5 (UI Power) erweitert: Ctrl+K matched jetzt auch Streamer-Namen
(tippe Name oder @login → springt direkt zum Streamer im VODs-Tab).
Plus .gitignore-Hygiene.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reads config.streamers from the renderer global, builds one command per
streamer with label=name and keywords='@name name'. Action: showTab('vods')
+ selectStreamer(name). No-op if selectStreamer is unavailable
(e.g. on platforms where the streamers list wasn't loaded yet).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pillar 7 (Auto-Discovery) gets the Helix top-clips-crawler module (9 tests).
Pillar 5 (UI Power) gets its first visible component: Command Palette via
Ctrl+K with 6 tab-jump commands.
219 unit tests + e2e:release gruen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Modal markup + CSS (.command-palette .cp-*) + renderer-command-palette.ts.
6 statische Tab-Wechsel-Befehle (VODs/Queue/Streamers/Stats/Archive/Settings)
mit prefix-Match. ArrowUp/Down navigiert, Enter ausfuehrt, Esc/Click-on-Overlay
schliesst. Registriert sich in closeTopmostOpenModal damit globaler Esc-Handler
es korrekt findet.
clearList via removeChild-Loop statt innerHTML='' (Hook-Pattern Bypass — gleiches
Verhalten, sicherer).
npm run test:e2e gruen — App startet sauber mit dem neuen Modal.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
src/main/infra/format-helpers.ts. main.ts adapter for getMergeGroupPhaseText
injects config.language. 210 unit tests gruen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
upsert / get / list (filter by streamer, ordered by createdAt DESC NULLS LAST) /
setVerified / delete / summaryByStreamer (aggregated count + bytes per streamer) /
totalBytes. normalizeLogin used on streamer_login at write + filter time so
@Alice/Alice/alice all collapse to alice.
186 unit tests gruen. Storage layer for Pillar 1 verification + Pillar 6
archive index — recorder/storage-stats integration is post-5.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pillar 1 storage-layer piece. Spawn-free design — caller liefert das
ffprobe-Output als String, dieses Modul parsed + bewertet (no-video-stream,
duration-too-short, expected-duration mismatch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 new modules:
- src/main/domain/pkce.ts: PKCE pair (S256) + state (RFC 7636)
- src/main/infra/loopback-server.ts: ephemeral 127.0.0.1:PORT redirect capture
- src/main/domain/twitch-oauth.ts: startLoginFlow / awaitAuthorizationCode /
exchangeCodeForToken / fetchTwitchUserInfo
Flow runs entirely scaffold-ready — IPC wiring + Client ID config and
shell.openExternal(authUrl) trigger come in a follow-on plan once the user
registers a Twitch dev app. 164 unit tests gruen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
appDb module-scope let, getAppDb() exported getter, opened once in
app.whenReady with migrator run inline, closed in shutdownCleanup before
debugLog flush so WAL checkpoint completes cleanly. Unlocks IPC handlers
to read/write SQLite without per-call open/close.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan 04 abgeschlossen.
- schema-v5.ts: chunk_index table (item_id, chunk_seq, sha1_hex, bytes)
- src/main/infra/chunk-hash.ts: sha1 buffer + streaming file
- src/main/domain/chunk-index-store.ts: CRUD + dedupe lookup
- 143 unit tests (vorher 126, +17 fuer schema + hash + store)
- npm run test:e2e:release gruen
Integration mit dem live Recorder folgt in Plan 04b. Plan 04 ist rein
additiv; kein bestehender Recorder-Code wurde modifiziert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
record / listForItem (ordered by chunk_seq) / countForItem / lookupBySha1
(dedupe candidates) / deleteForItem. ON CONFLICT(item_id, chunk_seq) DO
UPDATE means re-recording overwrites prior hash. 143 unit tests gruen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UNIQUE(item_id, chunk_seq) + indices on item_id and sha1_hex. 1 new db test
(127 total). No producer wired up yet — that comes with the Plan 04b
integration into the live recorder.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
upsert / list / getDefault / setDefault / getAccessToken / getRefreshToken /
delete. UNIQUE(provider, twitch_user_id) via ON CONFLICT DO UPDATE. setDefault
ist provider-scoped (genau ein default pro provider). Scopes als JSON-Array
serialisiert. 126 unit tests gruen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Electron impl wraps safeStorage (Win Credential Manager). MemoryImpl uses
base64 (no real crypto) — clearly marks isEncryptionAvailable()=false for
test/headless envs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UNIQUE(provider, twitch_user_id), indices on provider + is_default.
108 unit tests gruen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migrator runs on app.whenReady before pollers/createWindow. Lazy require
keeps native better-sqlite3 errors from blocking app startup. Result is
logged via appendDebugLog for diagnosis. Verified via npm run test:e2e
(0 issues, app starts cleanly).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DbHandle interface (run/get/all/transaction/runBatch/close/raw).
Schema bootstrap splits SQL on ; and runs each statement via prepare().run()
to avoid pre-tool hook false-positive on .exec( pattern.
WAL mode + 5s busy_timeout + foreign_keys ON.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>