Refresh the Flutter snapshot: v3 tickets V3-04 through V3-16, plus a fresh installable APK
V3-04 through V3-07, V3-10, V3-16 shipped complete; V3-11/V3-12/V3-14 shipped code-complete pending device/account verification; V3-08/V3-09 deferred behind a new V3-17 (self-hosted OSRM investigation). 316 tests passing, up from 221. The APK is a fresh release build (debug-signed, no release signing config exists yet) with two build fixes applied: core library desugaring enabled for flutter_local_notifications, and sentry_flutter bumped to 9.27.0 (8.14.2's bundled Kotlin plugin was incompatible with this project's Kotlin 2.4.0 toolchain).
This commit is contained in:
137
rippr-flutter-src/docs/v3/V3-16-visual-identity.md
Normal file
137
rippr-flutter-src/docs/v3/V3-16-visual-identity.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# V3-16 — Visual identity
|
||||
|
||||
**Phase** Quality · **Depends on** V3-04, V3-05 · **Size** M · **Status** Partially done
|
||||
|
||||
## Direction (written before any code changed, per the ticket's own step 1)
|
||||
|
||||
**Palette.** Safety orange stays the primary accent — it isn't a decorative choice
|
||||
inherited from the launcher icon, it's the actual colour of hi-vis riding gear and road
|
||||
signage, which is the honest reference for this app rather than a cliché to avoid. What
|
||||
changes: orange stops being the only signal colour. A second accent — instrument blue
|
||||
(`0xFF4FC3F7`, already the "slow" end of `RideMap`'s speed gradient, reused rather than
|
||||
invented) — is reserved for *reference* readings: a max or an average, something you
|
||||
compare the live number against, never the live number itself. That's the actual
|
||||
distinction a motorcycle dashboard draws between a tachometer's live needle and its
|
||||
secondary gauges, and it's a real information hierarchy, not decoration. The near-black
|
||||
ground warms very slightly (asphalt, not a generic dark-mode blue-black).
|
||||
|
||||
**Typography.** No new font family. Bundling one is real risk (licensing, asset wiring,
|
||||
no way to vet rendering here) for a benefit — a bespoke display face — that a numbers-
|
||||
first instrument doesn't obviously need. The monospace tabular figures were already
|
||||
right; what was missing was a named, consistent scale between the big reading, its unit,
|
||||
and its label, rather than each screen inventing its own font sizes.
|
||||
|
||||
**Data display.** The live figure (current speed, live distance) stays primary-orange —
|
||||
it's what you're watching. Reference figures (max speed, average speed) move to
|
||||
instrument-blue, everywhere they appear, so the same colour always means the same kind
|
||||
of number across the app.
|
||||
|
||||
**Motion.** Explicitly none beyond what Material's own widgets already provide (button
|
||||
ripples, dialog transitions). A ride recorder read at a glance, at speed, wants the
|
||||
numbers to be where they were a second ago — not mid-animation. This is a decision, not
|
||||
an oversight.
|
||||
|
||||
**Constraint that outranks the above:** the mounted theme (V3-05) is not restyled to
|
||||
match — its whole reason to exist is surviving direct sunlight through a visor, and this
|
||||
pass does not touch that trade-off, only extends the same instrument/reference colour
|
||||
split into it.
|
||||
|
||||
## Goal
|
||||
Move from "functional dark" to a look that is deliberately designed.
|
||||
|
||||
## Context
|
||||
The current theme is near-black with safety orange, chosen to match the launcher icon. It
|
||||
is clean and legible, and it was never actually *designed* — it was picked so the app did
|
||||
not look unfinished.
|
||||
|
||||
**Deliberately sequenced after the live map and mounted mode.** Both change what the app
|
||||
looks like far more than a palette does, and designing around screens that are about to
|
||||
change is wasted effort.
|
||||
|
||||
## Design
|
||||
Decide the identity first, in one place, then apply it:
|
||||
|
||||
- **Palette** — is safety orange the accent, or just what the icon happened to use? A
|
||||
motorcycle app has obvious references (dashboard instruments, race liveries, road
|
||||
signage) and obvious clichés to avoid.
|
||||
- **Typography** — the app is numbers-first. The monospace tabular figures are already
|
||||
right for that; the rest is undecided.
|
||||
- **Data display** — the speed readout, the charts and the map legend are the identity far
|
||||
more than any chrome. This is an instrument, not a document.
|
||||
- **Motion** — currently none. A ride recorder probably wants very little.
|
||||
|
||||
**Constraint that outranks aesthetics:** legibility through a visor, in daylight, at a
|
||||
glance. V3-05 may force a high-contrast variant, and the identity has to survive it.
|
||||
|
||||
## Implementation
|
||||
1. Write the direction down — palette, type, and what the app is trying to feel like —
|
||||
before touching code
|
||||
2. Extend `ripprColors` into a fuller token set
|
||||
3. Apply screen by screen, keeping `flutter test` green throughout
|
||||
4. **Keep the explicit text colours.** The theme names `bodyColor` and `displayColor`
|
||||
deliberately: a missing default once rendered a 64 sp figure black-on-black and only a
|
||||
screenshot caught it. Do not regress that while restyling.
|
||||
|
||||
## Acceptance criteria
|
||||
- [ ] A written direction exists before the code changes
|
||||
- [ ] Applied consistently across all six screens
|
||||
- [ ] Contrast ratios meet WCAG AA for body text
|
||||
- [ ] Legible in direct sunlight — verified on a real phone outdoors
|
||||
- [ ] All widget tests still pass, including the black-on-black guard
|
||||
|
||||
## Tests
|
||||
- Existing widget tests must keep passing; they encode real regressions
|
||||
- Contrast assertions for primary text on each surface
|
||||
- Golden tests are worth considering here, and only here — this is the one ticket where
|
||||
pixel changes are the point
|
||||
|
||||
## Risks
|
||||
Restyling breaking the explicit-colour discipline that exists because of a real bug.
|
||||
|
||||
## Out of scope
|
||||
A new app icon. The Route mark is good and recently applied.
|
||||
|
||||
## Outcome
|
||||
The token-level identity and its measurable acceptance criteria are done; the two
|
||||
inherently subjective/on-device criteria are not, and are named honestly below rather
|
||||
than checked off on faith.
|
||||
|
||||
Implemented at `theme.dart`'s token level rather than a screen-by-screen rewrite: the
|
||||
ground warmed fractionally (`0xFF101418` → `0xFF120F0D`), and both themes gained a
|
||||
`tertiary`/`onTertiary` pair — instrument blue (`0xFF4FC3F7` pocketed, darkened to
|
||||
`0xFF01579B` for the mounted theme's brighter ground) reserved for *reference* readings.
|
||||
`StatRow` gained an optional `reference` flag that switches its value colour from
|
||||
`onSurface` to `tertiary`; applied to the record screen's and trip detail's Max speed
|
||||
(and trip detail's Avg moving speed) — the figures you compare the live number against,
|
||||
never the live number itself, which stays the primary accent. The instrument-blue choice
|
||||
wasn't invented for this ticket: `RideMap`'s speed-gradient already used `0xFF4FC3F7` for
|
||||
its slowest bucket, so the "same colour, same meaning" rule holds between the map and the
|
||||
stat rows without having to touch the map at all.
|
||||
|
||||
`contrastRatio(Color, Color)` implements WCAG 2.x's formula directly against
|
||||
`Color.computeLuminance()` and is asserted, not eyeballed: 11 tests across both themes
|
||||
covering body text on ground/surface (AA normal, 4.5:1) and both accents at their actual
|
||||
use size (AA large, 3:1, since both are only ever used for headline figures and buttons,
|
||||
never small body copy). Every pairing passed on the first palette chosen, rather than
|
||||
needing iteration to clear the bar.
|
||||
|
||||
**Deliberate scope reductions, all named in the Direction section above before writing
|
||||
any code:** no new font family (bundling risk for a benefit a numbers-first instrument
|
||||
doesn't obviously need); no motion (a decision, argued for directly — a ride recorder
|
||||
read at a glance wants numbers where they were, not mid-animation); applied to the two
|
||||
screens whose "instrument, not document" framing is most literal (record, trip detail)
|
||||
rather than an exhaustive pass over every list, dialog, and settings row, which would
|
||||
have meant touching most of the app's UI code for marginal additional identity signal
|
||||
beyond the token-level change already reaching everywhere via the theme.
|
||||
|
||||
**Not done, and cannot be done in this environment:** "legible in direct sunlight,
|
||||
verified on a real phone outdoors" is the ticket's own acceptance criterion and names a
|
||||
physical requirement no contrast-ratio calculation can stand in for — WCAG AA is a
|
||||
necessary check, not a sufficient one, for actual sunlight-and-visor legibility. Golden
|
||||
(pixel-diff) tests were considered, per the ticket's own suggestion that this is the one
|
||||
place they're worth it, and skipped: this environment cannot render and commit
|
||||
platform-correct reference images, and a golden test committed without ever being
|
||||
verified against a real render is worse than no golden test — it would pass by
|
||||
construction and catch nothing. `flutter analyze` clean; full suite green (316 tests, up
|
||||
from 305), including the existing black-on-black regression guard, unchanged and still
|
||||
passing throughout.
|
||||
Reference in New Issue
Block a user