Commit graph

55 commits

Author SHA1 Message Date
ec2b1f03b1 lists: measure a card, not the header, for cards-per-row
LEFT from any card of a grid's first row opened the menu drawer. The
cells-per-row estimate read the width of the first visible grid item,
but near the top that is the page header, which spans the whole width —
so it computed one card per row and treated every card as a row start.
Skip full-span items when estimating; row starts resolve correctly even
while the header is on screen.
2026-10-13 23:17:03 +00:00
710a0f23cc channels: drop the all-channels listing walk
The 13-page listing only produced rows that were never rendered
(followed-instance groups had no UI consumer) and 12 of its pages were
spent finding locals already known. What the cards still show and the
video listing cannot carry — banners, exact subscriber counts — comes
from one per-channel call per local channel, in six parallel batches.
Zero-video local channels are no longer listed: they had nothing on
their cards anyway; the signed-in user's own channels stay complete
via the account listing.
2026-10-13 23:03:58 +00:00
29dee24849 channels: build the local rows from the video listing
Entering Channels walked thirteen 100-row pages of the all-channels
listing (1237 rows on this instance, most of them federated follows)
and re-sorted the rows after every page; the signed-in user's own row
only appeared at page 12, which read as "loading one channel after
another" and never kept icons visible above the fold.

The local video listing already carries what the cards show: paging its
few pages yields exact per-channel video/play counts, covers, channel
avatars and the owner accounts for the row headers — no per-channel
requests. The all-channels listing now runs behind it as a background
metadata refinement (banners, exact subscriber counts, zero-video local
channels, followed platforms) and rebuilds rows only when a group
actually changes, so the visible rows don't churn. Re-entering the
screen within five minutes repaints the kept rows untouched.

Closes #13
2026-10-13 14:25:31 +00:00
f5b37dd21d player: wire the D-pad through the action row, panels and playlist deletion
The action-row buttons lost their modifier in the icon-only rework and
became unfocusable, so D-pad DOWN off the paused seek bar landed nowhere
(requestFocus failed with "not initialized"). The modifier is attached
to the button again and the seek bar ↔ buttons directions are wired
explicitly, because the default focus search skips the bar.

Pill visuals use the new state language: no border, gray pill = focused,
red glyph = subscribed/rated; the sidebar panels keep the D-pad inside
their lists (consumed at the edges). BACK/Esc acts on ACTION_DOWN only —
its trailing UP saw the panel already closed and exited the video — and
panels return focus to the pill that opened them.

Playlist deletion is a requested scope addition: holding OK on a row
asks via a dialog (DELETE /video-playlists/{id}); the toggle moves to
key release so the hold cannot double-act. Holding also exposed an
activation bug: the CENTER helper consumed only ACTION_DOWN and the
release leaked into the underlying clickable — now consumed, repeats
swallowed, so Create (and the cards) fire exactly once per press.

An info panel shows the video description with clickable links: inline
(pointer) plus a focusable link row list (D-pad); PeerTube video and
channel links play/drill in-app, other links open the browser.

Closes #12
2026-10-13 14:25:21 +00:00
c70d98ac64 home: Drop the logo 2dp more for balance
At 16dp bottom padding the glyph floated 3-4dp under the title baseline
and read as loosely attached; at 14dp the TV's bottom edge touches the
baseline so it reads as an object standing next to the text.
2026-10-13 09:26:35 +00:00
3d4de90671 player: Align the controls overlay; consistent stat icons
The overlay's spacing came out lopsided: 34dp between the seek bar and
the action row while the title/channel line sat tight, the first pill's
glyph ~50dp from the screen edge vs the 24dp-aligned text behind it, and
the time text at a different right margin than the track it belongs to.
Positioning recipe (measured on the emulator, cross-checked with a
second model): seek-row box 28dp with no horizontal padding so the track
spans the same 24dp margins as the text, action row 6dp below it, first
pill drops its start padding so the glyph aligns flush with the text
margin, time padded 3dp so its right edge tracks the rounded cap.

