Build the Wolfskin TV shell and navigation #29

Merged
justinzeus merged 5 commits from fm/tv-slice2-shell into main 2026-11-05 16:49:28 +01:00
Owner

Outcome

Build slice 2: the independent Wolfskin TV shell and navigation beside the merged Wholphin v1.1.0 UI. This is not a release or TV-install request.

AI-assisted (fork-internal work, not an upstream contribution)

Ready for the requested pill-border one-frame re-check: #29

Tested and pushed head: 74033ee1da402396e61b4e0dc0502ecd43000d74. B1, B2, N1 and N4 passed re-review at 2ea9301069d7d2adc5a956487d83ec284897fafe; this head adds only the scoped pill-outline correction, generated tokens and documentation. The upstream-background/native-focus blocker and deterministic-weight blocker from the review of 9be55c57 are addressed. The plain main integration is retained; no rebase/force-push and no public design-module API rename occurred. This is a re-review handoff, not a claim that the new head has already been approved.

Settings pill outline correction (B3)

The dedicated Shell.settingsPillStroke is 1 dp / 2 stage px, not the 2 dp focus-ring token. The draw is inset half a stroke inside the unchanged 20 dp / 40 stage px border-box; horizontal padding includes the border. Colours now match white22% at rest and ink40% when focused. Browser dash measurement gives 6 px dash / 4 px gap (3:2 times stroke width); those ratios are tokenized. Draw geometry/path effects are cached. Existing public symbols and the approved source JSON are unchanged.

Refreshed st-091, st-092, st-093 pairs and metrics are below; the other eight pairs are byte-identical to the prior reviewed head. Native-resolution border detail: prototype left, Compose right. Nominal geometry is corrected; this is not a pixel-equality sign-off. The supplied RGB<205 focused-frame scan still sees 2 versus 3 dark raster rows and 40 versus 42 px outer dark spans; the existing 1.02 focused-row scale remains. Raw row/coverage scans and the crop are attached for the requested one-frame visual decision rather than hiding the remaining raster-edge difference.

Previously passed review corrections

  • Upstream restoration and first-row Up (B1): the real upstream Backdrop/top scrim is composed behind native destinations; the shell is transparent for native content and opaque for owned/picker surfaces. Up first asks the real focus manager to move within content; only failure enters the virtual nav. Native focus cannot accidentally enter the nav's pointer click targets.
  • Behavior regressions: with the actual rollout flag off, real Compose key events move second row → first row → nav; Down restores the first row. A separate actual-root pixel regression verifies the upstream dynamic Backdrop is visible when off and the owned background covers it when on. The root uses a red backdrop-state fixture and the real unauthenticated placeholder, not real server content or an authentication claim. Off-path native frame; owned switch-on frame.
  • Deterministic weights (B2): source 600/650 now resolve consistently to the explicit platform Bold (700) face in the generator; pills use the same resolved label weight. Source 400/800 are unchanged, and the approved tokens.json SHA256 remains 5b939f1f3eb90b428008fda75720f6e3a4e844c486668931db3d52bffb987b53. The previous Regular fallback was a defect, not “font metrics”. All eight affected pairs were re-captured and inspected; the other three are byte-identical to prior inspected pairs.
  • Nav label colour (N1): 200 ms CSS ease-out tween implemented; the blob still uses its critically damped stiffness-650 spring. A dedicated reduced-motion test was not added.
  • Locale branding (N4): documented, not fully migrated. Default English/Hungarian/Polish overrides and default launcher artwork are covered; legacy Wolfskin strings remain in 25 translated main folders. appstore/firetv combine upstream icons with some legacy text and are not certified by this slice.

Refs: approved TV spec, especially sections 2, 4–7 and 10; locked Opus prototype.

Implemented

  • :wolfskin-design: copied approved tokens.json with provenance; deterministic Kotlin/CSS/checksum generator and fail-closed drift check; fixed colours/type, focus ring/scales, three durations, 140 ms caption fade and critically damped stiffness-650 nav spring.
  • One signed-in UI entry hook at MainContent.kt; one Destination.Wolfskin subtype/renderer branch. Wolfskin code lives under app/src/wolfskin/java; upstream ApplicationContent.kt and NavigationManager.kt are untouched. Jellyfin reads use a small facade, not Conductarr calls.
  • Per-person tabs and right-side avatar; no Search, coming-in tray or nav gear. Owned-screen nav stays visible/interactive and receives Up from first-row content. Last-person-only Down reaches the picker gear. Loading/error picker states and fixed person colours are included.
  • Back ladder, OK repeat/hold/key-up guard, per-person/per-root scroll/focus memory and current-Home reselect reset.
  • Wolfskin Settings list and clearly labelled Requests/no-data screen; real upstream settings hand-offs, with User profile settings opening HomeSettingsPage. Advanced settings → Wolfskin screens is the persistent, Advanced-only rollout switch: off restores upstream Home/library content, on restores the Wolfskin placeholders/shell.
  • Debug-only household projection from the locked fixture; saved Jellyfin identities use existing authentication rather than borrowing another person's token. No Conductarr sign-in or network integration.
  • Default-flavour branding overrides, compiled-resource branding gate and complete retirement of WOLFSKIN = 8 (numeric slot reserved). Existing legal notices/credit survive.

Exit-check evidence

Download raw exit-check evidence, comparison script, all 11 pairs/captures/diff maps and build logs. No APK is attached or distributed.

