Dialogue.  · Row unification

Row unification — one anatomy, nine dresses

The study the owner asked for (2026-08-11): every list row in the app, measured; which differences carry meaning and which are drift; whether one design language can hold them; and the path that gets there without a big-bang rewrite. The persona Studio is excluded — it is being overhauled separately.

The finding, in one line. The app has one row anatomy — lead · main (title over sub-line) · trail — worn by nine CSS families whose differences are mostly accidents of when each was written. Two rows have earned their differences (the chat row, the form row); TWO (.mep-row, .fr-door) turned out to be dead CSS — zero call sites each; the Me landing and the add doors both wear the workhorse already, and the census’s「uses」column had counted their own selectors as callers. The remaining five are the same row drawn five times, and three of today's recent bugs (v761, v779, v781) were paid directly to that duplication.

1 · The census — drawn, then counted

First the words, marked on one row (fig 1). Then each family drawn with its own real numbers — the same specimen content in every card, so the only thing that differs is the family's own dress. The dashed coral line in every card is the 16px reference inset: where a row's content starts somewhere else, the drift is visible instead of numeric.

hairline — the line between two rows (soft or full-strength: one of the drift axes) min-height side inset — row edge → first content (16 is the house line) lead lead — avatar (38/40/46) or icon (16/17/26) gap — lead → text (10 / 11 / 12 / 13 today) title — the row's face (15px; weight varies by surface) sub-line — the small grey second line (11.5px sans… or 10px mono in one family) trail trail — chevron · toggle · time · buttons pad-y — row edge → content, vertically (9–13 today)
fig 1 · the vocabulary of this page, marked once. Every family below is this same row wearing its own numbers.

The nine, drawn true

Chat row .chat-item
the Chats tab list — and the search results, and a profile's Shared chats
D Da Vinci-ish Yesterday Leonardo da Vinci · with Bobby, mary
inset 12 · gap 12 · lead 46 · title 15/600 read-font · sub 11.5 · hair full · min-h 72
Roster / settings row .drw-item
the Friends roster · every Settings row · the invite pickers — the house workhorse. The Account rows (Change password · Devices · Invite code) wear the same icon slot BARE; Devices adds the sub-line and a button trail — one family, two dresses
D Charles Darwin Naturalist
inset 16 · gap 13 · lead 40 · title 15/400 · sub 11.5 · hair soft · min-h 60
…the workhorse’s wardrobe .drw-item, dressed
the owner’s two screenshots, answered: same row, same numbers — only the LEAD and the TRAIL change. This is why the census counts ONE family here, not four.
dress · coloured tile — the Me landing (pic 1)
Notifications 3
dress · bare icon — the Account rows (pic 2)
Devices Signed in on 13 devices Log out others
dress · the tick — the invite pickers
N Nora
constant: inset 16 · gap 13 · title 15 · hair soft · min-h 60 — lead: 40 avatar / 26 tile / bare-in-slot · trail: › / badge+› / button / tick
Control row .priv-row
Privacy & friends · Change password · Invite code — a named thing with its control
Let people find me by -ish ID Off by default. Your link still reaches you.
inset 16 · gap 12 · lead — · title 14 · sub 11.5 · hair full
Store row .storerow
Settings → My contents: Archived chats · Deleted chats · Deleted Seen · Deleted persona
Da Vinci-ish Leonardo · 1 Jul 2026 RestorePurge
inset 4 · gap 10 · lead — · title 14.5 serif · sub 10 mono · hair full
Friend request .fr-req
the Friends tab — the requests waiting at the top of the roster
N Nora AcceptIgnore
inset 16 · gap 11 · lead 40 · title 15/400 · sub — · hair soft
Notification row .nf-row
Me → Notifications — the notices list
B Bobby mentioned you in Da Vinci-ish · Yesterday
inset 16 · gap 11 · lead 38 · title 15/400 · sub 11.5 · hair soft
Me landing row .drw-item + .drw-ico
Notes · Notifications · Stats · Settings · Admin console — the workhorse in a coloured 26px tile. The census’s .mep-row is DEAD CSS: zero call sites (the old Me sheet’s row) — caught by the owner asking 「are these Me rows?」
Settings
it IS the roster row: inset 16 · gap 13 · lead 26 tile · title 15/400 · hair soft · min-h 60 — only the tile’s colour is its own
Add door .fr-door dead — v784
Friends → ⊕ — the「add by link · handle · scan」door rows
Add by handle
inset 2 · gap 12 · lead 26 ico · title 14 · sub — · hair full
Field row .acct-field earned
Change password · Invite code — the one row you TYPE in (v764–768)
New password ••••••••
inset 16 · label 15 muted ABOVE a full-width box — the deliberate exception

