Files
KwabaTV/docs/superpowers/specs/2026-09-16-kwabatv-rebuild-design.md
T
GGBandClaude Sonnet 5 12661077ff Add KwabaTV rebuild design spec
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
2026-09-16 19:53:28 +02:00

10 KiB
Raw Blame History

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_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/<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_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.