Each half of a pair is 1920×1080 at density 2.0 (w960dp-h540dp-xhdpi, native Robolectric SDK 34/Roborazzi); frozen prototype left, Compose right. All eleven pairs were inspected side by side. These are measured differences, not a claim of pixel equality. Platform Roboto is required by the spec. The declared 600/650 → Bold 700 face mapping is listed per frame below; remaining browser/Roboto width, tracking, pill/ellipsis sizing and rasterisation differences remain. Weight is no longer dismissed as antialiasing. Some full-frame numeric errors slightly increase after restoring the intended emphasis; these measurements are not a pixel-equality claim. The repaired picker caption stays on one line; the unavailable panel uses the locked panel dimensions and two-line message.

Mean RGB error is averaged over channels/pixels on a 0–255 scale. The percentage counts pixels whose maximum absolute channel error exceeds 12. Nav-band error covers the top 150 pixels, including the underlying background; it is not an isolated geometry mask. Large Home differences are intentional because Home content is explicitly outside this slice. st-094 is the profile hand-off according to the frozen STATES entry, not Playback.

Capture pair Effective Compose weight Full mean RGB error Pixels >12 Nav-band mean error Explained difference
st-001-pair.png names Bold 700 (source 650); title/letters 800 3.2414 2.0285% 1.9655 Resolved weights are explicit; remaining width/tracking/rasterisation and reference-JPEG differences.
st-002-pair.png names Bold 700; gear caption source 400 3.1181 1.9594% 1.9655 Resolved weights are explicit; remaining width/tracking/rasterisation and reference-JPEG differences.
st-003-pair.png wordmark/title 800 (unchanged) 2.6324 1.5365% 1.9655 Resolved weights are explicit; remaining width/tracking/rasterisation and reference-JPEG differences.
st-004-pair.png error/button 700; body 400 (unchanged) 3.8487 2.4791% 1.9655 Resolved weights are explicit; remaining width/tracking/rasterisation and reference-JPEG differences.
st-010-pair.png nav Bold 700 (source 600) 44.1388 73.2190% 19.1171 Home is a labelled out-of-slice placeholder; compare shell/nav only. Nav-band metric includes the missing hero background.
st-011-pair.png nav Bold 700 (source 600) 48.1198 48.6890% 5.8376 Home shelves are out of scope; this captures the scrolled shell/nav state, not implemented shelves.
st-014-pair.png nav Bold 700 (source 600) 44.1512 73.1725% 19.2067 Home is a labelled out-of-slice placeholder; compare shell/nav only. Nav-band metric includes the missing hero background.
st-091-pair.png nav/pills/titles Bold 700; body 400 8.8245 6.8173% 5.7960 Pill border: 1dp/2px nominal inset; 40px box; white22% / ink40%; dash6/gap4. Resolved weights are explicit; remaining width/tracking/rasterisation and reference-JPEG differences.
st-092-pair.png nav/pills/titles Bold 700; body 400 8.9893 6.9175% 5.7960 Pill border: 1dp/2px nominal inset; 40px box; white22% / ink40%; dash6/gap4. Requests count 5 is capture-fixture input only; no running Requests data/provider exists. Font metrics also differ.
st-093-pair.png nav/pills/titles Bold 700; body 400 5.5056 4.2465% 6.0696 Pill border: 1dp/2px nominal inset; 40px box; white22% / ink40%; dash6/gap4. Resolved weights are explicit; remaining width/tracking/rasterisation and reference-JPEG differences.
st-094-pair.png upstream typography unchanged 17.4691 38.2254% 15.0751 Real upstream HomeSettingsPage replaces the prototype profile-handoff stand-in; no Wolfskin nav. Playback has a separate 11-press replay.

Keys and motion

JVM Compose tests replay actual Android key events through the shell adapter: switch person 6, Home hero → Settings 8, Settings → Back → Back 10, Home hero → Playback hand-off 11 (shell portion). They also cover last-person gear reachability, hold/repeat/key-up behavior, nav wrap and independent memory/reselect behavior. Persisted preference tests exercise the actual rollout setter and upstream-vs-Wolfskin destination selector. Two additional native tests cover first-row-only Up/Down restoration with the rollout off, and actual Backdrop visibility/owned-background occlusion across the switch.

Actual Compose spring samples (stiffness 650, damping ratio 1):

Elapsed ms Blob x, px
50 554.4835
99 628.6950
150 665.8796
200 680.18365

The motion test checks no overshoot, monotonic movement and settling within 1 px by 300 ms. Captures exercise the actual native UI; they are not HTML redraws.

Commands and results

Command/scenario Result
python3 wolfskin-design/generate_tokens.py --check Passed. Copied source SHA256 matches the approved spec; an isolated changed-token copy fails the negative drift check.
make test via unchanged serialized docker/gradle.sh Passed: 19 design + 555 app tests, zero failures/errors/skips. All existing tests retained; the existing palette test imports the new palette without weakening assertions/thresholds. Final Gradle result: BUILD SUCCESSFUL in 4m 10s.
Native screenshot comparison Eleven correctly sized capture pairs, raw JSON/Markdown metrics and 4× visual diff maps generated; all pairs inspected.
Non-Gradle AGP sanity + AAPT format smoke Public API signatures checked against pinned AGP 9.5.0. Existing debug-linked table decoded 1013 string/plural/array resource keys, including all configurations; rejection boundaries retain the exact two legal exceptions.
Final authorized make apk-debug Passed: BUILD SUCCESSFUL in 1m 9s. Four default/debug APKs compiled. The real branding task scanned 1013 resource keys/all configurations in each packaged APK and passed. Debug MPV stub/decoder limitations are unchanged; artifacts are compile proof only.