The same census as numbers

familylives inusesmin-hpadgap leadtitlesub-linehairline
.chat-item+.ci-mainchat list · search results · shared chats 227210/16·1212 46 avread-font 600 · 1511.5 sans--line, full
.drw-itemfriends roster · settings · pickers 48609/1613 40 av / 16 icosans 400 · 1511.5 sans (.drw-rs)--hair-soft, full
.priv-rowthe Privacy & friends toggle rows — reused by Change password · Invite code 2713/1612 sans · 14 (15 in as-page)11.5 sans (.ps)--line, full
.storerowarchived / deleted / Seen / persona stores 911/4 (16 in as-page)10 serif · 14.5mono · 10--line, full
.fr-reqfriend requests 612/1611 40 avsans 400 · 15--hair-soft, full
.fr-doorDEAD — zero call sites (the add doors were rebuilt as .drw-item rows long ago); deleted v784 with .dsc-ico012/21226 icosans · 14--line, full
.mep-rowDEAD — zero call sites (the old Me sheet’s row); the Me landing really uses .drw-item + the coloured 26px .drw-ico tile0500/151117 icosans · 15--line, border-top
.nf-rownotifications 1013/1611 38 avsans · 1511.5--hair-soft
.acct-fieldaccount forms (label over box) 1211/16 sans muted · 15--hair-soft

Read down any column and the drift is plain: gap is 10, 11, 12 or 13 depending on which year the row was written; vertical padding is 9, 11, 12 or 13; the sub-line is 11.5px sans in four families and 10px mono in one; the hairline is soft in three families and full-strength in five — the exact split v693 already outlawed inside one list, still alive between lists; and the lead avatar is 38, 40 or 46px.

2 · Which differences carry meaning

differenceverdictwhy
chat row: 46px avatar, 72px height, timestamp + unread + meta stack MEANINGit is the app's densest row — three lines of live state, owner-locked avatar spec (docs/chat-list-avatars.html)
chat row: read-font + read-scale on title/meta MEANINGthe reading-typography dial applies to content surfaces, and the chat list is one
form row: label ABOVE a full-width box (password, invite) MEANINGv764–768 settled it: a field you type into wants the row's width; a control you glance at sits beside its label
gap 10 vs 11 vs 12 vs 13DRIFTno rule decides; each author picked
pad-y 9 vs 11 vs 12 vs 13DRIFTsame
store title in serif · store meta in 10px monoDRIFT the store predates the drawer language; v761 already had to re-dress its headings once
hairline --line vs --hair-softDRIFTv693 ruled ONE hairline weight; the rule was applied per-list, never app-wide
lead avatar 38 vs 40DRIFTnf-row's 38 is 2px off the roster's 40 for no reason anyone can name
priv-row 14px face vs everyone's 15DRIFTalready overridden to 15 wherever it shares a screen with drawer rows (v761)

3 · The one anatomy

lead Title — 15px, weight by surface Sub-line — 11.5px muted, one style everywhere trail lead = 40px avatar · 16px icon · none · trail = chevron · toggle · time · action buttons · none NAV — 60px min roster · settings · stores · notifications CHAT — 72px min 46px avatar · time · unread · read-scale FIELD — label over box password · invite — the one row you type in
fig 1 · every non-Studio row is one of three densities of one anatomy. Shared: 16px side inset · one gap · one sub-line style · one hairline (--hair-soft).

