The canvas was #F1F0EC — a warm grey that made every screen look slightly
dull. It is white.
── You cannot have both ──
That canvas was not an accident: it was deepened from #F7F6F4 on 2026-09-23
precisely so a white card would sit *on* something and its 1px edge could go
quiet. A white card on a white page has nothing to sit on.
Measured rather than judged by eye: after the canvas change, a pixel scan
across a card boundary on the Orders golden read #FFFFFF from end to end. The
shadow contributed nothing findable. The cards had not become subtle, they had
disappeared.
So the edge comes back. `DmCard` drew its border only for tones that are not
white — the warm canvas was doing that job for the white ones — and now draws
it for every tone, with the lift still underneath. Border plus a soft shadow is
the iOS and Revolut shape rather than the borderless Airbnb one, and that is
the price of a white page. Paid deliberately.
── What moved with it ──
`surfaceAlt` lifts to #F4F2EF. With a white page it is the only token that
makes a surface read as recessed — filled inputs, tiles, skeletons, the sunken
card — so it can no longer sit within a point of the canvas.
`DmCardTone.sunken` filled itself with the canvas colour, which on a white page
is not sunken but invisible. It takes `surfaceAlt`.
Four passes, and the shape they landed on.
The type face is Plus Jakarta Sans (variable, wght 200-800), which brings a
fix with it: it carries the rupee glyph and Switzer does not, so prices stop
being set in Geist Mono to work around a missing character. Mono stays where
it is earned - references and phone numbers, read digit by digit.
Surfaces lift rather than outline. Cards carry two very soft shadow layers
instead of a hairline, because eight outlined boxes down a screen read as a
wireframe. The tab bar floats as a pill again for the same reason it was
right to: it is now the same kind of object as everything above it.
Screens:
* Home is the greeting, the address, the sphere and one card. The card lost
its progress bar - a filling line says "wait", and a parcel two days into
a journey is not something anyone is waiting through - and gained the size
that buys.
* Orders cards are four bands: identity, destination, route, and whatever is
happening right now. Plus a search field, because the list is the archive.
* Tracking leads with the state at display size, then TRIP MILESTONES with a
step counter, then the courier.
* Review is a route thread over two particular cards.
* Account opens on the person: avatar, name, and two counted figures.
Three real bugs the redesign surfaced:
* Quick dispatch handed `loadCities()` straight to a FutureBuilder, so the
catalogue was refetched on every rebuild and Home never settled.
* Order cards showed the whole visit's weight on one destination's row -
somebody else's parcel. Per group now, and only once actually weighed.
* The pickup window was printed beside "In transit", where it reads as a
delivery time nobody promised.
Nothing invented. The reference shows EXPRESS PRIORITY, CARBON OFFSET,
CONCIERGE ELITE and hub-to-hub routing; this backend sends none of them, so
they are absent rather than mocked up.
flutter analyze: clean. flutter test: 88 passing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EqVJPB9B4QuieZnBAAKgYQ
Book a pickup, track it to delivery — the rebuilt customer app.
- Design language from doormile-screens.html: brand #8F0F06, Manrope +
Geist Mono (variable fonts), bordered cards instead of shadows, crimson
brand headers, sliding tab indicator, mono for anything read digit by digit.
- lib/data (one live API implementation, plus a debug-only offline fake),
lib/state, lib/ui (tokens, widgets, screens).
- 84 tests, plus a design snapshot harness that renders every screen with the
real fonts: flutter test test/design_snapshot_test.dart --run-skipped
--update-goldens
This replaces the previous app (pubspec 'doormile', app id
com.doormile.customer). That tree remains in history at 6c7d656; note its
android/app/google-services.json is not carried over, and the application id
here is in.doormile.customer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>