Merge pull request 'docs(android): M12 phase 1 as landed' (#113) from docs/android-theming-nav-phase-1 into edge

Reviewed-on: #113
This commit is contained in:
2026-08-08 09:57:00 +00:00
2 changed files with 54 additions and 2 deletions

View File

@@ -1042,7 +1042,10 @@ push, and Play (M6M8) 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)

View File

@@ -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