The stat icons now match the Channels view (Groups = subscribers,
PlayArrow = views next to the numbers), the overlay's subscribe became
Groups instead of the YouTube-notifications bell (the bell read as
notifications), and federated channels without an avatar get a
letter-circle fallback so the channel row keeps its shape.
2026-10-13 08:29:43 +00:00
3d5fd44a59 channels: Parallelize stats and persist them
The channels screen enumerated every channel and then fetched each
channel's stats strictly one after another, with a row rebuild and a
row re-save after every single one — with several dozen local channels,
each stat call walking up to four video pages, that took minutes.
Stats now fetch in parallel batches of six and are persisted alongside
the rows snapshot, so an existing install paints the channels screen
with full stats instantly.

Closes #9
2026-10-13 08:29:43 +00:00
01c08e1dbb player: Icon-only action row
Bell, thumbs, chat bubble and playlist icons instead of text labels —
no translutable strings in the action row; labels live on as
contentDescription for accessibility. Like/dislike get proper icons
instead of emoji text with the count as a digit next to them.
2026-10-13 07:51:44 +00:00
4af904bf2a home: Nudge the logo below the text baseline
On one baseline the glyph looked like it was sinking into the title;
1dp lower matches how the launcher icon sits next to text.

Closes #11
2026-10-13 07:45:17 +00:00
9ca65c121b player: Action row with subscribe, rating, comments and playlists
SmartTube keeps its action buttons under the seek bar — we had nothing.
The paused/controls overlay gained: subscribe (POST /users/me/
subscriptions with the channel URI, state via the bulk exist endpoint),
like/dislike with immediate count and fill feedback (PUT /videos/{uuid}/
rate), and right-sidebar panels for comments (GET /videos/{uuid}/
comment-threads, paged, read-only; the instance has comments disabled on
some videos, which surfaces as a friendly empty state) and playlists
(checkboxes add/remove the video via the playlist-elements endpoints, a
text field creates new ones). Opening a panel shrinks the video so it
stays fully visible on the left; BACK or LEFT closes again.

Two server quirks are handled: the subscription exist-check settles a
moment after the POST (optimistic update + delayed re-verify instead of
trusting an instant answer), and this emulator translates CENTER into
the platform click as well, so a single press could subscribe and
instantly unsubscribe — activation is now guarded per request.

Closes #10
2026-10-13 07:45:17 +00:00
9f1c6d54c1 cache: Show the last session's lists instantly at cold start
Opening the app (especially the Channels screen) waited for the API and
then for every image before anything painted. JSON list snapshots now
live in cacheDir: on the next start the last loaded page paints
immediately, then the load that runs anyway silently replaces the first
page with fresh data, so stale content can never stick (issue asks
exactly for that show-cached-revalidate behavior). Logout wipes the
snapshots because they contain account-private listings; History and
Search results are never snapshotted. Thumbnails were already
disk-cached by Coil (the instance serves them with a one-year max-age).

Closes #9
2026-10-13 07:45:17 +00:00
fdc570ac9e home: Title bar with account and channel pages
The header-removal experiment went too far: the app now has a slim
persistent title bar — logo + "JTube TV" on the left (it anchors the
layout while the drawer is open), avatar + display name on the right as
a purely visual indicator so it never steals D-pad focus. LEFT from the
first card of every row opens the drawer (the subscriptions row silently
skipped it, and the grid only wired the very first item), and UP no
longer surprises by opening anything.

The channel page that the Channels screen and the player's channel icon
point at never actually rendered — ListSource.Channel, its repository
call and the header state all existed without a UI. Videos of one
channel now open as a grid with banner, avatar, name and handle; page
headers (search field, channel banner) live inside the grid so they
scroll along instead of stacking under the cards.

