# 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 `KeyEvent`s (`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_URL` becomes `https://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/`) 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_LAUNCHER` manifest 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.png` 512×512, `banner_tv_art.png` 640×360) rather than regenerating them. - Distribution stays APK sideload only — no store listing. - Login is a one-time action: the WebView loads `/login` on 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 `/tv` room-list route and the popup's 7th "Räume" button are **website changes**, made in the `kwaba-sync` repo, 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/NEXT` are 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` — new `GET /tv` route (room list) - `public/` — new markup/CSS/JS for the room-list page (DPAD focus navigation between cards), reusing existing branding/CSS tokens - `public/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, `dispatchKeyEvent` key-mapping table above, fullscreen tracking via `WebChromeClient`, current-URL tracking for the Back-button branch - `app/src/main/res/xml/network_security_config.xml` - `app/src/main/AndroidManifest.xml` — `LEANBACK_LAUNCHER`, permissions - `app/src/main/res/` — icon/banner drawables from the reused branding assets - `docs/superpowers/specs/` and `docs/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.