Merchant and venue dashboards: what to review โ
| Field | Value |
|---|---|
| Branch | feat/admin-and-merchant-portals, draft PR #976: feat(admin): portal navigation, financials and monitoring, plus the agent-process work behind them, not merged |
| Project | README.md |
| Issue | #320: Phase 3: Venue analytics, aggregate interest and vibe insights for merchants |
| Servers you need | Admin and merchant portal on http://localhost:3001 (npm run admin:dev). Admin Storybook on http://localhost:6008 (npm run storybook:admin) |
| Local origins | Required for System Health and the Dashboard rail. See the warning below |
Why do some of these rows say "needs the local origins"? โ
The admin portal proxies /api/analytics to the DEPLOYED analytics-api, so branch backend changes are invisible on your machine. โ
.env.localpointsANALYTICS_API_ORIGINathttps://analytics-api-...run.app, which runs whatever last merged todev. A surface reading that origin shows Aug 24 behaviour no matter what this branch did.- Restart the admin dev server after the
.env.localorigin change lands, or the row will lie to you. - The clearest tell: System Health Uptime shows 4 endpoints against the deployed origin and 9 against a local analytics-api. One browser, one session, only the origin changed.
- Tracked as
#1002: fix(dev-env): local dev proxies venues and analytics to Cloud Run, so branch backend changes are invisible locally.
What changed here? โ
The admin sidebar was rearranged and every financial surface was consolidated onto one page. โ
- Sections now read Main, Platform, Operations, Development, Settings. System Health left Operations for Development.
- Billing became Financials, route and all.
/admin/billingredirects to/admin/financials. - The Costs tab moved out of System Health and into Financials as its own tab, and the two overlapping cost blocks on System Health went with it.
The admin Dashboard is the design you approved, wired and routed. The page it replaced is deleted. โ
/admin/dashboardrenders it, and/adminnow redirects there rather than rendering it under a URL the sidebar could not mark. Real counts, a real changelog out ofdocs/changelogs/dev/.- The split reading page shipped on 2026-08-28 (orientation block, announcement sketch, release stream, 240px rail) is gone, along with
DashboardHome,StatusRail,ReleaseStream,AnnouncementStream,OrientationBlockandDashPanel. Its two data sources are what the new page reads. - Its predecessor, the eleven-card grid, went in the same direction and for the same reason. That step was a PM decision under an autonomy grant; the reasoning is in
dashboard-redesign.md.
System Health stopped reporting green while things were broken. โ
- Uptime went from 4 hardcoded URLs (three of which could never pass) to 9 derived from
packages/shared/services. - The Errors tab stopped reading the empty
clientErrorsFirestore collection and now reads Cloud Logging. It found 34 server errors in 7 days on a tab that reported none. - Three metric grids stopped stretching two cards to 509px when a row was sparse.
The merchant portal got two rounds of per-screen defect fixes, an offer-builder pass, and its first accessibility sweep. โ
- M1 to M5, then N1 to N7, plus the sidebar footer (
#988: bug(merchant): the sidebar profile footer is not pinned to the bottom of the rail, it rises up under the nav), the switcher (#980: bug(admin): the merchant switcher shows 'Merchant' instead of the business name, because a dedupe ref outlives a StrictMode remount) and venue addresses (#993: bug(data): seeded venue addresses have no commas, so the merchant Venues page reads as one run-on string). - The offer builder was driven end to end for the first time: four fixes, including a title that could publish ten characters over what its card renders while every indicator said it was fine.
- Nineteen surfaces swept with axe. Twelve clean; everything left is two shared colour tokens.
The admin Storybook now ships inside the portal, and its tree was rebuilt to match the app one. โ
npm run build-storybook:adminbuildstooling/.storybook-admin, andadmin:buildbundles it to/storybook-admin/inapps/admin/dist. The web Storybook keeps/storybook/. Before this, admin stories rendered ONLY on a 6008 somebody started by hand, soSchemaViewerandReportBuilder, which nothing else mounts, were reachable nowhere.- Sidebar: Development > Storybook > View Storybook (Admin), beside the existing View Storybook.
Open the two Storybooks side by side. That is the check. โ
| Roots, in order | Second level | |
|---|---|---|
Web :6006 | Foundations, User Flows, Screens, Components | Primitives pinned first, then feature groups |
Admin :6008 | Foundations, Screens, Components | Primitives pinned first, then feature groups |
- Captures, taken at the same depth (roots and groups open, components closed): admin and web.
- The root ORDER is now pinned in
tooling/.storybook-admin/preview.js, matching web's pin. It used to fall back to alphabetical, which put Components first and made the same structure read as a different one. - Admin has no
User Flowsbecause it has no flow components. Nothing else about the shape differs. Components/BrandandComponents/Offersin the admin tree are the SHARED@lantern/uicomponents, which the merchant portal really does render (apps/admin/src/components/offers/AdSlot.jsxwraps one). Their titles live inpackages/uiand are the same in both Storybooks, so they cannot be relabelled for admin alone.
Admin has its own Foundations now, mirroring web's, with admin's values. โ
- Four pages in
apps/admin/src/design/, the same folder shape asapps/web/src/design/: Colors, Typography, CTAs, Icons. - No Brand page. The mark is shared and web's
Foundations/Brandalready documents it; a second copy would be a second source of truth for one component. - Every value is read live from
:rootat render time. No page carries a hex literal. A token name that stops resolving renders the word MISSING instead of a blank swatch, so drift shows up on the page rather than hiding on it. - Colors > Semantic is the one to look at first.
--accent-500and--warningboth resolve to#f59e0b, so the colour that means "clickable" and the colour that means "be careful" are one colour. The page computes that from the live values and says so in a banner, and the banner will change on its own when the ruling (links move to blue) lands. Capture:screens/sb-found-colors-semantic.png. - Colors > Surfaces carries the card rule: every card is
--surface, title bands are--surface-dark, and the four surface tokens that are NOT card colours are labelled as such. Admin Overrides is the short list of shared tokens admin redefines, which is the reason the two Storybooks are separate instances at all. - Typography > Bare elements shows the
body,h1andh2rules that fire with no class, which is the other reason the previews cannot be shared. - CTAs renders the real
.btnclasses, so it tracks the stylesheet. Icons maps the provider and status registries straight out of@lantern/ui/icons, so a new provider appears with no edit here. - Verified: all ten Foundations stories render with 0 axe violations, 0 incomplete, 0 unresolved tokens and 0 page errors.
color-contrastmeasured 23 nodes on Semantic and 11 on CTAs, so it ran rather than being skipped. Harness:harness/foundations-capture.mjs, report:harness/foundations-axe.json.
The Dashboard she is reviewing is no longer called a "launch dashboard". โ
- Her words, 2026-08-30: it is the admin dashboard, full stop.
LaunchDashboardisAdminDashboard,LaunchCardisDashboardCard,launchFixtures.jsisdashboardFixtures.js, and thelaunch/subfolder is gone: the files sit inapps/admin/src/admin/dashboard/, which is now the only Dashboard there. LaunchTimelinekeeps its name. That component IS the launch timeline, which is the one place the word was hers.- Its captures are
screens/admin-dash-*.png, regenerated byharness/admin-dash-capture.mjs. The axe results came back identical to the pre-rename run, which is the check that the rename changed nothing but names.
The design survey and the merchant design brief are evidence, not builds. โ
design-survey.mdmeasures 30 items across all three surfaces with options rather than verdicts.merchant-design-brief.mdmeasures all seven merchant routes at 1440x950. It proposes no layouts, because the shape is yours to name.
Where do I look, and what am I looking for? โ
The admin Dashboard http://localhost:3001/admin โ
- Needs the local origins for the rail's counts to be current.
- You should land on
/admin/dashboard, with Dashboard lit in the sidebar. That redirect is the fix for the page having no nav marker at all on/admin. - The counts rail is on the right: the lit tile spans it and carries the flame, the other six are a two-up grid. A count the platform could not answer says "Not available" in words, and a queue cut at its page limit reads "100+", never a flat 100.
- The changelog reads the docs API, which serves the repo's
devbranch. Until this branch merges the newest day it can show is 21 August, which is correct rather than stale. A cold IndexedDB cache can take about 20 seconds to fill; it says "Reading the changelog" while it does, and says something different if the read FAILS. - Announcements are empty on purpose. The card says so and names #992. Post announcement opens the modal you approved; it still posts nowhere, so anything typed into it is lost on Post.
- "View all" on both cards does nothing on purpose (#1009). It is a grey span rather than a blue link, so it does not read as a broken link.
- Open finding, still not fixed:
useDashboardRailnever setsloading: trueon a RE-read, so a slow request can overwrite a newer snapshot. Refresh now disables while it works, which narrows the window, but it does not close the race.
Dashboard pieces in Storybook http://localhost:6008 โ
- The page, plus Post Announcement open, Narrow, Loading and Empty.
- Its parts: Launch timeline, Announcements, Changelog, Counts rail, Post Announcement modal.
- The stories render from FIXTURES and the routed page reads real services, so the new states real data forced (a count still in flight, a changelog read that failed, a queue cut at its limit) each have a story of their own now. That is the only way to look at them without breaking a service on purpose.
Components/Dashboard/DashboardHome/*is gone, because the page it documented is gone. The remaining nesting underComponents/Dashboard/AdminDashboard/stays as it is.- The admin Storybook is new on this branch. It did not exist before, so none of these rendered anywhere until now. It is also bundled into the built portal, so on a deployed build these open from the sidebar rather than needing a local 6008.
Financials http://localhost:3001/admin/financials โ
- Needs the local origins for the vendor and reports tabs.
- Six tabs: Overview, Reports, Vendors, Costs, Settings. Costs sits between Vendors and Settings.
- The Costs tab should render on its own even when the rest of the page is loading or failed, and the page Refresh button should drive it.
- Check
/admin/billingredirects here rather than falling through to the Dashboard. - The sidebar entry should read Financials, under Operations.
System Health http://localhost:3001/admin/system โ
- Needs the local origins. This is the surface where the deployed proxy is most misleading.
- Uptime tab: 9 endpoints, not 4. Two frontends plus the seven registered APIs. Against the deployed origin you will see 4, three of them red, and that is the old behaviour, not a regression.
- Errors tab: a Server Errors section reading Cloud Logging, grouped by source. The client half should now say "nothing currently writes to the clientErrors collection, so treat this as not instrumented rather than none happened", not "No errors logged".
- Usage, Firebase and Cloudflare tabs: every metric card should render 247px wide at 1, 2, 3 and 4 cards. Nothing should stretch to half the row.
- The two cost cards should be gone from the Overview. Cost is not a health signal, per your ruling.
- System Health should sit under Development in the sidebar, not Operations.
- Open findings, not fixed: the uptime rollup sets status only from
summary.down, so all nine endpoints timing out still reportsoperational; and the Errors feed infers truncation fromentries.length >= 200rather than the page token, so a capped number is stated as an exact count.
The merchant portal http://localhost:3001/admin/merchants/all, then click a merchant โ
- The merchant routes are
/merchant/<merchantId>/overview,/offers,/venues,/notes,/photos,/account,/settings. You reach them by clicking through from All Merchants, which is why no direct link is given here. - Overview: the Active Offers counter should show the limit that actually governs its field. The empty-value glyph should be consistent, not a bare hyphen with nothing behind it.
- Account: "Member since" should render a real date. Before this branch it could never resolve for any merchant.
- The sidebar footer should be pinned to the bottom of the rail on every merchant page. That is
#988: bug(merchant): the sidebar profile footer is not pinned to the bottom of the rail, it rises up under the nav. - There is 407.9px of empty space between "Settings" and the footer. That is survey item M5 returning by design. You ruled leave it on 2026-08-28, so it is not a defect to report.
- The switcher should name the business, not read "VIEWING Merchant". It broke only under StrictMode, so a single reload is the test.
- Venues: addresses should read with commas. Fixed forward at the importer, so a venue seeded before the fix may still read as one run-on string until it is re-imported.
The offer builder /merchant/<merchantId>/offers/create โ
- Character limits should block publish on all three paths, including the inherited one. Before this branch a title could publish ten characters over what its card renders.
- A placement row should no longer nest its checkbox inside a
role="button"div, and the checkbox and the row should not share an accessible name. - The per-placement pages rendering no text inputs is the inheritance model working, not a broken page.
- Typing in a field, navigating away and back does not revert it. That hypothesis was tested and killed; do not spend time on it.
What is deliberately NOT done? โ
No alerting backend exists. The rail is shaped to take a threshold row and nothing feeds it. โ
- Your side quest for a dedicated alerts page is recorded and not scoped.
The merchant design overhaul has not started, and the direction is not picked. โ
- Your verdict on 2026-08-28 was that every merchant page except the offer creation and builder flow needs a considerable overhaul.
merchant-design-brief.mdhas all seven routes measured with screenshots, seven observations and the four questions a decision would need to answer. Nothing is being drawn against it until you name the shape.
The two failing colour tokens are approved work, not started. โ
--muted-dark-2fails contrast wherever it is used as text (14 nodes, 5 surfaces).--secondary-accent-blue-500misses 4.5 by a whisker on a card (4 nodes, 3 surfaces). Both measured inaxe-sweep-0828.md.- You approved the split directly on 2026-08-28. Do not re-ask. It was not started because the code review was taken up first.
Seven merchant defects were reported rather than fixed, each with a reason, in merchant-defect-survey-0828.md. โ
- N8 (raw venue id fallback), N9 (the Archive button is the loudest thing on the screen), N10 (the header prints the title twice), N11 (no row for a rejected offer, which is
#996: bug(merchant): a rejected offer has no home, and the reason the API sends for it is read by nothing), N12, N13.
Monitoring the Discord bot was removed rather than left red. โ
- It is a gateway client with no HTTP listener, so its probe could never pass. It is now unmonitored, and monitoring it for real needs either an HTTP health surface on the bot or a gateway-side check.
What open findings touch this project? โ
Cross-referenced from branch-review-0828.md. Nine of the eleven findings are still open; one was downgraded by the reviewer and the loose harness file was moved on 2026-08-30.
| Finding | Where it shows | Status |
|---|---|---|
The uptime rollup reports operational when every endpoint times out | System Health, Uptime tab | Open, not fixed |
| The Errors feed states a capped number as an exact count | System Health, Errors tab | Open, not fixed |
useDashboardRail never sets loading: true on a re-read, so a slow request can overwrite a newer snapshot | Admin Dashboard rail | Open, low priority. Refresh disabling while it works narrows the window; it does not close the race |
m2.e2e.mjs is a one-off Playwright harness sitting at the repo root | No visual surface | Fixed 2026-08-30: moved to harness/m2.e2e.mjs |
- The two Dashboard rail counts that saturated at 100 and the Errors 24h card that under-reported were fixed, in
review-remediation.md.