Build TV libraries and continuous See all grids #30

Merged
justinzeus merged 8 commits from fm/tv-slice4-libraries into main 2026-11-05 17:35:12 +01:00
Owner

Outcome

Build TV Libraries (Movies, Shows and Anime) and continuous See all grids. Ready for review, base main, pushed head 321f2d41bbb401160c5f118eb51f6e522b4b1ce9. Includes the merged shell and navigation, its settings-pill border correction (74033ee1), and origin/main merge 7a878b2d. No release or TV-install request.

AI-assisted (fork-internal)

Refs: approved TV spec, sections 2, 4.4–4.9, 5, 6.4, 7.1–7.3, 8–10; locked Opus prototype.

Review corrections

  • Header-avatar initial (B3): Avatar has an additive optional letterColor defaulting to the shipped onFocus; only LibraryHeader passes muted. This matches the locked header CSS override. Nav, picker, Settings and other callers are untouched; no public API was renamed.
  • Visible recovery focus: Down clamps the short final title row before entering the separate Try again / Show more row. The permanent real-Android-key regression failed before the fix (expected 19, reached 20) and now passes: twenty titles, index 14 → 19 → 24; another Down stays on the visible tail, OK activates recovery once, and Up returns to title 18. No phantom slot can activate recovery along that path.
  • Caption separator opacity: generated captionSeparatorOpacity token multiplies the existing muted colour alpha, matching CSS opacity: .4; preserves the existing separator spacing and accent availability text. No full-strength separator remains in the recaptured movie/catalogue captions.
  • Rail typography: follows prototype CSS: resting 24 px / weight 700; focused 28 px / weight 700. This deliberately overrides spec 4.3’s generic caption-table entry (24 px / 400) for this component, as directed in review.
  • Unavailable count: hides the unknown count instead of showing fixture totals or implying a known zero. This is an explicit review-directed difference from the frozen st-027 count.

Implemented

  • Six-column lazy poster grid; shared token geometry, focused caption/one-line measured rating plate; RGB565 artwork with no crossfade; one bounded 64 MiB memory / 128 MiB disk image cache, four concurrent reads and next-row prefetch.
  • A–Z ignoring leading articles; Recently added; lead-score Rating sort with unrated last/A–Z ties; All/Unwatched and mixed-list All/Movies/Series filters. Header counts follow the filtered catalogue list when available.
  • Existing shell key adapter, key-up card activation/hold guard, per-person/root retained list state, remembered card, Back ladder and reselect reset. Row clamps and toolbar/nav traversal; A–Z rail only at 200 titles; letter jump, commit and return-on-Back. Pointer hover/click/wheel reentry stays D-pad-first.
  • Full-list See all destination/entry card and provider seam. Read-ahead within ten cards; canonical media-type/TMDB deduplication; failed and duplicate-only reads pause on Try again / Show more until explicit recovery.
  • Debug-only locked fixture/local poster art. Release Jellyfin reads use the existing facade and 200-item chunks; atomic per-server/person ten-minute snapshots paint saved data before refresh and retain it offline. Production ratings remain absent until Conductarr sign-in exists.
  • Title activation pushes the clearly named library-title/{id} sibling-fill destination. Home/Discover caller API: WolfskinSeeAllLists.open(person, key, title, fullList, navigation, loadNextPage); those screen slices own the callers.

Exit-check evidence

Every native half is 1920×1080, density 2.0, Robolectric SDK 34/Roborazzi; frozen prototype left, Compose right. All sixteen pairs were regenerated on final head 321f2d41. The avatar pass inspected the reference/before/native atlas and measured all visible header initials; original full-screen reviews covered the sixteen layouts. No browser/native pixel-equality sign-off.

Download current raw captures, references, amplified diffs, numeric reports, header-colour/native-scope smoke, frame-local font measurements, final build log, exact final-head label and all 79 JUnit XML files. No APK is included or distributed. This archive and the table below supersede earlier attached proof, retained only as history.

Font ruling: keep the spec’s platform Roboto; frozen host-browser frames use DejaVu Sans. No font is bundled, and no per-title/per-row compensation is applied. Wrapping parity is not claimed. Each frame’s last column gives measured ink extents or wrapping; measurement rectangles/thresholds are in the font report and raw data. No ellipsis-parity claim is made.

Difference legend: F = Roboto/DejaVu glyph widths, weights, line boxes and rasterisation, vector marks, native image sampling and reference JPEG compression. P = corrected full line boxes/border space preserve panel top/1100 px width; native height 264 px versus Chrome 263 px is integer line-box rounding (the initial 12 px shortfall was repaired). U = review-directed omission of the unavailable header’s fixture count, an explicit difference from st-027’s “29 series”. W = tail explanation wraps differently, measured below. S = prototype-only sample label omitted as required. R = frozen Anime frame shows Chainsaw Man’s 97% critics score despite being named “TMDB only”; the capture is authoritative.

