Implement approved TV motion and shaped loading states #51
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fm/tv-motion-build"
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
Implement the approved direction A, “As recommended”: shaped breathing placeholders, network-only art reveal, reserved late-line geometry, bounded hero hand-offs and reduced motion. Existing navigation remains authoritative; pending Discover lanes are skipped rather than becoming empty focus targets.
AI-assisted (fork-internal): implementation, regression coverage and this review description were produced with GPT.
Reviewed motion follow-up —
fdff85c8Current pushed head:
fdff85c8a11b3f5df1d4536d69afd3c4e351c683, one follow-up commit on the existing branch; based on the reviewed16684454integration head.lateLineFade. No slot or layout changes. Rating plates retain their existing fade, without nesting another one.make test, noapk-debug, no new gallery capture round. Both production modules compiled through the focused gate. APK/checksum and gallery evidence below are explicitly earlier-head history, not rebuilt/captured evidence for this follow-up. No device access or performance measurement.rowRevealreplays after lazy disposal/recomposition. Its ordinaryremember(identity, enabled)loses theAnimatablewhen disposed; changing a key cannot retain that state. Left unchanged because it requires saved/hoisted state rather than a one-line key fix. Per-card staggering and other review notes remain outside this follow-up.Earlier rebased-head verification —
16684454(historical)At integration: mergeable. Rebased onto
origin/mainatfd48426fa7fa0982a67e7d788b394f871bd3f77f. That earlier head was166844541821b155673b8d3a4e06af7b76f2c231. No merge was performed.tokens.sha256; Kotlin, CSS and drift hashes were regenerated from merged JSON, never hand-edited. Removed stale “crossfade off” token metadata.MotionSurfaceTest,HeroMotionTest,NetworkArtTransitionTest,HomeRowsTest,WolfskinImagesTest,FocusRingTest,TitleAsyncFocusTest,DiscoverLoadingFocusTest,WolfskinArtworkTest,DiscoverSkeletonKeysTest,LibraryReadFacadeTestandImageUrlServiceTest.make apk-debugpassed, including branding checks for all four debug APKs (1016 resources each).Wholphin-default-debug-1.1.0-151-g16684454-64-armeabi-v7a.apk; SHA-256836ede23855d99b34a0bc3e7b69a3b4041f28ecf86a3d8a028495201b9f571c6.make testand no new native gallery capture round on the rebased head. Conflict resolution did not change motion/layout visuals; the gallery below remains explicitly pre-rebase capture evidence. Initial full-suite results and known picker timeouts are retained below as history, not a new rebased-head failure claim.Change
tokens.jsonand the generator; generated Kotlin/CSS and drift hashes are committed. Remove the superseded 140 ms focus token.surface2fill, no loading sentences, no shimmer or per-card pulse clocks. Row entry is 180 ms / 24 px; focus is 180 ms ease-out; captions rise 10 px after 60 ms.NetworkArtTransition: network success fades for 160 ms; memory/disk/local results and errors do not fade.WolfskinImages.ktchanges only the transition import/builder hook—no URL logic, DNS or read-facade edits.MotionDurationScale == 0orLocalReducedMotiondisables pulse/translation/scroll animation; permitted fades cap at 80 ms.data/tv-v2/motion-2026-11-06/.Native proof
Native before/after gallery — 24 PNGs, all verified 1920×1080, plus layout JSON and checksums. Published files are mode 660, with the existing pages read ACL. Ten skeleton states are embedded below; the gallery also includes early/late series frames.
Late-series geometry
The same native capture harness fails on the original surface and passes on the changed surface after a readiness response 3400 ms later:
Original coordinates · Changed coordinates
Animated-layer budget
Code-level active animation owners, not a measured allocation trace:
One shared pulse clock drives these layers; it adds no compositing layer. Late title metadata adds at most 4 bounded alpha layers (title ratings, play-target ratings, genres, overview), never another nested rating-plate fade. A hero change adds at most 2 bounded owners (outgoing art and copy); focus changes can animate 2 card transforms, 1 caption and up to 2 rating plates. Image fades are painter draws without added compositing layers. No animated owner is full-screen. Hero blend bounds are 1920×820 on Home and 1920×740 on Discover. A native boundary regression checks both layer bounds and pixels: a 200 dp / 400 px test region ends at y=400, and the lower backdrop is already the incoming image. The earlier failed probe was a manually-clocked test holding the initial composition; explicit snapshot delivery fixed the harness, without weakening pixel assertions.
Initial verification — pre-rebase head
d525f1aemake test, on initial pre-rebase commitd525f1ae: design 198/198 passed; app 706 tests, 111 failures. Of those, 109 could not start because this isolated worktree lacked the declared offline Android 28/30 Robolectric jars (60 / 49 cases). The cache-population task resolved only the newer SDK; the two immutable jars were copied from the existing shared artifact cache into this worktree's ignored build directory. No source or shared-cache changes.PersonPickerSessionTest.fastLeftLeftOkKeepsPickerClosedPersonPickerSessionTest.avatarRightThenOneOkKeepsPickerClosedNeither name is new relative to the launch brief's six-name prior-review baseline. Picker-specific code is untouched; these failures were not hidden or repinned.
make apk-debug: passed, producing four debug APKs; branding inspection passed for 1016 resources per APK. The armv7 debug APK SHA-256 is86d4cc5b6ab92f6f411a0e732a12a399cfcc4a356d509da95c73ff41be611f41. No optimized APK was built or installed.Machine-readable verification · Exact original full-gate failure names
All 109 original missing-SDK setup failures — every case passed after cache repair
com.github.damontecres.wholphin.ui.playback.PlaybackViewModelTestsDirect play includes the opened live stream IDPlayback ends with no next upShow next up never without automatic playplayItemInPlaylistShow next up never with automatic playMedia segment not at current positionMedia segment ask to skipPlay previousMedia segment ignoreMedia segment ask to skip and clickedShow next up at end of playbackPlay two intros firstMedia segment ask to skip and dismissedPlay next upMedia segments auto skipPlay intro firstDon't play introIntro playback ends always play nextcom.github.damontecres.wholphin.test.TestVoiceInputManagerinitial state is Idlecallbacks from previous recognizer are ignored after cleanuponPartialResults updates partialResultaudio focus is abandoned when recognizer is destroyedonResults resets soundLevel to zeroonReadyForSpeech transitions state to ListeningonRmsChanged clamps low values to 0acknowledge works from Error stateonResults transitions to Result state with correct textstartListening fails when audio focus not grantedstartListening is ignored when already listeningonError maps ERROR_SERVER to correct resourceinitial partialResult is emptyonError maps ERROR_RECOGNIZER_BUSY to correct resource after retry exhaustedaudio focus error is retryablestartListening calls recognizer startListeningstartListening does not create recognizer when no networkacknowledge resets state to IdlestartListening creates SpeechRecognizeronRmsChanged updates soundLevel with normalized valuecleanup resets state to IdlestartListening sets recognition listenerstartListening fails immediately when no network capabilitiesstartListening requests audio focusinitial soundLevel is zeroonError maps ERROR_AUDIO to correct resourcecleanup calls destroy on recognizerstartListening resets partialResultstartListening transitions state to StartingonRmsChanged clamps high values to 1network error from fast-fail is retryableonError resets soundLevel to zeroonError maps ERROR_SPEECH_TIMEOUT to correct resourceonPermissionDenied sets correct error resourcestartListening fails immediately when no internet capabilityonEndOfSpeech transitions to Processing stateonError maps ERROR_NETWORK_TIMEOUT to correct resourceonError transitions to Error stateonError maps ERROR_NETWORK to correct resourcestartListening does not create recognizer when audio focus deniedcleanup resets soundLevel to zerocleanup calls cancel on recognizerisAvailable returns mocked valueonError maps ERROR_CLIENT to correct resourceonError maps ERROR_NO_MATCH to correct resourcestartListening is ignored when in Starting stateonError maps ERROR_INSUFFICIENT_PERMISSIONS to correct resourcestartListening fails immediately when no networkonPermissionGranted calls startListeningcleanup resets partialResult to emptystopListening triggers cleanuponError maps unknown error to unknown resourceonError ERROR_SPEECH_TIMEOUT is retryableonPartialResults ignores blank textonResults with null bundle transitions to Error stateonResults with blank text transitions to Error stateonPermissionDenied transitions to Error stateonResults with empty text transitions to Error statecom.github.damontecres.wholphin.util.profile.TestDeviceProfileUtilsTest force enable overrides AV1 Profile 5 device support checkTest force disable overrides AV1 Profile 5 device support checkTest force enable overrides HEVC Profile 7 device support checkTest AV1 Profile 5 unsupported when device does not support itTest force disable overrides HEVC Profile 7 device support checkTest HEVC Profile 7 unsupported when device does not support itcom.github.damontecres.wholphin.test.TestVoiceInputManagerAutoFocuspermanent audio focus loss stops listeningtransient audio focus loss is ignoredcom.github.damontecres.wholphin.ui.playback.PlayExternalViewModelTestExplicit VLC starts from beginning at zero positionImplicit player preserves positive resume positionPlayback list starts from beginningUnavailable selected player falls back to implicit start from beginningVLC starts from beginning without subtitles or durationExplicit VLC preserves positive resume positionImplicit player starts VLC from beginning at zero positionExisting player extras and playback-start reporting remain unchangedcom.github.damontecres.wholphin.services.IntentServiceTestTest parseIntent with view, itemId, and switch userTest auto sign in, cold load, with protected profileTest parseIntent with view and switch userTest auto sign disabled in with unprotected profileTest parseIntent with play itemId and paramsTest auto sign in, hot load, with protected profileTest parseIntent with searchTest parseIntent with play but no itemIdTest user specified with unprotected profileTest parseIntent with view and no itemIdTest user specified does not existTest parseIntent with play itemIdTest auto sign in, no current userTest parseIntent with search queryTest auto sign in with unprotected profileTest user specified with protected profileTest parseIntent with view itemOn-device review checklist
Scope and limits
No ADB, device access, installs, optimized APK, service restart or server configuration change. Native JVM rendering proves layout/focus/alpha behavior, not Xiaomi GPU frame timing. Published proof is behind the existing pages sign-in. No merge is performed by this lane.
d525f1aed3to1668445418Code review (Claude): PASS, no blocking finding.
Reviewed head:
166844541821b155673b8d3a4e06af7b76f2c231(rebased onto mainfd48426f, mergeable). Read-only review: no Gradle/make run.What I read: the full non-asset diff (40 source/token files), DiscoverState key path, hero art/copy hand-off, both Coil loaders, post-rebase
WolfskinImages.kt/WolfskinTokens.kt(PR 49/50 content intact; only the transition hook and the Motion token group changed), and all 4 new test files (no existing test touched or weakened).generate_tokens.py --checkpasses; 47/47 prototype checksums OK.Checked and fine: pending Discover lanes are skipped in both directions and Down/Up/OK/Back can't land on or be swallowed by a pending lane; Discover loading restores nav focus; hero buttons use the latest selection, fast changes retain the latest target; hero art releases on success, error, no-art fallback and local bitmap; only network successes fade (cache/disk/local/errors do not); pulse/translation off and fades capped at 80 ms under both reduced-motion controls.
Non-blocking notes (full text in the review report):
onSuccess(stale outgoing layer kept; visually harmless). Suggest a ~1.5 s release cap.rowRevealreplays whenever a lazy row is composed anew; watch the on-box Home scroll measurement.NetworkArtTransition.reducedis a global mirrored from the theme (stock flavour never sets it).d525f1aeand "non-mergeable"; gate results are from the pre-rebase commit.Design review (Claude, design lane): PASS, no blocking finding
Reviewed head:
166844541821b155673b8d3a4e06af7b76f2c231(basefd48426fa7fa0982a67e7d788b394f871bd3f77f). Read-only review; no build run.Verdict. No visible unapproved deviation that blocks, no regression of a locked screen. Every motion number in the PR equals direction A on the motion page (pulse 1400 ms 0.55 to 1 to 0.55 with ease-in-out, 160 ms network-only art reveal, 180 ms ease-out focus, caption 60 ms delay + 180 ms in / 100 ms out with 10 px rise, row reveal 180 ms with 24 px, copy entry 16 px, hero backdrop 320 ms ease-in-out, copy out 120 ms / in at 140 ms for 200 ms with 14 px, late line 200 ms alpha only, 56 px reserved slot, reduced motion fades capped at 80 ms). Focus ring code is unchanged apart from its timing. Token drift check (
generate_tokens.py --check) passes at this SHA.Skeleton shapes. Poster 256x384 / pitch 296, tile 400x225 / pitch 440, cast circle 200 / pitch 244, hero blocks 640x84, 460x34, 260x76, title header: all match the locked captures and the prototype geometry; Library row positions are identical before and after. Fill is
surface2; sampled native pixels are (26,26,36) at peak and (18,18,24) at trough, which is surface2 at 1.0 and 0.55 over the near-black background. No loading sentence, spinner or shimmer anywhere. Discover (hero + lane) and Person have new silhouettes; the prototype has no frames for them.Late lines. Published layout JSON: overview/actions move 55/56 px before and 0 px after. The prototype's early and late series frames show the same reserved slot.
Evidence (on the firstmate host):
/root/.treehouse/firstmate-7bab20/2/firstmate/data/review-51-design/evidence/holds ten side-by-side images (prototype or locked capture on the left, this PR's native after on the right, 01 to 11), the before/after pixel diffs, and the harness used to freeze the prototype's own animation clock. Full report:/root/.treehouse/firstmate-7bab20/2/firstmate/data/review-51-design/report.md. The prototype was rendered at 1920x1080 in headless Chromium (chrome-headless-shell, fresh profile,--no-sandbox; full--headless=newhung on this box). The prototype uses its own fixtures, so I compared geometry, fill, phase and layout rather than content.Non-blocking notes (details and fixes in the report):
d525f1ae(before the rebase); the rebase only brought main's image-size changes into the design module. Nav band differs (gradient before, solid after) on scrolled captures, which looks like a harness scroll-state difference sinceShellSurface.kt:95is unchanged.rowRevealis keyed on composition, so lazy Library/Home rows that are recomposed may replay the 180 ms rise on scroll; not observable in stills. Worth checking in the on-device recording.Commit status
review/design-claudeset to success on this SHA.Delta review of the follow-up commit
fdff85c8a1on top of16684454(code PASS and design PASS were posted on16684454; both statuses are carried to this head). Read the full delta (7 files, +149/-15): late ratings, genres and overview now use the same lateLineFade as the readiness row (slot already reserved, no geometry change); the caption rise is driven by an entry-progress state so the 100 ms exit is opacity only; one shared skeleton pulse clock per screen (SkeletonPulseScope inside WolfskinTheme, subscribers counted, the infinite transition runs only while a shelf is loading and reduced motion keeps it idle) so all loading shelves breathe in phase, same 1400 ms 0.55-1-0.55. All skeletonPulse call sites sit in design composables rendered under WolfskinTheme. 35 focused cases and token drift pass on the lane. No new blocking finding.NOT merged: the prototype card-by-card stagger difference is a held captain call (Justin decides on the TV during the morning walk); the PR merges after his word. Known: rowReveal replays when a lazy row is disposed and recomposed (not a one-line fix; check on the box).