Rebuilds the deleted WebView-shell Android app, now pointed at the kwaba-sync website's server-side TV mode instead of the desktop site. Key changes from the original: room selection via a new website /tv room-list route instead of a baked-in room ID, and the Menu-button-to-popup mapping is already confirmed working on real hardware ahead of any app code existing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LHgWjmEJXsJEi1EVAoTp4K
10 KiB
KwabaTV Rebuild — Design
Date: 2026-09-16 Status: approved, pending implementation plan
Background
The original KwabaTV Android app (package de.ggb.kwabatv) was a pure
WebView shell around the normal desktop Kwaba Sync site, mapping Fire TV
remote input to the site's existing desktop keyboard shortcuts via real
Android KeyEvents (webView.dispatchKeyEvent(), not JS-simulated events —
Chromium requires a trusted user gesture for autoplay-unlock and
requestFullscreen()). Hardware testing on that app surfaced real layout/
focus-navigation problems (documented in Claude outputs/kwaba-tv-mode- handover.md), which led to building a proper server-side TV mode directly
in the kwaba-sync website first (/tv/:id, a pinned 60/20/80 layout, a
menu popup, guarded shortcuts) rather than continuing to patch the app
against a desktop-shaped page. That work is done, deployed, and separately
specced in kwaba-sync's own
docs/superpowers/specs/2026-09-15-tv-mode-design.md.
The original app's project folder was deleted. This spec covers rebuilding it from scratch, now that it has a real TV-shaped page to point at instead of the desktop site.
Goal
A thin WebView-shell Android app for Fire TV / Android TV that loads
Kwaba Sync's TV-mode pages and forwards real remote-control key events to
them — reusing the desktop-shortcut-to-KeyEvent mechanism proven in the
original hardware test, retargeted at the new /tv routes and their
simplified key surface (individual per-action shortcuts mostly collapsed
into the one menu popup).
Decisions made during this design pass
New repo, new location. C:\Projekte\kwabatv, its own git repo on the
same Gitea instance (ssh://git@git.whodovoodoo.de:2222/GGB/KwabaTV.git,
under the user's own account, not David's — this is a personal project,
distinct from contributing to kwaba-sync). Different tech stack (native
Kotlin/Android vs. the Node web repo), different release process (APK
sideload, no CONTRIBUTING.md deploy webhook), so it does not live inside
kwaba-sync.
Room targeting: a new /tv room-list route, not a baked-in room ID.
The obvious approach (bake a fixed room ID into BuildConfig.WEBSITE_URL,
matching the original app's fixed-URL design) was rejected: the user wants
to pick from multiple rooms via a DPAD-navigable list, not type a room ID
into a settings screen or rebuild the APK to change rooms. Rather than
build a native Android room-list screen (fetch /api/rooms directly,
render it with Android's own TV-focus system), the design extends the
website with one more server-side TV-mode route: GET /tv, a DPAD-
navigable room list in the same spirit as /tv/:id — arrow-key focus
between room cards, Enter/OK joins the focused one. This means:
BuildConfig.WEBSITE_URLbecomeshttps://kwaba.whodovoodoo.pasteaf.de/tv— the room list, not a specific room. No per-build room configuration at all.- No native Android list/focus UI to write — the WebView shell handles both screens identically, since both are server-rendered pages.
- Rejected alternative (native Android screen) would have meant real new app-side UI code and a second navigation model (native list → WebView room) instead of one consistent WebView-only flow.
Reachable from inside a room too. The existing TV-mode menu popup
(#tvMenuOverlay in kwaba-sync, built in the prior spec) gets a 7th
button, "Räume", linking to /tv — so leaving a room to pick another one
doesn't require the Back button's room-list navigation (below); it's also
reachable through the same menu used for Resync/Subtitles/Skip/Spoiler/
Mute/Fullscreen.
Key mapping, reusing the original's real-KeyEvent mechanism, retargeted:
| Remote input | Android event | Sent to WebView | Website behavior |
|---|---|---|---|
| DPAD OK/Center, Play/Pause | KEYCODE_DPAD_CENTER / KEYCODE_MEDIA_PLAY_PAUSE |
Space |
Play/Pause (and the one-time "Beitreten" tap) |
| DPAD Left/Right, tap | KEYCODE_DPAD_LEFT/RIGHT, repeatCount==0 |
ArrowLeft/ArrowRight |
5s seek |
| DPAD Left/Right, held | same keycodes, repeatCount>0, app-side throttled to ~1 event/200ms |
Shift+ArrowLeft/Right |
30s seek. Android's own hardware key-repeat rate is faster than 200ms, so the app must throttle before forwarding — otherwise this floods the room's sync socket with seek commands broadcast to every viewer far faster than intended. |
| DPAD Up/Down, Prev/Next media keys | KEYCODE_DPAD_UP/DOWN, KEYCODE_MEDIA_PREVIOUS/NEXT |
P / N |
Prev/next title — deliberately not ArrowUp/Down: those are explicit no-ops under body.tv in the website (added specifically because volume is TV/AVR-controlled, not site-controlled) |
| Menu button | KEYCODE_MENU |
ContextMenu |
Opens the popup (Resync/Subtitles/Skip/Spoiler/Mute/Fullscreen/Räume). Confirmed working: the user tested a real Fire TV remote's Menu button against the deployed /tv/:id page (via a browser, ahead of this app existing) and the popup opened — the riskiest unknown in this design is already de-risked before any app code exists. |
| Back, in room + fullscreen | KEYCODE_BACK |
Escape |
Browser exits HTML5 fullscreen natively on Escape (standard browser/WebView behavior) — the page itself has no matching keydown case, which is fine, nothing else needs to happen. |
| Back, in room + not fullscreen | KEYCODE_BACK |
(not a key event) | App navigates the WebView directly to /tv (room list) itself — native-side navigation, not something the website reacts to. Reuses fullscreen-state tracking the app needs anyway (see below) and the WebView's current-URL tracking to distinguish "in a room" from "on the room list". |
| Back, in room list | KEYCODE_BACK |
(consumed) | Does nothing — blocks Android's default back-behavior, which would otherwise exit the app. |
| Home | KEYCODE_HOME |
not intercepted | Native Fire TV home; app doesn't touch it. |
Fullscreen-state tracking (needed for the Back-button branch above) comes
from WebChromeClient.onShowCustomView/onHideCustomView — the standard
Android WebView hook fired when a page enters/exits HTML5 fullscreen via
requestFullscreen(). Any WebView that supports fullscreen video already
needs this implemented, so it's not new complexity, just something to
carry over correctly. "In a room vs. on the room list" comes from tracking
the WebView's currently-loaded URL path (/tv exactly vs. /tv/<id>) via
onPageFinished/shouldOverrideUrlLoading.
Fixed decisions carried over unchanged from the original app
(From Claude outputs/README.md and the handover doc — still correct, not
revisited in this pass.)
- Package
de.ggb.kwabatv,compileSdk 34/minSdk 21/targetSdk 34, Gradle 8.7 + AGP 8.5.2 (the tested, working combination). LEANBACK_LAUNCHERmanifest category for the Fire TV launcher entry.- Standard
network_security_config.xml(system trust anchors only) — the domain runs Let's Encrypt, no certificate pinning needed. - Reuse the existing branding assets already sitting in this session's
Claude outputs/folder (ic_launcher_art.png512×512,banner_tv_art.png640×360) rather than regenerating them. - Distribution stays APK sideload only — no store listing.
- Login is a one-time action: the WebView loads
/loginon first launch if there's no session cookie yet; persistent cookie/DOM storage keeps the session alive across app restarts. No native login screen. - No chat, no mic/camera permission requests (
onPermissionRequest→deny()) — matches the website's own "no chat in TV mode" decision and the fact that Fire TV Sticks typically have no camera anyway. - Volume is not touched by the app; the website already forces its internal volume to 100 in TV mode and the TV/AVR handles real volume.
Explicitly out of scope for this pass
- The website's
/tvroom-list route and the popup's 7th "Räume" button are website changes, made in thekwaba-syncrepo, not this one — tracked here only as a dependency this app needs, with its own implementation plan on that side. - Real further hardware quirks beyond the Menu-key mapping (already
confirmed working) — e.g. whether
KEYCODE_MEDIA_PREVIOUS/NEXTare actually sent by either of the user's two test remotes, exact fullscreen visual behavior — are Task-5-style manual verification once the app is built, not something to speculate about further in this spec. - The two items already deferred in the website's own TV-mode spec (the
suspected buffer/sync feedback loop, and YouTube's native controls
showing despite
controls=0) remain deferred; unrelated to the app.
Anticipated file changes
In kwaba-sync (separate implementation plan on that side):
src/server.js— newGET /tvroute (room list)public/— new markup/CSS/JS for the room-list page (DPAD focus navigation between cards), reusing existing branding/CSS tokenspublic/room.html+public/js/room.js— 7th "Räume" button in the existing#tvMenuOverlay
In this repo (kwabatv):
- Standard Android Studio project scaffold (
settings.gradle.kts,build.gradle.kts, Gradle wrapper pinned to 8.7) app/src/main/java/de/ggb/kwabatv/MainActivity.kt— WebView shell,dispatchKeyEventkey-mapping table above, fullscreen tracking viaWebChromeClient, current-URL tracking for the Back-button branchapp/src/main/res/xml/network_security_config.xmlapp/src/main/AndroidManifest.xml—LEANBACK_LAUNCHER, permissionsapp/src/main/res/— icon/banner drawables from the reused branding assetsdocs/superpowers/specs/anddocs/superpowers/plans/— this spec and its implementation plan
Testing approach
Real hardware is available this time — two devices (a TV remote and other hardware with a Fire TV remote) — so verification is a direct build → sideload → test loop rather than the browser-only approximation the website work had to use. Per key-mapping row above:
- Confirmed already: Menu button → popup opens.
- To verify once the app exists: DPAD tap vs. hold seek timing/throttling, Prev/Next media keys (not guaranteed to exist on every remote), Back- button behavior in all three states (fullscreen / in-room / room-list), Home behavior, fullscreen entry/exit visually, and cross-checking behavior across both available remote/hardware combinations in case they differ.