Closes #7
2026-10-13 00:05:30 +00:00
a6dff3435d player: Own the controls UI end to end
Space and Enter now toggle playback while playing (the remote OK always
did), and the native PlayerView controller is switched off completely:
hovering the video with a mouse revealed the old play UI with skip
buttons and a settings gear, which must never appear. Our SmartTube-style
overlay is the only controls surface — persistent when paused, shown
transiently for 5 s while playing — with the transport icon row dropped,
position/duration in one style right of the bar, the absolute published
date under the title and a clickable channel icon that opens the
channel's video page.

The loading indicator now tracks the player's real buffering state
(STATE_READY) instead of ending with metadata resolution, so the window
is never black without an indicator. Closes #8
2026-10-13 00:05:19 +00:00
15a481b7c0 profile: Fetch the user profile on every app start
The login flow stored displayName/avatar inside one swallow-all try that
serverConfig() also had to survive first, and /users/me carries both in
its nested account object on current PeerTube versions (verified live
against 8.3.1) — so stored sessions kept showing the raw username and no
avatar. The profile now refreshes after login and whenever an existing
session starts, and the relative avatar fallback no longer points into
/api/v1.
2026-10-13 00:05:10 +00:00
a71de07969 emu: Stop silent early exits in the emulator start script
The script's set -euo pipefail made it die without any visible error in
two places: an empty /tmp/jtube-adbkeys/ failed the glob cat (the error
itself was discarded, so the output just stopped mid-script — this hits
any host after a reboot), and the boot-wait loop was killed on its first
iteration because adb exits non-zero while the device is still booting.
Both were swallowing the whole && chain (adb connect, scrcpy), making it
look like the emulator was broken when it was fine or about to be.

