commit 12661077ff0ddc934e476c5a8d92c381d88ae884 Author: ggb.kevin Date: Wed Sep 16 19:53:28 2026 +0200 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 Claude-Session: https://claude.ai/code/session_01LHgWjmEJXsJEi1EVAoTp4K diff --git a/docs/superpowers/specs/2026-09-16-kwabatv-rebuild-design.md b/docs/superpowers/specs/2026-09-16-kwabatv-rebuild-design.md new file mode 100644 index 0000000..dd3d763 --- /dev/null +++ b/docs/superpowers/specs/2026-09-16-kwabatv-rebuild-design.md @@ -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/`) 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.