docs(android): M12 phase 3 as landed
§5.3 rewritten against what the build measured rather than what the plan estimated, plus a "Phase 3 as landed" section and the ✅ in §8. Three corrections to the drafted section: - The APK estimate was wrong by more than 3×. "Roughly 1.5-2.5 MB" becomes a measured before/after table: 5,031,411 B → 13,574,703 B, +8.15 MiB. The section now records that Merriweather is 6.08 MiB of that on its own, and the two cheaper options the org lead costed and declined, so the number is not re-litigated from scratch later. - Family lookup must be global, never scoped to the role's option list. Two of the three presets publish a font their own role's dropdown does not offer, because resolveThemeTokens copies preset tokens verbatim — the same bypass §5.4 already records for --shadow-card. - Italics mirror the website's four rather than being skipped, and every bundled family supplies the four weights the scale asks for (with IM Fell English's single weight and Cinzel's untouched 500/600/700 as the two documented exceptions), because a family is not confined to the role its dropdown lives in. Android-app: RunicGateway/Android-app#TBD Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -239,21 +239,57 @@ is constructed server-side and the only part that carries the choice:
|
|||||||
<anything unrecognized> → the role's shipped family
|
<anything unrecognized> → the role's shipped family
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**The lookup is one global map, never scoped to the role's option list** —
|
||||||
|
settled in phase 3, and the same trap §5.4 hit with `--shadow-card`. The server
|
||||||
|
validates an *admin-entered* font against `FONT_OPTIONS[role]`, but a preset's
|
||||||
|
tokens are copied verbatim by `resolveThemeTokens` and never pass through that
|
||||||
|
list: `modern` publishes `--display: 'Work Sans', Arial, sans-serif`, which the
|
||||||
|
display dropdown does not offer, and `fantasy` publishes
|
||||||
|
`--sans: 'EB Garamond', Georgia, serif`, which the sans dropdown does not either.
|
||||||
|
A per-role lookup would have missed the display face of one preset and the label
|
||||||
|
face of the other.
|
||||||
|
|
||||||
The three roles map onto `Type.kt`'s existing three groups verbatim:
|
The three roles map onto `Type.kt`'s existing three groups verbatim:
|
||||||
`--display` → the Cinzel display/headline/title block, `--serif` → the `AppSerif`
|
`--display` → the Cinzel display/headline/title block, `--serif` → the `AppSerif`
|
||||||
body block, `--sans` → the `AppSans` label block. Sizes, weights and tracking do
|
body block, `--sans` → the `AppSans` label block. Sizes, weights and tracking do
|
||||||
not move — only the family.
|
not move — only the family.
|
||||||
|
|
||||||
**IM Fell English has no bold weight** (the website doc records the same). A
|
Because a family is not confined to the role its dropdown lives in, **every
|
||||||
`FontWeight.Bold` request against it must resolve to its single weight rather
|
bundled family supplies all four weights the scale asks for** — 400 (body), 500
|
||||||
than synthesize; check what Compose does here on device and pin the behavior in
|
and 700 (labels), 600 (display) — pinned through `FontVariation` on the variable
|
||||||
the phase's notes.
|
faces. Two exceptions, both upstream facts rather than choices:
|
||||||
|
|
||||||
APK cost: roughly **1.5–2.5 MB** across seven families, variable-axis where Google
|
- **IM Fell English has one weight per style.** Its 400 face answers all four
|
||||||
Fonts publishes one (Cinzel, EB Garamond, Merriweather, Playfair Display, Inter,
|
requests and Android synthesises the bold; the website's dropdown labels it
|
||||||
Work Sans, Source Sans 3) and single-weight for IM Fell English. Measure the
|
"(no bold weight)" for the same reason. What that synthesis actually looks like
|
||||||
release APK before and after, and record both numbers in the PR — R8 does not
|
is an AC-5 observation, not something a unit test can pin.
|
||||||
shrink `res/font/`.
|
- **Cinzel keeps the 500/600/700 it shipped with in M5.** It is the only family
|
||||||
|
the server offers in the display role alone, so nothing can ask it for 400 and
|
||||||
|
adding an instance would have edited M5's type for no reachable case.
|
||||||
|
|
||||||
|
**Italics mirror the website's set**, decided in phase 3: EB Garamond, IM Fell
|
||||||
|
English, Merriweather and Playfair Display carry a true italic, exactly the four
|
||||||
|
`client/index.html` requests one for. Inter, Work Sans, Source Sans 3 and Cinzel
|
||||||
|
are upright-only and Compose skews them — which is what the app already did for
|
||||||
|
every family before this milestone, and what the web does for its own
|
||||||
|
upright-only faces. The app draws italic in two places.
|
||||||
|
|
||||||
|
APK cost, **measured, not estimated** — the drafted "roughly 1.5–2.5 MB" was
|
||||||
|
wrong by more than 3×. Unsigned release APK, R8 full-mode + resource shrink, on
|
||||||
|
`edge` at the phase 2 merge:
|
||||||
|
|
||||||
|
| | added (compressed) | release APK |
|
||||||
|
|---|---|---|
|
||||||
|
| before phase 3 | — | 5,031,411 B — 4.80 MiB |
|
||||||
|
| after phase 3 | 8,543,292 B — 8.15 MiB | 13,574,703 B — **12.94 MiB** |
|
||||||
|
|
||||||
|
**Merriweather is 6.08 MiB of the 8.15** — upstream ships it as a three-axis
|
||||||
|
`[opsz,wdth,wght]` variable font with a full charset, 4.6 MB per style, and it
|
||||||
|
deflates only 31% where the others manage 50–60%. Bundling it verbatim anyway was
|
||||||
|
the org lead's call, taken with the cheaper options costed: Google's own static
|
||||||
|
400/700 builds would have held the whole app near 6.8 MiB, and not bundling it at
|
||||||
|
all near 6.2 MiB, at the price of a serif option that silently does nothing on
|
||||||
|
Android. R8 does not shrink `res/font/`; the APK's deflate is the only saving.
|
||||||
|
|
||||||
### 5.4 Shadow depth
|
### 5.4 Shadow depth
|
||||||
|
|
||||||
@@ -514,7 +550,7 @@ PR against `main`.
|
|||||||
| **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 |
|
| **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) |
|
| **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 |
|
| **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 |
|
| **4** | **Brand assets** | Logo in the drawer header and top bar, hero on Home (§5.6). Coil + `LocalAssetResolver` already exist; renders nothing when unset |
|
||||||
| **5** | **Public nav: label / order / hidden** | The path→route table (§6.2), `Routes.news(category)`, the merge, drawer wiring. AC-3 |
|
| **5** | **Public nav: label / order / hidden** | The path→route table (§6.2), `Routes.news(category)`, the merge, drawer wiring. AC-3 |
|
||||||
| **6** | **Public nav: sections & added links** | Drawer groups, `pruneNav` port, link path validation, native-route resolution + Custom Tab fallback (§6.3) |
|
| **6** | **Public nav: sections & added links** | Drawer groups, `pruneNav` port, link path validation, native-route resolution + Custom Tab fallback (§6.3) |
|
||||||
@@ -660,6 +696,49 @@ proven not to have moved anything.
|
|||||||
in phase 8, where the new shadow should be looked at with the flat build beside
|
in phase 8, where the new shadow should be looked at with the flat build beside
|
||||||
it.
|
it.
|
||||||
|
|
||||||
|
### Phase 3 as landed
|
||||||
|
|
||||||
|
- **The APK nearly tripled, and that was a decision rather than a discovery.**
|
||||||
|
§5.3 now carries the measured before/after and the two cheaper options that
|
||||||
|
were costed and declined. The one number worth remembering: Merriweather is
|
||||||
|
75% of the payload, because upstream publishes it as a three-axis variable font
|
||||||
|
that barely compresses. Anyone revisiting the app's size should start there and
|
||||||
|
nowhere else.
|
||||||
|
- **The per-role font list is not the set of values a role can hold.** Two of the
|
||||||
|
three presets publish a font their own role's dropdown does not offer (§5.3).
|
||||||
|
The resolution map is therefore global, keyed by the lowercased first family
|
||||||
|
name, and the tests assert both preset cases by name so a future per-role
|
||||||
|
"tidy-up" fails loudly.
|
||||||
|
- **`Typography` implements `equals`** — like phase 2's `Shapes` and unlike phase
|
||||||
|
1's `ColorScheme`, checked the same way in the material3 1.3.0 bytecode. AC-1's
|
||||||
|
type half is one comparison against a verbatim copy of the pre-M12 scale held in
|
||||||
|
the test, so a stray edit to a size or a letter-spacing in `Type.kt` fails there
|
||||||
|
rather than quietly redefining what "shipped" means.
|
||||||
|
- **No `LocalShardTypeface`, deliberately.** The palette and the structure each
|
||||||
|
needed a composition local for the parts `MaterialTheme` cannot carry; the
|
||||||
|
families need none. Every text style in the app comes from
|
||||||
|
`MaterialTheme.typography`, and the two places that override anything
|
||||||
|
(`NotificationsScreen`'s hint, `BlockRenderer`'s italic title) override the
|
||||||
|
*style*, not the family. `FontFamily.Monospace` on the recovery-codes screen
|
||||||
|
stays fixed, as a semantic choice rather than a themed one.
|
||||||
|
- **`Type.kt`'s `val Typography` became `shardTypography(faces)`**, which is the
|
||||||
|
whole migration: the three families were referenced from that one file and
|
||||||
|
nowhere else, so unlike phase 1's colour imports and phase 2's 24 `Card(` sites
|
||||||
|
there was no call-site sweep at all.
|
||||||
|
- **Licences live in `app/licenses/`**, one `*-OFL.txt` per family, never under
|
||||||
|
`res/font/` — aapt rejects a `.txt` there, the M5 gotcha. Three of the seven
|
||||||
|
carry a Reserved Font Name (Merriweather, Playfair Display, Source Sans 3),
|
||||||
|
which is a further reason the binaries are taken verbatim rather than subsetted
|
||||||
|
or instanced locally.
|
||||||
|
- Tests: `ShardTypefaceTest` (15 — the shipped families and the pre-M12 scale, the
|
||||||
|
`runic-gateway` no-op, both preset bypasses by name, all thirteen shortlist
|
||||||
|
options, first-name parsing against quoting/casing/whitespace, per-field
|
||||||
|
fallback, a colours-only theme, blank and comma-only stacks, and a themed scale
|
||||||
|
proved to differ from the shipped one *only* in its families). **401 unit tests
|
||||||
|
green** (386 + 15), `lintDebug` and `assembleDebug` clean. Not exercised on
|
||||||
|
device — that is AC-5, in phase 8, where IM Fell English's synthesised bold is
|
||||||
|
the thing to look at.
|
||||||
|
|
||||||
### Phase 7, cancelled
|
### Phase 7, cancelled
|
||||||
|
|
||||||
Reading `nav_admin` / `nav_player` is **not scheduled**. The measurement in §6.4
|
Reading `nav_admin` / `nav_player` is **not scheduled**. The measurement in §6.4
|
||||||
|
|||||||
Reference in New Issue
Block a user