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
The effort pass, end to end. Every screen was run through one test — if I
remove this sentence, does the customer make a worse decision? — and the parts
that failed it are gone.
The flow
Home ▸ BOOK ▸ Where is it going? ▸ When shall we collect? ▸ details ▸ booked
BOOK opens a sheet, not a form. The destination is browsed state-then-district
because a flat list of every serviceable district survives twelve and not
sixty, and search cuts across states because somebody who knows they are
sending to Chennai should not have to know which state it is in. Districts
multi-select, but only where the server allows it: BookingLimits advertises
maxDestinations: 1 until the Miler build keys on consignmentid, and a sheet
that ignored that would sell a booking the network cannot complete.
The pickup window is now a step the customer answers rather than a slot chosen
for them. A pickup window is a promise about somebody's afternoon.
What the screens stopped saying
Home lost the orb caption for returning customers and a four-cell live card.
Send lost the city strip, both address fields, the optional disclosure and
three sentences about charging — the route, the packages and the button are
what is left. Tracking lost a radar with a bike in it, a Milers-in-your-zone
count, a "Step 2 of 7" and a sentence describing the screen you were looking
at. The window sheet lost "Fastest pickup", "4 Milers nearby" and "Relaxed
evening handover".
Type
Poppins, which has no variable release — four static cuts, and the sans styles
set fontWeight alone because fontVariations on a static font is ignored in
silence. Every weight dropped a step and the tracking went deeper: Poppins is
built on near-circles and carries more ink than the humanist faces before it.
Objects
One lit sphere on Home, and the primary button now takes its gradient and rim
because a committing action that is not lit like the hero reads as a different
material. The tracking rail's connector is crimson as far as the parcel has
come, so the line is the progress bar. Confirmation is a white tick on green:
crimson is this app's action colour and that screen has nothing left to do.
Bugs found on the way
The OTP screen dropped digits. Four fields passing focus along lose a keystroke
that arrives mid-transition, so "1234" became "124" and the screen answered
"That code did not match" — blaming the customer for its own race. One field
now, four boxes that only draw.
Nothing ever asked for the customer's location: detectPickupLocation was the
OTP screen's job, so a restored session or an auto-login never triggered the
permission prompt and the pickup map had nothing to centre on.
The launcher icon and both splash screens pointed at a house drawn as two
vector paths — a placeholder that shipped.
The splash clock started when the widget was built rather than when it was
visible, so the truck got 0.45s of a 1.8s beat behind Android's own splash.
It waits on waitUntilFirstFrameRasterized now, raced against a timeout so a
binding that never reports one cannot strand the app.
Also: design/screens/ holds all 19 screens under readable names, tool/ has the
scripts that refresh them and rebrand the Lottie, and DESIGN.md is current.
flutter analyze clean. 88 tests, 1 skipped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EqVJPB9B4QuieZnBAAKgYQ
Picks up where 0d66627 left off. Three things.
SIGN-IN, CUT TO THE QUESTION IT ASKS
It opened with a quarter-screen crimson hero carrying a lockup, a CUSTOMER
badge, a display-size promise and a sub-line, then a Phone/Email toggle, then a
labelled field, then a card explaining what a verification code is. Six things
to read before the one thing to do.
Nobody arrives at a sign-in screen needing to be sold the product. It is a
heading, a field and a button now, which is where Uber, Bolt and Porter put
them. The toggle became one quiet line under the field — choosing a method was
the first decision on the screen, before the customer had seen what was being
asked, and almost everyone uses the phone. The privacy card went: it explained
that a code would be sent, which the next screen demonstrates a second later.
`DmTextField.label` is nullable for this — "Enter your mobile number" above a
field captioned "Phone number" is one sentence printed twice.
BOOKING, DOWN TO ONE SCREENFUL
Landmark, recipient name and recipient phone are all optional and were all
drawn at the weight of the two fields that are not, putting six rows of "you
may skip this" between the address and the button. They fold behind one row
that counts what is filled in rather than just saying "optional".
Four crimson section heads became one. An accent used five times on a screen is
not an accent; crimson now marks the destination, which is the only choice that
changes the price.
Together those put the window, the package count and the CTA above the fold.
THE OFFLINE BUILD, BACK, UNDER TWO RULES
Deleted on 15 Sep after it cost two rounds of hunting for bookings in the admin
console that had never left the phone. That was not caused by the fake
existing — it was caused by a fake that did not announce itself and that
nothing stopped from shipping. Both are closed:
* `useDevData` is false in a release whatever the defines say;
* `describe` leads with DEV DATA (offline) and shows "no network" rather than
a host the build never contacts.
It is opt-in — `flutter run` still talks to the real API — which is the
property whose absence caused the original mess. `devAutoLogin` is deliberately
false under FLUTTER_TEST so the widget tests keep driving the real entrance.
flutter run --dart-define=DM_MOCK=true
86 tests green, analyze clean.
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>