The stale bridge-emulator.sh duplicate (different port, broken vendor
keys path) is removed in the same spirit — keeping it invites running
the wrong script. Docs updated to match the one script.
2026-10-13 08:14:15 +09:00
c19024355c player: Usable D-pad controls when paused
The paused state was unusable (#4): the semi-transparent info overlay
covered the seek line, and the key handler kept owning CENTER/LEFT/RIGHT
(they toggled/seeked directly), so the transport buttons could never be
selected at all.

Replace it with a SmartTube-style pause screen: a big seek bar above an
info bar (never covered again — the panel below it is near-opaque) and
focusable Back 10 s / Play / Forward 10 s buttons. While paused the
arrows pick a control and OK activates it; the bar itself scrubs ±10 s
with arrows and OK starts playback. Focus moves into the overlay on
pause (Play button) and back to the plain key handler on resume, so
while playing, OK still toggles pause and LEFT/RIGHT still seek.

Refs #4
2026-10-12 09:54:58 +00:00
1c8fff7eca home: Make the "New local videos" row reachable by DOWN
On the TV, DOWN from "Continue watching" often skipped the row below
and landed on "New from your subscriptions": the new-videos row is
rendered as a non-focusable "Loading…" while its items are still in
flight, and every FocusScrollRow auto-focused its first card when its
items arrived, so rows loaded out of order stole focus in a race.

Start fetching new local videos eagerly with the rest of the home data,
restrict the load-time auto-focus to the first row only, and cancel it
when the user has already pressed a D-pad key meanwhile. Also rename
the row to "New local videos" to distinguish it from the federated
subscription feed.

Refs #3
2026-10-12 09:49:15 +00:00
20e6b07107 subs: Show the subscription feed as one paged video grid
Jeena wanted subscriptions to behave exactly like My videos — a single
plain video list paged on scroll — instead of the SmartTube-style
per-channel rows, which felt noisy when many channels are subscribed.
The plain grid path already existed in VideoListScreen, so switch the
Subscriptions destination over to it and drop the rows screen.

Refs #5
2026-10-12 09:47:18 +00:00
3635a4ed74 home: Restore row focus when returning from the player
BACK from a video dropped the user on a random spot (often the search
screen, #2): the restore token was only bumped when it was already >= 1,
so the very first back never re-focused anything, and HomeRowsViewModel
refresh() cleared the continue-watching rows synchronously, disposing
the rows before their focus-restore effect could run. Bump the token on
every playing -> not-playing transition and keep the rows alive during
a refresh instead.

Refs #2
2026-10-12 09:42:09 +00:00
40a9d1e2c6 docs: Agent container emulator workflow
Document how the host emulator is driven over adb from the agent
container: the start script now boots headless by default and trusts
remote adb keys via ADB_VENDOR_KEYS at launch, since the TV system
image is a user build that blocks pushing keys to adb_keys after boot.
AGENTS.md points to the new doc and fixes the stale SDK path.
2026-10-12 09:42:08 +00:00
ed65b77e90 player: Pause and mute out when the app goes to the background
Pressing HOME kept the audio running, so a video started in another
app played on top of it and returning landed on the home view with an
invisible player. The player now pauses itself on ON_PAUSE, reports
the position to the server and resumes automatically when the app
comes back. The playing screen is also kept across activity
recreation now, so returning shows the video, not the home view.

Ref #6
2026-10-12 03:23:34 +00:00
a5d88aeea1 docs: Real screenshots
Replace the placeholder-content screenshots with a demo instance that
serves real CC-licensed open movies (Blender Foundation, framatube),
so the app looks like what it is: a video player with real thumbnails,
real durations and real playback.
2026-10-12 03:06:15 +00:00
344a211d72 player: Fix vertical videos stretched to full screen
On the HiMedia/Amlogic TV the video renders through the vendor video
plane, which scales the decoded stream to the whole panel and ignores
the letterbox geometry AspectRatioFrameLayout lays out for the
SurfaceView. Portrait videos therefore filled the screen instead of
being letterboxed; on emulators the same code was correct.

Render the video in a TextureView instead: it lives in the normal
view tree, so the aspect ratio is honored on every device. Verified
on the HiMedia box (vertical short letterboxed, landscape fills the
screen as before) and on the emulator.

Closes #1
2026-10-12 02:48:11 +00:00
116b6926e4 docs: README for humans, with logo and screenshots
Rewrite the README around what a user sees: what the app is for, how
the screens look and what to press. Add the logo, five screenshots
taken against a local mock PeerTube instance with placeholder content
(no private media), and a debug-only manifest that allows plain HTTP
so those mock instances work.
2026-10-12 01:18:02 +00:00
669c4af359 license: Adopt GPL-3.0-or-later
Decide on the license before the source is shared publicly: GPL v3
keeps forks and improvements of this app free software too.
2026-10-12 00:41:17 +00:00
0cebe3b710 docs: Remove the test account name from the public repo
The account name identifies a real, seeded account on the instance;
the public source doesn't need it to make sense. Credentials were
never in the repo and still live only in sops.
2026-10-12 00:33:42 +00:00
89c1a13b8b docs: Drop instance-specific storage-security notes
The public repo must not document this instance's object-storage
access-control posture; findings like that belong in private ops
notes. The engineering guidance (never rely on URL obscurity, always
request file tokens) stays.
2026-10-12 00:27:08 +00:00
5a78849a9e ui: Use the JTube TV logo for the launcher icon and TV banner
Replace the placeholder red play-square with the real logo (the
orange TV from https://jeena.net/jtube-logo.svg) converted to vector
drawables. The old placeholder still lives on in git history; the
launcher now matches the website branding.
2026-10-12 00:26:47 +00:00
7584730646 channels: Show a banner and harden D-pad activation
Channel pages now load the channel record itself and use the newest
video's thumbnail as a banner fallback, so channels never render
without a header image. Focus cards in re-sorting rows only hand out
initial focus once per screen visit so streaming stats can't steal
focus mid-navigation.

D-pad activation now also accepts ENTER/NUMPAD_ENTER/SPACE, matching
how the Compose platform fires clickable cards; consuming in the
preview phase keeps OK from double-activating.
2026-10-12 00:26:47 +00:00
28f0751e48 scripts: emulator-seed.sh — seed a fresh emulated device with the test account 2026-10-11 07:47:06 +00:00
b8893172d4 ui: CENTER activates everything (clickable lacks DPAD_CENTER)
Compose clickable handles Enter/Space only; whether the TV's OK
(DPAD_CENTER) triggers click depends on the device's input stack -
worked on the API-36 host emulator, dead on the API-34 ATD and
possibly some TVs. A preview-tunnelled center handler now activates
drawer items, top-bar buttons, video and channel cards, and the
login/search buttons deterministically, exactly once (preview
consumption prevents the platform path from double-firing).
2026-10-11 07:44:35 +00:00
d469101eb0 ui: Explicit UP from first content row to the top bar
Focus search silently failed to move from the first content card up to
the top bar (why the menu felt unreachable); the first row's cards now
have a wired UP target (the search button) in rows and grids.
2026-10-11 07:31:18 +00:00
828bfdafa9 ui: BACK = up the hierarchy (channel->channels->home->exit guard)
The BACK-opens-drawer behavior made navigation loops with the drawer
focus restore; replaced with the TV-standard: BACK leaves the player,
closes the drawer, leaves a channel drilldown, or goes Home; from Home
it is the double-press exit guard. Drawer opens via the account
button / LEFT on a leftmost card.
2026-10-11 07:28:19 +00:00
d55c265469 channels: Render rows early (pages only), guard rebuild, Long-safe sort keys
The channels screen waited for every listing page AND all per-channel
stats before showing anything - a 40s+ blank spinner. Rows now render
per page (unsorted), re-sorting as stat batches arrive. rebuildRows is
guarded; the earlier ClassCastException (Integer into a Long
comparator) surfaced via the sort call and can no longer abort the
app. All sort keys go through Long-typed helpers.
2026-10-11 07:01:27 +00:00
8d21cf35ec ui: Fix drawer focus-eater + BACK/RIGHT/LEFT menu affordances
The drawer's tap-to-close scrim was focusable: LEFT/RIGHT from a
drawer item moved focus onto the invisible fullscreen scrim, and OK
mysteriously dismissed - the drawer felt un-navigable. Scrim is no
longer focusable; RIGHT (from the drawer) closes it and BACK closes
it; closing restores focus to the search button. LEFT on any row's
leftmost card opens the drawer (used to be only the first row).
Banner prefetch keys now match the render keys (480x270) so channel
banners stop reloading on focus movement.
2026-10-11 06:45:56 +00:00
f5535eec3f ui: All list screens use home-style 4-per-row card size
Grid screens (My videos, History, Search, channel pages) switch from
3-column to adaptive columns sized exactly like the Home rows' cards,
with the compact single-line title, so every screen reads the same.
2026-10-11 06:13:59 +00:00
b3b39976fe channels: Per-user rows, empty-channels row, redesigned stats card, subs detail
Channels now render as SmartTube rows and scroll vertically
(LazyColumn - the unscrollable Column hid rows 2+). Local channels
group one row per ownerAccount, sorted by total video count, with all
zero-video channels merged into a final 'No videos yet' row.
Followed-instance channels group per platform. The channel card
matches the video-card language: banner center-cropped, round avatar
bottom-left, channel name, 'owner display name - platform display
name' (instance names via /config, local cached at login; no domain
strings), and an icon stat line (videos | subscribers | plays); counts
load per channel in the background since the listing endpoint doesn't
carry them (verified on the live API).

Subscriptions destination switches to per-channel rows, matching the
Home card size.
2026-10-11 05:42:02 +00:00
5ea1ca5124 ui: Fix recompose loop during scroll (2fps vertical)
Focus requesters were stored in snapshot-state maps and written during
composition of every list item: each write invalidated the reading
scope, which recomposed the (whole page of) items, wrote again, ...
This feedback loop is why vertical scrolling ran at ~2fps on the TV
while the same build scrolled fine in the emulator's short bursts.
Plain HashMaps kill the loop - focus handling needs no recomposition.
2026-10-11 05:17:12 +00:00
f7273121ae ui: Real account button (avatar + display name), stable list keys
The top bar now mirrors SmartTube: a 44dp circle with the actual user
avatar (persisted at login alongside the display name; falls back to
the person icon) and the display name next to it.

Scrolling jank: add stable keys to grids and rows (item/channel ids)
- without keys Compose rebinds the whole list during scroll and
re-fires every AsyncImage + focus request, which is the realistic
culprit for the 2fps vertical scrolling on the TV.
2026-10-11 05:14:32 +00:00
cc1c1d1c9d scripts: bridge-emulator.sh (host) + image prefetching
Perf work for the TV FPS complaint: every page that loads from the API
also warms Coil's disk/memory caches (video thumbnails at the same
480x270 the card renders, channel banners/avatars) so rows scroll
without popping in over the network. Prefetch keys match render keys
exactly so cache hits are guaranteed.

bridge-emulator.sh runs OUTSIDE the container on the emulator host:
boots jtubesc if needed (ADB_VENDOR_KEYS whitelists the host adb key),
installs/starts JTube TV and binds :5557 -> adbd 5601 so the container
and LAN can drive it. Run it once in your rutherford shell.
2026-10-11 01:15:51 +00:00
0d583c9660 player: Treat Escape (and plain keyboard back) as BACK
scrcpy testing from a laptop keyboard: Ctrl+B depends on the mod
binding and was unreliable; the app itself now consumes KEYCODE_ESCAPE
- player exits to the list, drawer and channel drilldowns close.
Right-click in scrcpy remains the other back.
2026-10-11 00:48:23 +00:00
e55f7f53c1 ui: First card auto-focus, visible top-bar focus, account button
On launch and on every destination switch the first card of the
content gets focus automatically - invisible focus on chrome confused
'Do I need to press DOWN first?'. The search button shows a white ring
when focused, and the top bar gained a real account button (person
icon + logged-in username) replacing the cryptic monitor icon.

start-emulator.sh: headless-safe boot (no -gpuhost crash without a
display), whitelists adb public keys headlessly (drop them into
/tmp/jtube-adbkeys/), and starts the socat bridge itself.
2026-10-10 23:52:01 +00:00
b84be3e65e scripts: Host-side GPU emulator launcher for scrcpy testing
scrcpy cannot talk to headless emulators inside the container: the
AOSP ATD image lacks framework pieces (KeyCharacterMap NPE) and there
is no GPU/KVM in the container at all. The script boots the android-tv
image image on the host with -gpu host and a window (so the RSA dialog
is clickable), installs the current debug APK and leaves the app
running; the container's socat bridge then exposes it as
rutherford:5556 for adb/scrcpy from anywhere.
2026-10-10 23:26:57 +00:00
51077b8352 player,perf: Pause info overlay, no controller flash, cheap thumbnails
Paused playback now shows a bottom panel with channel + title + views
+ upload date and position instead of the controller bar (which hid
itself while paused - it was colliding with custom info). The
controller no longer flashes 4 s at playback start: it only reveals
on UP/DOWN-style keys.

For the TV frame-rate complaint: thumbnails decode at 480x270 RGB_565
via a global Coil loader and avatars at 160px, instead of full-size
1080p+ jpegs re-composited per scroll frame. Measured on the
software-rendered emulator: stable 10% janky / p50 18ms during row
scrolling; real-hardware measurement via scripts/fps-check.sh when
the couch is free.

BACK from a Home row also refocuses the watched card now (row-local
focus requesters + restore token), fixing the 'focus lands two-three
cards left' complaint.
2026-10-10 22:53:03 +00:00
694467b038 ui: Visible drawer focus + subscriptions row on Home
Menu items now flip to white/dark on focus (before, focus moved
invisibly so navigation looked broken) with explicit UP/DOWN key
handling. Home gains a 'New from your subscriptions' row (first page,
refreshed with the rest after playback).
2026-10-10 22:29:00 +00:00
60ef94c52e ui: Eased card-row scrolling, dark text on focused white cards
Focused-card rows now animate the newly focused card to the left edge
(animateScrollToItem) instead of the abrupt bring-into-view jump -
SmartTube-style easing. Text inside a focused (white) card flips to a
dark gray-on-white palette instead of staying light gray. LEFT on the
first card of a row/chain opens the menu drawer.
2026-10-10 22:24:06 +00:00
9635e31589 ui: SmartTube layout - hidden rail, top bar, white focus cards
Drop the always-visible sidebar: content is now full width so ~4.5
compact cards fit per row (width derived from the screen). A slide-in
menu drawer opens via the avatar (BACK/LEFT close it) and carries the
destinations + sign out with legible labels (#B0B0B0) replacing the
illegible dark-grey rail. Cards no longer scale on focus: they flip
to a white background with dark text and a white frame like
SmartTube. Grids move to 3 columns with the freed width.
2026-10-10 22:16:29 +00:00
14cd3d3976 ui: Rebuild all screens when the session epoch changes
Signing out and logging in as another account kept showing the
previous account's videos: the list ViewModels live in the activity
store and are keyed by destination only. A session epoch now
increments on logout and on each successful login; every ViewModel
key, grid scroll state and per-item focus map is keyed by it, so an
account switch starts from genuinely fresh data.
2026-10-10 12:41:55 +00:00
9d89c1c060 player,ui: Fix the remote-blocking bugs from the Claude UX run
Claude Code drove the emulator through the TESTS.md checklist
(reports/ux-2026-10-10.md) and found the release blockers. Fixes:
player ignores the D-pad after controller auto-hide (BUG-2, focus +
key forwarding: CENTER play/pause, LEFT/RIGHT seek, others show
controls), Channels 404 for email logins (BUG-1, authenticate the
users/me call at login), focus + history lost after BACK (BUG-3/7,
restore token re-focuses the opened card and History/Continue
watching refresh), Home rows clipped below 1080px (BUG-4, scrollable
LazyColumn), text-field D-pad focus traps (BUG-5, IME actions + key
escapes), Continue watching started at 0:00 and overwrote server
position (BUG-6, resume from userHistory + persist on exit), leaked
report timer (BUG-8), token sent to third-party media hosts (BUG-9,
ResolvingDataSource appends videoFileToken per request, Bearer only
for instance host), player error Retry (BUG-10), raw error strings
(BUG-13), y-ago format bug (BUG-14), sign-out double press (BUG-15),
T18 expectation corrected (server omits private videos from history).

Verified on emuluator: CENTER pauses + shows controller; BACK returns
to refreshed Continue watching with progress bars.
2026-10-10 12:23:11 +00:00
43056065e1 ui: SmartTube-style home rows, focus memory and UX-review fixes
The home screen is now stacked horizontal rows ('Continue watching'
with red progress bars from server-reported positions, 'New videos'
paged) instead of one tall list - matching the SmartTube look the app
is modeled on. The player renders as an overlay so the list beneath
keeps its scroll and focus across BACK (was resetting to the top),
grid scroll state is saved per destination, cards render focus
scale/border via tv-material, thumbnails carry relative dates and
privacy badges only where meaningful, overscan margins guard the 1080p
safe area, and the setup screen got a password reveal, clipboard paste
helpers and an always-focusable validating Sign in.

UX review by Claude Opus 5.5 (claude CLI) on emulator screenshots;
remaining suggestions are triaged in BACKLOG.md.
2026-10-10 11:29:08 +00:00