Polish Requests transitions and shell hosts #75

Closed
justinzeus wants to merge 0 commits from fm/polish-lane-a into fm/tv-round2
Owner

Requests and shell polish

Implement P06, P07, P09, P10, P12 and the P02/E02 shell host installs. All 58 named tests pass; all before/after native frame sets are published.

  • P06: Requests and its single scrim fade together, with no translation or scale. Closing returns logical focus immediately; the retained panel ignores input until its exit fade completes. Reopening retargets the current fade.
  • P07: The three Requests loading rows breathe as one group on the shared skeleton clock. Close remains steady. Reduced motion disables the pulse.
  • P09: Only a pure Right/Left expansion toggle animates row placement and readiness reveal. Focus changes, window shifts and refreshes retain their existing jumps; reduced motion snaps.
  • P10: Empty Requests says “Nothing coming in.” and “New requests show up here.” and has the existing loading-state Close button. OK and pointer Close use the same close path.
  • P12: Missing or failed posters show a centred 40 px film/TV glyph in dim. Pending poster loads remain blank; successful image handling is unchanged.
  • P02/E02 shell integration: Install the shared recovery-toast and content focus-ring hosts below the nav and overlays. Their implementation and motion proofs belong to the foundation lane.

Approved decisions

  • P09: “The bounded window shrinks from 5 to 4 rows while expanded (ComingInState.windowSize). The row leaving the window disappears in the first frame, as today, instead of sliding off the bottom edge as the composited mock suggests. Changing the approved window semantics is out of scope.”
  • P10: “The mock draws Close as a 150 × 68 px pill filled #464654. That is a drawing approximation of the approved focused secondary pill (76 px PillButton, buttonSecondaryFocused). The build reuses the exact loading-state Close, so the empty and loading panels match.”
  • P10: “The mock puts the new line's baseline 48 px under the first. The nearest existing token gives 36.4 + receiptGap 10 = 46.4 px. No new token is added for 1.6 px.”
  • P12: The approved 40 px glyph uses the foundation's new requests.thumbGlyph token; no new motion token is introduced.

AI assistance: implementation, tests and PR description were prepared with AI assistance. The upstream contribution policy requests a human-authored description; firstmate should arrange that review before merge.

Files

  • wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInSheet.kt: Requests fade, grouped skeleton pulse, readiness placement/reveal, empty Close and missing-poster glyphs.
  • wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ShellState.kt: empty Ready OK closes; distinguish remote expansion toggles from focus/data changes.
  • wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ShellSurface.kt: retain the Requests container through exit; install recovery-toast and focus-ring hosts.
  • wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/RequestsPolishTest.kt: native regressions and frame sequences for P06/P07/P09/P10/P12, including reduced-motion cases.
  • wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/RequestsKeysTest.kt: amend only the approved empty-Ready expectation.

Native evidence

Before/after frame directories: /root/.treehouse/firstmate-7bab20/2/firstmate/data/ui-polish-build/lane-a/{P06,P07,P09,P10,P12}/{before,after}/.

The before sets were captured on foundation 74b737fb52bfbb2cc7afa0b0288ae59e67232320, before the feature changes. P06/P07/P09 include sample CSVs; P10/P12 are settled native frames. Toast/focus-host motion evidence is owned by the foundation lane.

No TV-box, ADB or emulator commands, APK assembly, full-suite run, infrastructure changes or merges were performed. Written design-spec updates are owned by the foundation lane, not duplicated here.

Approved implementation corrections

Firstmate approved explicit P09 placement offsets after the specified conditional animateItem proved unable to animate: all-null animation specifications remove the modifier at rest, so there was no previous placement to animate from. AndroidX also substitutes its default spring when interrupting a non-spring item animation.

Requests now captures current visual row origins when a pure Left/Right toggle arrives, measures the final layout once, and interpolates placement offsets and the readiness clip using one lateLineFade 200 ms / easeOut clock. Per-frame progress reads stay in placement/drawing, with no extra layer or per-frame remeasure; a retarget samples the current visual placement once. Retargeting starts from the current visual offsets. Focus/window/data changes and reduced motion discard those offsets and snap.

The P09 fixed-top assertion allows only the offset induced by the existing centre-origin 1.02 focus scale plus 0.5 px; the scale and settled pixels are unchanged. P12 checks centre-column dim stroke pixels and tolerates native antialiasing between absent/failed film glyph crops. Measured crop difference: 0.01125/255 mean RGB-channel difference, with 100% of channels within 8/255. This compares native states, not a mock raster.

Unaffected settled Requests captures st-082, st-083, st-085, st-086, st-088, st-111 are byte-identical to their baseline PNGs after final verification of the approved placement mechanism.

