Two merchant portal finds: GrowthChart and MerchantSwitcher โ
- Status: one fixed, one diagnosed and left alone for routing, 2026-08-27.
- Source: raised by another session, outside every live lane's territory that day.
- Related:
merchant-defect-fixes.md, the M1 to M5 set from the same portal.
GrowthChart called everything a signup โ
Fixed. The empty state now uses the props the component already takes. โ
GrowthChart.jsxtakesmetricandmetricPluralfor exactly this purpose, resolves them into a localpluralon line 16, and uses them correctly in the summary line and the hover tooltip. The empty branch alone hardcoded "No signups in this window".- The one live caller is the merchant Overview ad-views chart,
Overview.jsx:354, which passesmetric="impression" metricPlural="impressions". So an impressions chart with no data announced that there were no signups. - The fix reads
No {plural} in this window.pluralis already computed above the early return, so nothing was plumbed. Themetric = 'signup'default is untouched, so any caller that omits the props keeps today's wording. - Verified: eslint clean, and the 124 merchant tests still pass.
The merchant switcher says "Viewing Merchant" instead of the business name โ
NOT fixed. It is not a one-line read, so it is reported for routing. โ
MerchantSwitcher.jsxlives inshared/, which was task 4's territory on 2026-08-27, and the standing instruction was to touch it only if the real name was already in scope.- It is not. The component has no business name from its caller:
MerchantShell.jsxholds onlyurlMerchantIdanduser, and renders<MerchantSwitcher />with no props. The switcher fetches the name itself and falls back to the literal'Merchant'.
The cause is its own two guards disagreeing under StrictMode. โ
- The name effect carries both a
cancelledflag and anameFetchInFlightref that dedupes concurrent requests for the same id. nameFetchInFlightis a ref, so it survives StrictMode's simulated remount, whilecancelleddoes not. The sequence: first mount starts the fetch and marks the id in flight, cleanup setscancelled = true, the second mount hits the in-flight guard and returns WITHOUT starting a replacement, then the original fetch resolves, seescancelled, and discards the name it just retrieved.- Nothing ever writes
names[currentId], so the label stays on the'Merchant'placeholder for the life of the session.apps/admin/src/main.jsxwraps the app inReact.StrictMode, so this is the live path, not a hypothetical.
This is confirmed, not inferred. โ
- A throwaway probe rendered the component twice with an identical mocked
getMerchantData: once bare and once inside<StrictMode>. Bare resolved to "Acme Cafe"; StrictMode never did. The probe was deleted rather than committed, since a real regression test belongs in__tests__/MerchantSwitcher.test.jsxalongside the fix. - That existing suite passes today because none of its cases render under StrictMode, which is why the bug reached the browser.
Why the page header on the same screen is fine. โ
Overview.jsx:135fetches the same field from the same function, but with a plaincancelledguard and no in-flight dedupe. StrictMode's second mount simply starts a second fetch, which resolves and sets the name. Two guards are what breaks the switcher; one guard works.
What a fix would have to decide. โ
- Whether the dedupe ref is worth keeping at all. Dropping it restores the Overview shape and fixes the bug outright, at the cost of one duplicate request per mount in dev.
- If it is kept, clearing the id from
nameFetchInFlighton cleanup would let the remount start its own fetch, but the two guards would still need to agree about which one owns the retry. - Either way it needs a browser pass, since the symptom only appears under StrictMode.