Validation history is included rather than hidden: initial new capture/key defects were repaired; Maven Central later returned 429 for Robolectric runtimes, so approved SHA512-verified existing host artifacts seeded the existing cache with a worktree-guarded temporary init script. That temporary init hook was removed after green proof; no wrapper, Dockerfile, volume definition or shared daemon changed. The first APK attempt exposed the private incremental-XML input defect; the next exposed applied-script plugin classpath isolation. The final check consumes public packaged APK artifacts from the existing typed app build script and passed. The approved plain merge retained both the Conductarr-client and Wolfskin-design module wires and passed its combined-tree gates. The first review-fix test run then caught an incorrect new near-black expectation: the approved owned background is #07070a, not RGB <.02. Instruction 012 authorized only correcting that new expectation to the declared token with one 8-bit-quantum tolerance, one extra make test, then the still-unused make apk-debug. Final results on the exact pushed head are 19 design + 555 app tests passed and four debug APKs built/branding-checked. Instruction 013 then authorized exactly one make test and one make apk-debug for the pill correction: both passed on 74033ee1 (4m 10s and 1m 9s respectively, excluding any serialized-lock wait). Each additional Gradle invocation was explicitly authorized; no further Gradle run followed this pill-head green APK proof. The temporary offline hook was removed. make apk was deliberately not used because it is the release target; the approved substitute is make apk-debug.

Every pre-existing tracked file changed

The table includes fork build/docs files as well as upstream files. Launcher paths in main are restored to upstream artwork; their existing Wolfskin counterparts now live in app/src/default/res/. Theme-retirement switch sites are exhaustiveness cleanup, not additional shell hooks.

File Why changed
app/build.gradle.kts Wire the design module, native capture tests/offline SDK directory and packaged-resource branding gate.
app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt Remove the retired fork palette wrapper; Wolfskin-owned surfaces now apply the design-module theme.
app/src/main/java/com/github/damontecres/wholphin/MainContent.kt Replace the signed-in ApplicationContent call with the one Wolfskin shell entry hook.
app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt Add the Advanced-only rollout switch and remove the retired theme default.
app/src/main/java/com/github/damontecres/wholphin/ui/cards/WatchedIcon.kt Remove the retired theme-enum branch.
app/src/main/java/com/github/damontecres/wholphin/ui/components/SliderBar.kt Remove the retired theme-enum branch.
app/src/main/java/com/github/damontecres/wholphin/ui/nav/Destination.kt Add the one serializable Wolfskin destination subtype.
app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt Register the one Wolfskin destination-content branch; other branches stay upstream.
app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt Remove the retired theme-enum branch.
app/src/main/java/com/github/damontecres/wholphin/ui/preferences/SwitchPreference.kt Remove the retired theme-enum branch.
app/src/main/java/com/github/damontecres/wholphin/ui/theme/Theme.kt Remove the retired palette import/branch and restore the upstream default/fallback.
app/src/main/java/com/github/damontecres/wholphin/ui/theme/colors/WolfskinThemeColors.kt Delete the obsolete app-local palette after moving owned UI to the design module.
app/src/main/proto/WholphinDataStore.proto Retire WOLFSKIN = 8 and reserve its numeric value.
app/src/main/res/mipmap-anydpi-v26/ic_banner.xml Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-hdpi/ic_launcher.webp Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-mdpi/ic_launcher.webp Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-xhdpi/ic_banner.png Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-xhdpi/ic_banner_foreground.png Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-xhdpi/ic_launcher.webp Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override.
app/src/main/res/values/strings.xml Restore upstream app branding here for flavour isolation and remove the retired theme option; default-flavour overrides supply Wolfskin labels.
app/src/main/res/values/wolfskin.xml Name the Advanced rollout switch; retain original legal credit and project URL.
app/src/test/java/com/github/damontecres/wholphin/ui/WolfskinThemeColorsTest.kt Point the existing contrast/palette tests at the new design palette; preserve assertions and thresholds.
docker/README.md Document shell scope, generator/drift checks, fixtures, captures, offline runtimes and packaged-resource branding.
settings.gradle.kts Include the new :wolfskin-design module.

New files are confined to the design module, Wolfskin UI/read facade, debug fixture, default-flavour overrides and focused native/key/routing tests. No aliases or compatibility palette remain. The review repair changes only existing Wolfskin-owned UI/token generation, its native regressions and fork build documentation; no extra upstream hook/file is introduced.

Not included / deployment boundary

Home, libraries, title pages and Discover content are not implemented in this slice: owned routes are visibly labelled placeholders, or existing upstream Home/library content is selected by the Advanced switch. Requests data/count integration and Conductarr sign-in are not implemented; the count-five screenshot is fixture-only. A complete locale migration/non-default-flavour certification, dedicated reduced-motion verification, pointer scrim consumption and the other non-blocking review suggestions are not included in this repair. No player redesign, release build, release/tag/release-asset publication, signing/version configuration change, ADB/device interaction, installation or TV performance profiling occurred. Cross-family review and any merge belong to firstmate; release and installation still require Justin's word.

