Skip to content

Surface design pass โ€‹

  • Status: steps 1 and 2 done, 2026-08-27, plus the first three items of step 5 the same day (the PageHeader subtitle, merchant Overview's ambiguous empty value, and the rendered-prose em dash sweep). Steps 3, 4, 6 and 7 not started, and step 5 has items left.
  • Issue: #293: Need to refine the design
  • Launch plan: PROTOTYPE.md Phase 2: Build, row "Polish and refinement", P0. (Was ALPHA.md Phase 1 before the 2026-08-31 timeline shift moved Build into Prototype.)
  • Scope: planning.md is what this pass covers, in order. backlog.md is what it deliberately leaves alone.
  • Decisions: decisions.md. All five gating questions are answered, four by a PM session and one by a builder, none by her. Every one is reversible on sight.
  • Step 1 output: design-survey-web.md, the signed-in web surfaces, measured live.
  • What was proven and what was not: verification.md. The sweep is verified live on both portals; the two new components' internals are verified by test, because dev data currently has no empty field to render one. Step 5's three items are verified live across 15 routes, 10 admin and 5 merchant.
  • Input: design-survey.md, 30 measured items across all three surfaces, run 2026-08-24.

What is this? โ€‹

A deliberate design pass over the app, the merchant surfaces and the admin portal, so the three read as one finished product. โ€‹

  • The plan's stated reason: real users meet strangers through this, and unfinished edges read as untrustworthy.
  • "One product" is not a token problem. packages/ui/theme.css is already a written contract, imported by both apps, and it names its own intentional divergences. packages/ui/fonts.css is imported by web, admin and the site. The shared layer exists.
  • What is not shared is everything above the tokens: components, copy register, the glyph that means "no value", the vocabulary a status badge uses, whether an icon is Lucide or a text character, and what a clickable row does. That is what this pass is.

#293 is the CATCH-ALL for design refinement, by the operator's intent. โ€‹

  • It is deliberately scalable rather than scoped to one screen. Anything that is design polish belongs under it.
  • Its body was written from one symptom (the frens screens). It has been rewritten to describe the row it actually tracks, with the frens instances kept as concrete examples rather than as the boundary.
  • Practical consequence: do not open a sibling issue per surface. Work lands here, and the issue stays the umbrella.

There are three surfaces but only two codebases, and that changes the blast radius. โ€‹

  • The app is apps/web.
  • The admin portal and the merchant portal are both apps/admin, and the merchant half is a second consumer of the admin shared layer: apps/admin/src/merchant imports shared/components/PageHeader at 7 call sites and shared/components/StyledSelect at 3.
  • So a fix in an apps/admin/src/shared primitive lands on a merchant's screen too, whether or not anyone checked. Every shared-primitive item in this pass gets verified on both.

What is the current state? โ€‹

Steps 1 and 2 are done. Steps 3 through 7 are not started. โ€‹

  • Step 1 ran on 2026-08-27: 12 signed-in web routes driven headless and measured, output in design-survey-web.md with the captures and the harness beside it. It closed the survey's W3 coverage gap.
  • Step 2 landed the same day: one empty-value grammar across both portals, two components, 63 sites swept, zero em dashes left rendering in either portal. Verified live on http://localhost:3001.
  • Five of the seven steps remain, sequenced in planning.md.

The pass covers 10 of the survey's 30 items and leaves 20. โ€‹

  • The rule that splits them: this pass owns a defect that appears on more than one surface, lives in a primitive more than one surface consumes, or breaks a grammar the other surfaces follow. It does not own a defect confined to one screen, and it does not touch a screen #320 is about to redesign.
  • The item-by-item split is in planning.md (in) and backlog.md (out, each with what would promote it).

Two things were found that the survey does not carry. โ€‹

  • Admin and merchant surfaces are absent from Storybook. The one Storybook globs apps/web/src/** and packages/ui/** only, so the two *.stories.jsx files under apps/admin/src are loaded by nothing. Detail and blast radius in planning.md, step 7.
  • The text-glyph-instead-of-icon defect is not web-only. The survey's W1 found three sites in apps/web. apps/admin has 15 more across 9 files, three of which are shared components, including PageTabs.jsx, which renders on every admin page that has tab options.

What has to be decided before this starts? โ€‹

All five are now answered in decisions.md, with the reasoning and who decided. The questions are kept below as they were written, because a decision is only reversible if the fork it closed is still legible.

None was invented; each is a genuine fork.

1. Which glyph means "no value", and is one glyph enough? โ€‹

  • The survey's X2 measured three vocabularies in apps/admin alone: 39 em dash, 6 en dash, 18 bare hyphen.
  • The fork is not just which character. One of the two meanings is "the server withheld this under k-anonymity" and the other is "there is nothing here", and Overview.jsx:144 already distinguishes them deliberately.
  • Survey options: one component and one glyph, two components because there are two meanings, or a word instead of punctuation.

2. Does the public site come with this pass? โ€‹

  • Default taken here: no. apps/site has its own plan row, "Splash page refinement", with #848 and #593.
  • It shares @lantern/ui/fonts.css and @lantern/ui/colors but not the token stylesheet, so it is a genuinely separate visual system today.

3. Is the #293 versus #320 boundary the one written here? โ€‹

  • The rule above hands every dashboard screen to #320 and keeps the grammars here. It is a clean line, but it is a choice, and it decides who fixes roughly 20 items.

4. Does the admin portal get into Storybook, and does the story-coverage gate widen to it? โ€‹

  • Two decisions, not one. Loading admin stories is a Storybook config change. Gating admin components on story coverage is a ratchet-baseline decision that would land a large grandfathered count.

5. How does PageHeader stop truncating its subtitle? โ€‹

  • The survey's X3 has three options with no obvious winner: treat long copy as an authoring bug, let the header grow to two lines (which breaks the flush alignment its comment protects), or move the explanatory sentence into the page body on every screen.

Built with VitePress