The P12 before directory also includes the specifically requested historical native reference, tv-fb1-truth/after/requests-history.png, alongside the same-data rebased-base fixture capture. The historical frame predates the current shell copy/layout and is not used for an unrelated full-screen pixel comparison.

Verification commands and progression

Baseline capture:

bash docker/gradle.sh debug :wolfskin-design:testDebugUnitTest --tests org.justinvisser.wolfskin.design.RequestsPolishTest --tests org.justinvisser.wolfskin.design.RequestsCaptureTest

13 tests: 12 passed; P09's capture-only guard initially hit the pre-existing focus-scale bounds difference. Other before strips were captured.

Baseline P09 continuation after correcting that temporary guard:

bash docker/gradle.sh debug :wolfskin-design:testDebugUnitTest --tests org.justinvisser.wolfskin.design.RequestsPolishTest.readinessExpansionPushesRowsBelowWithoutRemeasure

Passed; all P09 before strips captured. The capture-only guard was removed before feature validation.

Full named design/app batch:

bash docker/gradle.sh debug :wolfskin-design:testDebugUnitTest --tests org.justinvisser.wolfskin.design.RequestsPolishTest --tests org.justinvisser.wolfskin.design.RequestsKeysTest --tests org.justinvisser.wolfskin.design.RequestsCaptureTest --tests org.justinvisser.wolfskin.design.ComingInRatingsGateTest --tests org.justinvisser.wolfskin.design.ShellKeysTest --tests org.justinvisser.wolfskin.design.ShellCaptureTest --tests org.justinvisser.wolfskin.design.RecoveryStateTest --tests org.justinvisser.wolfskin.design.RecoveryCaptureTest :wolfskin-design:cacheRobolectricSdk :app:testDefaultDebugUnitTest --tests org.justinvisser.wolfskin.tv.RequestsRefreshTest --tests org.justinvisser.wolfskin.tv.SettingsHandOffCaptureTest

First run: 54/56 design tests passed; P09 visual-bounds and P12 exact-crop assertions failed. Firstmate clarified both assertions without changing settled pixels. Second run: 55/56 passed; P09 reached and exposed the actual placement snap caused by conditional animateItem. App tests were not reached in either fail-fast run. The approved mechanism correction follows those results; no assertion was removed to hide the placement failure.

Approved placement batch used the same full named command with --continue immediately after debug: 55/56 design tests passed. Expansion/collapse, reduced-motion, interrupted retargeting and settled-pixel checks passed. The additional refresh check crossed the existing 350 ms rating dwell on the newly focused request (826 → 850 px); its fixture now settles that independent metadata change before checking refresh snapping. The app compiled, but its test task depends on a successful design test task, so --continue could not bypass that dependency. No app test success is claimed for that run.

Final metadata-isolated batch:

bash docker/gradle.sh debug --continue :wolfskin-design:testDebugUnitTest --tests org.justinvisser.wolfskin.design.RequestsPolishTest --tests org.justinvisser.wolfskin.design.RequestsKeysTest --tests org.justinvisser.wolfskin.design.RequestsCaptureTest --tests org.justinvisser.wolfskin.design.ComingInRatingsGateTest --tests org.justinvisser.wolfskin.design.ShellKeysTest --tests org.justinvisser.wolfskin.design.ShellCaptureTest --tests org.justinvisser.wolfskin.design.RecoveryStateTest --tests org.justinvisser.wolfskin.design.RecoveryCaptureTest :wolfskin-design:cacheRobolectricSdk :app:testDefaultDebugUnitTest --tests org.justinvisser.wolfskin.tv.RequestsRefreshTest --tests org.justinvisser.wolfskin.tv.SettingsHandOffCaptureTest

BUILD SUCCESSFUL: 56/56 design tests and 2/2 app tests, no skips. The same command had one prior 55/56 design run described above; the final run settled independent rating metadata before the pure-toggle/refresh scenarios and kept all movement assertions. checkTokenDrift also passed as part of the build.

