diff --git a/data/tv-spec/spec.md b/data/tv-spec/spec.md index 1f765f22..ec13f225 100644 --- a/data/tv-spec/spec.md +++ b/data/tv-spec/spec.md @@ -4,10 +4,16 @@ ## Who’s watching -Round-2 correction: the Settings gear is centred beneath the **last avatar**, independent of selection. Down from **any avatar** reaches it; Up returns to the avatar that supplied focus, including after visiting Settings and returning. Left/Right still traverse only avatars. This supersedes the revision-5 whole-row centring and final-avatar-only Down rule. The 4 px accent straddle ring, 1.08 scale and focus-only Settings caption are unchanged. +The Settings gear is horizontally centred beneath the complete people row, independent of person count or selection. Justin confirmed this placement. It uses the same 4 px accent straddle ring as the people, with the existing 1.08 scale and focus-only Settings caption. + +Down from any avatar reaches the gear; Up returns to its origin, including after visiting Settings. Left/Right avatar traversal is unchanged. + +Source — Justin (2026-11-07): “the settings icon moved, but pressing down only works from the last profile icon. I want it to always move there when pressing down in that screen”. Root content Back moves to the nav; the next Back **finishes and removes Wolfskin’s Android task**, returning to the launcher without visiting an old Home/setup interface. This applies to Home, Discover, Movies, Shows and Anime. Cold-launch picker Back exits; a reopened picker first dismisses to its opener’s nav. Pushed title/person/settings/Updates pages pop their parent stack, and menus/sheets/unsaved-settings prompts get first refusal. A singleton full-screen page exits rather than becoming a dead end. +Source — Justin (2026-11-07): “pressing back enough times should close wolfskin, currently there is no way to go back to projectivity apart from weird methods via the old ui”. + ## Focus visibility “Centered” means consistent visibility, not geometric centering. Keep the approved shelf rule: one-card lookahead, shifting after the second-last visible slot, with first/last positions clamped to the actual contents. Short rows do not manufacture empty scroll space. Poster, tile, wide, service, episode, cast and related shelves follow the same rule. @@ -18,7 +24,7 @@ Library and Person grids retain six columns and the existing Level 2 row spacing ## Home continuation and navigation — round 2 amendment -Home has one **Continue watching** lane for in-progress films/episodes and next-up episodes. Use the existing landscape-tile geometry, order by most recent actual watch activity, and show a series once: its resumable episode takes precedence over its next-up episode. Unknown activity dates retain source order after known dates. The non-rotating hero keeps its resume → next-up → newest → pick precedence and is not duplicated in the lane. +Justin asked for one **Continue watching** lane merging in-progress films/episodes and next-up episodes. **Implementation choice:** reuse the existing Next up landscape-tile geometry for the merged lane, rather than the old Resume/wide geometry. Order by most recent actual watch activity and show a series once: its resumable episode takes precedence over its next-up episode. Unknown activity dates retain source order after known dates. The non-rotating hero keeps its resume → next-up → newest → pick precedence and is not duplicated in the lane. The lane retains the `cw` identity. Legacy local, server, web and plugin Home settings merge saved Resume/Next up rows at the first watch row's position and preserve that row's view options. Existing serialized types remain readable; new settings offer only Continue watching. @@ -26,7 +32,7 @@ Every navigation entry owns its own viewport even while an outgoing page and inc An unvisited Home row starts at its leftmost card rather than inheriting horizontal alignment from the previous row. Visited rows retain their remembered column and first-visible position across row changes, title Back and root-tab leave/return. Home does not adopt Discover’s tab-leave memory reset; Library keeps standard grid navigation. Outgoing title/person entries cannot intercept Android Back during their exit animation. -Landscape-card bottom gradients extend through the reserved state-line area, ending at the solid bottom backing with no exposed artwork band. The title-gradient top padding is **36 px / 18 dp**, down from 54 px / 27 dp; the approved 92% bottom opacity is unchanged. Captions and state lines retain their existing geometry. +Landscape-card bottom gradients extend through the reserved state-line area, ending at the solid bottom backing with no exposed artwork band, as Justin requested. **Implementation choice for the amount:** title-gradient top padding is **36 px / 18 dp**, down from 54 px / 27 dp. The existing approved 92% bottom opacity is unchanged. Captions and state lines retain their existing geometry. ## Episode artwork and dimensions diff --git a/docker/README.md b/docker/README.md index fb980012..f36b7f64 100644 --- a/docker/README.md +++ b/docker/README.md @@ -233,9 +233,13 @@ The stroke is inset within the fixed 20 dp / 40 px height; horizontal border-box padding includes it. The 6 px dash / 4 px gap follows the browser's 3:2 stroke-width ratios. Resting borders are white at 22% opacity; focused borders use ink at 40%. -The small gear beneath the last avatar opens Settings with Requests focused. +The small gear centred beneath the complete people row opens Settings with Requests focused. Down from any avatar reaches it; Up returns to the originating avatar, including after a Settings visit. Left/Right avatar traversal is unchanged. +The navigation requirement comes from Justin's words: “the settings icon moved, +but pressing down only works from the last profile icon. I want it to always +move there when pressing down in that screen”. The approved gear placement stays +centred beneath the complete people row. Settings groups hand off to existing upstream pages; Requests opens the household Coming-in sheet described below. **Advanced settings → Wolfskin screens** is the persistent rollout switch: turning it off renders upstream Home and library destinations with @@ -251,6 +255,9 @@ pages pop normally. Child dialogs, title menus, request sheets and unsaved-setti prompts keep Back priority. Singleton full-screen pages also exit; they cannot fall through to the older setup/user UI. +Source — Justin: “pressing back enough times should close wolfskin, currently +there is no way to go back to projectivity apart from weird methods via the old ui”. + `rememberWolfskinBackHandler` owns Android Back at the app root, including a single-entry `NavDisplay`; full-screen hand-offs use the same `NavigationManager` exit event rather than exposing the parent setup stack. `WolfskinRootExitTest` @@ -680,9 +687,8 @@ The [revision-5 amendment](../data/tv-spec/spec.md) and [native frame prototype delta](../data/tv-v2/feedback-2026-11-06/index.html) retain the approved frames except for the requested feedback changes: -- Round 2 supersedes revision 5: the picker gear sits under the last avatar, - is reachable with Down from every avatar, and returns Up to its origin. - It retains the picker-person ring. +- The picker gear retains its approved whole-row centring and picker-person ring. + Down from every avatar reaches it; Up returns to the originating avatar. - Shelf tracks share second-last-slot lookahead, count clamping and settled offsets. Home records completed frame positioning rather than an animation that may still be cancelled; first mount restores the intended shelf item. diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WhosWatching.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WhosWatching.kt index b140deb2..18e8c341 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WhosWatching.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WhosWatching.kt @@ -26,7 +26,7 @@ import org.justinvisser.wolfskin.design.WolfskinTokens.Color as C import org.justinvisser.wolfskin.design.WolfskinTokens.Layout as L import org.justinvisser.wolfskin.design.WolfskinTokens.Shell as S -/** The picker covers the nav; Down from any person reaches Settings below the final avatar. */ +/** The picker covers the nav; Down from any person reaches the centred Settings gear. */ @Composable fun WhosWatching(state: ShellState, modifier: Modifier = Modifier) { Box(modifier.fillMaxSize().background(C.bg)) { @@ -53,7 +53,7 @@ private fun PeoplePicker(state: ShellState) { } } } - val left = (S.stageWidth - L.gear + (L.pickerAvatar + L.pickerGap) * (state.people.size - 1)) / 2 + val left = (S.stageWidth - L.gear) / 2 Box(Modifier.offset(left, S.pickerGearTop).size(L.gear).testTag("picker-settings").focusScale(state.gearFocused, WolfskinTokens.Focus.scaleCard) .focusRing(state.gearFocused, L.gear / 2, WolfskinTokens.Focus.ringPickerRing) .clickable { state.gearFocused = true; state.key(ShellKey.Ok) }, contentAlignment = Alignment.Center) { diff --git a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/PickerNavigationTest.kt b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/PickerNavigationTest.kt index 35916d32..61a05809 100644 --- a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/PickerNavigationTest.kt +++ b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/PickerNavigationTest.kt @@ -49,16 +49,17 @@ class PickerNavigationTest { } } - @Test fun settingsGearIsCenteredUnderTheFinalAvatarForAnyProfileCount() { + @Test fun settingsGearIsCenteredUnderTheWholePeopleRowForAnyProfileCount() { val state = ShellState(fixturePeople(), "justin") rule.setContent { WolfskinTheme { ShellSurface(state) } } for (count in 1..5) { val people = (0 until count).map { ShellPerson("person-$it", "Person $it", "$it", false) } rule.runOnIdle { state.updatePeople(people); state.gearFocused = true } rule.mainClock.advanceTimeBy(400) + val first = rule.onNodeWithTag("picker-person:${people.first().key}").fetchSemanticsNode().boundsInRoot val last = rule.onNodeWithTag("picker-person:${people.last().key}").fetchSemanticsNode().boundsInRoot val gear = rule.onNodeWithTag("picker-settings").fetchSemanticsNode().boundsInRoot - assertEquals("Settings must stay under the last profile for $count people", last.center.x, gear.center.x, .1f) + assertEquals("Settings must stay centred under the whole row for $count people", (first.center.x + last.center.x) / 2, gear.center.x, .1f) assertTrue("Settings must be below the avatars", gear.top > last.bottom) } }