State / capture pair Mean RGB error Pixels >12 Nav mean error Difference Measured font residual (reference → native)
Shared Movies (st-021) 3.9647 4.5747% 5.2954 F Header ink: 237×50 → 197×47 px
Rating sort (st-022) 4.1125 4.9806% 5.2954 F Header ink: 237×50 → 197×47 px
Right edge (st-023) 3.7974 4.3259% 5.2954 F Header ink: 237×50 → 197×47 px
Anime (st-024) 4.3274 7.5516% 6.0099 F Header ink: 217×50 → 179×47 px
Unwatched (st-025) 3.8904 4.7792% 5.2954 F Header ink: 237×50 → 197×47 px
Empty (st-026) 3.5260 2.4489% 5.1223 F + P Header ink: 237×50 → 197×47 px
Jellyfin down (st-027) 3.3728 2.3660% 5.1064 F + P + U Header ink: 219×50 → 181×48 px
Loading (st-028) 1.8487 0.8370% 5.1370 F Header ink: 237×50 → 197×47 px
Full picks (st-030) 5.4098 8.2754% 6.0590 F Header ink: 504×56 → 407×59 px
Duplicate-only tail (st-035) 2.3782 1.6947% 5.8935 F + W Tail explanation: 3 → 2 lines
Trending (st-039) 4.3604 6.1770% 6.0590 F + S Header ink: 301×56 → 245×59 px
Widest plate (st-095) 3.3224 5.4058% 5.1404 F Caption ink: 330×22 → 256×21 px
Critics plate (st-097) 3.9647 4.5747% 5.2954 F Header ink: 237×50 → 197×47 px
Anime plate (st-098) 4.4373 8.0678% 6.0099 F + R Header ink: 217×50 → 179×47 px
Cached rating offline (st-112) 3.9647 4.5747% 5.2954 F Header ink: 237×50 → 197×47 px
Uncached rating offline (st-113) 3.7661 4.3530% 5.2954 F Header ink: 237×50 → 197×47 px

Mean RGB error averages absolute RGB-channel differences on a 0–255 scale. The percentage counts pixels whose maximum channel difference exceeds 12. Nav error covers the top 150 pixels, including background.

Header-avatar colour and unchanged generic avatars

Reference / previous / corrected colour atlas; all sixteen measurements and pixel-scope assertions; raw JSON. RGB below is the median of the strongest inner-glyph pixels, independent of differing glyph shapes. The source now uses the exact existing muted token; these rendered medians are not a claim of JPEG/PNG pixel equality.

Frame / initial Reference RGB Before RGB Corrected RGB
st-021 / S (220, 244, 218) (11, 11, 16) (221, 240, 219)
st-030 / J (203, 228.5, 245.5) (11, 11, 16) (206, 232, 248)
st-027 / M (243, 225, 214) (11, 11, 16) (247, 222, 211)

The native smoke asserts every visible header initial moved closer to its frozen reference, and zero pixels changed outside the header-avatar rectangle. This includes unchanged dark navigation initials in every frame. Fourteen headers are visible; st-035 and st-095 have scrolled past the header and remain entirely pixel-identical to the prior head. Existing sixteen screenshot tests exercise the surface; no unit test of colour forwarding/default wiring was added.

Accepted separator remeasurement (unchanged by B3)

Brightest RGB channel in isolated separator/year rectangles; bounds and background-adjusted ratios are in the measurement report and raw JSON. The remaining one-level dot difference is [INFERENCE] consistent with JPEG/native glyph rasterisation and alpha quantisation; pixel equality is not asserted. CSS opacity multiplies the muted alpha rather than replacing it.

Capture / caption pair Reference dot Native dot Reference facts Native facts
Movie (st-021) 81 80 199 184
Catalogue (st-030) 81 80 198 184

Keys and data

  • Approved screens/library.json press-path data replays actual Android events through the real shell/grid: Spirited Away in 20 presses. Other paths cover short rows/recovery tails, toolbar sort, Back/Down restoration, rail threshold/jump/return, and key-up/held-repeat protection.
  • English/Dutch one-decimal scores, unchanged RT percentages, duplicate pause/explicit continuation, same-page retry, fresh-cache skip, saved-before-failed-refresh, forced recovery and server/person isolation are exercised.
  • Spec metadata note: the accepted 20-key path selects fixture index 68 (69th title), while the prose calls it 68th. Locked ordering, keys and final title agree; behavior was not changed to fit the rank typo.

Commands and results — final avatar-correction head

Gate Observed result
Historical targeted B1 regression, before its fix Expected failure: real Down reached 20 instead of 19. Original result/XML retained in the archive; the same regression passes in the final suite.
ONE make test on 321f2d41 Passed: 557 app + 45 design + 17 client = 619 tests; zero failures/errors/skips. BUILD SUCCESSFUL in 2m 9s. All existing tests retained; no assertion weakened. Tail regression and approved 20-key replay passed.
ONE make apk-debug on 321f2d41 Passed: BUILD SUCCESSFUL in 51s. Four packaged debug APK branding checks passed, inspecting 1013 resource keys/configurations each. Compile proof only; no APK distributed.
Native header-colour/scope smoke Passed across all sixteen recaptured states: fourteen visible initials closer to their references; zero pixels changed outside their header avatars; two offscreen-header states entirely unchanged.
Token drift / font measurements Drift check passed in both gates. Frame-local ink extents and tail line runs recorded without changing fonts or layout. No live server/network calls in added tests/captures.