## Outcome Build slice 2: the independent Wolfskin TV shell and navigation beside the merged Wholphin v1.1.0 UI. This is **not a release or TV-install request**. AI-assisted (fork-internal work, not an upstream contribution) **Ready for the requested pill-border one-frame re-check:** https://git.justinvisser.org/justinzeus/wolfskin/pulls/29 Tested and pushed head: `74033ee1da402396e61b4e0dc0502ecd43000d74`. B1, B2, N1 and N4 passed re-review at `2ea9301069d7d2adc5a956487d83ec284897fafe`; this head adds only the scoped pill-outline correction, generated tokens and documentation. The upstream-background/native-focus blocker and deterministic-weight blocker from the review of `9be55c57` are addressed. The plain main integration is retained; no rebase/force-push and no public design-module API rename occurred. This is a re-review handoff, not a claim that the new head has already been approved. ### Settings pill outline correction (B3) The dedicated `Shell.settingsPillStroke` is **1 dp / 2 stage px**, not the 2 dp focus-ring token. The draw is inset half a stroke inside the unchanged **20 dp / 40 stage px border-box**; horizontal padding includes the border. Colours now match white22% at rest and ink40% when focused. Browser dash measurement gives **6 px dash / 4 px gap** (3:2 times stroke width); those ratios are tokenized. Draw geometry/path effects are cached. Existing public symbols and the approved source JSON are unchanged. Refreshed `st-091`, `st-092`, `st-093` pairs and metrics are below; the other eight pairs are byte-identical to the prior reviewed head. [Native-resolution border detail: prototype left, Compose right](https://git.justinvisser.org/attachments/86ce20a7-e3a9-48a6-a80c-1e6ab84beaaa). Nominal geometry is corrected; this is not a pixel-equality sign-off. The supplied RGB<205 focused-frame scan still sees **2 versus 3 dark raster rows** and **40 versus 42 px outer dark spans**; the existing 1.02 focused-row scale remains. Raw row/coverage scans and the crop are attached for the requested one-frame visual decision rather than hiding the remaining raster-edge difference. ### Previously passed review corrections - **Upstream restoration and first-row Up (B1):** the real upstream `Backdrop`/top scrim is composed behind native destinations; the shell is transparent for native content and opaque for owned/picker surfaces. Up first asks the real focus manager to move within content; only failure enters the virtual nav. Native focus cannot accidentally enter the nav's pointer click targets. - **Behavior regressions:** with the actual rollout flag off, real Compose key events move second row → first row → nav; Down restores the first row. A separate actual-root pixel regression verifies the upstream dynamic Backdrop is visible when off and the owned background covers it when on. The root uses a red backdrop-state fixture and the real unauthenticated placeholder, not real server content or an authentication claim. [Off-path native frame](https://git.justinvisser.org/attachments/2c775bb9-3c1e-4c27-ba1c-11f38525b05b); [owned switch-on frame](https://git.justinvisser.org/attachments/f8c7711e-f01a-4061-b6a2-b7297e6fa92a). - **Deterministic weights (B2):** source 600/650 now resolve consistently to the explicit platform **Bold (700)** face in the generator; pills use the same resolved label weight. Source 400/800 are unchanged, and the approved `tokens.json` SHA256 remains `5b939f1f3eb90b428008fda75720f6e3a4e844c486668931db3d52bffb987b53`. The previous Regular fallback was a defect, not “font metrics”. All eight affected pairs were re-captured and inspected; the other three are byte-identical to prior inspected pairs. - **Nav label colour (N1):** 200 ms CSS ease-out tween implemented; the blob still uses its critically damped stiffness-650 spring. A dedicated reduced-motion test was not added. - **Locale branding (N4):** documented, not fully migrated. Default English/Hungarian/Polish overrides and default launcher artwork are covered; legacy Wolfskin strings remain in 25 translated `main` folders. `appstore`/`firetv` combine upstream icons with some legacy text and are not certified by this slice. Refs: [approved TV spec](https://pages.justinvisser.org/wolfskin/tv-spec), especially sections 2, 4–7 and 10; [locked Opus prototype](https://pages.justinvisser.org/wolfskin/tv-prototype-v2-opus/). ## Implemented - `:wolfskin-design`: copied approved `tokens.json` with provenance; deterministic Kotlin/CSS/checksum generator and fail-closed drift check; fixed colours/type, focus ring/scales, three durations, 140 ms caption fade and critically damped stiffness-650 nav spring. - One signed-in UI entry hook at `MainContent.kt`; one `Destination.Wolfskin` subtype/renderer branch. Wolfskin code lives under `app/src/wolfskin/java`; upstream `ApplicationContent.kt` and `NavigationManager.kt` are untouched. Jellyfin reads use a small facade, not Conductarr calls. - Per-person tabs and right-side avatar; no Search, coming-in tray or nav gear. Owned-screen nav stays visible/interactive and receives Up from first-row content. Last-person-only Down reaches the picker gear. Loading/error picker states and fixed person colours are included. - Back ladder, OK repeat/hold/key-up guard, per-person/per-root scroll/focus memory and current-Home reselect reset. - Wolfskin Settings list and clearly labelled Requests/no-data screen; real upstream settings hand-offs, with User profile settings opening `HomeSettingsPage`. Advanced settings → **Wolfskin screens** is the persistent, Advanced-only rollout switch: off restores upstream Home/library content, on restores the Wolfskin placeholders/shell. - Debug-only household projection from the locked fixture; saved Jellyfin identities use existing authentication rather than borrowing another person's token. No Conductarr sign-in or network integration. - Default-flavour branding overrides, compiled-resource branding gate and complete retirement of `WOLFSKIN = 8` (numeric slot reserved). Existing legal notices/credit survive. ## Exit-check evidence [Download raw exit-check evidence, comparison script, all 11 pairs/captures/diff maps and build logs](https://git.justinvisser.org/attachments/2b55b807-fb8d-4639-9e68-248c9a50c159). No APK is attached or distributed. Each half of a pair is **1920×1080 at density 2.0** (`w960dp-h540dp-xhdpi`, native Robolectric SDK 34/Roborazzi); frozen prototype left, Compose right. All eleven pairs were inspected side by side. These are measured differences, **not a claim of pixel equality**. Platform Roboto is required by the spec. The declared 600/650 → Bold 700 face mapping is listed per frame below; remaining browser/Roboto width, tracking, pill/ellipsis sizing and rasterisation differences remain. Weight is no longer dismissed as antialiasing. Some full-frame numeric errors slightly increase after restoring the intended emphasis; these measurements are not a pixel-equality claim. The repaired picker caption stays on one line; the unavailable panel uses the locked panel dimensions and two-line message. Mean RGB error is averaged over channels/pixels on a 0–255 scale. The percentage counts pixels whose maximum absolute channel error exceeds 12. Nav-band error covers the top 150 pixels, including the underlying background; it is not an isolated geometry mask. Large Home differences are intentional because Home content is explicitly outside this slice. `st-094` is the profile hand-off according to the frozen STATES entry, not Playback. | Capture pair | Effective Compose weight | Full mean RGB error | Pixels >12 | Nav-band mean error | Explained difference | |---|---|---:|---:|---:|---| | [st-001-pair.png](https://git.justinvisser.org/attachments/943c6eee-a1aa-42f4-a55f-2357cae85441) | names Bold 700 (source 650); title/letters 800 | 3.2414 | 2.0285% | 1.9655 | Resolved weights are explicit; remaining width/tracking/rasterisation and reference-JPEG differences. | | [st-002-pair.png](https://git.justinvisser.org/attachments/deedb694-e1e6-4c42-995a-5be8fd18783c) | names Bold 700; gear caption source 400 | 3.1181 | 1.9594% | 1.9655 | Resolved weights are explicit; remaining width/tracking/rasterisation and reference-JPEG differences. | | [st-003-pair.png](https://git.justinvisser.org/attachments/344c9dfe-1ec3-45e9-9e77-959aa8d973dc) | wordmark/title 800 (unchanged) | 2.6324 | 1.5365% | 1.9655 | Resolved weights are explicit; remaining width/tracking/rasterisation and reference-JPEG differences. | | [st-004-pair.png](https://git.justinvisser.org/attachments/d6d668c7-0ace-4ecb-844b-f7c3431914b3) | error/button 700; body 400 (unchanged) | 3.8487 | 2.4791% | 1.9655 | Resolved weights are explicit; remaining width/tracking/rasterisation and reference-JPEG differences. | | [st-010-pair.png](https://git.justinvisser.org/attachments/b3d7ae4d-0a33-4af7-8d3a-4bf9dc1ab999) | nav Bold 700 (source 600) | 44.1388 | 73.2190% | 19.1171 | Home is a labelled out-of-slice placeholder; compare shell/nav only. Nav-band metric includes the missing hero background. | | [st-011-pair.png](https://git.justinvisser.org/attachments/27b2b672-3f78-4df9-aee2-d8bb782e165f) | nav Bold 700 (source 600) | 48.1198 | 48.6890% | 5.8376 | Home shelves are out of scope; this captures the scrolled shell/nav state, not implemented shelves. | | [st-014-pair.png](https://git.justinvisser.org/attachments/d3ce6a0c-5ceb-4639-a506-fc1af541863d) | nav Bold 700 (source 600) | 44.1512 | 73.1725% | 19.2067 | Home is a labelled out-of-slice placeholder; compare shell/nav only. Nav-band metric includes the missing hero background. | | [st-091-pair.png](https://git.justinvisser.org/attachments/2af0694b-e341-45db-b929-30df8973a763) | nav/pills/titles Bold 700; body 400 | 8.8245 | 6.8173% | 5.7960 | Pill border: 1dp/2px nominal inset; 40px box; white22% / ink40%; dash6/gap4. Resolved weights are explicit; remaining width/tracking/rasterisation and reference-JPEG differences. | | [st-092-pair.png](https://git.justinvisser.org/attachments/8198e565-4ab0-40e5-922f-f93643428fb5) | nav/pills/titles Bold 700; body 400 | 8.9893 | 6.9175% | 5.7960 | Pill border: 1dp/2px nominal inset; 40px box; white22% / ink40%; dash6/gap4. Requests count 5 is capture-fixture input only; no running Requests data/provider exists. Font metrics also differ. | | [st-093-pair.png](https://git.justinvisser.org/attachments/60d39574-558e-4d7d-a3cf-9dfcd4ce488e) | nav/pills/titles Bold 700; body 400 | 5.5056 | 4.2465% | 6.0696 | Pill border: 1dp/2px nominal inset; 40px box; white22% / ink40%; dash6/gap4. Resolved weights are explicit; remaining width/tracking/rasterisation and reference-JPEG differences. | | [st-094-pair.png](https://git.justinvisser.org/attachments/cadbcd75-5218-4290-915b-194e9a3bc0ce) | upstream typography unchanged | 17.4691 | 38.2254% | 15.0751 | Real upstream HomeSettingsPage replaces the prototype profile-handoff stand-in; no Wolfskin nav. Playback has a separate 11-press replay. | ### Keys and motion JVM Compose tests replay actual Android key events through the shell adapter: switch person **6**, Home hero → Settings **8**, Settings → Back → Back **10**, Home hero → Playback hand-off **11** (shell portion). They also cover last-person gear reachability, hold/repeat/key-up behavior, nav wrap and independent memory/reselect behavior. Persisted preference tests exercise the actual rollout setter and upstream-vs-Wolfskin destination selector. Two additional native tests cover first-row-only Up/Down restoration with the rollout off, and actual Backdrop visibility/owned-background occlusion across the switch. Actual Compose spring samples (stiffness 650, damping ratio 1): | Elapsed ms | Blob x, px | |---:|---:| | 50 | 554.4835 | | 99 | 628.6950 | | 150 | 665.8796 | | 200 | 680.18365 | The motion test checks no overshoot, monotonic movement and settling within 1 px by 300 ms. Captures exercise the actual native UI; they are not HTML redraws. ### Commands and results | Command/scenario | Result | |---|---| | `python3 wolfskin-design/generate_tokens.py --check` | Passed. Copied source SHA256 matches the approved spec; an isolated changed-token copy fails the negative drift check. | | `make test` via unchanged serialized `docker/gradle.sh` | **Passed: 19 design + 555 app tests, zero failures/errors/skips.** All existing tests retained; the existing palette test imports the new palette without weakening assertions/thresholds. Final Gradle result: `BUILD SUCCESSFUL in 4m 10s`. | | Native screenshot comparison | Eleven correctly sized capture pairs, raw JSON/Markdown metrics and 4× visual diff maps generated; all pairs inspected. | | Non-Gradle AGP sanity + AAPT format smoke | Public API signatures checked against pinned AGP 9.5.0. Existing debug-linked table decoded 1013 string/plural/array resource keys, including all configurations; rejection boundaries retain the exact two legal exceptions. | | Final authorized `make apk-debug` | **Passed: `BUILD SUCCESSFUL in 1m 9s`.** Four default/debug APKs compiled. The real branding task scanned 1013 resource keys/all configurations in each packaged APK and passed. Debug MPV stub/decoder limitations are unchanged; artifacts are compile proof only. | Validation history is included rather than hidden: initial new capture/key defects were repaired; Maven Central later returned 429 for Robolectric runtimes, so approved SHA512-verified existing host artifacts seeded the existing cache with a worktree-guarded temporary init script. That temporary init hook was removed after green proof; no wrapper, Dockerfile, volume definition or shared daemon changed. The first APK attempt exposed the private incremental-XML input defect; the next exposed applied-script plugin classpath isolation. The final check consumes public packaged APK artifacts from the existing typed app build script and passed. The approved plain merge retained both the Conductarr-client and Wolfskin-design module wires and passed its combined-tree gates. The first review-fix test run then caught an incorrect new near-black expectation: the approved owned background is #07070a, not RGB <.02. Instruction 012 authorized only correcting that new expectation to the declared token with one 8-bit-quantum tolerance, one extra make test, then the still-unused make apk-debug. Final results on the exact pushed head are 19 design + 555 app tests passed and four debug APKs built/branding-checked. Instruction 013 then authorized exactly one make test and one make apk-debug for the pill correction: both passed on 74033ee1 (4m 10s and 1m 9s respectively, excluding any serialized-lock wait). Each additional Gradle invocation was explicitly authorized; no further Gradle run followed this pill-head green APK proof. The temporary offline hook was removed. `make apk` was deliberately **not** used because it is the release target; the approved substitute is `make apk-debug`. ## Every pre-existing tracked file changed The table includes fork build/docs files as well as upstream files. Launcher paths in `main` are restored to upstream artwork; their existing Wolfskin counterparts now live in `app/src/default/res/`. Theme-retirement switch sites are exhaustiveness cleanup, not additional shell hooks. | File | Why changed | |---|---| | `app/build.gradle.kts` | Wire the design module, native capture tests/offline SDK directory and packaged-resource branding gate. | | `app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt` | Remove the retired fork palette wrapper; Wolfskin-owned surfaces now apply the design-module theme. | | `app/src/main/java/com/github/damontecres/wholphin/MainContent.kt` | Replace the signed-in ApplicationContent call with the one Wolfskin shell entry hook. | | `app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt` | Add the Advanced-only rollout switch and remove the retired theme default. | | `app/src/main/java/com/github/damontecres/wholphin/ui/cards/WatchedIcon.kt` | Remove the retired theme-enum branch. | | `app/src/main/java/com/github/damontecres/wholphin/ui/components/SliderBar.kt` | Remove the retired theme-enum branch. | | `app/src/main/java/com/github/damontecres/wholphin/ui/nav/Destination.kt` | Add the one serializable Wolfskin destination subtype. | | `app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt` | Register the one Wolfskin destination-content branch; other branches stay upstream. | | `app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt` | Remove the retired theme-enum branch. | | `app/src/main/java/com/github/damontecres/wholphin/ui/preferences/SwitchPreference.kt` | Remove the retired theme-enum branch. | | `app/src/main/java/com/github/damontecres/wholphin/ui/theme/Theme.kt` | Remove the retired palette import/branch and restore the upstream default/fallback. | | `app/src/main/java/com/github/damontecres/wholphin/ui/theme/colors/WolfskinThemeColors.kt` | Delete the obsolete app-local palette after moving owned UI to the design module. | | `app/src/main/proto/WholphinDataStore.proto` | Retire WOLFSKIN = 8 and reserve its numeric value. | | `app/src/main/res/mipmap-anydpi-v26/ic_banner.xml` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-hdpi/ic_launcher.webp` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-hdpi/ic_launcher_round.webp` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-mdpi/ic_launcher.webp` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-mdpi/ic_launcher_round.webp` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-xhdpi/ic_banner.png` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-xhdpi/ic_banner_foreground.png` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-xhdpi/ic_launcher.webp` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-xxhdpi/ic_launcher.webp` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp` | Restore upstream artwork in main; the existing Wolfskin artwork moves to the matching default-flavour override. | | `app/src/main/res/values/strings.xml` | Restore upstream app branding here for flavour isolation and remove the retired theme option; default-flavour overrides supply Wolfskin labels. | | `app/src/main/res/values/wolfskin.xml` | Name the Advanced rollout switch; retain original legal credit and project URL. | | `app/src/test/java/com/github/damontecres/wholphin/ui/WolfskinThemeColorsTest.kt` | Point the existing contrast/palette tests at the new design palette; preserve assertions and thresholds. | | `docker/README.md` | Document shell scope, generator/drift checks, fixtures, captures, offline runtimes and packaged-resource branding. | | `settings.gradle.kts` | Include the new :wolfskin-design module. | New files are confined to the design module, Wolfskin UI/read facade, debug fixture, default-flavour overrides and focused native/key/routing tests. No aliases or compatibility palette remain. The review repair changes only existing Wolfskin-owned UI/token generation, its native regressions and fork build documentation; no extra upstream hook/file is introduced. ## Not included / deployment boundary Home, libraries, title pages and Discover content are **not implemented in this slice**: owned routes are visibly labelled placeholders, or existing upstream Home/library content is selected by the Advanced switch. Requests data/count integration and Conductarr sign-in are not implemented; the count-five screenshot is fixture-only. A complete locale migration/non-default-flavour certification, dedicated reduced-motion verification, pointer scrim consumption and the other non-blocking review suggestions are not included in this repair. No player redesign, release build, release/tag/release-asset publication, signing/version configuration change, ADB/device interaction, installation or TV performance profiling occurred. Cross-family review and any merge belong to firstmate; release and installation still require Justin's word.
justinzeus added 1 commit 2026-11-05 14:40:27 +01:00
justinzeus added 1 commit 2026-11-05 15:01:38 +01:00
Integrate the Conductarr client with the Wolfskin shell
Some checks failed
review/design-claude Changes requested: switch-off path loses backdrop/Up focus; font weight deviation in captures
9be55c570a
Author
Owner

