Fix Home focus, Continue watching, picker and Back exit #59
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fm/tv-r2-home"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Outcome
Fix Home’s Back/Down focus loop at navigation-entry ownership; merge Resume and Next up into Continue watching; remove the artwork strip below landscape-card gradients. Also make Settings reachable from every picker avatar and make root Back finish Wolfskin’s Android task without an old-UI detour.
Base:
fm/tv-round2. Previous full-suite baseline:5811ab05556266ffba8b86e211b1684b2b96dd94. AI-assisted implementation and description for this internal Wolfskin fork.Latest picker-centering correction
Current head:
f375d88030abd14bf7b6b262792af6f5e20b3acf. Picker correction:7c67596c; hero stability:f375d880. Restore the approved gear centred under the whole people row, while keeping every-avatar Down, origin-return Up and root task exit. The previous last-avatar placement was wrong and is removed from code, spec, README and proof labeling.Touched-only verification: 3 PickerNavigationTest cases + st002_gear passed, zero failures/errors/skips. Native geometry covers 1–5 people; the 1920×1080 gear capture was inspected. No full suite, APK build or device work for this hotfix. The gallery now shows incorrect last-avatar placement → restored approved whole-row centring.
Home hero late-primary stability hotfix
HomeHeadertreated!watchReadyas Details-only, then replaced that primary Details button with Resume/Play + secondary Details. The native failing-before regression observed Details move x=96 → 338 px.HeroMotion.ktchange or Discover/title layout change. When integrating, retain the sibling Discover branch’sHeroMotionScopeand actions-outside-copy structure: this hotfix changes only the action-row contents and Layout import, not the copy layer.st-007-mindel-resume-in-the-hero.jpg), plus st-006 — Justin’s Play-next hero. The absent localopus-final-2026-11-05directory was not substituted with an invented image: the approved images were read from the published written specification. This retains the approved primary + secondary Details arrangement with existing native metrics; it is not a new full-frame pixel-parity claim.HomeHeroActionsTestandHomeCaptureTest.st007_mindelResumeboth passed, zero failures/errors/skips. The bounds invariant covers Resume episode, Play film and Play episode labels. Native pending/ready Resume pairs and the actual household st-007 Home were inspected. Together with the picker check above: 6 selected tests/captures passed; no full suite, APK build or box work.The gallery separates latest touched-only verification and its three repaired pending/ready hero pairs from the earlier full-suite baseline.
Latest Home-entry and Back-ownership additions
Home-entry production changes:
c5dbe910; real outgoing-entry regressions:a27e922675e2142907408bd75e97270e319cd48c.TitleDestinationgates its shared title/person Android Back handler with the existingScreenEntryactive-ownership flag viaisScreenEntryActive(). A popped entry can remain composed for the exit animation without swallowing the following Home Back or accidentally exiting the task.Touched-only verification for these additions
HomeRowEntryTest, two adjusted visited-row cases,HomeNavigationTest, title/person viewport-Back casesTitleEntryBackTest, active-title sheet return, episode-parent Back, title sheet Back, Home root/hardware/Coming-in BackThe two new integration cases mount actual
TitleDestinationand retain a loaded outgoing title/person after pop. They assert pop → incoming Home nav → task exit on the following Back, with no premature exit. Row-entry cases cover mixed card widths, visited column/track retention and tab leave/return.Per the installation-priority scope, no full
make testormake apk-debugwas rerun for these additions. One full suite and Opt check belongs to the merged-round candidate and still gates merging. The full-suite/APK results below remain the earlier5811ab05baseline, not a new full run on this head. The gallery labels those earlier frames separately from the latest touched-only picker/hero evidence.Root causes and changes
Home focus loop
Sequential Down alone did not reproduce the box report. Keeping an outgoing real episode surface alive while Home returns did:
HomeNavigationTest.outgoingEpisodeDuringBackCannotTakeOverTheHomeScrollerexpected row 3 after Down but remained on row 2 before the fix.The outgoing title/person surface reread
shell.viewafter the shared current route changed to Home. Two lazy frames then shared Home’sLazyListState; the outgoing page could also reacquire native focus. Stable row IDs cannot fix a viewport owned by the wrong entry.ScreenEntrynow supplies each entry’s own remembered viewport and active state. Only the active entry acquires native focus or registers the shell’s logical key controller. No row-name special case, new scrolling timer, or focus request against an uncomposed card.Shared changes in
HomeScreen,ShellSurface,LibraryScreen,DiscoverScreen,TitlePageandPersonPageare limited to viewport/input ownership. The Discover/title edits are not hero-transition or page-layout work. Existing frame/shelf timing and pending-row skipping remain intact.One watch lane, including saved settings
buildHomeRowsmerges in-progress titles and next-up episodes intocw, sorts by actual watch activity, and chooses one episode per series, preferring its resumable episode. Hero precedence is unchanged and its title/series stays off the shelf. Skeletons use the same merged lane.HomeReadFacadeprojects stable series identity and latest actual series-watch dates. Optional enrichment is bounded to three concurrent series reads; unknown dates remain unknown.HomeLibraryCachepersists optional fields and reopens older entries without them. Debug fixtures use their recorded dates.HomeSettingsServicenormalizes local/server/web/plugin settings; the first saved watch row retains its position and view options. Old serialized variants still decode, and the existing combined preset identity is unchanged. Add-row settings offer only Continue watching; obsolete Split/Combine UI actions are removed. The legacy service deduplicates series and uses actual watched-episode activity rather than premiere dates.Bottom gradients
HomeShelf.TileOverlayput the state-line reserve outside its gradient background, exposing artwork below it on unwatched cards. The reserve now sits inside the background. The gradient starts lower withHome.cardOverlayTop = 18 dp(was 27 dp), reaches the solid bottom backing, and retains the approved 92% opacity. Caption/state-line geometry is unchanged.Applies to Continue watching/Next up episode tiles, Resume/wide cards, New in your library and other Home landscape shelves using that component. Title episode tiles already extend their gradient to the bottom without the offending outer reserve. Discover/Library poster cards do not use this bottom title scrim. Hero scrims, rating plates and the correct solid state-line backing remain unchanged.
Picker and task exit
ShellAction.Exitand callsActivity.finishAndRemoveTask. Home, Discover, Movies, Shows and Anime no longer detour through Home or an upstream setup interface.rememberWolfskinBackHandlerowns Android Back even for a singletonNavDisplay.NavigationManager.goBackpops a parent, or requests task exit when no parent exists. Cold picker Back exits; reopened picker Back first dismisses to nav. The loading picker also has a working exit callback.Choices I made
Continue watching matches existing copy. Use the existing Next up landscape-tile geometry for the merged Wolfskin lane. Keep series names on Home episode tiles; removing them belongs only to the separate title-page change.
Preserve hero precedence: resume → next-up → newest → pick, independently of shelf recency.
Keep
cwand persisted combined-row identity. Older cached episodes without series IDs fall back to saved series names until refreshed. Equal/unknown dates retain stable source order after known dates.Keep the first saved legacy watch row’s position and options, rather than resetting customized Home settings.
Gradient amount: 18 dp top padding is an implementation-selected amount within Justin’s request to lower the gradient; 92% bottom opacity is existing approved styling.
Root task exit and every-avatar Down are Justin’s explicit requests, not agent-selected product choices. His exact words are cited in
data/tv-spec/spec.mdanddocker/README.md. Those documents now distinguish the two implementation-selected visual details above from requested behavior.Previous full-suite verification baseline
5811ab05make apk-debug1.1.0-174-g5811ab05-65The final
make testused fresh generated binary result history, unchanged heap/worker/fork settings and an external class-start observer. The session picker started 94/116 and saved-people picker 98/116, both passing late rather than being promoted to the front by previous failures.The first merged-head full gate had one new SavedPeople timeout. Its await now publishes pending Compose snapshots and pumps Android’s host looper, matching the existing session-picker convention. Its five-second deadline, blocked first response, identity assertions and pre-network visibility requirement are unchanged. No ignored/excluded tests, longer timeout, serial/heap workaround, fake fallback or runtime exception suppression.
Native proof and limits
Home round-2 native proof: six same-data gradient pairs, actual merged Home, fifteen manually clocked scroll frames with CSV, picker before/after, and five actual-root content/nav pairs with observed task-exit state. All native source frames are 1920×1080.
The throwaway exit smoke passed 18 cases: five actual WolfskinApp roots, two recovery-picker surfaces and eleven singleton full-screen dispatch variants. The variant matrix does not mount each individual full-screen page; actual Updates/Settings UI is covered by permanent native regressions. Temporary probes/capture harnesses do not ship.
No TV/device access, ADB, installation, optimized APK or merge. Native JVM frames do not certify physical GPU jank or Projectivy foreground behavior after task removal; those remain for the integrated box walk. Discover hero transitions,
HeroMotion.ktand title/episode layout are owned by the sibling changes, not this slice.Code review (Claude): no blocking findings. Reviewed head
5811ab05556266ffba8b86e211b1684b2b96dd94(read-only; no Gradle run; the author's test gate was not reproduced).Checked: the
ScreenEntryownership change across Home, shell, Library, Discover, title and person pages (an outgoing entry can no longer take native focus, register the key controller, or share Home's list state); the merged Continue watching lane (one episode per series, resumable preferred, newest watch activity first, unknown dates last, hero precedence kept, skeleton lane); cache compatibility for older saved Home entries; saved-settings migration (position and options of the first watch row kept, old serialized rows still decode, nothing rewritten until the user saves); root Back, task exit and the picker Down-to-Settings change; and that no test was excluded or weakened (the saved-people await keeps its 5 s deadline and every assertion).Non-blocking notes:
DiscoverScreen.kt'sDisposableEffect: keep this PR's active-entry gate and the Discover PR'sresetLaneMemory()inonDispose; the resolution is additive and safe.TitlePage.ktandspec.mdmerge cleanly;HomeHeader.ktandShellState.ktare touched by only one PR.DiscoverStateandDiscoverShelfstill read the current route's view. Pre-existing, visual-only during exit animations.HomeRowsTest.firstReadSkeletonHasNoFocusableContentlost its row-shape assertion instead of getting the new 4-row shape.getItemscall passes no user id.Full report:
/root/.treehouse/firstmate-7bab20/2/firstmate/data/review-59-code/report.md.Design review (claude): PASS, no blocking finding
Reviewed SHA:
5811ab05556266ffba8b86e211b1684b2b96dd94. Full report:/root/.treehouse/firstmate-7bab20/2/firstmate/data/review-59-design/report.md. Evidence: the published frames at/opt/stacks/pages/store/wolfskin/tv-round2-home/(https://pages.justinvisser.org/wolfskin/tv-round2-home), compared with PR 54's baseline/opt/stacks/pages/store/wolfskin/tv-card-meta-build/native/home-wide-unwatched.pngand the approveddata/tv-v2/opus-final-2026-11-05/assets/home-justin.jpg.What I checked, with numbers:
cardOverlayTop36 px / 18 dp agrees across home-tokens.json, tokens.css and WolfskinTokens.kt; opacity stays 0.92. No hard edge; captions legible. Settled by Justin's "starts too high / image leaks out below".Non-blocking notes (full wording in the report):
HomeCardKind.Widehas no production producer left.cardOverlayTop.Overlap with the Discover PR (#58, not reviewed): the Discover root frames show its hero pager; shared files DiscoverScreen.kt, TitlePage.kt, spec.md.
Not run: Gradle/make/tests. Physical Projectivy foreground and GPU jank remain for the box walk.
Combined round-2 review (code + design) of the Home PR at head
a27e922675e2142907408bd75e97270e319cd48c.Code verdict: APPROVE (status
review/code-claude= success).Design verdict: BLOCK (status
review/design-claude= failure), one blocker.Blocking (B1) - profile picker Settings gear moved without Justin asking. Commit
9cfe9362moved the gear from centred beneath the whole people row (approved, spec revision 5, Justin confirmed) to centred beneath the last avatar (WhosWatching.kt:56), made Down reach it from any avatar and Up return to the origin (ShellState.kt:200), and rewrotedata/tv-spec/spec.md:7(alsodocker/README.md:237and:683) as a "Round-2 correction". Justin's round-2 words do not cover the picker. Fix: revert the position, the Down/Up rule and the spec/README text; a delta check of the fix follows. (My earlier design pass on5811ab05wrongly accepted this; withdrawn.)Delta checked and fine (code):
HomeFocus.kt): an unvisited row starts at column 0 and first-visible 0, a visited row keeps its column and first-visible index, nothing resets Home memory on tab leave (HomeRowEntryTestgoes Home -> Discover -> Home and asserts the same view, column, track offset). The focus-loop fix andScreenEntryownership are untouched; Library is not in the delta.TitleDestination.kt:343): an outgoing title/person entry is inactive, so its handler is disabled and the second Back reaches the app-level handler for the incoming Home. The first Back still pops; sheets and menus keep first refusal; the unsaved-settings handler is untouched.TitleEntryBackTestcovers title and person.Non-blocking notes:
ShellState.back,spec.md:9,README.md:248) and cold-launch picker Back exits. Justin's words in context.md do not mention it; confirm it was requested.pickerIndexto the last avatar; this goes away with the B1 revert.spec.md:21), is a reasonable reading of "merge resume and next up", not his words; it belongs under "Choices I made".home-tokens.json, entry ownership, continue-watching lane). No approved frame file was edited.DiscoverScreen.kt, resolved correctly (keep this PR'sentryActivekey plus Discover'sresetLaneMemory()inonDispose). After both land,spec.mdline 72 (Discover branch's sentence that Home's nearest-centre shelves are unchanged) contradicts this PR's leftmost rule.Full report:
/root/.treehouse/firstmate-7bab20/2/firstmate/data/round2-review/report.md. Not run: Gradle/make (separate gate), nothing on the box.Delta check (fix only) of the Home PR at head
f375d88030abd14bf7b6b262792af6f5e20b3acf, diffa27e9226..f375d880.Code verdict: APPROVE (
review/code-claude= success).Design verdict: PASS (
review/design-claude= success). The blocker B1 is resolved and nothing new blocks.B1, Settings gear: resolved.
WhosWatching.kt:56is(stageWidth - gear) / 2again, the same formula asorigin/fm/tv-round2, so the gear is centred beneath the whole people row for any count.PickerNavigationTestnow pins the gear centre to the midpoint of the first and last avatar centres for 1 to 5 people. The native framepicker-restored.pngshows the gear centred under the middle avatar, with ring and caption as approved.spec.md:7carries the revision-5 text again. Down from any avatar, Up back to the sending avatar and the root-Back exit are now stated separately with Justin's quoted words (spec and README), not as a "Round-2 correction". I took the quotes from firstmate's brief; I could not check them against a source I can read.Hero buttons stay put when Resume/Play data arrives: PASS.
Layoutslot and placed only whenwatchReady. Unplaced, it is neither drawn nor focusable. Details sits after the reserved slot in both states, so its position cannot change.HomeHeroActionsTestasserts identical Details bounds for Resume episode, Play film and Play episode.hotfix-verification.jsonshows the pre-fix failure (Details x 96 -> 338). The native pending/ready frames andhero-st007.pngmatch the approved st-007/st-006 layout.lateLineFadetoken.count = 1, Details is column 0 and activates Details, and Right is clamped. Ready gives Resume column 0 and Details column 1, as in the approved frames.Merge with the Discover PR (
79f64f6e): exactly one conflict, the knownDiscoverScreen.kthunk, resolved the same way.HomeHeader.ktnow changes on both sides and auto-merges cleanly. The merged file keepsHeroMotionScopeandHeroCopyHandoffaround the copy only, with the buttons outside it.Non-blocking notes:
spec.md:72after the merge still says Home's nearest-centre shelves are unchanged.Full report (delta section 2):
/root/.treehouse/firstmate-7bab20/2/firstmate/data/round2-review/report.md. Not run: Gradle/make, nothing on the box.Pull request closed