origin/main was fetched before the correction and had not moved from 7a878b2d, so no merge was necessary. The source and documentation were committed before both gates; they ran on the final pushed head. The checkout-scoped untracked offline hook used the approved preseeded Robolectric cache and was removed afterwards; no tracked build/cache/credential workaround changed. The stale final-head proof label is corrected.

Upstream files touched

None relative to main. Runtime edits remain in app/src/wolfskin/java, debug fixtures, new tests and :wolfskin-design. Existing documentation docker/README.md explains the library/provider/focus/verification rules. Shared shell ShellState, Caption, Glyph and Avatar are extended, not forked; tokens regenerate Kotlin/CSS/checksums together. No upstream app/src/main file, build configuration, player or preference implementation changes in this slice.

Not done / boundary

Review-directed follow-ups, not implemented here:

  • Progressive library publication: the release facade still collects all 200-item read chunks before publishing new library data; saved snapshots paint first, but an uncached read does not publish partial chunks progressively.
  • Canonical row identity: lazy row keys still use the first title’s raw id; move to canonical catalogueKey before production mixed-catalogue consumers can supply movie/series ID collisions.

Conductarr sign-in/live ratings and catalogue integration, Home/Discover/title screens and caller-side cutovers remain their assigned slices. This supplies their full-list/provider/title-destination seams, not replacement screens. Native/browser font and intrinsic-layout residuals above remain visible. No release build/tag/asset/signing/version change, ADB, device interaction, installation or TV profiling occurred. Firstmate arranges review and any merge; release/install still require Justin’s word.

