Redesign on the Stitch reference, in Plus Jakarta Sans
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
This commit is contained in:
181
design/DESIGN.md
181
design/DESIGN.md
@@ -95,32 +95,28 @@ their own position without being told.
|
||||
|
||||
### 2.2 Typography
|
||||
|
||||
**Poppins for voice, Geist Mono for data.** Poppins (Indian Type Foundry, SIL
|
||||
Open Font License) is the app's voice as of 2026-09-22.
|
||||
**Switzer for voice, Geist Mono for data.** Switzer (Indian Type Foundry, via
|
||||
Fontshare, ITF Free Font License — commercial use permitted) is the app's voice
|
||||
as of 2026-09-23.
|
||||
|
||||
**Poppins has no variable release.** Google Fonts ships it as static cuts only,
|
||||
so `pubspec.yaml` declares one file per weight and the sans styles below set
|
||||
`fontWeight` and nothing else — `fontVariations` on a static font is ignored in
|
||||
silence, which is worse than useless, because it reads as if it were doing the
|
||||
work. Geist Mono is still variable and its three styles still carry the axis.
|
||||
It is a neue-grotesque: flat-sided bowls, near-vertical terminals, a
|
||||
straight-tailed `y`. It was chosen against a reference whose own face is almost
|
||||
certainly PP Neue Montreal or Neue Haas Grotesk Display — both commercial, and
|
||||
neither identifiable with certainty from a skewed mockup. Switzer is the
|
||||
closest thing that can actually be bundled.
|
||||
|
||||
Only the four weights the design uses are bundled — **500, 600, 700, 800** —
|
||||
because each is a separate ~155 KB file. That is ~625 KB of sans against the
|
||||
165 KB a variable file cost, and it is the price of this typeface. Asking for
|
||||
w900 anywhere would not fail; Flutter would quietly synthesise it from Bold,
|
||||
and it would look like it.
|
||||
**One variable file again, 141 KB.** Poppins held this slot for a day and has
|
||||
no variable release: four static cuts at ~155 KB each, 625 KB of sans, and for
|
||||
that release the sans styles carried `fontWeight` alone because
|
||||
`fontVariations` on a static font is ignored in silence. The axis is back, so
|
||||
every style below sets both — the axis does the work, the enum keeps the
|
||||
semantics and the system-font fallback correct.
|
||||
|
||||
**Every weight is one step lighter than the face before it.** Poppins is built
|
||||
on near-circles and carries far more ink at the same nominal weight than a
|
||||
humanist sans. What was w900 is w800, w800 is w700, w700 is w600. At the old
|
||||
numbers the headings looked inflated rather than strong.
|
||||
|
||||
**And the tracking is deeper again** — the opposite correction from the last
|
||||
pass. Poppins is wide, round and has generous sidebearings, so display sizes
|
||||
need pulling in harder than anything this app has used: the header runs at
|
||||
**−1.2**. Body sizes stay near zero, because that same roundness is what makes
|
||||
it legible small and tracking it in takes that back. Tracking belongs to a
|
||||
typeface, not to a design.
|
||||
**Tighter than anything before it.** A grotesque at display size wants pulling
|
||||
in hard, and the reference sets its headlines almost touching: the header runs
|
||||
at **−1.4**. Body sizes stay near zero. Tracking belongs to a typeface, not to
|
||||
a design, and this is the third time these numbers have been rewritten for that
|
||||
reason.
|
||||
|
||||
Letter spacing is authored in em by the design and converted per size, because
|
||||
−0.035em is a different number of pixels on a 34pt header than on a 15pt row
|
||||
@@ -129,13 +125,13 @@ tight.
|
||||
|
||||
| Style | Size / Height / Weight | Tracking | Used for |
|
||||
| --- | --- | --- | --- |
|
||||
| `headerTitle` | 34 / 1.0 / 800 | **−1.2** | The brand header's own title |
|
||||
| `display` | 31 / 1.1 / 800 | −0.95 | The one dominant line on a screen |
|
||||
| `title` | 25 / 1.16 / 800 | −0.65 | Screen headings on canvas |
|
||||
| `sheetTitle` | 21 / 1.2 / 800 | −0.55 | Bottom-sheet titles |
|
||||
| `heading` | 18 / 1.25 / 700 | −0.4 | The head above a group of cards |
|
||||
| `cardTitle` | 15 / 1.4 / 600 | −0.15 | A row's own line — the workhorse |
|
||||
| `body` | 15 / 1.5 / 500 | −0.1 | Paragraph text |
|
||||
| `headerTitle` | 34 / 1.0 / 800 | **−1.4** | The tab root's own title |
|
||||
| `display` | 31 / 1.1 / 800 | −1.1 | The one dominant line on a screen |
|
||||
| `title` | 25 / 1.16 / 800 | −0.8 | Screen headings on canvas |
|
||||
| `sheetTitle` | 21 / 1.2 / 800 | −0.65 | Bottom-sheet titles |
|
||||
| `heading` | 18 / 1.25 / 700 | −0.45 | The head above a group of cards |
|
||||
| `cardTitle` | 15 / 1.4 / 600 | −0.2 | A row's own line — the workhorse |
|
||||
| `body` | 15 / 1.5 / 500 | −0.12 | Paragraph text |
|
||||
| `lede` | 14.5 / 1.55 / 500 | — | Paragraph under a screen heading |
|
||||
| `small` | 13.5 / 1.45 / 500 | — | The supporting line under a row title |
|
||||
| `tiny` | 13 / 1.4 / 500 | — | The quietest line |
|
||||
@@ -516,9 +512,7 @@ attached.
|
||||
names, with an index. It is a copy, and a stale copy is worse than none, so
|
||||
`tool/screens.sh` refreshes it in one command after the goldens are rendered.
|
||||
|
||||
The harness loads every Poppins cut, Geist Mono **and the Lucide icon font**
|
||||
first — every cut, because Poppins is static and a `FontLoader` given one file
|
||||
renders w800 headings by smearing the one weight it has —
|
||||
The harness loads Switzer, Geist Mono **and the Lucide icon font** first —
|
||||
without that, Flutter's test harness draws Ahem's black boxes and hollow
|
||||
squares and the pictures are worthless.
|
||||
|
||||
@@ -734,7 +728,126 @@ having been pushed up by the thing that was tapped.
|
||||
|
||||
---
|
||||
|
||||
## 11. If you are adding something
|
||||
## 11. The reference pass (2026-09-23)
|
||||
|
||||
A set of logistics-app references — white cards on a soft canvas, one dark
|
||||
accent, big reference numbers, labelled columns — and one instruction: *less
|
||||
red, less boxy, more modern*. What follows is what that actually meant in this
|
||||
codebase.
|
||||
|
||||
### 11.1 The redness had one source
|
||||
|
||||
`DmBrandHeader` was 260pt of solid brand on Home, Orders and Account — three of
|
||||
five screens opening on a wall of it. Every `onBrand*` token existed only to
|
||||
survive that field, and the accent that is supposed to mean *do this* was the
|
||||
background of half the product.
|
||||
|
||||
**It is the canvas now, with ink type.** What is left of the brand on those
|
||||
screens is the sphere, the primary button and a live dot, which is what an
|
||||
accent is for. The arcs survive at 3.5% crimson instead of 15% white — they
|
||||
were struck from the mark's geometry and were never the colour doing the work.
|
||||
|
||||
Knock-ons: the header's address track became a bordered field and then a bare
|
||||
line (below); the avatar and the Account/Orders actions dropped their on-brand
|
||||
variants; the Miler card lost its crimson wash, which on a header-less screen
|
||||
was the loudest surface left.
|
||||
|
||||
### 11.2 The canvas was doing no work
|
||||
|
||||
At `#F7F6F4` a white card was a **3% step** off the background, so the 1px
|
||||
border was separating everything and every screen read as a list of outlined
|
||||
boxes. Canvas → `#F1F0EC`, card radius **14 → 18**. The edge is still there; it
|
||||
stopped being the only thing holding the layout together.
|
||||
|
||||
That change immediately exposed two screens whose content was floating on bare
|
||||
canvas — the send screen's route and package rows, invisible as a problem at 3%
|
||||
contrast and unfinished-looking at the new one. Both are on cards now.
|
||||
|
||||
**The gutter came in too**, `DmSpace.pad` **24 → 16**. Twenty-four was right
|
||||
when the header was a crimson field and the canvas held loose rows; on a page
|
||||
of white cards it left a broad margin down both sides and the cards read as
|
||||
narrower than the screen.
|
||||
|
||||
### 11.3 Orders
|
||||
|
||||
The filter left the header. It was a segmented control recessed into the
|
||||
crimson field, which made it look like part of the title bar rather than
|
||||
something to press. It is three chips on the canvas, **ink** when selected —
|
||||
one of three is always on, and crimson there would be a permanent red block on
|
||||
a screen whose crimson is meant to be the status pills, the thing that changes.
|
||||
|
||||
The card follows the reference: **reference number and status pill, then From /
|
||||
To as two columns, then Placed / Contents, then a horizontal journey rail.**
|
||||
|
||||
Two columns rather than a vertical thread, because the rail cost the full width
|
||||
of the card to say two things; side by side, from and to are one glance and the
|
||||
same route fits in half the height — which is what lets the journey rail exist
|
||||
without the card becoming a page.
|
||||
|
||||
**The rail labels one node, not five.** The reference labels all three of its
|
||||
nodes; that works at three and does not at five — "Booking created / Order
|
||||
created / In transit / Out for delivery / Delivered" across 320pt is five
|
||||
ellipses. The line carries *how far*, the word carries *where*.
|
||||
|
||||
"SEPTEMBER 2026" went from the header: it sat above a list that is not grouped
|
||||
by month, does not scroll by month, and says "Today" and "Yesterday" on its own
|
||||
rows.
|
||||
|
||||
### 11.4 Tracking, rebuilt
|
||||
|
||||
**The map went.** It was 178pt of basemap, two labels and a pin, on a screen
|
||||
whose question is "where is my parcel". The pin was the *pickup address* — a
|
||||
place the customer chose and already knows — not the Miler, who only has a
|
||||
position for the twenty minutes between assignment and the door. A picture that
|
||||
cannot show the thing it implies invites somebody to stare at a stationary dot
|
||||
and conclude nothing is happening. The radar that preceded it went for the same
|
||||
reason: animation standing in for an answer.
|
||||
|
||||
**The crimson head card went.** "Arun Kumar is assigned" at 27pt on a filled
|
||||
brand field, with a supporting line, a progress rail and the window under it.
|
||||
Crimson is the action colour and tracking is a screen you read. It is a white
|
||||
card now: the reference at 26pt, a status pill, and the route as a thread.
|
||||
|
||||
**No progress bar inside it.** The journey rail below is the same five
|
||||
milestones with the same fill, and two of them on one screen is the app
|
||||
disagreeing with itself about which is the answer.
|
||||
|
||||
**The Miler card moved to the foot.** It sat directly under the reference,
|
||||
which put *a person* between the customer and the answer they opened the screen
|
||||
for. It is **who**; the screen's question is **where**. The journey has the
|
||||
middle of the page now.
|
||||
|
||||
Also gone: a duplicate route card at the foot, the duplicate reference in the
|
||||
top bar, and the white bed under the footer buttons.
|
||||
|
||||
### 11.5 Small things that were wrong
|
||||
|
||||
**The destination node is a map pin.** A rotated diamond distinguished the ends
|
||||
of the thread — its whole argument — and did that by being a shape with no
|
||||
meaning attached. A pin is the mark every customer already reads as *a place*,
|
||||
so the thread needs no legend: hollow circle where you are, pin where it is
|
||||
going.
|
||||
|
||||
**The status pill was floating mid-row.** `Flexible` gives a child the space it
|
||||
needs and then leaves it at the *start* of what it was allotted. It takes an
|
||||
`Align(centerRight)` to put a status where a status belongs.
|
||||
|
||||
**Home's address is a line, not a field.** It was a bordered white box with a
|
||||
chevron — a text input that takes no text, which is the shape ride apps stopped
|
||||
using years ago. A dot, the place, and a quiet "Change".
|
||||
|
||||
**BOOK / NOW is stacked**, which bought back size: both words get the sphere's
|
||||
full width, so it is 30pt over two lines rather than the 24pt a single line had
|
||||
to shrink to.
|
||||
|
||||
**Settings rows stopped truncating.** Two rounds of flex arithmetic produced
|
||||
"Payment metho…" and "38 Mettupalaya…" in turn. The fix was not a third ratio:
|
||||
the label never truncates, and a `value` is a word — anything longer belongs on
|
||||
the screen the row opens.
|
||||
|
||||
---
|
||||
|
||||
## 12. If you are adding something
|
||||
|
||||
1. Take the value from `tokens.dart`. If it is not there, add it there.
|
||||
2. Ask what the screen's one decision is. If your addition is not it, it is
|
||||
|
||||
Reference in New Issue
Block a user