docs(android): build contract for admin theming and navigation parity (M12) #111

Merged
whitlocktech merged 1 commits from docs/android-theming-nav-plan into main 2026-08-08 06:45:17 +00:00
Member

What & why

The website merged runtime admin theming, brand assets and nav overrides to main (website#126 / docs#109). The Android app reads exactly one field of itbrand.accent — and renders a hardcoded APP_MENU, so an admin who re-skins the site from Admin → Appearance and restructures the header from Admin → Navigation sees none of it on the phone.

This adds docs/android/THEMING_AND_NAV.md as the design of record for M12, plus the PLAN.md §9 entry that anchors it — mirroring how the website side kept its build contract separate from its plan.

Plan only. No app code, no backend work. Everything the app will consume is already live on website/main: GET /public/settings gained theme (the full resolved token map) and nav_public, its brand block now returns effective values, and GET /api/v1/settings/nav serves the admin/player overrides to any authenticated account.

The points that shaped it

  • The app's palette is already the runic-gateway preset, value for value — M5 was drawn from the same theme.css the preset was later extracted from. So the website's governing invariant ("an untouched instance renders as before") carries over as a testable ColorScheme equality assertion, not an approximation.
  • Radii apply as a ratio against that baseline, not as literal dp. The app's Shapes came from the M5 mockup and genuinely differ (medium 12dp vs --radius-card 10px); a literal mapping would restyle the untouched app the day this ships, and copying the app's scale into the server would be a second source of truth.
  • Fonts are bundled, not downloadable — the Play Store font provider makes a de-Googled device fall back silently. Seven families join the already-bundled Cinzel (~1.5–2.5 MB against a 4.2 MB signed release).
  • Nav overrides are keyed by website paths, so the app needs a path → route table — the one new cross-repo coupling here. An override for a path the app does not surface in its menu (champs / guilds / governors / houses, which live behind the Shard hub) is ignored: a nav override may never introduce navigation.
  • The gates are untouched. MenuAccess and MenuEntry.feature still run after the merge, so hidden: false cannot un-hide what a role or the shard's visibility config withholds — the same boundary the website's §7 draws.
  • A trap worth the paragraph it gets: theme_visual and brand_assets ride along in the same payload as the resolved theme and brand. Re-deriving a palette from those raw rows would be a second resolveThemeTokens in Kotlin, guaranteed to drift. The app models neither.

Two things measured rather than assumed

  • The Color.kt migration is one file and seven constants. ThemeComponents.kt's other seven imports are semantic pill colors that stay fixed; ShardComponents.kt's two are semantic as well. Nothing else in the app reaches past MaterialTheme.colorScheme — the M5 design premise paying off, and most of why this milestone is affordable.
  • Phase 7 is thin, and the doc says so. nav_player reaches two app rows and nav_admin two (/player, /account, /admin, /admin/moderation). The sidebar's other ~18 rows are admin configuration the app excludes, and two of the app's four staff entries are aggregates with no single web row. Hence label-and-hidden-only, and hence that phase is scheduled last and marked optional — dropping it costs nothing phases 0–6 depend on.

Shape of the build

Nine phases into a fresh edge in Android-app/ and docs/, reaching main as one edgemain merge — the same shape the website side used. Phase 0 (contract + appearance store) carries a hard rule: it must change nothing on screen.

How it was tested

Documentation only — nothing to build or run. Every claim about current behavior was checked against the code rather than from memory:

  • the 15-token ↔ ui/theme/Color.kt mapping, value by value, against server/src/config/themePresets.js;
  • the direct ui.theme.Shard* import surface (two files, sixteen imports, seven themable);
  • the app's Shapes vs the web radius tokens, which is what forced the ratio decision;
  • the font shortlist (12 options, 8 families) in themePresets.js FONT_OPTIONS;
  • the public NAV array in SiteHeader.jsx and the player/admin navs against APP_MENU / Routes.kt, which is where the unmapped rows came from;
  • getPublic() in settings.model.js and the /api/v1/settings router group for the wire shapes.

Checklist

  • I have read CONTRIBUTING.md.
  • The change builds and existing tests/checks pass locally. (Markdown only.)
  • I have added or updated tests/docs where it makes sense. (This PR is the docs.)
  • My commits are reasonably scoped with clear messages.

AI-assisted contributions (required)

  • AI tools were used. Tool(s): Claude Code (Opus 5). I have reviewed and understand every change, and take responsibility for it. AI-authored commits are marked with a Co-Authored-By trailer.

License

  • I agree that my contribution is licensed under this project's license (GNU GPL v3.0 or later), and I have the right to contribute it.
## What & why The website merged runtime admin theming, brand assets and nav overrides to `main` (website#126 / docs#109). The Android app reads **exactly one field of it** — `brand.accent` — and renders a hardcoded `APP_MENU`, so an admin who re-skins the site from Admin → Appearance and restructures the header from Admin → Navigation sees none of it on the phone. This adds **`docs/android/THEMING_AND_NAV.md`** as the design of record for **M12**, plus the `PLAN.md` §9 entry that anchors it — mirroring how the website side kept its build contract separate from its plan. **Plan only.** No app code, no backend work. Everything the app will consume is already live on `website/main`: `GET /public/settings` gained `theme` (the full resolved token map) and `nav_public`, its `brand` block now returns *effective* values, and `GET /api/v1/settings/nav` serves the admin/player overrides to any authenticated account. ### The points that shaped it - **The app's palette is already the `runic-gateway` preset**, value for value — M5 was drawn from the same `theme.css` the preset was later extracted from. So the website's governing invariant ("an untouched instance renders as before") carries over as a *testable `ColorScheme` equality assertion*, not an approximation. - **Radii apply as a ratio against that baseline, not as literal dp.** The app's `Shapes` came from the M5 mockup and genuinely differ (`medium` 12dp vs `--radius-card` 10px); a literal mapping would restyle the untouched app the day this ships, and copying the app's scale into the server would be a second source of truth. - **Fonts are bundled, not downloadable** — the Play Store font provider makes a de-Googled device fall back silently. Seven families join the already-bundled Cinzel (~1.5–2.5 MB against a 4.2 MB signed release). - **Nav overrides are keyed by *website* paths**, so the app needs a path → route table — the one new cross-repo coupling here. An override for a path the app does not surface in its menu (champs / guilds / governors / houses, which live behind the Shard hub) is **ignored**: a nav override may never *introduce* navigation. - **The gates are untouched.** `MenuAccess` and `MenuEntry.feature` still run *after* the merge, so `hidden: false` cannot un-hide what a role or the shard's visibility config withholds — the same boundary the website's §7 draws. - **A trap worth the paragraph it gets:** `theme_visual` and `brand_assets` ride along in the same payload as the resolved `theme` and `brand`. Re-deriving a palette from those raw rows would be a second `resolveThemeTokens` in Kotlin, guaranteed to drift. The app models neither. ### Two things measured rather than assumed - **The `Color.kt` migration is one file and seven constants.** `ThemeComponents.kt`'s other seven imports are semantic pill colors that stay fixed; `ShardComponents.kt`'s two are semantic as well. Nothing else in the app reaches past `MaterialTheme.colorScheme` — the M5 design premise paying off, and most of why this milestone is affordable. - **Phase 7 is thin, and the doc says so.** `nav_player` reaches two app rows and `nav_admin` two (`/player`, `/account`, `/admin`, `/admin/moderation`). The sidebar's other ~18 rows are admin *configuration* the app excludes, and two of the app's four staff entries are aggregates with no single web row. Hence label-and-hidden-only, and hence that phase is scheduled **last and marked optional** — dropping it costs nothing phases 0–6 depend on. ### Shape of the build Nine phases into a fresh **`edge`** in `Android-app/` and `docs/`, reaching `main` as **one `edge` → `main` merge** — the same shape the website side used. Phase 0 (contract + appearance store) carries a hard rule: it must change nothing on screen. ## How it was tested Documentation only — nothing to build or run. Every claim about current behavior was checked against the code rather than from memory: - the 15-token ↔ `ui/theme/Color.kt` mapping, value by value, against `server/src/config/themePresets.js`; - the direct `ui.theme.Shard*` import surface (two files, sixteen imports, seven themable); - the app's `Shapes` vs the web radius tokens, which is what forced the ratio decision; - the font shortlist (12 options, 8 families) in `themePresets.js` `FONT_OPTIONS`; - the public `NAV` array in `SiteHeader.jsx` and the player/admin navs against `APP_MENU` / `Routes.kt`, which is where the unmapped rows came from; - `getPublic()` in `settings.model.js` and the `/api/v1/settings` router group for the wire shapes. ## Checklist - [x] I have read [CONTRIBUTING.md](CONTRIBUTING.md). - [x] The change builds and existing tests/checks pass locally. *(Markdown only.)* - [x] I have added or updated tests/docs where it makes sense. *(This PR is the docs.)* - [x] My commits are reasonably scoped with clear messages. ## AI-assisted contributions (required) - [x] AI tools were used. Tool(s): `Claude Code (Opus 5)`. I have reviewed and understand every change, and take responsibility for it. AI-authored commits are marked with a `Co-Authored-By` trailer. ## License - [x] I agree that my contribution is licensed under this project's license (**GNU GPL v3.0 or later**), and I have the right to contribute it.
wtclaude added 1 commit 2026-08-08 06:40:35 +00:00
The website merged runtime admin theming, brand assets and nav overrides to
main (website#126 / docs#109). The app reads exactly one field of it --
brand.accent -- and renders a hardcoded APP_MENU, so an admin who re-skins the
site and restructures the header sees none of it on the phone.

Adds docs/android/THEMING_AND_NAV.md as the design of record for M12, and the
PLAN.md §9 entry that anchors it. Plan only: no app code, no backend work.
Everything consumed is already live on website/main.

The points that shaped it:

- The app's ui/theme/Color.kt palette is already, value for value, the
  runic-gateway preset -- M5 was drawn from the same theme.css the preset was
  later extracted from. So "an untouched instance is unchanged" carries over as
  a testable ColorScheme equality assertion, not an approximation.
- Radii apply as a ratio against that baseline, not as literal dp. The app's
  Shapes came from the M5 mockup and genuinely differ (medium 12dp vs
  --radius-card 10px); a literal mapping would restyle the untouched app the
  day this ships, and copying the app's scale into the server would be a second
  source of truth.
- Fonts are bundled, not downloadable: the Play Store font provider makes a
  de-Googled device fall back silently. Seven families join the bundled Cinzel.
- Nav overrides are keyed by website paths, so the app needs a path -> route
  table -- the one new cross-repo coupling here. An override for a path the app
  does not surface in its menu is ignored: a nav override may never introduce
  navigation.
- The gates are untouched. MenuAccess and MenuEntry.feature still run after the
  merge, so hidden:false cannot un-hide what a role or the shard's visibility
  config withholds.
- Read the resolved theme/brand fields, never the raw theme_visual/brand_assets
  rows that ride along in the same payload -- re-deriving a palette from them
  would be a second resolveThemeTokens in Kotlin, guaranteed to drift.

Nine phases into a fresh edge in both repos, reaching main as one edge -> main
merge, the same shape the website side used. Phase 0 must change nothing on
screen. Phase 7 (the authenticated navs) is marked optional: nav_player reaches
two app rows and nav_admin two, which is a thin return for a new authenticated
fetch and its cache teardown.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgfKv5cz5pbY3dPeofSE5a
wtclaude changed title from docs(android): build contract for admin theming &amp; navigation parity (M12) to docs(android): build contract for admin theming and navigation parity (M12) 2026-08-08 06:40:41 +00:00
whitlocktech merged commit e7dea29885 into main 2026-08-08 06:45:17 +00:00
whitlocktech deleted branch docs/android-theming-nav-plan 2026-08-08 06:45:18 +00:00
Sign in to join this conversation.
No description provided.