Class Passed
RequestsPolishTest 5
RequestsKeysTest 6
RequestsCaptureTest 8
ComingInRatingsGateTest 3
ShellKeysTest 8
ShellCaptureTest 11
RecoveryStateTest 5
RecoveryCaptureTest 10
RequestsRefreshTest 1
SettingsHandOffCaptureTest 1
## Requests and shell polish Implement P06, P07, P09, P10, P12 and the P02/E02 shell host installs. All 58 named tests pass; all before/after native frame sets are published. - P06: Requests and its single scrim fade together, with no translation or scale. Closing returns logical focus immediately; the retained panel ignores input until its exit fade completes. Reopening retargets the current fade. - P07: The three Requests loading rows breathe as one group on the shared skeleton clock. Close remains steady. Reduced motion disables the pulse. - P09: Only a pure Right/Left expansion toggle animates row placement and readiness reveal. Focus changes, window shifts and refreshes retain their existing jumps; reduced motion snaps. - P10: Empty Requests says “Nothing coming in.” and “New requests show up here.” and has the existing loading-state Close button. OK and pointer Close use the same close path. - P12: Missing or failed posters show a centred 40 px film/TV glyph in dim. Pending poster loads remain blank; successful image handling is unchanged. - P02/E02 shell integration: Install the shared recovery-toast and content focus-ring hosts below the nav and overlays. Their implementation and motion proofs belong to the foundation lane. ## Approved decisions - P09: “The bounded window shrinks from 5 to 4 rows while expanded (`ComingInState.windowSize`). The row leaving the window disappears in the first frame, as today, instead of sliding off the bottom edge as the composited mock suggests. Changing the approved window semantics is out of scope.” - P10: “The mock draws Close as a 150 × 68 px pill filled `#464654`. That is a drawing approximation of the approved focused secondary pill (76 px PillButton, `buttonSecondaryFocused`). The build reuses the exact loading-state Close, so the empty and loading panels match.” - P10: “The mock puts the new line's baseline 48 px under the first. The nearest existing token gives 36.4 + `receiptGap` 10 = 46.4 px. No new token is added for 1.6 px.” - P12: The approved 40 px glyph uses the foundation's new `requests.thumbGlyph` token; no new motion token is introduced. AI assistance: implementation, tests and PR description were prepared with AI assistance. The upstream contribution policy requests a human-authored description; firstmate should arrange that review before merge. ## Files - `wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInSheet.kt`: Requests fade, grouped skeleton pulse, readiness placement/reveal, empty Close and missing-poster glyphs. - `wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ShellState.kt`: empty Ready OK closes; distinguish remote expansion toggles from focus/data changes. - `wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ShellSurface.kt`: retain the Requests container through exit; install recovery-toast and focus-ring hosts. - `wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/RequestsPolishTest.kt`: native regressions and frame sequences for P06/P07/P09/P10/P12, including reduced-motion cases. - `wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/RequestsKeysTest.kt`: amend only the approved empty-Ready expectation. ## Native evidence Before/after frame directories: `/root/.treehouse/firstmate-7bab20/2/firstmate/data/ui-polish-build/lane-a/{P06,P07,P09,P10,P12}/{before,after}/`. The before sets were captured on foundation `74b737fb52bfbb2cc7afa0b0288ae59e67232320`, before the feature changes. P06/P07/P09 include sample CSVs; P10/P12 are settled native frames. Toast/focus-host motion evidence is owned by the foundation lane. No TV-box, ADB or emulator commands, APK assembly, full-suite run, infrastructure changes or merges were performed. Written design-spec updates are owned by the foundation lane, not duplicated here. ## Approved implementation corrections Firstmate approved explicit P09 placement offsets after the specified conditional `animateItem` proved unable to animate: [all-null animation specifications remove the modifier at rest](https://github.com/androidx/androidx/blob/androidx-main/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemScopeImpl.kt), so there was no previous placement to animate from. [AndroidX also substitutes its default spring when interrupting a non-spring item animation](https://github.com/androidx/androidx/blob/androidx-main/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemAnimation.kt). Requests now captures current visual row origins when a pure Left/Right toggle arrives, measures the final layout once, and interpolates placement offsets and the readiness clip using one `lateLineFade` 200 ms / `easeOut` clock. Per-frame progress reads stay in placement/drawing, with no extra layer or per-frame remeasure; a retarget samples the current visual placement once. Retargeting starts from the current visual offsets. Focus/window/data changes and reduced motion discard those offsets and snap. The P09 fixed-top assertion allows only the offset induced by the existing centre-origin 1.02 focus scale plus 0.5 px; the scale and settled pixels are unchanged. P12 checks centre-column dim stroke pixels and tolerates native antialiasing between absent/failed film glyph crops. Measured crop difference: 0.01125/255 mean RGB-channel difference, with 100% of channels within 8/255. This compares native states, not a mock raster. Unaffected settled Requests captures `st-082`, `st-083`, `st-085`, `st-086`, `st-088`, `st-111` are byte-identical to their baseline PNGs after final verification of the approved placement mechanism. The P12 before directory also includes the specifically requested historical native reference, `tv-fb1-truth/after/requests-history.png`, alongside the same-data rebased-base fixture capture. The historical frame predates the current shell copy/layout and is not used for an unrelated full-screen pixel comparison. ## Verification commands and progression Baseline capture: ```sh bash docker/gradle.sh debug :wolfskin-design:testDebugUnitTest --tests org.justinvisser.wolfskin.design.RequestsPolishTest --tests org.justinvisser.wolfskin.design.RequestsCaptureTest ``` 13 tests: 12 passed; P09's capture-only guard initially hit the pre-existing focus-scale bounds difference. Other before strips were captured. Baseline P09 continuation after correcting that temporary guard: ```sh bash docker/gradle.sh debug :wolfskin-design:testDebugUnitTest --tests org.justinvisser.wolfskin.design.RequestsPolishTest.readinessExpansionPushesRowsBelowWithoutRemeasure ``` Passed; all P09 before strips captured. The capture-only guard was removed before feature validation. Full named design/app batch: ```sh bash docker/gradle.sh debug :wolfskin-design:testDebugUnitTest --tests org.justinvisser.wolfskin.design.RequestsPolishTest --tests org.justinvisser.wolfskin.design.RequestsKeysTest --tests org.justinvisser.wolfskin.design.RequestsCaptureTest --tests org.justinvisser.wolfskin.design.ComingInRatingsGateTest --tests org.justinvisser.wolfskin.design.ShellKeysTest --tests org.justinvisser.wolfskin.design.ShellCaptureTest --tests org.justinvisser.wolfskin.design.RecoveryStateTest --tests org.justinvisser.wolfskin.design.RecoveryCaptureTest :wolfskin-design:cacheRobolectricSdk :app:testDefaultDebugUnitTest --tests org.justinvisser.wolfskin.tv.RequestsRefreshTest --tests org.justinvisser.wolfskin.tv.SettingsHandOffCaptureTest ``` First run: 54/56 design tests passed; P09 visual-bounds and P12 exact-crop assertions failed. Firstmate clarified both assertions without changing settled pixels. Second run: 55/56 passed; P09 reached and exposed the actual placement snap caused by conditional `animateItem`. App tests were not reached in either fail-fast run. The approved mechanism correction follows those results; no assertion was removed to hide the placement failure. Approved placement batch used the same full named command with `--continue` immediately after `debug`: 55/56 design tests passed. Expansion/collapse, reduced-motion, interrupted retargeting and settled-pixel checks passed. The additional refresh check crossed the existing 350 ms rating dwell on the newly focused request (826 → 850 px); its fixture now settles that independent metadata change before checking refresh snapping. The app compiled, but its test task depends on a successful design test task, so `--continue` could not bypass that dependency. No app test success is claimed for that run. Final metadata-isolated batch: ```sh bash docker/gradle.sh debug --continue :wolfskin-design:testDebugUnitTest --tests org.justinvisser.wolfskin.design.RequestsPolishTest --tests org.justinvisser.wolfskin.design.RequestsKeysTest --tests org.justinvisser.wolfskin.design.RequestsCaptureTest --tests org.justinvisser.wolfskin.design.ComingInRatingsGateTest --tests org.justinvisser.wolfskin.design.ShellKeysTest --tests org.justinvisser.wolfskin.design.ShellCaptureTest --tests org.justinvisser.wolfskin.design.RecoveryStateTest --tests org.justinvisser.wolfskin.design.RecoveryCaptureTest :wolfskin-design:cacheRobolectricSdk :app:testDefaultDebugUnitTest --tests org.justinvisser.wolfskin.tv.RequestsRefreshTest --tests org.justinvisser.wolfskin.tv.SettingsHandOffCaptureTest ``` **BUILD SUCCESSFUL: 56/56 design tests and 2/2 app tests, no skips.** The same command had one prior 55/56 design run described above; the final run settled independent rating metadata before the pure-toggle/refresh scenarios and kept all movement assertions. `checkTokenDrift` also passed as part of the build. | Class | Passed | |---|---:| | `RequestsPolishTest` | 5 | | `RequestsKeysTest` | 6 | | `RequestsCaptureTest` | 8 | | `ComingInRatingsGateTest` | 3 | | `ShellKeysTest` | 8 | | `ShellCaptureTest` | 11 | | `RecoveryStateTest` | 5 | | `RecoveryCaptureTest` | 10 | | `RequestsRefreshTest` | 1 | | `SettingsHandOffCaptureTest` | 1 |
justinzeus added 2 commits 2026-11-08 01:56:14 +01:00
Author
Owner

Landed through the round 2 candidate #82 (merged into fm/tv-round2 as c21d17df). Closing as superseded; nothing from this branch is lost.

Landed through the round 2 candidate https://git.justinvisser.org/justinzeus/wolfskin/pulls/82 (merged into fm/tv-round2 as c21d17df). Closing as superseded; nothing from this branch is lost.
justinzeus closed this pull request 2026-11-08 13:59:33 +01:00

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
exploring
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: justinzeus/wolfskin#75
No description provided.