Cross-family design review (Claude Sonnet): changes requested. Reviewed head 9be55c570afd863b858484bc3b1b47a8a90dc678; no Gradle was run by the reviewer.

Blocking

  1. Switch-off path is not upstream-faithful. (a) Verified: the upstream Backdrop composable is only called from ApplicationContent.kt, which WolfskinApp replaces and never redraws, and ShellSurface paints an opaque background, so upstream Home/library/title pages show no backdrop when "Wolfskin screens" is off. (b) Inferred from Compose key dispatch: the shell's onKeyEvent turns every unconsumed D-pad Up into "focus the nav", so with upstream content Up from a lower row would jump to the nav instead of the row above; no test uses focusable content. Fix: draw the backdrop in the off path, move focus up first and go to the nav only when it cannot move, and add a two-row focusable-content test.
  2. Font weight deviation in the evidence frames. Tokens at weight 600/650 (nav labels, person names, pill text, labels) render at Regular in the Compose captures while the prototype is bold (st-001, 002, 010, 011, 014, 091-093). The PR table attributes all differences to metrics/antialiasing. Fix and re-capture, or get Justin's explicit acceptance.

Checked and fine: geometry/colour/focus ring/gear/skeleton/error panel/Settings rows match the prototype; claimed diff numbers reproduce independently (ffmpeg luma check) and compare.py does what the PR says; press counts 6/8/10/11 are asserted by real key-replay tests that would fail on regression; held-OK guard test is real; spring samples 554.5/628.7/665.9/680.2 px at 50/99/150/200 ms match spec; one hook, one Destination.Wolfskin subclass, upstream files touched are the spec list plus the branding/theme-retirement reverts (diffed against v1.1.0; icons byte-identical to the old main files); theme slot 8 reserved with a safe fallback; hidden switch only under Advanced settings, default on; token drift check fails on a mutated token (scratch copy, no Gradle) and tokens.json equals the approved spec source; no existing test weakened; no credential handling beyond the debug fixture, no Conductarr calls, no release/signing/version change; commits clean.