4 · Before / after — the owner's cut

The owner's decision (2026-08-11): unify everything — only the chat avatar stays 46. So the unified numbers are: inset 16 · gap 12 · title 15 · sub-line 11.5 sans · hairline soft · lead 40 avatar / 26 icon-tile (chat avatar 46) · min-height 60 nav / 72 chat. Kept on purpose: the chat title's semi-bold + reading-font dial (both owner calls), and the field row's label-over-box shape. Below, every family that changes — BEFORE wearing today's numbers, AFTER wearing the unified ones, the same dashed 16px guide through both.

Store row .storerow — the biggest move
before
Da Vinci-ish Leonardo · 1 Jul 2026 RestorePurge
after
Da Vinci-ish Leonardo · 1 Jul 2026 RestorePurge
inset 4 → 16 · gap 10 → 12 · title 14.5 serif → 15 sans · sub 10 mono → 11.5 sans · hair full → soft
Add door .fr-door MOOT — the doors were already drawer items; rules deleted v784
before
Add by handle
after
Add by handle
inset 2 → 16 · title 14 → 15 · lead bare 26 → the house 26px tile · hair full → soft
Chat row .chat-item — avatar stays 46
before
D Da Vinci-ish Yesterday Leonardo da Vinci · with Bobby, mary
after
D Da Vinci-ish Yesterday Leonardo da Vinci · with Bobby, mary
left inset 12 → 16 · hair full → soft · KEPT: avatar 46 · title 600 + read-font · time · unread · min-h 72
Control row .priv-row
before
Let people find me by -ish ID Off by default. Your link still reaches you.
after
Let people find me by -ish ID Off by default. Your link still reaches you.
title 14 → 15 · hair full → soft · KEPT: label-left/control-right, and v768's stacked box for fields
Notification row .nf-row
before
B Bobby mentioned you in Da Vinci-ish · Yesterday
after
B Bobby mentioned you in Da Vinci-ish · Yesterday
lead 38 → 40 · gap 11 → 12 · pad-y 13 → 11
Roster / settings row .drw-item — the reference, barely moves
before
D Charles Darwin Naturalist
after
D Charles Darwin Naturalist
gap 13 → 12 — and nothing else; the friend-request row (11→12) and the Me landing rows (13→12) make the same 1px move — the Me rows were never a family of their own
Wardrobe · coloured tile (the Me landing) .drw-item
before
Notifications 3
after
Notifications 3
gap 13 → 12 — and nothing else: the dress survives unification untouched, which the after confirms
Wardrobe · bare icon (the Account rows) .drw-item
before
Devices Signed in on 13 devices Log out others
after
Devices Signed in on 13 devices Log out others
gap 13 → 12 — and nothing else: the dress survives unification untouched, which the after confirms
Wardrobe · the tick (the invite pickers) .drw-item
before
N Nora
after
N Nora
gap 13 → 12 — and nothing else: the dress survives unification untouched, which the after confirms
Two judgment calls the mockup surfaces — the owner's to veto. ① The add door adopts the house 26px icon tile — the Me landing already wears it coloured, the Account rows wear the same slot bare. Coloured or bare-in-slot is the one dress choice left, and the column aligns either way. ② The store rows stay lead-less (their left edge is the title): giving them the chat's 46px avatar is possible but heavier, and the page is a utility list.

5 · The path — tokens first, merges second