## Outcome Build TV Libraries (Movies, Shows and Anime) and continuous See all grids. **Ready for review, base `main`, pushed head `321f2d41bbb401160c5f118eb51f6e522b4b1ce9`.** Includes the merged [shell and navigation](https://git.justinvisser.org/justinzeus/wolfskin/pulls/29), its settings-pill border correction (`74033ee1`), and `origin/main` merge `7a878b2d`. **No release or TV-install request.** AI-assisted (fork-internal) Refs: [approved TV spec](https://pages.justinvisser.org/wolfskin/tv-spec), sections 2, 4.4–4.9, 5, 6.4, 7.1–7.3, 8–10; [locked Opus prototype](https://pages.justinvisser.org/wolfskin/tv-prototype-v2-opus/). ## Review corrections - **Header-avatar initial (B3):** `Avatar` has an additive optional `letterColor` defaulting to the shipped `onFocus`; only `LibraryHeader` passes `muted`. This matches the locked header CSS override. Nav, picker, Settings and other callers are untouched; no public API was renamed. - **Visible recovery focus:** Down clamps the short final title row before entering the separate Try again / Show more row. The permanent real-Android-key regression failed before the fix (`expected 19, reached 20`) and now passes: twenty titles, index `14 → 19 → 24`; another Down stays on the visible tail, OK activates recovery once, and Up returns to title 18. No phantom slot can activate recovery along that path. - **Caption separator opacity:** generated `captionSeparatorOpacity` token multiplies the existing muted colour alpha, matching CSS `opacity: .4`; preserves the existing separator spacing and accent availability text. No full-strength separator remains in the recaptured movie/catalogue captions. - **Rail typography:** follows prototype CSS: resting 24 px / weight 700; focused 28 px / weight 700. This deliberately overrides spec 4.3’s generic caption-table entry (24 px / 400) for this component, as directed in review. - **Unavailable count:** hides the unknown count instead of showing fixture totals or implying a known zero. This is an explicit review-directed difference from the frozen st-027 count. ## Implemented - Six-column lazy poster grid; shared token geometry, focused caption/one-line measured rating plate; RGB565 artwork with no crossfade; one bounded 64 MiB memory / 128 MiB disk image cache, four concurrent reads and next-row prefetch. - A–Z ignoring leading articles; Recently added; lead-score Rating sort with unrated last/A–Z ties; All/Unwatched and mixed-list All/Movies/Series filters. Header counts follow the filtered catalogue list when available. - Existing shell key adapter, key-up card activation/hold guard, per-person/root retained list state, remembered card, Back ladder and reselect reset. Row clamps and toolbar/nav traversal; A–Z rail only at 200 titles; letter jump, commit and return-on-Back. Pointer hover/click/wheel reentry stays D-pad-first. - Full-list See all destination/entry card and provider seam. Read-ahead within ten cards; canonical media-type/TMDB deduplication; failed and duplicate-only reads pause on Try again / Show more until explicit recovery. - Debug-only locked fixture/local poster art. Release Jellyfin reads use the existing facade and 200-item chunks; atomic per-server/person ten-minute snapshots paint saved data before refresh and retain it offline. Production ratings remain absent until Conductarr sign-in exists. - Title activation pushes the clearly named `library-title/{id}` sibling-fill destination. Home/Discover caller API: `WolfskinSeeAllLists.open(person, key, title, fullList, navigation, loadNextPage)`; those screen slices own the callers. ## Exit-check evidence Every native half is **1920×1080, density 2.0**, Robolectric SDK 34/Roborazzi; frozen prototype left, Compose right. All sixteen pairs were regenerated on final head `321f2d41`. The avatar pass inspected the reference/before/native atlas and measured all visible header initials; original full-screen reviews covered the sixteen layouts. **No browser/native pixel-equality sign-off.** [Download current raw captures, references, amplified diffs, numeric reports, header-colour/native-scope smoke, frame-local font measurements, final build log, exact final-head label and all 79 JUnit XML files](https://git.justinvisser.org/attachments/fcfb9423-9ff2-4116-bd69-c2cc40d918cf). No APK is included or distributed. This archive and the table below supersede earlier attached proof, retained only as history. **Font ruling:** keep the spec’s platform Roboto; frozen host-browser frames use DejaVu Sans. No font is bundled, and no per-title/per-row compensation is applied. **Wrapping parity is not claimed.** Each frame’s last column gives measured ink extents or wrapping; measurement rectangles/thresholds are in the [font report](https://git.justinvisser.org/attachments/7c96d3af-722d-473b-b157-08b0cc7fa4fd) and [raw data](https://git.justinvisser.org/attachments/35953707-990e-4a59-80f5-067f3146de46). No ellipsis-parity claim is made. **Difference legend:** F = Roboto/DejaVu glyph widths, weights, line boxes and rasterisation, vector marks, native image sampling and reference JPEG compression. P = corrected full line boxes/border space preserve panel top/1100 px width; native height 264 px versus Chrome 263 px is integer line-box rounding (the initial 12 px shortfall was repaired). U = review-directed omission of the unavailable header’s fixture count, an explicit difference from st-027’s “29 series”. W = tail explanation wraps differently, measured below. S = prototype-only sample label omitted as required. R = frozen Anime frame shows Chainsaw Man’s 97% critics score despite being named “TMDB only”; the capture is authoritative. | State / capture pair | Mean RGB error | Pixels >12 | Nav mean error | Difference | Measured font residual (reference → native) | |---|---:|---:|---:|---|---| | [Shared Movies (st-021)](https://git.justinvisser.org/attachments/a01dadb7-2ffd-47f4-8711-c7e396fbec31) | 3.9647 | 4.5747% | 5.2954 | F | Header ink: 237×50 → 197×47 px | | [Rating sort (st-022)](https://git.justinvisser.org/attachments/2a74d685-1f3b-4b20-853e-54c379eec043) | 4.1125 | 4.9806% | 5.2954 | F | Header ink: 237×50 → 197×47 px | | [Right edge (st-023)](https://git.justinvisser.org/attachments/c23d53a8-43a6-4964-8ed3-40cd5206f220) | 3.7974 | 4.3259% | 5.2954 | F | Header ink: 237×50 → 197×47 px | | [Anime (st-024)](https://git.justinvisser.org/attachments/074cda76-c334-40e1-bcd0-ab9f958af091) | 4.3274 | 7.5516% | 6.0099 | F | Header ink: 217×50 → 179×47 px | | [Unwatched (st-025)](https://git.justinvisser.org/attachments/c6859f5b-5b6b-40e0-bdfb-7c2fa867aa08) | 3.8904 | 4.7792% | 5.2954 | F | Header ink: 237×50 → 197×47 px | | [Empty (st-026)](https://git.justinvisser.org/attachments/a07c335d-b35e-4185-8723-909cf0e5e783) | 3.5260 | 2.4489% | 5.1223 | F + P | Header ink: 237×50 → 197×47 px | | [Jellyfin down (st-027)](https://git.justinvisser.org/attachments/79ca1f57-c655-490c-bef6-fe2763290228) | 3.3728 | 2.3660% | 5.1064 | F + P + U | Header ink: 219×50 → 181×48 px | | [Loading (st-028)](https://git.justinvisser.org/attachments/f2e77e39-b5d4-45e9-9045-ecb275846607) | 1.8487 | 0.8370% | 5.1370 | F | Header ink: 237×50 → 197×47 px | | [Full picks (st-030)](https://git.justinvisser.org/attachments/f17b7803-3149-41c0-8fed-8d30546affb3) | 5.4098 | 8.2754% | 6.0590 | F | Header ink: 504×56 → 407×59 px | | [Duplicate-only tail (st-035)](https://git.justinvisser.org/attachments/5b0d8e30-99cb-4af6-bd5d-cd3ef087b31c) | 2.3782 | 1.6947% | 5.8935 | F + W | Tail explanation: 3 → 2 lines | | [Trending (st-039)](https://git.justinvisser.org/attachments/5d5efd8e-9efe-4118-99e2-de10e10ab45b) | 4.3604 | 6.1770% | 6.0590 | F + S | Header ink: 301×56 → 245×59 px | | [Widest plate (st-095)](https://git.justinvisser.org/attachments/d01f84f7-0e2f-460d-bce7-6ab9c665b56e) | 3.3224 | 5.4058% | 5.1404 | F | Caption ink: 330×22 → 256×21 px | | [Critics plate (st-097)](https://git.justinvisser.org/attachments/8593ef9d-1533-4ae9-9d4d-3eaa7bdd81d0) | 3.9647 | 4.5747% | 5.2954 | F | Header ink: 237×50 → 197×47 px | | [Anime plate (st-098)](https://git.justinvisser.org/attachments/be208cc6-3f9f-4ef0-8cc1-ed66267647bc) | 4.4373 | 8.0678% | 6.0099 | F + R | Header ink: 217×50 → 179×47 px | | [Cached rating offline (st-112)](https://git.justinvisser.org/attachments/51d1aad4-7aae-42fb-adc5-29aacde17e11) | 3.9647 | 4.5747% | 5.2954 | F | Header ink: 237×50 → 197×47 px | | [Uncached rating offline (st-113)](https://git.justinvisser.org/attachments/129bbc12-4ef6-477d-af43-20c26aba38eb) | 3.7661 | 4.3530% | 5.2954 | F | Header ink: 237×50 → 197×47 px | Mean RGB error averages absolute RGB-channel differences on a 0–255 scale. The percentage counts pixels whose maximum channel difference exceeds 12. Nav error covers the top 150 pixels, including background. ### Header-avatar colour and unchanged generic avatars [Reference / previous / corrected colour atlas](https://git.justinvisser.org/attachments/11d6908c-f33d-4f2d-a31e-40bba0982ede); [all sixteen measurements and pixel-scope assertions](https://git.justinvisser.org/attachments/93bf98fd-0405-4f2d-b643-269a81c02ab9); [raw JSON](https://git.justinvisser.org/attachments/e6a324f5-3f61-4a35-b4ec-1437908f5bac). RGB below is the median of the strongest inner-glyph pixels, independent of differing glyph shapes. The source now uses the exact existing `muted` token; these rendered medians are not a claim of JPEG/PNG pixel equality. | Frame / initial | Reference RGB | Before RGB | Corrected RGB | |---|---|---|---| | st-021 / S | (220, 244, 218) | (11, 11, 16) | (221, 240, 219) | | st-030 / J | (203, 228.5, 245.5) | (11, 11, 16) | (206, 232, 248) | | st-027 / M | (243, 225, 214) | (11, 11, 16) | (247, 222, 211) | The native smoke asserts every visible header initial moved closer to its frozen reference, and **zero pixels changed outside the header-avatar rectangle**. This includes unchanged dark navigation initials in every frame. Fourteen headers are visible; st-035 and st-095 have scrolled past the header and remain entirely pixel-identical to the prior head. Existing sixteen screenshot tests exercise the surface; no unit test of colour forwarding/default wiring was added. ### Accepted separator remeasurement (unchanged by B3) Brightest RGB channel in isolated separator/year rectangles; bounds and background-adjusted ratios are in the [measurement report](https://git.justinvisser.org/attachments/aa4eac7a-aefc-40d9-953e-1ab3db86a90d) and [raw JSON](https://git.justinvisser.org/attachments/945b6183-11ea-47eb-97d4-77cc433a7f8a). The remaining one-level dot difference is [INFERENCE] consistent with JPEG/native glyph rasterisation and alpha quantisation; pixel equality is not asserted. CSS opacity multiplies the muted alpha rather than replacing it. | Capture / caption pair | Reference dot | Native dot | Reference facts | Native facts | |---|---:|---:|---:|---:| | [Movie (st-021)](https://git.justinvisser.org/attachments/9c586908-e0f5-4e3f-b1e9-48114cc5c55c) | 81 | 80 | 199 | 184 | | [Catalogue (st-030)](https://git.justinvisser.org/attachments/dcce606d-b50a-43bd-a829-8aafcf9637b3) | 81 | 80 | 198 | 184 | ### Keys and data - Approved `screens/library.json` press-path data replays actual Android events through the real shell/grid: **Spirited Away in 20 presses**. Other paths cover short rows/recovery tails, toolbar sort, Back/Down restoration, rail threshold/jump/return, and key-up/held-repeat protection. - English/Dutch one-decimal scores, unchanged RT percentages, duplicate pause/explicit continuation, same-page retry, fresh-cache skip, saved-before-failed-refresh, forced recovery and server/person isolation are exercised. - Spec metadata note: the accepted 20-key path selects fixture index 68 (69th title), while the prose calls it 68th. Locked ordering, keys and final title agree; behavior was not changed to fit the rank typo. ## Commands and results — final avatar-correction head | Gate | Observed result | |---|---| | Historical targeted B1 regression, before its fix | Expected failure: real Down reached 20 instead of 19. Original result/XML retained in the archive; the same regression passes in the final suite. | | **ONE `make test` on `321f2d41`** | **Passed: 557 app + 45 design + 17 client = 619 tests; zero failures/errors/skips. `BUILD SUCCESSFUL in 2m 9s`.** All existing tests retained; no assertion weakened. Tail regression and approved 20-key replay passed. | | **ONE `make apk-debug` on `321f2d41`** | **Passed: `BUILD SUCCESSFUL in 51s`.** Four packaged debug APK branding checks passed, inspecting 1013 resource keys/configurations each. Compile proof only; no APK distributed. | | Native header-colour/scope smoke | Passed across all sixteen recaptured states: fourteen visible initials closer to their references; zero pixels changed outside their header avatars; two offscreen-header states entirely unchanged. | | Token drift / font measurements | Drift check passed in both gates. Frame-local ink extents and tail line runs recorded without changing fonts or layout. No live server/network calls in added tests/captures. | `origin/main` was fetched before the correction and had not moved from `7a878b2d`, so no merge was necessary. The source and documentation were committed before both gates; they ran on the final pushed head. The checkout-scoped untracked offline hook used the approved preseeded Robolectric cache and was removed afterwards; no tracked build/cache/credential workaround changed. The stale final-head proof label is corrected. ## Upstream files touched **None relative to `main`.** Runtime edits remain in `app/src/wolfskin/java`, debug fixtures, new tests and `:wolfskin-design`. Existing documentation `docker/README.md` explains the library/provider/focus/verification rules. Shared shell `ShellState`, `Caption`, `Glyph` and `Avatar` are extended, not forked; tokens regenerate Kotlin/CSS/checksums together. No upstream `app/src/main` file, build configuration, player or preference implementation changes in this slice. ## Not done / boundary Review-directed follow-ups, **not implemented here**: - **Progressive library publication:** the release facade still collects all 200-item read chunks before publishing new library data; saved snapshots paint first, but an uncached read does not publish partial chunks progressively. - **Canonical row identity:** lazy row keys still use the first title’s raw `id`; move to canonical `catalogueKey` before production mixed-catalogue consumers can supply movie/series ID collisions. Conductarr sign-in/live ratings and catalogue integration, Home/Discover/title screens and caller-side cutovers remain their assigned slices. This supplies their full-list/provider/title-destination seams, not replacement screens. Native/browser font and intrinsic-layout residuals above remain visible. No release build/tag/asset/signing/version change, ADB, device interaction, installation or TV profiling occurred. Firstmate arranges review and any merge; release/install still require Justin’s word.
justinzeus added 1 commit 2026-11-05 15:54:29 +01:00
justinzeus added 1 commit 2026-11-05 16:05:27 +01:00
justinzeus added 1 commit 2026-11-05 16:18:26 +01:00
Merge reviewed TV shell changes into libraries
Some checks failed
review/design-claude Changes requested: tail-card Down focus bug and caption separator opacity
3da635b4cb
Author
Owner