Non-blocking (nav label colour fade missing, pointer reaches nav under the Requests sheet, MainActivity outer theme not exactly upstream, locale branding half-moved, dead card key-up path, test gaps, build coupling) are in the full report: /root/.treehouse/firstmate-7bab20/2/firstmate/data/review-wolfskin-29/report.md.

Commit status review/design-claude set to failure on this head.

**Cross-family design review (Claude Sonnet): changes requested.** Reviewed head `9be55c570afd863b858484bc3b1b47a8a90dc678`; no Gradle was run by the reviewer. **Blocking** 1. **Switch-off path is not upstream-faithful.** (a) Verified: the upstream `Backdrop` composable is only called from `ApplicationContent.kt`, which `WolfskinApp` replaces and never redraws, and `ShellSurface` paints an opaque background, so upstream Home/library/title pages show no backdrop when "Wolfskin screens" is off. (b) Inferred from Compose key dispatch: the shell's `onKeyEvent` turns every unconsumed D-pad Up into "focus the nav", so with upstream content Up from a lower row would jump to the nav instead of the row above; no test uses focusable content. Fix: draw the backdrop in the off path, move focus up first and go to the nav only when it cannot move, and add a two-row focusable-content test. 2. **Font weight deviation in the evidence frames.** Tokens at weight 600/650 (nav labels, person names, pill text, labels) render at Regular in the Compose captures while the prototype is bold (st-001, 002, 010, 011, 014, 091-093). The PR table attributes all differences to metrics/antialiasing. Fix and re-capture, or get Justin's explicit acceptance. **Checked and fine**: geometry/colour/focus ring/gear/skeleton/error panel/Settings rows match the prototype; claimed diff numbers reproduce independently (ffmpeg luma check) and `compare.py` does what the PR says; press counts 6/8/10/11 are asserted by real key-replay tests that would fail on regression; held-OK guard test is real; spring samples 554.5/628.7/665.9/680.2 px at 50/99/150/200 ms match spec; one hook, one `Destination.Wolfskin` subclass, upstream files touched are the spec list plus the branding/theme-retirement reverts (diffed against v1.1.0; icons byte-identical to the old main files); theme slot 8 reserved with a safe fallback; hidden switch only under Advanced settings, default on; token drift check fails on a mutated token (scratch copy, no Gradle) and tokens.json equals the approved spec source; no existing test weakened; no credential handling beyond the debug fixture, no Conductarr calls, no release/signing/version change; commits clean. Non-blocking (nav label colour fade missing, pointer reaches nav under the Requests sheet, MainActivity outer theme not exactly upstream, locale branding half-moved, dead card key-up path, test gaps, build coupling) are in the full report: `/root/.treehouse/firstmate-7bab20/2/firstmate/data/review-wolfskin-29/report.md`. Commit status `review/design-claude` set to failure on this head.
justinzeus added 2 commits 2026-11-05 16:03:42 +01:00
Assert the declared owned-screen background colour
Some checks failed
review/design-claude B1/B2 fixed; settings pill dashed stroke 2x spec (blocking)
2ea9301069
Author
Owner

