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
This commit is contained in:
GGB
2026-09-16 19:53:28 +02:00
co-authored by Claude Sonnet 5
commit 12661077ff
@@ -0,0 +1,166 @@
# 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/<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.