Design and spec review (Claude Sonnet, cross-family): CHANGES REQUESTED

Reviewed head: 3da635b4cb26593521ae2ae89612e9edfad18214, judged as git diff origin/fm/tv-slice2-shell...HEAD (shell base 2ea93010). Read-only review: no Gradle run; the lane's make test and make apk-debug results are taken from the PR body.

Blocking (2, both small)

  1. Down onto a tail card can land on an empty slot. With a Try again / Show more tail and a short last row (20 titles + tail is the normal case), Down from columns 3, 4 and 6 of the row above goes to index 20, 21 or 23, where no card exists. Nothing shows focus, and OK on key-up force-reads the next page. LibraryGridState Down uses last = tailIndex for the clamp instead of the last real card. The prototype goes from the short row to the one-card tail row. No existing test has a tail with focus movement. Fix: step to the last real card on a short row, and go to the tail only from the last item row; add a test (20 items, tail = Retry, index 14, Down must reach 19, next Down 24).
  2. Caption separator dot is at full brightness. Spec 4.4 and the prototype dim the "·" between facts to 40% opacity. Measured on st-021: reference dot peak 79 against text 197; Compose dot 182, same as the text. Every focused caption in the exit set shows it, and it is not a font effect. Fix: style the separator span with a 40% alpha token, re-capture st-021 and one catalogue state.