Design re-review (Claude, scoped to the delta from 9be55c57 to 2ea93010): changes requested.

Reviewed head: 2ea9301069d7d2adc5a956487d83ec284897fafe. Read-only; no Gradle/make run.

Fixed:

  • Upstream backdrop and Up focus with the switch off (round-1 B1): fixed. WolfskinApp now draws the real upstream Backdrop (same composable and BackdropService flow as ApplicationContent) behind native content, and the shell is transparent there. ShellSurface calls moveFocus(Up) first and enters the nav only when focus cannot move; nav items no longer take native focus (canFocus = false). The new UpstreamContentFocusTest (real two-row focusable slot, real key events; real red dynamic backdrop pixel off, owned-background pixel on) would fail if either fix were reverted, by reading the code. Lane: 19 + 555 tests green.
  • Font weights (round-1 B2): fixed. 600/650 now render as the platform Bold 700 face, which is what the prototype shows. Nav labels, person names, pill text and Settings titles in the refreshed pairs match the prototype's boldness. The PR table now names the weight per frame, and its attachments are the refreshed files. Note: the mapping is Bold 700, not 600/650; this is documented.
  • Nav label colour fade (N1): matches the prototype's transition: color .2s ease-out. Locale branding (N4): honestly documented as not migrated.
  • Generator output is committed, generate_tokens.py --check passes, tokens.json sha unchanged. No public design-module symbol removed or renamed. No tests deleted or weakened. Nothing outside the allowed file list changed.

