A single-language interface makes the app less usable for part of the audience
and encourages more hardcoded copy as new screens and states are added.
Introducing a shared i18n path with a persisted language preference gives the
UI one consistent way to present text and lowers the risk of partial or
inconsistent localization over time.
The surrounding copy cleanup belongs in the same change because it aligns
existing labels, messages, and settings text with that translation structure
instead of leaving mixed localization approaches in place.
German documentation in CLAUDE.md was a barrier for any contributor or
tool without German language context. English is the project's working
language for code and commits — the docs should match.
README.md (bilingual) received new entries for logout confirmation,
shared types layer, ChapterPanel, and QualityPanel in both EN and DE.
Duplicate raw string literals across handlers.ts, preload/index.ts made
typos undetectable at compile time and renames a multi-file grep problem.
A single channels.ts now owns all channel names; both files import from it.
Also fixes two bugs found during testing:
- LoginScreen and AccountScreen buttons were not triggerable via gamepad
A-button: synthetic KeyboardEvents are untrusted and don't fire native
button clicks. Added explicit keydown handlers that call the action directly.
- AppShell loading overlay could disappear while hlsPayload was still null
if the playback:event 'started' IPC message arrived before playback:hls-url.
Overlay now stays visible until hlsPayload is set, regardless of playState.
handleWatchVodFromChapter was identical to handleWatchVod except for an
optional startSeconds argument that the bridge already supports. Merged
into a single function with startSeconds?: number.
openChapterPanel was a wrapper around showChapterPanel whose two branches
both executed the same code. Removed the wrapper; call sites now call
showChapterPanel directly.
Also removes stale mpv references from CLAUDE.md that were superseded by
the hls.js migration commit.
mpv was replaced by hls.js in v0.4.0 but its five build modules
(libass, libplacebo, libXpresent, Lua 5.2, mpv itself) and the
ffmpeg7 patch were never removed — dead weight in every build since.
The Joystick API (/dev/input/js*) numbers buttons depending on driver
and connection type. Xbox One controllers via Bluetooth with a Share
button shifted X from button number 2 to 3, silently breaking the
mapping.
The evdev interface provides standardized BTN_* codes (BTN_SOUTH=A,
BTN_NORTH=X, etc.) that remain consistent across Xbox, PlayStation,
and Nintendo controllers over both USB and Bluetooth.
js* devices remain the discovery path, while /sys/class/input/jsN is
used to resolve the corresponding event* device. If the /sys lookup
fails, JoystickFallbackReader falls back to the old Joystick API.
Quality selection during playback:
- Add getAvailableQualities() in streamlink via --json, sorted by fixed order
- Expose playback:get-qualities IPC channel (handlers, preload, type declarations)
- Add quality panel in PlaybackOverlay:
- Button + dropdown
- Keyboard navigation (X: open, arrows: navigate, Enter: confirm, Escape: close)
- Quality change triggers streamlink restart; VOD resumes at current position
- Available in ChannelScreen (Live + VOD) and AppShell global overlay (Live)
- Add QualityIcon
Chapter panel UX improvements:
- Video no longer pauses when chapter panel opens
- PlaybackOverlay is hidden via conditional render instead
- Remove wasPlayingBeforeChapters state (no longer needed)
- X button seeks to video start when no chapters are found, with hint in panel UI
VOD cards and defaults:
- Show view count with eye icon on VOD cards (matching playback overlay style)
- Change default streamBadgeMode from 'language' to 'both'