Design parity. All 16 pairs viewed at native resolution (st-021 to 028, 030, 035, 039, 095, 097, 098, 112, 113) and measured: focus ring 4 px both sides, panel/chip/tail borders 2 px both sides, chip height 68 and gap 16, grid pitch, plate marks and 2-of-3 prefix on st-095, tail row placement on st-035, empty/unreachable/loading panels. Font weight checked by stem width (consistent with Bold on both sides). Nothing else differs beyond Roboto versus browser metrics. The 1 px panel line-box difference the lane reported is real and accepted.

Spec and tests. The Spirited Away test replays the approved pressPaths (identical to the spec's library.json) as real Android keys through the real shell and grid handler, and asserts the end card; it fails on any clamp, row or sort-order break. The 200-title rail gate is tested at 199/200 and with a non-A-Z sort, including jump and Back. Key-up and hold guard, Back ladder, view memory, ratings formatting and the plate rule all follow the spec. The spec's "68th of 84" is a typo for the 69th card (index 68); no behaviour change needed.

Architecture and hygiene. No upstream file touched (spec 2.7 list respected); the existing shell key adapter, tokens, focus and back ladder are reused; no hand-written Conductarr HTTP; no network in tests; OK goes to the named library-title/{id} stub. No test deleted or weakened, no release/signing/version change, plain imperative commits without trailers.

Non-blocking, with severity

  • Low: rail letters use weight 400 / 24 px; the prototype CSS uses 700, and 28 px when focused (not shown in any approved capture; unreachable until 200 titles). Match it in the same pass.
  • Low: the header count shows "0 series" over "Couldn't reach your library" in production when there is no saved copy (the capture passes only because the fixture list is loaded).
  • Medium-low: readLibrary publishes only after every 200-item chunk is read; spec 6.4 says read ahead of focus.
  • Low: row key uses id while the card key uses catalogueKey; LibrarySeeAllCard is unused and unverified; the plate fit is not remembered; 8 MB of posters and the fixture JSON are committed twice (debug assets and design test resources); LibraryFixture ships in the design module's main source set.

Full evidence, measurements and file:line references are in the review report held by firstmate.

**Design and spec review (Claude Sonnet, cross-family): CHANGES REQUESTED** Reviewed head: `3da635b4cb26593521ae2ae89612e9edfad18214`, judged as `git diff origin/fm/tv-slice2-shell...HEAD` (shell base `2ea93010`). Read-only review: no Gradle run; the lane's `make test` and `make apk-debug` results are taken from the PR body. **Blocking (2, both small)** 1. **Down onto a tail card can land on an empty slot.** With a Try again / Show more tail and a short last row (20 titles + tail is the normal case), Down from columns 3, 4 and 6 of the row above goes to index 20, 21 or 23, where no card exists. Nothing shows focus, and OK on key-up force-reads the next page. `LibraryGridState` Down uses `last = tailIndex` for the clamp instead of the last real card. The prototype goes from the short row to the one-card tail row. No existing test has a tail with focus movement. Fix: step to the last real card on a short row, and go to the tail only from the last item row; add a test (20 items, `tail = Retry`, index 14, Down must reach 19, next Down 24). 2. **Caption separator dot is at full brightness.** Spec 4.4 and the prototype dim the "·" between facts to 40% opacity. Measured on st-021: reference dot peak 79 against text 197; Compose dot 182, same as the text. Every focused caption in the exit set shows it, and it is not a font effect. Fix: style the separator span with a 40% alpha token, re-capture st-021 and one catalogue state. **Design parity.** All 16 pairs viewed at native resolution (st-021 to 028, 030, 035, 039, 095, 097, 098, 112, 113) and measured: focus ring 4 px both sides, panel/chip/tail borders 2 px both sides, chip height 68 and gap 16, grid pitch, plate marks and 2-of-3 prefix on st-095, tail row placement on st-035, empty/unreachable/loading panels. Font weight checked by stem width (consistent with Bold on both sides). Nothing else differs beyond Roboto versus browser metrics. The 1 px panel line-box difference the lane reported is real and accepted. **Spec and tests.** The Spirited Away test replays the approved `pressPaths` (identical to the spec's `library.json`) as real Android keys through the real shell and grid handler, and asserts the end card; it fails on any clamp, row or sort-order break. The 200-title rail gate is tested at 199/200 and with a non-A-Z sort, including jump and Back. Key-up and hold guard, Back ladder, view memory, ratings formatting and the plate rule all follow the spec. The spec's "68th of 84" is a typo for the 69th card (index 68); no behaviour change needed. **Architecture and hygiene.** No upstream file touched (spec 2.7 list respected); the existing shell key adapter, tokens, focus and back ladder are reused; no hand-written Conductarr HTTP; no network in tests; OK goes to the named `library-title/{id}` stub. No test deleted or weakened, no release/signing/version change, plain imperative commits without trailers. **Non-blocking, with severity** - Low: rail letters use weight 400 / 24 px; the prototype CSS uses 700, and 28 px when focused (not shown in any approved capture; unreachable until 200 titles). Match it in the same pass. - Low: the header count shows "0 series" over "Couldn't reach your library" in production when there is no saved copy (the capture passes only because the fixture list is loaded). - Medium-low: `readLibrary` publishes only after every 200-item chunk is read; spec 6.4 says read ahead of focus. - Low: row key uses `id` while the card key uses `catalogueKey`; `LibrarySeeAllCard` is unused and unverified; the plate fit is not `remember`ed; 8 MB of posters and the fixture JSON are committed twice (debug assets and design test resources); `LibraryFixture` ships in the design module's main source set. Full evidence, measurements and file:line references are in the review report held by firstmate.
justinzeus changed target branch from fm/tv-slice2-shell to main 2026-11-05 16:49:41 +01:00
justinzeus added 4 commits 2026-11-05 16:55:13 +01:00
Author
Owner