Blocking (new, in code the delta did not touch; I missed it in round 1): the dashed outline on the Settings pills ("Wolfskin screen", "Wolfskin", "upstream screen") is twice the prototype's thickness. Spec: 2 px dashed at the 1920 stage = 1 dp. SettingsPill strokes with the focus-ring token (2 dp = 4 px). Measured on st-091 at native resolution: prototype 2 px, Compose 4 to 5 px. It is visible in the pairs and the PR table does not name it. Fix: use a 1 dp shell token for the pill outline, re-capture st-091/092/093, and name it in the table.

Everything else in the delta is sound. Commit status review/design-claude = failure on 2ea93010.

**Design re-review (Claude, scoped to the delta from 9be55c57 to 2ea93010): changes requested.** Reviewed head: `2ea9301069d7d2adc5a956487d83ec284897fafe`. Read-only; no Gradle/make run. Fixed: - **Upstream backdrop and Up focus with the switch off (round-1 B1): fixed.** `WolfskinApp` now draws the real upstream `Backdrop` (same composable and `BackdropService` flow as `ApplicationContent`) behind native content, and the shell is transparent there. `ShellSurface` calls `moveFocus(Up)` first and enters the nav only when focus cannot move; nav items no longer take native focus (`canFocus = false`). The new `UpstreamContentFocusTest` (real two-row focusable slot, real key events; real red dynamic backdrop pixel off, owned-background pixel on) would fail if either fix were reverted, by reading the code. Lane: 19 + 555 tests green. - **Font weights (round-1 B2): fixed.** 600/650 now render as the platform Bold 700 face, which is what the prototype shows. Nav labels, person names, pill text and Settings titles in the refreshed pairs match the prototype's boldness. The PR table now names the weight per frame, and its attachments are the refreshed files. Note: the mapping is Bold 700, not 600/650; this is documented. - Nav label colour fade (N1): matches the prototype's `transition: color .2s ease-out`. Locale branding (N4): honestly documented as not migrated. - Generator output is committed, `generate_tokens.py --check` passes, `tokens.json` sha unchanged. No public design-module symbol removed or renamed. No tests deleted or weakened. Nothing outside the allowed file list changed. Blocking (new, in code the delta did not touch; I missed it in round 1): the dashed outline on the Settings pills ("Wolfskin screen", "Wolfskin", "upstream screen") is twice the prototype's thickness. Spec: 2 px dashed at the 1920 stage = 1 dp. `SettingsPill` strokes with the focus-ring token (2 dp = 4 px). Measured on st-091 at native resolution: prototype 2 px, Compose 4 to 5 px. It is visible in the pairs and the PR table does not name it. Fix: use a 1 dp shell token for the pill outline, re-capture st-091/092/093, and name it in the table. Everything else in the delta is sound. Commit status `review/design-claude` = failure on `2ea93010`.
justinzeus added 1 commit 2026-11-05 16:42:06 +01:00
Match the settings pill border to the prototype
All checks were successful
review/design-claude B1/B2/N1/N4 passed Sonnet re-review at 2ea93010; B3 pill border one-frame re-check by firstmate: dash rhythm and 1dp stroke match, delta additive (6 files, pill only)
74033ee1da
justinzeus merged commit 7a878b2d81 into main 2026-11-05 16:49:28 +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#29
No description provided.