feat(theme): resolve the shard's palette into the Material scheme (M12 phase 1) #34
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/m12-phase-1-colors"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What & why
M12 phase 1 — colors. Design of record:
docs/android/THEMING_AND_NAV.md§5.1. Docs pair: docs#113.Phase 0 landed the store and deliberately read nothing out of it. This is the first phase that shows: the fifteen themable tokens of
GET /public/settings'thememap are parsed into aShardPaletteand applied field by field over the shipped palette. Ten of them have a Material role and go throughdarkColorScheme; the other five reach screens through a newLocalShardPalette.The invariant that makes this cheap is now a test rather than a claim: the app's M5 palette is the website's
runic-gatewaypreset, so an instance with notheme_visualrow does not resolve to something close to today's scheme — it resolves to the same one, role for role.No backend work; all of it is live on
website/main.The proof (AC-1) needed two things the plan didn't anticipate
ColorSchemehas noequals. material3 1.3.0 declares none — checked against the artifact rather than assumed. So "the fullColorSchemeequality, not a spot check" is a field-by-field compare by reflection over everyColor-valued getter (36 roles), not a hand-written list of the roles the mapping happens to set. A role Material adds, or one the mapping forgets, cannot escape it; a guard on the count fails if the reflection ever stops seeing them.ShardColorScheme, held in the test rather than referenced — the same device that lockedhtmlShell's output on the website side. The proof is against what the app used to do, not against what the new code does today.One visible consequence, and it is the intended fix
§5.1 called
RunicGatewayTheme(accent)"wrong twice over" — it put--accentonprimary, which the contract assigns to--accent-bright, and ignored the other fourteen tokens. Correcting it moves something for one class of instance, so stating it plainly:An instance that wants accented buttons sets the accent from Admin → Appearance, which is what the token map is for. The env path itself keeps working, which §5.1 requires: the server resolves
brand.accentastheme['--accent'] || env, so the token and the brand can never disagree, and the token wins where a client is holding a stale brand anyway.Smaller calls, all recorded in the doc
ShardPillFgis derived, not a sixteenth token. §5.1 lists it as themable but the table has fifteen rows and none is it — its value isShardCta's, both--accent-bright. It followsctaunder the rule §5.1 states forShardOnCta, so the neutral pill's text tracks the CTA fill instead of freezing at today's literal.ShardPalette.resolve(theme, brandAccent)takes a map andshardColorScheme(palette)takes a palette; neither knows aboutSiteAppearance, so AC-1 and AC-2 are plain JVM assertions with no Compose test rule.RunicGatewayThemeis the one place the two meet.toneColorsbecame@Composableto read the palette. Private, called only fromStatusPill— the alternative would have leaked a palette parameter into a public signature.The migration check §5.1 asked for
ThemeComponents.ktwas the only file reaching pastMaterialTheme.colorSchemefor a themable color, and leaving one behind is a card that stays blue on a Fantasy shard without failing to compile. After the migration:Nine imports, all of them semantic constants that are never themed — exactly what §5.1 predicted would be left.
How it was tested
./gradlew testDebugUnitTest lintDebug assembleDebug— 373 unit tests green (360 before), lint and assemble clean.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) andShardColorSchemeTest(4 — AC-1 from both ends, the role-set guard, and a themed shard moving its roles and only its roles).Checklist
AI-assisted contributions (required)
Claude Code (Opus 5). I have reviewed and understand every change, and take responsibility for it. AI-authored commits are marked with aCo-Authored-Bytrailer.License