Design re-review (Claude Sonnet): changes requested, 1 blocking item

Reviewed head: 60a0da13a2d222dc177ed85ad465d8cbedb3c541 (previous review head 3da635b4cb26593521ae2ae89612e9edfad18214). Read-only pass, no Gradle. Full report: /root/.treehouse/firstmate-7bab20/2/firstmate/data/review-wolfskin-30-r2/report.md.

Fixed from the first review

  • Down on a short last row with a Try again / Show more card (the earlier blocker): fixed. LibraryGridState Down now clamps to the last real title, then enters the tail card; I traced 20 titles plus a tail (14, 15, 17 now go to 19, then 24; the old code landed on empty slots 20, 21, 23). The new test downClampsShortLastRowBeforeEnteringRecoveryTail uses a real tail and a short last row and would fail on the old code (first Down gives 20, not 19).
  • Caption separator dot at 40% opacity (the earlier blocker): fixed with a token (captionSeparatorOpacity 0.4 applied to the muted colour's alpha). Measured on the refreshed captures against the prototype frames: dot peak 80 vs 81 in all 16 states (was 182 vs 79).
  • Rail letters now bold 24 px / focused bold 28 px as in the prototype CSS; the item count is hidden while the library is unavailable (the lane's and my suggested deviation from the st-027 frame, which shows a fixture count).
  • Diffing the 16 refreshed captures against the previously reviewed set: only the separator dots and the Unavailable count changed. No existing test removed or weakened, no public design API renamed or removed (three tokens added).

Blocking: header avatar initial colour
In every locked frame the person avatar in the library header draws its initial in the muted colour (the prototype's .lib-h span{color:var(--muted)} overrides .av{color:#0b0b10}). Compose draws it dark (onFocus) via the shared Avatar (Identity.kt). Measured on st-021: reference glyph about (220,245,218) vs dark ink in Compose; visible in all 16 pairs. This was present on the previous head too and I missed it in round 1. The fix is small: give Avatar an optional letter-colour parameter and pass muted from LibraryHeader, then re-capture one state. (If Justin prefers the spec's onFocus wording this needs his explicit approval.)

After that fix I see nothing else blocking.

**Design re-review (Claude Sonnet): changes requested, 1 blocking item** Reviewed head: `60a0da13a2d222dc177ed85ad465d8cbedb3c541` (previous review head `3da635b4cb26593521ae2ae89612e9edfad18214`). Read-only pass, no Gradle. Full report: `/root/.treehouse/firstmate-7bab20/2/firstmate/data/review-wolfskin-30-r2/report.md`. **Fixed from the first review** - Down on a short last row with a Try again / Show more card (the earlier blocker): fixed. `LibraryGridState` Down now clamps to the last real title, then enters the tail card; I traced 20 titles plus a tail (14, 15, 17 now go to 19, then 24; the old code landed on empty slots 20, 21, 23). The new test `downClampsShortLastRowBeforeEnteringRecoveryTail` uses a real tail and a short last row and would fail on the old code (first Down gives 20, not 19). - Caption separator dot at 40% opacity (the earlier blocker): fixed with a token (`captionSeparatorOpacity` 0.4 applied to the muted colour's alpha). Measured on the refreshed captures against the prototype frames: dot peak 80 vs 81 in all 16 states (was 182 vs 79). - Rail letters now bold 24 px / focused bold 28 px as in the prototype CSS; the item count is hidden while the library is unavailable (the lane's and my suggested deviation from the st-027 frame, which shows a fixture count). - Diffing the 16 refreshed captures against the previously reviewed set: only the separator dots and the Unavailable count changed. No existing test removed or weakened, no public design API renamed or removed (three tokens added). **Blocking: header avatar initial colour** In every locked frame the person avatar in the library header draws its initial in the muted colour (the prototype's `.lib-h span{color:var(--muted)}` overrides `.av{color:#0b0b10}`). Compose draws it dark (`onFocus`) via the shared `Avatar` (`Identity.kt`). Measured on st-021: reference glyph about (220,245,218) vs dark ink in Compose; visible in all 16 pairs. This was present on the previous head too and I missed it in round 1. The fix is small: give `Avatar` an optional letter-colour parameter and pass `muted` from `LibraryHeader`, then re-capture one state. (If Justin prefers the spec's `onFocus` wording this needs his explicit approval.) After that fix I see nothing else blocking.
justinzeus added 1 commit 2026-11-05 17:28:38 +01:00
Match library header initials to the locked muted colour
All checks were successful
review/design-claude B1/B2 passed Sonnet re-review at 60a0da13; B3 (header avatar initial muted) checked by firstmate: 3 files, default-preserving letterColor param, only LibraryHeader passes muted
321f2d41bb
justinzeus merged commit 96538f3c7a into main 2026-11-05 17:35:12 +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#30
No description provided.