diff --git a/android/PLAN.md b/android/PLAN.md index c4043e7..59343f1 100644 --- a/android/PLAN.md +++ b/android/PLAN.md @@ -1042,7 +1042,10 @@ push, and Play (M6–M8) follow the designed app. appearance store) carries a hard rule: it must change nothing on screen. **Phase 0 landed 2026-08-08** (Android-app#33 / docs#112): `SiteAppearance` now holds the brand, the resolved theme tokens and the parsed `nav_public`, refreshed on resume, with nothing yet reading the last - two — see THEMING_AND_NAV.md "Phase 0 as landed". + two — see THEMING_AND_NAV.md "Phase 0 as landed". **Phase 1 landed 2026-08-08** + (Android-app#34 / docs#113): the fifteen color tokens now resolve into a `ShardPalette` that + feeds both the Material scheme and a `LocalShardPalette`, and the no-op invariant is a test — + the shipped palette reproduces the pre-M12 `ColorScheme` role for role. ### Deferred (not a milestone) diff --git a/android/THEMING_AND_NAV.md b/android/THEMING_AND_NAV.md index cd60828..d14cb92 100644 --- a/android/THEMING_AND_NAV.md +++ b/android/THEMING_AND_NAV.md @@ -484,7 +484,7 @@ PR against `main`. | # | Phase | Ships | |---|---|---| | **0** ✅ | **Contract & appearance store** | `SettingsDto` gains `theme` and `nav_public`; `SiteAppearance` replaces the bare `BrandDto` in `AppViewModel`; second-stage JSON parse; resume refresh (§5.5). **No visual change** — this phase must be invisible | -| **1** | **Colors** | `ShardPalette` + `LocalShardPalette`; all direct `Color.kt` imports migrated; `RunicGatewayTheme(appearance)`; AC-1 + AC-2 tests | +| **1** ✅ | **Colors** | `ShardPalette` + `LocalShardPalette`; all direct `Color.kt` imports migrated; `RunicGatewayTheme(appearance)`; AC-1 + AC-2 tests | | **2** | **Radii & shadow** | Ratio-scaled `Shapes` (§5.2), elevation map (§5.4) | | **3** | **Fonts** | Seven bundled families + licenses; stack → `FontFamily` resolution; `Type.kt` takes its three families from the resolved theme. APK size recorded | | **4** | **Brand assets** | Logo in the drawer header and top bar, hero on Home (§5.6). Coil + `LocalAssetResolver` already exist; renders nothing when unset | @@ -537,6 +537,55 @@ proven not to have moved anything. concrete classes with `Context`/prefs dependencies, and all of the phase's logic is in the two pure modules above. +### Phase 1 as landed + +- **`ColorScheme` does not implement `equals`.** AC-1 asks for "the full + `ColorScheme` equality, not a spot check", and material3 1.3.0 simply has no + `equals`/`hashCode` on it (checked against the artifact, not assumed). The + proof is therefore a **field-by-field compare by reflection** over every + `Color`-valued getter — 36 roles in 1.3.0 — rather than a hand-written list of + the roles the mapping happens to set. A role added to Material, or one the + mapping forgets, cannot escape the assertion, and a guard on the role count + fails if the reflection ever stops seeing them. The expected value is a + **verbatim copy of the pre-M12 `ShardColorScheme`** held in the test, the same + device the website used to lock `htmlShell`'s output: the proof is against what + the app used to do, not against what the new code does today. +- **`ShardPillFg` is derived, not a sixteenth token.** §5.1 lists it among + `ThemeComponents.kt`'s themable imports but the token table has fifteen rows + and none of them is it — because its value *is* `ShardCta`'s, both + `--accent-bright`. It follows `cta` under the same rule §5.1 states for + `ShardOnCta`, so the neutral pill's text tracks the CTA fill rather than + freezing at today's literal. +- **An env-accent instance changes, and that is the intended fix.** §5.1 called + the old `RunicGatewayTheme(accent)` "wrong twice over"; correcting it has a + visible consequence worth stating plainly. A shard with a `BRAND_ACCENT_COLOR` + and **no** `theme_visual` row previously had that color on + `primary`/`secondary`/`tertiary` — so its filled CTA buttons carried the accent. + It now seeds `--accent` only, which is `secondary`/`tertiary`, and `primary` + returns to `--accent-bright`. Links and highlights keep the brand color; filled + buttons go back to the light CTA fill the design specifies. An instance that + wants its buttons accented sets the accent from Admin → Appearance, which is + what the token map is for. +- **The palette resolution is pure and the theme is the only composable.** + `ShardPalette.resolve(theme, brandAccent)` takes a token map, and + `shardColorScheme(palette)` takes a palette — neither knows about + `SiteAppearance`, so both AC-1 and AC-2 are plain JVM assertions with no + Compose test rule. `RunicGatewayTheme` is the one place the two meet. +- **The §5.1 grep came out exactly as predicted.** After the migration, + `ui.theme.Shard` imports outside `ui/theme/` are the seven semantic constants + in `ThemeComponents.kt` and the two in `ShardComponents.kt` — nothing themable + left behind, which was the phase's stated correctness risk. +- **`toneColors` became `@Composable`** to read the palette. It is private and + called only from `StatusPill`, so this costs nothing; the alternative — passing + a palette parameter through the pill's public signature — would have leaked the + theme into every call site. +- Tests: `ShardPaletteTest` (9 — the shipped no-op, all fifteen tokens landing in + the right field, AC-2's one-good-four-bad map, unknown tokens ignored, the + brand-accent fallback and the token beating it, a malformed brand accent, and + both derived colors) and `ShardColorSchemeTest` (4). **373 unit tests green**, + `lintDebug` and `assembleDebug` clean. Not exercised on device — that is AC-5, + in phase 8. + ### Phase 7, cancelled Reading `nav_admin` / `nav_player` is **not scheduled**. The measurement in §6.4