Merchant portal defects M1 to M5 โ
- Status: all five fixed and confirmed in a running browser, 2026-08-27.
- Source:
design-survey.md, section "What is wrong in the merchant portal?". - Scope:
apps/admin/src/merchantonly.apps/admin/src/sharedwas off limits (task 4 owned it that session), and nothing here needed it. - Not in scope: M6, the six thin merchant pages. It is a content question, not a defect.
What changed? โ
| Item | Fix | Where |
|---|---|---|
| M1. Sub-metric icon breaks its row | Dropped the Users icon from the "Unique visitors" <dt> | Overview.jsx |
| M2. Recent Offers titles wrap to three and four lines | Audience label and status badge moved to a second line, title takes the full row width and truncates | Overview.jsx, Overview.css |
| M3. Ad Views renders a 370px flat zero chart | Compact empty state when the visible window has no non-zero day | Overview.jsx, Overview.css |
| M4. "Clicks ยท โ CTR" is broken text | CTR clause renders only when there are impressions to divide by | Overview.jsx |
| M5. Merchant sidebar carries ~420px of empty nav | Nav sizes to its content under a merchant-only .sidebar--merchant scope | MerchantShell.jsx, MerchantShell.css |
Two new CSS files, colocated, rather than edits to the shared sheet. โ
apps/admin/src/shared/styles/styles.cssis the ADMIN portal's stylesheet as well, so a rule added there for a merchant panel is a rule the admin portal inherits. That is thedesignskill's own instruction for a screen-specific section, and it was also the session's hard boundary.- Every selector in both files is scoped to markup only the merchant portal renders: the new
.offer-row--stackedand.overview-chart-emptyclasses, and.sidebar--merchant.
What did the browser actually show? โ
- Target:
http://localhost:3001/merchant/m_KlAXPi6q4RvB/overview, the operator's own running stack. Nothing was restarted, rebuilt or killed; no stack of my own was stood up. - Signed in two ways: as Agent Probe (admin viewing a merchant, which is the shape the survey ran) and as the test merchant (their own login, whose sidebar footer is a different component in the same slot).
- Viewport 1440x950, plus a 390x844 pass for the mobile drawer.
- Harness:
harness/m-check.mjs(before and after measurement),harness/m-populated.mjs(the non-empty branch),harness/m-sweep.mjs(siblings and breakpoints). Raw output inharness/m-before.json,m-after.json,m-populated.json,m-sweep.json.
Measured before and after, on the live page. โ
| Item | Before | After |
|---|---|---|
| M1 | "Unique visitors" row 31.8px, siblings 20.8px | 20.8px, every sub-metric row equal |
| M2 | Title column 67.1px, titles over 3 and 4 lines | Title column 230.1px, every title 1 line |
| M3 | 370px chart, flat line at zero, y-axis 0 to 1 | 153.4px empty state, no chart, panel 261.4px |
| M4 | Clicks ยท โ CTR | Clicks |
| M5 | 407.9px between "Settings" and the footer | 40px |
The populated path was exercised too, not just the empty one. โ
- An empty-state fix that hides real data is worse than the defect. The delivery response was intercepted in the browser and refilled with 30 dense days, which is why
m-populated.jsonrecords the chart back at 586x260, the empty state absent, and the label readingClicks ยท 6% CTR. - Nothing was written to shared dev data to do this. The interception happens in the browser context.
The siblings that share the touched classes were checked for regressions. โ
- The admin portal sidebar is untouched:
merchantClass: falseandflex: 1/1/0%on/admin/users, exactly as before. - The merchant Offers tab still renders the wide row:
.offer-rowwithout--stacked, titles 695px on one line, unchanged. - The merchant rail is correct collapsed (72px, 40px gap) and as a mobile drawer (390px, footer bottom at 582px inside an 844px viewport).
What did the survey get wrong or leave out? โ
M2 is worse than the survey measured, and the cause is one property. โ
- The survey described a squeezed title column. The live number is 67px, which is narrower than the word "pastry".
- The cause is
flex-shrink: 0on.offer-row__audience. The audience label holds its full width no matter how narrow the row gets, so every pixel of the squeeze lands on the title. The survey's "the subtitle truncates toLantern A..." is the same property doing the same thing to the venue name.
M5 is 408px, not "roughly 420px". โ
- Close enough that the survey's framing stands. Recorded because the fix is verified against the real number.
M3's empty state has to key on the VISIBLE window, not the fetch. โ
- The panel fetches 30 days and the toggle slices 7 or 30 out of it. A merchant with views 20 days ago and none since would have got the chart on 30d and a flat zero line on 7d. The check runs on the sliced series, so both windows agree.
What was found and NOT fixed here? โ
GrowthChart's empty message says "No signups in this window" whatever it is charting. โ
apps/admin/src/components/GrowthChart.jsx:42hardcodes the word, while the component takesmetricandmetricPluralprops precisely so it can say the right one.- It is unreachable from the merchant Overview after this change (the empty state now intercepts first) but reachable from any other caller passing an empty array.
- Not fixed because the file is outside
apps/admin/src/merchant.
The merchant switcher reads "VIEWING Merchant" rather than the business name. โ
- Visible in
harness/m-after-overview-viewport.png, where the page header on the same screen correctly reads "Round Trip Cafe 15890656". MerchantSwitcherlives inapps/admin/src/shared/components/, so it was out of scope. Worth raising: a switcher that will not name what it switched to is the same failure as A1 and the Offer review venue id.
What is related? โ
design-survey.md, the survey these five come from.backlog.md, where the merchant dashboard parity item still waits on her ruling.