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>
Reasoning: stateful main.ts split (former Plan 02-04) requires state-strategy
design that's better informed after features land. SQLite is the first
user-visible 5.0 milestone and architecturally independent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8 pure helpers (normalizeAutoRecordPollSeconds, normalizeAutoRecordList,
normalizeStreamlinkQuality, normalizeFilenameTemplate,
normalizeMetadataCacheMinutes, normalizePerformanceMode, isPlainObject,
normalizeLogin) plus VALID_STREAMLINK_QUALITIES + PerformanceMode type.
getStreamlinkStreamArg and normalizeConfigTemplates stay in main.ts
because they read globals (config / DEFAULT_*).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure variant takes language as parameter. main.ts retains 2-arg adapter
that injects config.language so call-sites are unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three more user-triggered status messages that updated silently for screen-reader users:
- clipStatus — clip download progress/result text below the URL input on the Clips tab
- eventsViewerStatus — loading/error message inside the events viewer modal
- chatViewerStatus — loading/error message + filter result count inside the chat viewer modal
All three get role="status" + aria-live="polite". Same reasoning as 4.6.153/154 — the user explicitly clicks a button or opens a modal, then the result fills in asynchronously. Without aria-live the announcement was lost.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Following the cleanupReport fix in 4.6.153, three more user-triggered refresh result strings were updating silently:
- statsLastScannedLabel — "Letzter Scan: <timestamp>" / "Scanne..." in the Archive-Statistik header after clicking Aktualisieren
- archiveSearchSummary — "{matchCount} matches (scanned {scanned}…)" / "Scanne..." after clicking Suchen
- storageSummary — "Total: {files} files, {size} — Free disk: {free}" / "Scanne..." after clicking Aktualisieren on the Storage card
All three are textContent updates triggered by an explicit user action that finishes asynchronously. Without aria-live, screen reader users hear nothing after pressing the action button — the result text fills in off-screen.
Added role="status" + aria-live="polite" to all three. "polite" because the result isn't urgent — the user requested it and waiting for a natural break in speech is fine.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Auto-Cleanup card has a "Vorschau" (dry-run) and "Jetzt ausfuehren" button pair followed by a cleanupReport div that displays the result text — "Wuerde X Dateien aelter als Y Tagen verschieben", "X Dateien archiviert", or "Fehler". Without an aria-live region, screen reader users who click the button never hear what happened: the focus is still on the button, the result text appears off to the side silently.
Added role="status" + aria-live="polite" so the report's textContent change gets announced when it's set. "polite" because the report isn't an alert/error — the user requested the action, so they can wait for the screen reader's natural break in current speech to announce the result.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The newStreamer text input (where the user types a Twitch username to add to the streamer list) had only a placeholder ("Streamer hinzufuegen..." / "Add streamer...") as its accessible-name source. Same problem as the 3 filter/search inputs fixed in 4.6.151: placeholder text is not a reliable screen-reader name and disappears once typing starts.
Wired up setAriaLabel('newStreamer', UI_TEXT.static.streamerAddAriaLabel) — reusing the locale key already in use for the adjacent "+" button (4.6.91), which means screen reader users hear "Streamer hinzufuegen" / "Add streamer" for both the input and the button that submits it. Zero new translation work, immediate a11y win.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>