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:
@@ -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.
|
||||||
Reference in New Issue
Block a user