Implement option B card metadata (stacked on https://git.justinvisser.org/justinzeus/wolfskin/pulls/51) #54

Merged
justinzeus merged 3 commits from fm/tv-card-meta-build into main 2026-11-07 16:01:07 +01:00
Owner

Outcome and stack

Implement revision-6 option B: compact card metadata, always-visible library captions and one opaque bottom state line. AI-assisted (fork-internal; not an upstream contribution).

Stacked on the TV loading/motion change: #51 . Base is fm/tv-motion-build, including motion follow-up fdff85c8. This must merge after that motion change. Firstmate will retarget this PR to main after the motion change merges; this PR must not merge first.

Review-ready, not merge-approved. The reported caption-relaunch and painted-skeleton-caption blockers are addressed by the scoped follow-up below. Design review classified the six retained visual differences as renderer/fixture artifacts, not approval blockers. Native A–Z sorting, existing screen chrome, platform typography and the previously approved Discover heading band were not changed to imitate stale fixture rendering.

Approved decision and bottom-pixel correction

Justin approved B, not A: watched is a 3 px accent line; resume remains 6 px proportional accent over the full-width color.progressTrack track.

His additional note, verbatim:

I can also see come of the card image below the examples. the lowest line of pixels in the card need to be color pixels, not the image leaking through

TitleCard now clips artwork out of the backing band before painting the opaque color.bg backing. Merely overdrawing after rounded clipping left poster colour in antialiased corners; the native regression caught that. The line has zero left/right inset, 2 px bottom backing and a 2 px separator above its fill, inside the 16 px art radius. The separate 4 px focus ring remains above it and scales with the card at 1.08. The track is precomposited/quantised once from the canonical tokens to preserve the approved #3d3d3f pixels instead of Skia alpha-rounding differences.

Some approved frames themselves leak artwork on their lowest row. Original Annie Hall rows contain white poster pixels; the focused Locke treatment row also varies with its artwork. The native build intentionally makes those rows solid. Original-frame samples: https://pages.justinvisser.org/wolfskin/tv-card-meta-build/approved-bottom-row.json .

Behaviour

  • Films: year + duration + 24 px watched check. Episodes: their own duration + check. Series: year + check, never a single series runtime. Unwatched/resuming cards have no watched check.
  • Movies, Shows, Anime, Home See all and person filmography always show a single-line, card-width ellipsised title plus compact subtitle. No 640 px caption extends into a neighbouring column. Compact metadata omits type/season/role/remaining-time prose; metadata gaps tighten before any duration or check can clip.
  • Home poster captions stay focus-only. Home landscape titles stay on the art; focused metadata sits below. Grid captions allocate no animation owner/compositing layer. Focus-only captions retain the motion follow-up’s opacity-only exit.
  • Unwatched/Discover: no state line, but an opaque lowest backing row. Watched: full-width 3 px accent. Resume: proportional 6 px accent on a full-width track. The old separate inset resume bars are removed.
  • Loading and ready rows reserve the same heights: a blank 96 px poster caption band, or blank 64 px below landscapes. Loading paints only art-sized blocks, never title/subtitle pills; shelf pulse ownership remains shared. Person-filmography ownership badges remain independent of watch-state rendering.
  • Token changes are generated from JSON; obsolete caption-width/progress-inset tokens and prose-caption paths are removed. The written TV spec and build/development notes are updated.

Same-data native evidence

Gallery: https://pages.justinvisser.org/wolfskin/tv-card-meta-build/ . Each pair retains the full native 1920×1080 image and a 3840×1080 side-by-side against the frozen revision-6 renderer replayed with treatment=B.

Surface Native/reference variants
Movies Watched, white watched, resume, unwatched, long title
Shows Watched, resume, unwatched
Home wide Resume, watched, unwatched
Home poster Watched, resume, long title
Discover No watch-state line/check
Treatments White/dark posters and landscape crops
Narrow stress 210 px poster / 327 px landscape, long titles and 2h 56m / 3h 12m durations
Pinned library Two complete art rows

Movies/Shows/pinned captures render LibraryScreen; Home and Discover render their production shelf components beneath the production shell at the reference’s pinned anchor. Additional narrow/treatment widths exercise the shared card/caption/rating components. The source fixture records, capture harness and native text measurements are retained in the gallery; the throwaway capture harness/art copies are removed from the app worktree. All watch states are fixed illustrative inputs, not mutations of household data.

Pixel proof

CardBottomPixelTest renders white Annie Hall, dark Locke and two real colourful posters across three states, all-rest and individual 1.08 focus: 15 captures / 60 full-width bottom-row comparisons. Every bottom-row pixel, including rounded-corner coverage, must equal an art-free backing/ring reference. Straight-edge pixels and fill/track samples additionally assert exact state colours; no tolerance or different-data screenshot substitutes for that proof.

Real poster Rest bottom Focused straight bottom Resume track Accent fill
Annie Hall #07070a #cc9fff #3d3d3f #cc9fff
Locke #07070a #cc9fff #3d3d3f #cc9fff
Apocalypse Now #07070a #cc9fff #3d3d3f #cc9fff
Avatar Aang #07070a #cc9fff #3d3d3f #cc9fff

Raw samples: https://pages.justinvisser.org/wolfskin/tv-card-meta-build/pixel-proof.json . CardGeometryTest asserts actual native grid/shelf pitches 524 / 632 / 522 / 441 px, initial library rows 372 / 896, pinned rows 168 / 692 ending at 1076, ring clearances 20.48 / 26.48, 24×24 checks and 210/327 px fitting. Both loading/ready shelf heights match. It also covers always-visible unfocused filmography, no aggregate series runtime and no watched check on a resuming item.

Retained renderer/fixture artifacts — disclosed, not approval blockers

Design review traced these differences to unchanged native behaviour or reference-renderer fixtures. None authorizes a new design deviation; unapproved design changes still block merge.

  1. Movies order / long-title viewport: the frame hand-places Steins;Gate sixth under an A–Z label. Existing native sorting puts it twelfth, changing the focused viewport. Fixture records/watch states are identical; sorting code is unchanged. Exact orders: https://pages.justinvisser.org/wolfskin/tv-card-meta-build/fixture-order-evidence.json .
  2. Discover spacing: the frame uses a stale 84 px heading band. Native retains the approved 104 px band from the later heading-gap feedback: first art is +20 px and second-row art +40 px against that frame. No Discover-spacing change ships here.
  3. Existing chrome: navigation placement/selection widths, avatar/header baselines, toolbar glyphs/chip widths and native “12 films”/“12 series” versus reference “12 titles” follow the unchanged shipped TV shell, not the simplified fixture chrome.
  4. Typography / plates: platform Roboto advances differ slightly (year 52 px vs 53.953125; “2h 56m” 79 px vs 80.68359375), affecting ellipsis/check placement. The reference forcibly sets rating plates to 24 px; the unchanged native adaptive plate fits this two-rating fixture at 28 px. Caption size/line boxes remain 28/32 and 24/26 px. No four-rating native capture is claimed.
  5. Rasterisation: Android/Skia glyphs, artwork resampling, rounded coverage and focus shadows differ from Chrome. Native focused Home/Discover titles are visibly brighter in the comparison; design review attributes this to reference shadow compositing [INFERENCE], not a changed colour token. Numeric geometry and state colours have separate native assertions.
  6. Home wide/tile on-art title is 6 px lower: the reference inherits a 42 px line box from line-height:1.5; native follows the written 32 px title line with the same 28 px bottom inset. Design review classified this visible offset as a renderer artifact, consistent with the locked native base.

The original 18-pair gallery remains frozen at fe44a3c2; its original blocker wording predates this review classification. Existing native/reference frames are not regenerated by the follow-up.

The solid lowest row is an explicitly requested correction, not a hidden deviation. In the treatment replay, the selected B root class applies B to both halves while the historical A/B explanatory labels are retained; only B ships.

Scoped review follow-up

Follow-up head: c2f7ccc438a623ef2bff9b858ea6591a675ab481 — one combined review-fix commit on the same stacked branch.

  • Key the focus-only caption effect to its remembered animation object, so replacing a title while its shelf stays focused restarts visibility. Two permanent native glyph-pixel regressions cover direct caption replacement and moving across a focused Home shelf.
  • Paint nothing in reserved loading caption bands. Poster/wide/tile heights and the shared pulse remain unchanged; a native regression asserts every reserved-band pixel equals the background.
  • Tighten measured pitch, pinned-row and ring-clearance tolerances to 0.1 px.
  • Remove unused wide/tile caption-band token fields and regenerate the Kotlin/hash outputs; landscape bands use the existing canonical metadata token.
  • Normalize completed Home progress like Library/Title: >= 1 is not resuming. A real-Annie-Hall pixel regression compares Home/library strips at 50%, 100% and 150%, including the 6 px → 3 px transition.
  • Combined focused gate: 20 passed, 0 failures/errors/skips, covering card pixels, caption motion, geometry, library spacing, Home viewport, motion surfaces and focus ring. Token drift passed. No full make test or make apk-debug rerun was performed, per steering.
  • One initial targeted run had a new test-harness failure, CardBottomPixelTest.completedHomeProgressUsesTheSameWatchedStripAsLibraryCards: it compared 10 px even when the watched backing occupies only 7 px, including 3 px of artwork. The regression now compares the entire actual backing (10 px resume / 7 px watched), with exact state-colour assertions; the final 20-case gate passes.

Follow-up native proof: focused Home title replacement, blank loading bands, completed Home/library strip parity, focused test counts.

Original full-suite / APK verification

Original full-suite/APK head: fe44a3c24f5cdd6e857f43445ca3309466201f83. Final fetch/rebase confirmed the stacked branch was current before those gates. The scoped follow-up above was validated separately; the existing APK is not a build of the follow-up head.

  • generate_tokens.py --check: passed.
  • Focused permanent card/caption/grid/filmography/focus/motion tests: 16 passed after proof-only sources/art were removed.
  • Same-data native capture pass: 18 capture cases, plus the pixel/geometry regressions, 23 passed.
  • Exactly one original-head make test and one original-head make apk-debug were run. Gradle used the repository’s serialized wrapper; no optimized build was run.
Original make test component Cases Failures Result
App 715 5 Known profile-picker timing family only
Design module 208 0 Passed, including new pixel/geometry regressions
Client unit results 26 0 recorded UP-TO-DATE; unit cases not re-executed; DNS/cache smoke did run

make test exits nonzero because of these known order-sensitive PersonPickerSessionTest timeouts, not a green full-suite claim. Firstmate’s steering identifies this family as fixed by the test-only sync change on main, absent from the stacked motion base, and explicitly says not to chase it here. Every observed failing method is listed:

Failing method in org.justinvisser.wolfskin.tv.PersonPickerSessionTest Classification
launchRightThenOneOkKeepsPickerClosed Known order-sensitive picker timeout; fixed on main
fastLeftLeftOkKeepsPickerClosed Known order-sensitive picker timeout; fixed on main
avatarRightThenOneOkKeepsPickerClosed Known order-sensitive picker timeout; fixed on main
launchLeftThenOneOkKeepsPickerClosed Known order-sensitive picker timeout; fixed on main
avatarLeftThenOneOkKeepsPickerClosed Known order-sensitive picker timeout; fixed on main

No other failing tests were observed in that original full-suite run. Machine-readable results/classification: https://pages.justinvisser.org/wolfskin/tv-card-meta-build/full-test-failures.json .

make apk-debug: passed (defaultDebug, application ID org.justinvisser.wolfskin.tv.debug), producing the universal APK and armv7/arm64/x86_64 splits. Universal artifact: Wholphin-default-debug-1.1.0-153-gfe44a3c2-65.apk; SHA-256 6b5d41da28dca13ac0c5dd42591c99773a68c20c10aa656a34047514a067c67f. Build-only proof; no installation or playback claim.

On-device checklist — not run; TV stays off tonight

  • Confirm unchanged grid-pitch scroll feel through Movies, Shows, Anime, See all and person filmography.
  • Inspect the bottom row on white, near-black and colourful real art at rest and focus; no poster pixels beneath the strip.
  • Confirm the focus ring stays independently above the strip, including scaled corners.
  • Confirm focused Home landscape metadata and caption motion on the real box.

No ADB, installation, TV actions, optimized APK or on-device GPU timing measurement was performed.

## Outcome and stack Implement revision-6 **option B**: compact card metadata, always-visible library captions and one opaque bottom state line. **AI-assisted (fork-internal; not an upstream contribution).** Stacked on the TV loading/motion change: https://git.justinvisser.org/justinzeus/wolfskin/pulls/51 . Base is `fm/tv-motion-build`, including motion follow-up `fdff85c8`. **This must merge after that motion change.** Firstmate will retarget this PR to `main` after the motion change merges; this PR must not merge first. **Review-ready, not merge-approved.** The reported caption-relaunch and painted-skeleton-caption blockers are addressed by the scoped follow-up below. Design review classified the six retained visual differences as renderer/fixture artifacts, not approval blockers. Native A–Z sorting, existing screen chrome, platform typography and the previously approved Discover heading band were not changed to imitate stale fixture rendering. ## Approved decision and bottom-pixel correction Justin approved **B**, not A: watched is a **3 px accent** line; resume remains **6 px proportional accent** over the full-width `color.progressTrack` track. His additional note, verbatim: > I can also see come of the card image below the examples. the lowest line of pixels in the card need to be color pixels, not the image leaking through `TitleCard` now clips artwork out of the backing band before painting the opaque `color.bg` backing. Merely overdrawing after rounded clipping left poster colour in antialiased corners; the native regression caught that. The line has zero left/right inset, 2 px bottom backing and a 2 px separator above its fill, inside the 16 px art radius. The separate 4 px focus ring remains above it and scales with the card at 1.08. The track is precomposited/quantised once from the canonical tokens to preserve the approved `#3d3d3f` pixels instead of Skia alpha-rounding differences. **Some approved frames themselves leak artwork on their lowest row.** Original Annie Hall rows contain white poster pixels; the focused Locke treatment row also varies with its artwork. The native build intentionally makes those rows solid. Original-frame samples: https://pages.justinvisser.org/wolfskin/tv-card-meta-build/approved-bottom-row.json . ## Behaviour - Films: year + duration + 24 px watched check. Episodes: their own duration + check. Series: year + check, never a single series runtime. Unwatched/resuming cards have no watched check. - Movies, Shows, Anime, Home See all and person filmography always show a single-line, card-width ellipsised title plus compact subtitle. No 640 px caption extends into a neighbouring column. Compact metadata omits type/season/role/remaining-time prose; metadata gaps tighten before any duration or check can clip. - Home poster captions stay focus-only. Home landscape titles stay on the art; focused metadata sits below. Grid captions allocate no animation owner/compositing layer. Focus-only captions retain the motion follow-up’s opacity-only exit. - Unwatched/Discover: no state line, but an opaque lowest backing row. Watched: full-width 3 px accent. Resume: proportional 6 px accent on a full-width track. The old separate inset resume bars are removed. - Loading and ready rows reserve the same heights: a blank 96 px poster caption band, or blank 64 px below landscapes. Loading paints only art-sized blocks, never title/subtitle pills; shelf pulse ownership remains shared. Person-filmography ownership badges remain independent of watch-state rendering. - Token changes are generated from JSON; obsolete caption-width/progress-inset tokens and prose-caption paths are removed. The written TV spec and build/development notes are updated. ## Same-data native evidence Gallery: https://pages.justinvisser.org/wolfskin/tv-card-meta-build/ . Each pair retains the full native **1920×1080** image and a **3840×1080** side-by-side against the frozen revision-6 renderer replayed with `treatment=B`. | Surface | Native/reference variants | |---|---| | Movies | [Watched](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#movies-watched), [white watched](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#movies-white-watched), [resume](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#movies-progress), [unwatched](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#movies-unwatched), [long title](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#movies-long-title) | | Shows | [Watched](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#shows-watched), [resume](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#shows-progress), [unwatched](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#shows-unwatched) | | Home wide | [Resume](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#home-wide-progress), [watched](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#home-wide-watched), [unwatched](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#home-wide-unwatched) | | Home poster | [Watched](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#home-poster-watched), [resume](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#home-poster-progress), [long title](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#home-poster-long-title) | | Discover | [No watch-state line/check](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#discover-no-watch-state) | | Treatments | [White/dark posters and landscape crops](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#treatments) | | Narrow stress | [210 px poster / 327 px landscape, long titles and 2h 56m / 3h 12m durations](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#narrow-duration-stress) | | Pinned library | [Two complete art rows](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/#movies-pinned) | Movies/Shows/pinned captures render `LibraryScreen`; Home and Discover render their production shelf components beneath the production shell at the reference’s pinned anchor. Additional narrow/treatment widths exercise the shared card/caption/rating components. The source fixture records, capture harness and native text measurements are retained in the gallery; the throwaway capture harness/art copies are removed from the app worktree. All watch states are fixed illustrative inputs, not mutations of household data. ### Pixel proof `CardBottomPixelTest` renders white Annie Hall, dark Locke and two real colourful posters across three states, all-rest and individual 1.08 focus: **15 captures / 60 full-width bottom-row comparisons**. Every bottom-row pixel, including rounded-corner coverage, must equal an art-free backing/ring reference. Straight-edge pixels and fill/track samples additionally assert exact state colours; no tolerance or different-data screenshot substitutes for that proof. | Real poster | Rest bottom | Focused straight bottom | Resume track | Accent fill | |---|---|---|---|---| | Annie Hall | `#07070a` | `#cc9fff` | `#3d3d3f` | `#cc9fff` | | Locke | `#07070a` | `#cc9fff` | `#3d3d3f` | `#cc9fff` | | Apocalypse Now | `#07070a` | `#cc9fff` | `#3d3d3f` | `#cc9fff` | | Avatar Aang | `#07070a` | `#cc9fff` | `#3d3d3f` | `#cc9fff` | Raw samples: https://pages.justinvisser.org/wolfskin/tv-card-meta-build/pixel-proof.json . `CardGeometryTest` asserts actual native grid/shelf pitches **524 / 632 / 522 / 441 px**, initial library rows **372 / 896**, pinned rows **168 / 692** ending at **1076**, ring clearances **20.48 / 26.48**, 24×24 checks and 210/327 px fitting. Both loading/ready shelf heights match. It also covers always-visible unfocused filmography, no aggregate series runtime and no watched check on a resuming item. ## Retained renderer/fixture artifacts — disclosed, not approval blockers Design review traced these differences to unchanged native behaviour or reference-renderer fixtures. None authorizes a new design deviation; unapproved design changes still block merge. 1. **Movies order / long-title viewport:** the frame hand-places Steins;Gate sixth under an A–Z label. Existing native sorting puts it twelfth, changing the focused viewport. Fixture records/watch states are identical; sorting code is unchanged. Exact orders: https://pages.justinvisser.org/wolfskin/tv-card-meta-build/fixture-order-evidence.json . 2. **Discover spacing:** the frame uses a stale 84 px heading band. Native retains the approved 104 px band from the later heading-gap feedback: first art is +20 px and second-row art +40 px against that frame. No Discover-spacing change ships here. 3. **Existing chrome:** navigation placement/selection widths, avatar/header baselines, toolbar glyphs/chip widths and native “12 films”/“12 series” versus reference “12 titles” follow the unchanged shipped TV shell, not the simplified fixture chrome. 4. **Typography / plates:** platform Roboto advances differ slightly (year 52 px vs 53.953125; “2h 56m” 79 px vs 80.68359375), affecting ellipsis/check placement. The reference forcibly sets rating plates to 24 px; the unchanged native adaptive plate fits this two-rating fixture at 28 px. Caption size/line boxes remain 28/32 and 24/26 px. No four-rating native capture is claimed. 5. **Rasterisation:** Android/Skia glyphs, artwork resampling, rounded coverage and focus shadows differ from Chrome. Native focused Home/Discover titles are visibly brighter in the comparison; design review attributes this to reference shadow compositing [INFERENCE], not a changed colour token. Numeric geometry and state colours have separate native assertions. 6. **Home wide/tile on-art title is 6 px lower:** the reference inherits a 42 px line box from `line-height:1.5`; native follows the written 32 px title line with the same 28 px bottom inset. Design review classified this visible offset as a renderer artifact, consistent with the locked native base. The original 18-pair gallery remains frozen at `fe44a3c2`; its original blocker wording predates this review classification. Existing native/reference frames are not regenerated by the follow-up. The solid lowest row is an explicitly requested correction, not a hidden deviation. In the treatment replay, the selected B root class applies B to both halves while the historical A/B explanatory labels are retained; only B ships. ## Scoped review follow-up Follow-up head: `c2f7ccc438a623ef2bff9b858ea6591a675ab481` — one combined review-fix commit on the same stacked branch. - Key the focus-only caption effect to its remembered animation object, so replacing a title while its shelf stays focused restarts visibility. Two permanent native glyph-pixel regressions cover direct caption replacement and moving across a focused Home shelf. - Paint nothing in reserved loading caption bands. Poster/wide/tile heights and the shared pulse remain unchanged; a native regression asserts every reserved-band pixel equals the background. - Tighten measured pitch, pinned-row and ring-clearance tolerances to **0.1 px**. - Remove unused wide/tile caption-band token fields and regenerate the Kotlin/hash outputs; landscape bands use the existing canonical metadata token. - Normalize completed Home progress like Library/Title: `>= 1` is not resuming. A real-Annie-Hall pixel regression compares Home/library strips at 50%, 100% and 150%, including the 6 px → 3 px transition. - **Combined focused gate: 20 passed, 0 failures/errors/skips**, covering card pixels, caption motion, geometry, library spacing, Home viewport, motion surfaces and focus ring. **Token drift passed.** No full `make test` or `make apk-debug` rerun was performed, per steering. - One initial targeted run had a **new test-harness failure**, `CardBottomPixelTest.completedHomeProgressUsesTheSameWatchedStripAsLibraryCards`: it compared 10 px even when the watched backing occupies only 7 px, including 3 px of artwork. The regression now compares the entire actual backing (10 px resume / 7 px watched), with exact state-colour assertions; the final 20-case gate passes. Follow-up native proof: [focused Home title replacement](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/followup/home-1.png), [blank loading bands](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/followup/loading-bands.png), [completed Home/library strip parity](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/followup/home-progress-100.png), [focused test counts](https://pages.justinvisser.org/wolfskin/tv-card-meta-build/followup/test-results.json). ## Original full-suite / APK verification Original full-suite/APK head: `fe44a3c24f5cdd6e857f43445ca3309466201f83`. Final fetch/rebase confirmed the stacked branch was current before those gates. The scoped follow-up above was validated separately; the existing APK is not a build of the follow-up head. - `generate_tokens.py --check`: **passed**. - Focused permanent card/caption/grid/filmography/focus/motion tests: **16 passed** after proof-only sources/art were removed. - Same-data native capture pass: **18 capture cases**, plus the pixel/geometry regressions, **23 passed**. - Exactly **one original-head `make test`** and **one original-head `make apk-debug`** were run. Gradle used the repository’s serialized wrapper; no optimized build was run. | Original `make test` component | Cases | Failures | Result | |---|---:|---:|---| | App | 715 | 5 | Known profile-picker timing family only | | Design module | 208 | 0 | Passed, including new pixel/geometry regressions | | Client unit results | 26 | 0 recorded | UP-TO-DATE; unit cases not re-executed; DNS/cache smoke did run | `make test` exits nonzero because of these **known order-sensitive `PersonPickerSessionTest` timeouts**, not a green full-suite claim. Firstmate’s steering identifies this family as fixed by the test-only sync change on `main`, absent from the stacked motion base, and explicitly says not to chase it here. Every observed failing method is listed: | Failing method in `org.justinvisser.wolfskin.tv.PersonPickerSessionTest` | Classification | |---|---| | `launchRightThenOneOkKeepsPickerClosed` | Known order-sensitive picker timeout; fixed on main | | `fastLeftLeftOkKeepsPickerClosed` | Known order-sensitive picker timeout; fixed on main | | `avatarRightThenOneOkKeepsPickerClosed` | Known order-sensitive picker timeout; fixed on main | | `launchLeftThenOneOkKeepsPickerClosed` | Known order-sensitive picker timeout; fixed on main | | `avatarLeftThenOneOkKeepsPickerClosed` | Known order-sensitive picker timeout; fixed on main | No other failing tests were observed in that original full-suite run. Machine-readable results/classification: https://pages.justinvisser.org/wolfskin/tv-card-meta-build/full-test-failures.json . `make apk-debug`: **passed** (`defaultDebug`, application ID `org.justinvisser.wolfskin.tv.debug`), producing the universal APK and armv7/arm64/x86_64 splits. Universal artifact: `Wholphin-default-debug-1.1.0-153-gfe44a3c2-65.apk`; SHA-256 `6b5d41da28dca13ac0c5dd42591c99773a68c20c10aa656a34047514a067c67f`. Build-only proof; no installation or playback claim. ## On-device checklist — not run; TV stays off tonight - [ ] Confirm unchanged grid-pitch scroll feel through Movies, Shows, Anime, See all and person filmography. - [ ] Inspect the bottom row on white, near-black and colourful real art at rest and focus; no poster pixels beneath the strip. - [ ] Confirm the focus ring stays independently above the strip, including scaled corners. - [ ] Confirm focused Home landscape metadata and caption motion on the real box. No ADB, installation, TV actions, optimized APK or on-device GPU timing measurement was performed.
justinzeus added 1 commit 2026-11-07 03:22:35 +01:00
Implement option B card metadata and solid bottom edges
Some checks failed
review/code-claude Blocking: CardCaption focus-only caption invisible after first focus move in a shelf
review/design-claude Design review: 1 blocking (unframed skeleton caption bars); other differences are renderer/fixture artifacts
fe44a3c24f
Author
Owner

Code review (claude), one round - verdict: BLOCK, 1 blocking finding. Reviewed head fe44a3c24f5cdd6e857f43445ca3309466201f83. Read: full non-test Kotlin diff, token JSON diffs, spec/README diffs, CardBottomPixelTest and CardGeometryTest in full; ran only generate_tokens.py --check (passes). No Gradle.

Blocking - Home poster / Home landscape / Discover captions vanish after the first D-pad move inside a shelf. In CardCaption (focus-only branch) the animation state is remember(title) { Animatable(0f) } but the effect is LaunchedEffect(focused, reduced). HomeShelf and DiscoverShelf use a single CardCaption per shelf whose title changes with the column and has no key. After moving Right/Left, a fresh alpha-0 Animatable is created and the effect does not relaunch, so the title/year/duration/check stay invisible until focus leaves and re-enters the shelf. The old Caption avoided this by keying the effect on the Animatable (LaunchedEffect(alpha, visible, reduced)). Library grids (not focus-only) and TitleShelf (one caption per item) are unaffected. No test changes the title of a focused focus-only caption. Fix: LaunchedEffect(alpha, focused, reduced) (or key(title)) plus a test that changes the title while focused and asserts the caption is visible.

Verified OK: bottom strip clips art out of the backing band before the opaque color.bg backing, inside the 16 px rounded clip, ring and 1.08 scale on the outer modifier above it; all card kinds go through TitleCard; CardBottomPixelTest compares the full lowest row (corners included) of four real posters x 3 states x rest/focus by exact ARGB, no tolerance. Metadata rules (film/episode/series, no check when resuming/unwatched) and null/zero/huge runtime handling; card-width single-line ellipsis; grid pitch 524 / poster shelf 632 unchanged; skeletons reserve the same slots; removed tokens/code really unused; generated files consistent; no key/Back handling touched.

Non-blocking notes:

  1. CardGeometryTest uses delta = 1f on pitch/ring/pinned-row assertions despite the "no tolerance" claim; tighten to ~0.1.
  2. tileCaptionBand/wideCaptionBand changed 0 -> 32dp in tokens but nothing reads them (real value is CardMetadata.landscapeBand).
  3. Home wide/tile shelf pitches do grow by 64 px (disclosed); Discover/catalogue captions no longer show "In X's library", request recipient/stage or "Hidden from your picks" (only the poster badge remains) and LibraryTitle.availability is now dead data - worth telling Justin.
  4. Home progress >= 1.0 draws a full 6 px resume bar with no check, while Library/Title paths map it to no bar/watched line.
  5. TitleShelf non-tile caption shows duration only for kind == "movie"; resume>0 with runtime 0 gives the watched line but no check.

Full report: firstmate data review-54-code/report.md.

**Code review (claude), one round - verdict: BLOCK, 1 blocking finding.** Reviewed head `fe44a3c24f5cdd6e857f43445ca3309466201f83`. Read: full non-test Kotlin diff, token JSON diffs, spec/README diffs, `CardBottomPixelTest` and `CardGeometryTest` in full; ran only `generate_tokens.py --check` (passes). No Gradle. **Blocking - Home poster / Home landscape / Discover captions vanish after the first D-pad move inside a shelf.** In `CardCaption` (focus-only branch) the animation state is `remember(title) { Animatable(0f) }` but the effect is `LaunchedEffect(focused, reduced)`. `HomeShelf` and `DiscoverShelf` use a single `CardCaption` per shelf whose `title` changes with the column and has no `key`. After moving Right/Left, a fresh alpha-0 `Animatable` is created and the effect does not relaunch, so the title/year/duration/check stay invisible until focus leaves and re-enters the shelf. The old `Caption` avoided this by keying the effect on the `Animatable` (`LaunchedEffect(alpha, visible, reduced)`). Library grids (not focus-only) and `TitleShelf` (one caption per item) are unaffected. No test changes the title of a focused focus-only caption. Fix: `LaunchedEffect(alpha, focused, reduced)` (or `key(title)`) plus a test that changes the title while focused and asserts the caption is visible. **Verified OK:** bottom strip clips art out of the backing band before the opaque `color.bg` backing, inside the 16 px rounded clip, ring and 1.08 scale on the outer modifier above it; all card kinds go through `TitleCard`; `CardBottomPixelTest` compares the full lowest row (corners included) of four real posters x 3 states x rest/focus by exact ARGB, no tolerance. Metadata rules (film/episode/series, no check when resuming/unwatched) and null/zero/huge runtime handling; card-width single-line ellipsis; grid pitch 524 / poster shelf 632 unchanged; skeletons reserve the same slots; removed tokens/code really unused; generated files consistent; no key/Back handling touched. **Non-blocking notes:** 1. `CardGeometryTest` uses `delta = 1f` on pitch/ring/pinned-row assertions despite the "no tolerance" claim; tighten to ~0.1. 2. `tileCaptionBand`/`wideCaptionBand` changed 0 -> 32dp in tokens but nothing reads them (real value is `CardMetadata.landscapeBand`). 3. Home wide/tile shelf pitches do grow by 64 px (disclosed); Discover/catalogue captions no longer show "In X's library", request recipient/stage or "Hidden from your picks" (only the poster badge remains) and `LibraryTitle.availability` is now dead data - worth telling Justin. 4. Home progress >= 1.0 draws a full 6 px resume bar with no check, while Library/Title paths map it to no bar/watched line. 5. `TitleShelf` non-tile caption shows duration only for `kind == "movie"`; resume>0 with runtime 0 gives the watched line but no check. Full report: firstmate data `review-54-code/report.md`.
Author
Owner

Design review (claude), one round - verdict: BLOCK, 1 blocking finding. Reviewed head fe44a3c24f5cdd6e857f43445ca3309466201f83 against the approved revision 6 option B frames (3 px accent watched line, 6 px accent resume fill, solid lowest row). I opened every one of the 18 native/reference pairs and measured the rasters (ffmpeg + numpy); no Gradle/make.

Blocking - loading skeletons paint title/subtitle placeholder bars that no approved frame shows. PosterCaptionSkeleton (LoadingSurface.kt:61, used by LibraryScreen.kt:124, HomeShelf.kt:57, DiscoverShelf.kt:69) draws a full-card-width 32 px bar and a half-width 26 px bar (surface2, 16 px radius) 38 px below every skeleton card. The approved loading reference (data/tv-v2/motion-2026-11-06/proto.js:59-75) is art-sized blocks only; the revision 6 frames contain no loading frame; the gallery has no skeleton capture. On Home posters, Home landscapes and Discover the ready state shows a caption only under the focused card, so the bars would vanish at ready under all the others. Heights/bands are fine (CardGeometryTest asserts loading = ready 632/522/441). Fix: reserve the band without painting bars (keeps every height), or publish native skeleton captures and get Justin's explicit approval for caption bars on the always-visible grids only. [INFERENCE] the bars render as written; I could not capture a skeleton without Gradle.

Verified against the approved frames: rest cards end in two solid #07070a backing rows on white, dark and colourful art (Home wide rest cards 480/480 px, where the reference leaks (229,218,212) art); strip stack identical row for row (2 px separator, 6 px fill/#3d3d3f track, fill ends at x=797 = 43% in both; watched 3 px #cc9fff); focused ring rows 770-773 unchanged above the strip; art rows 372/896, pinned 168/692..1075, Home poster 252/884 (632), Home wide 252/774 (522); caption title/meta glyph rows within +/-1 px (28/32, 24/26); checks 24x24; single-line ellipsis; metadata fits at 210/327 px.

The five listed reference differences - classification (none is a deviation of the built card/caption/state-line design; none needs Justin's approval, but he should be told 2, 4b and the extra one):

  1. Movies order / long-title viewport: fixture artifact. The frame hand-places Steins;Gate sixth under a Sort A-Z label; native A-Z is the existing sortName sort, untouched by the PR. Cards 1-5 identical in both.
  2. Discover spacing: fixture artifact, locked spec. Native art top 272 vs 252 (+20), row 2 924 vs 884 (+40), pitch 652 vs 632, heading y identical, art-to-caption offsets identical. 104 px band = discover-tokens.json:3 (Justin's 2026-11-06 "heading-to-card gap 60 px", on main since 9d2471c5); the frames' own "before" capture (rd3.png) pre-dates it. Do not change native.
  3. Existing chrome: renderer artifact. Native nav text x 353/519/688/908 vs reference 312/456/609/797; the real shipped capture sh_movies.png has the native positions, toolbar glyphs and "films" label. No chrome file is in the diff.
  4. Typography / plates: (a) platform Roboto advances (2007 52 vs 53.95 px, 2h 56m 79 vs 80.68): metadata ends 3 px earlier, one ellipsis one glyph earlier; line boxes exactly 28/32 and 24/26. (b) Plate 28 px vs 24 px: fixture artifact in unchanged code. The reference hard-codes font-size:24px (frames.html:375); the shipped TV capture shows the same 24 px; the plate rule (28 px, shrink to the 24 px floor, then drop trailing entries) is unchanged, and this fixture has only 2 ratings, which fit at 28. Non-blocking: add one native capture with the real ratings set.
  5. Rasterisation: artifact; no colour token differs.

Extra visible difference not listed in the PR (artifact, non-blocking): the on-art title on Home wide/tile cards sits 6 px lower in native (e.g. The White Lotus glyph rows 503-523 vs 497-517). The reference's .ov b inherits body{line-height:1.5} (42 px box) over the same 28 px bottom inset; native uses the README's 32 px title line (HomeShelf.kt:87).

Non-blocking notes: (1) with the 3 px watched line, bright-art wide cards show a 3 px band of bare unscrimmed art above the 2 px separator (rows 548-550 on Desperate Housewives); it is in the approved B frame and reproduced exactly. (2) Gallery lacks a skeleton capture and a 4-rating plate capture. (3) The earlier code-review blocker (focus-only captions vanish after the first D-pad move) still stands; none of the 18 first-focus captures can show it.

Full report: /root/.treehouse/firstmate-7bab20/2/firstmate/data/review-54-design/report.md. Evidence crops and measurement scripts: /root/.treehouse/firstmate-7bab20/2/firstmate/data/review-54-design/evidence/. Gallery inspected: /opt/stacks/pages/store/wolfskin/tv-card-meta-build/ (https://pages.justinvisser.org/wolfskin/tv-card-meta-build/).

**Design review (claude), one round - verdict: BLOCK, 1 blocking finding.** Reviewed head `fe44a3c24f5cdd6e857f43445ca3309466201f83` against the approved revision 6 option B frames (3 px accent watched line, 6 px accent resume fill, solid lowest row). I opened every one of the 18 native/reference pairs and measured the rasters (ffmpeg + numpy); no Gradle/make. **Blocking - loading skeletons paint title/subtitle placeholder bars that no approved frame shows.** `PosterCaptionSkeleton` (`LoadingSurface.kt:61`, used by `LibraryScreen.kt:124`, `HomeShelf.kt:57`, `DiscoverShelf.kt:69`) draws a full-card-width 32 px bar and a half-width 26 px bar (`surface2`, 16 px radius) 38 px below every skeleton card. The approved loading reference (`data/tv-v2/motion-2026-11-06/proto.js:59-75`) is art-sized blocks only; the revision 6 frames contain no loading frame; the gallery has no skeleton capture. On Home posters, Home landscapes and Discover the ready state shows a caption only under the focused card, so the bars would vanish at ready under all the others. Heights/bands are fine (`CardGeometryTest` asserts loading = ready 632/522/441). Fix: reserve the band without painting bars (keeps every height), or publish native skeleton captures and get Justin's explicit approval for caption bars on the always-visible grids only. [INFERENCE] the bars render as written; I could not capture a skeleton without Gradle. **Verified against the approved frames:** rest cards end in two solid `#07070a` backing rows on white, dark and colourful art (Home wide rest cards 480/480 px, where the reference leaks `(229,218,212)` art); strip stack identical row for row (2 px separator, 6 px fill/`#3d3d3f` track, fill ends at x=797 = 43% in both; watched 3 px `#cc9fff`); focused ring rows 770-773 unchanged above the strip; art rows 372/896, pinned 168/692..1075, Home poster 252/884 (632), Home wide 252/774 (522); caption title/meta glyph rows within +/-1 px (28/32, 24/26); checks 24x24; single-line ellipsis; metadata fits at 210/327 px. **The five listed reference differences - classification** (none is a deviation of the built card/caption/state-line design; none needs Justin's approval, but he should be told 2, 4b and the extra one): 1. Movies order / long-title viewport: fixture artifact. The frame hand-places Steins;Gate sixth under a Sort A-Z label; native A-Z is the existing `sortName` sort, untouched by the PR. Cards 1-5 identical in both. 2. Discover spacing: fixture artifact, locked spec. Native art top 272 vs 252 (+20), row 2 924 vs 884 (+40), pitch 652 vs 632, heading y identical, art-to-caption offsets identical. 104 px band = `discover-tokens.json:3` (Justin's 2026-11-06 "heading-to-card gap 60 px", on `main` since `9d2471c5`); the frames' own "before" capture (`rd3.png`) pre-dates it. Do not change native. 3. Existing chrome: renderer artifact. Native nav text x 353/519/688/908 vs reference 312/456/609/797; the real shipped capture `sh_movies.png` has the native positions, toolbar glyphs and "films" label. No chrome file is in the diff. 4. Typography / plates: (a) platform Roboto advances (`2007` 52 vs 53.95 px, `2h 56m` 79 vs 80.68): metadata ends 3 px earlier, one ellipsis one glyph earlier; line boxes exactly 28/32 and 24/26. (b) Plate 28 px vs 24 px: fixture artifact in unchanged code. The reference hard-codes `font-size:24px` (`frames.html:375`); the shipped TV capture shows the same 24 px; the plate rule (28 px, shrink to the 24 px floor, then drop trailing entries) is unchanged, and this fixture has only 2 ratings, which fit at 28. Non-blocking: add one native capture with the real ratings set. 5. Rasterisation: artifact; no colour token differs. **Extra visible difference not listed in the PR (artifact, non-blocking):** the on-art title on Home wide/tile cards sits 6 px lower in native (e.g. The White Lotus glyph rows 503-523 vs 497-517). The reference's `.ov b` inherits `body{line-height:1.5}` (42 px box) over the same 28 px bottom inset; native uses the README's 32 px title line (`HomeShelf.kt:87`). **Non-blocking notes:** (1) with the 3 px watched line, bright-art wide cards show a 3 px band of bare unscrimmed art above the 2 px separator (rows 548-550 on Desperate Housewives); it is in the approved B frame and reproduced exactly. (2) Gallery lacks a skeleton capture and a 4-rating plate capture. (3) The earlier code-review blocker (focus-only captions vanish after the first D-pad move) still stands; none of the 18 first-focus captures can show it. Full report: `/root/.treehouse/firstmate-7bab20/2/firstmate/data/review-54-design/report.md`. Evidence crops and measurement scripts: `/root/.treehouse/firstmate-7bab20/2/firstmate/data/review-54-design/evidence/`. Gallery inspected: `/opt/stacks/pages/store/wolfskin/tv-card-meta-build/` (https://pages.justinvisser.org/wolfskin/tv-card-meta-build/).
justinzeus added 1 commit 2026-11-07 03:53:47 +01:00
Fix focused card captions and leave loading bands blank
All checks were successful
review/code-claude Blocker fixed on c2f7ccc4, delta reviewed
review/design-claude Blocker fixed on c2f7ccc4, delta reviewed
c2f7ccc438
Author
Owner

Delta review of c2f7ccc438 against both blocking findings on fe44a3c2 (read the full delta: 11 files, +248/-44).

Code B1 (focus-only captions vanish after the first focus move) is fixed: CardCaption's LaunchedEffect is now keyed (alpha, focused, reduced), so a title change creates a new Animatable and relaunches the effect (same as the merged Caption), with a new CardCaptionMotionTest that changes the title of a focused focus-only caption. Also folded in: pitch/ring assertions tightened (0.1 instead of 1), the unused tileCaptionBand and wideCaptionBand tokens removed (generator and checksums regenerated; drift check passes on the lane), and Home progress >= 1 now draws no resume bar, matching Library and Title.

Design B1 (unapproved caption placeholder bars in loading skeletons) is fixed: PosterCaptionSkeleton now reserves the band with a spacer and paints nothing, heights unchanged (loading equals ready heights, still asserted). The five listed differences plus the extra one stay classified as renderer or fixture artifacts by the design review. No new blocking finding; review/code-claude and review/design-claude carried to this head.

Still held for Justin: this PR is stacked on PR 51 and does not merge before it; also his call that the cards no longer show Discover owner and request-stage captions (option B drops that prose, only the poster badge remains).

Delta review of c2f7ccc438a623ef2bff9b858ea6591a675ab481 against both blocking findings on fe44a3c2 (read the full delta: 11 files, +248/-44). Code B1 (focus-only captions vanish after the first focus move) is fixed: `CardCaption`'s `LaunchedEffect` is now keyed `(alpha, focused, reduced)`, so a title change creates a new Animatable and relaunches the effect (same as the merged `Caption`), with a new `CardCaptionMotionTest` that changes the title of a focused focus-only caption. Also folded in: pitch/ring assertions tightened (0.1 instead of 1), the unused `tileCaptionBand` and `wideCaptionBand` tokens removed (generator and checksums regenerated; drift check passes on the lane), and Home progress >= 1 now draws no resume bar, matching Library and Title. Design B1 (unapproved caption placeholder bars in loading skeletons) is fixed: `PosterCaptionSkeleton` now reserves the band with a spacer and paints nothing, heights unchanged (loading equals ready heights, still asserted). The five listed differences plus the extra one stay classified as renderer or fixture artifacts by the design review. No new blocking finding; review/code-claude and review/design-claude carried to this head. Still held for Justin: this PR is stacked on PR 51 and does not merge before it; also his call that the cards no longer show Discover owner and request-stage captions (option B drops that prose, only the poster badge remains).
justinzeus added 2 commits 2026-11-07 15:59:24 +01:00
Merge main into the card metadata branch
All checks were successful
review/code-claude Reviews carried: clean merge of main into c2f7ccc4
review/design-claude Reviews carried: clean merge of main into c2f7ccc4
165a3f5e7d
justinzeus changed target branch from fm/tv-motion-build to main 2026-11-07 15:59:26 +01:00
justinzeus merged commit 19bb587c95 into main 2026-11-07 16:01:07 +01:00
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#54
No description provided.