feat(appearance): read the admin theme and nav contract into a SiteAppearance (M12 phase 0) #33
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/m12-phase-0-appearance-store"
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 0 — the contract and the appearance store. Design of record:
docs/android/THEMING_AND_NAV.md§8. Docs pair: docs#112.The app reads exactly one field of the website's admin theming contract —
brand.accent. This lands the store the rest of the milestone builds on:GET /public/settings'theme(the resolved token map) andnav_public(the raw nav override row) are decoded, coerced and held beside the brand as oneSiteAppearance, refreshed on resume alongside the session re-validation.Nothing reads the two new fields yet, and that is the point. Phase 0's hard rule is that it must change nothing on screen — so
RunicAppstill takesbrand: BrandDto?, the theme is still seeded from the accent alone, andAppState.Readyis the only place a type changed. Everything after this is additive on top of a store already proven not to have moved anything.No backend work: all of it is live on
website/maintoday.The two judgement calls
Both in service of §2's forgiving-on-read rule, and both recorded in the doc's new "Phase 0 as landed":
themeis modeled as a rawJsonElement, notMap<String, String>?. §8 specified the typed map, and the contract does say so — it is a closed set of string-valued tokens, validated server-side on write. Butkotlinx.serializationfails the decode of the whole object on a value of an unexpected kind, andthemeshares its payload withbrandandpush: one odd token would have blanked the branding and dropped the push relay URL, which is the opposite of what §2 asks for. It is coerced field-by-field inSiteAppearance.frominstead, so a non-string or blank value costs exactly its own token.NONE. §5.5 said "best-effort"; the distinction it did not draw is that a moment of no connectivity on resume must not repaint a themed shard back to the shipped defaults. Only the initial load can produceNONE.What is deliberately not here
data/appearance/SettingsJson.ktmirrors the web client'slib/settingsJson.jsand makes the same single judgement (absent, malformed, or a storednull/number/string/array all read as absent). Readingitems/sections/linksout of the parsed object is phases 5 and 6's job, so no half-built nav model ships here.theme_visualandbrand_assetsare not modeled. They ride along in the same payload but they are inputs — a preset id and a sparse overlay — and re-deriving a palette from them would be a secondresolveThemeTokensin Kotlin, guaranteed to drift (§3).Also in this milestone
Phase 7 is cancelled — reading
nav_admin/nav_player. No code here either way (it was scheduled last), but the decision is recorded in docs#112: the two authenticated navs reach four app rows between them, which does not pay for a new authenticated fetch, a session-keyed cache and its teardown. The app therefore makes no authenticated settings call, and the player/staff drawer rows keep their coded — and so localized — labels. Nothing in phases 0–6 was scaffolding for it.How it was tested
./gradlew testDebugUnitTest lintDebug assembleDebug— 360 unit tests green, lint and assemble clean.SettingsJsonTest(6 cases — plain object, the wrapped public shape, absent, malformed, non-object kinds, and that the parse validates the kind not the shape) andSiteAppearanceTest(8 — the untouched instance resolving to no overrides, a failed call being the same state, token reading, empty-vs-absent, a bad token costing only itself, a wrong-kindthemenot costing the brand, the second-stage nav parse, and a malformed nav not costing the theme).PublicDtoTestfor the wire shapes, including that an older backend simply has both fields absent.AppViewModelitself stays untested: its four collaborators are concrete classes withContext/prefs dependencies, and all of this phase's logic lives in the two pure modules above.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