Scope decided by the owner (2026-08-11): unify everything; only the chat avatar stays 46. Method: by TOKENS now, by CLASS only where a family becomes identical. Stage 1 is invisible by construction and delivers the goal (tune one, tune all). Stage 2 deletes classes one family at a time, each behind a measurement. A big-bang single .row class is NOT recommended: 150 call sites, and this file's history says exactly what that buys.
  1. Stage 1 — the tokens. SHIPPED v783. Define --row-inset:16px · --row-gap:12px · --row-pad-y:11px · --row-title:15px · --row-sub:11.5px · --row-hair:var(--hair-soft) · --row-lead:40px · --row-lead-ico:26px · --row-lead-chat:46px and re-express all nine families in them. Where a family's current number differs from the token (gap 10/11/13, pad 9/13, sub 10 mono, hairline --line, lead 38), it adopts the token — these are the drift cells in §1, each a ≤3px move or a colour-weight change. The chat row keeps its two earned overrides as its own tokens. And the dead .mep-row rules are deleted outright — a dress with no wearer.
  2. Stage 2 — merges. SHIPPED v784 (smaller than planned, for the best reason: two of the merge candidates were corpses). .fr-req and .nf-row are .drw-item with a trailing action / unread dot → variants. .storerow is .drw-item with two trailing buttons → variant (its serif title and mono meta do not survive; v761–782 already moved it halfway). .priv-row stays as the control-row variant (label left, control right — including its stacked-box form from v768). .mep-row and .fr-door retire into .drw-item. Target: 9 families → 3 (.drw-item + variants · .chat-item · the field row).
  3. Stage 3 — the guard. SHIPPED v784: six row lints in the smoketest, canary-proven (a planted raw gap turns the gate red). A smoketest lint: no new row class may declare a raw px value for inset/gap/sub/hairline — new rows spend the tokens. That is what keeps the census from regrowing.

6 · Why this is worth doing — the receipts

Three of today's bugs were interest paid on this duplication:

Risks, named. ① The drift cells are real pixel changes — small, but the owner reviews after Stage 1 lands, not after each px. ② .priv-row powers three live pages; its variant must keep the v768 stacked-box form. ③ The chat row's avatar spec is owner-locked — Stage 1 touches its paddings/hairline only via tokens with today's values, moving nothing. ④ Search results draw chat rows inside foreign hosts (v779); the merge order keeps .chat-item untouched until the hosts share one inset token, which Stage 1 itself delivers.

7 · After — the calling convention

The owner’s question, answered: yes — after this, a new or reworked page is ordered by NAME, not by numbers. One sentence like「make these drawer items, bare icons, button trail」is a complete spec. This section is the menu that sentence draws from, and the study is the contract that the name always produces the same row.

you saywhat gets builtthe choices left to make
「drawer items」
the default for any list
.drw-item — the workhorse, at the tokens lead: 40 avatar · 26 coloured tile · bare icon · none
trail: › · count badge · action button · none
sub-line: yes / no
「control rows」
「like Privacy」
the control variant — label left, control right control: toggle · shown value · a box on its own line (the v768 form)
「chat rows」 roomRowEl(r) — a BUILDER, not markup: time, unread, meta and long-press menu come with it none — it is the one row ordered as a component, which is why search results and Shared chats already look right for free
「field rows」 .acct-field — label over a full-width box grouping: which fields share a card-gap (one question vs one act)
「store rows」 drawer items with a two-button trail (Stage 2, shipped) — not a family, just an orderthe verbs on the trail
What makes the promise hold, rather than depend on discipline. ① The numbers live in the tokens, so a named row cannot come out wrong-sized — and a global tune (「make all rows a little taller」) is one token edit, everywhere at once. ② The Stage-3 smoketest lint refuses any new row class that declares raw px for inset / gap / sub-line / hairline — a rushed session cannot fork the language again; the gate fails instead. ③ The menu above gets a twin in the style guide (the living UI reference) the day Stage 2 lands, so「which row style do I want」is a page you can look at, not a thing you ask.

And the same convention covers editing stale pages: 「this page’s rows should be drawer items」is the entire migration instruction — exactly the sentence that turned the store pages around in v761–782, except that after Stage 2 it is one class swap instead of a re-dressing.

Dialogue · row unification · study 2026-08-11 · status: ALL THREE STAGES SHIPPED (v783 tokens · v784 merges + lint). Final taxonomy: 3 families (workhorse · chat · field) + 3 structural variants (control · request · notification) + dresses · 2 corpses deleted · siblings: the style guide · friends