Skip to content

Merchant and venue dashboards: what to review โ€‹

FieldValue
Branchfeat/admin-and-merchant-portals, draft PR #976: feat(admin): portal navigation, financials and monitoring, plus the agent-process work behind them, not merged
ProjectREADME.md
Issue#320: Phase 3: Venue analytics, aggregate interest and vibe insights for merchants
Servers you needAdmin and merchant portal on http://localhost:3001 (npm run admin:dev). Admin Storybook on http://localhost:6008 (npm run storybook:admin)
Local originsRequired 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. โ€‹

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/billing redirects 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/dashboard renders it, and /admin now redirects there rather than rendering it under a URL the sidebar could not mark. Real counts, a real changelog out of docs/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, OrientationBlock and DashPanel. 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 clientErrors Firestore 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. โ€‹

The admin Storybook now ships inside the portal, and its tree was rebuilt to match the app one. โ€‹

  • npm run build-storybook:admin builds tooling/.storybook-admin, and admin:build bundles it to /storybook-admin/ in apps/admin/dist. The web Storybook keeps /storybook/. Before this, admin stories rendered ONLY on a 6008 somebody started by hand, so SchemaViewer and ReportBuilder, 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 orderSecond level
Web :6006Foundations, User Flows, Screens, ComponentsPrimitives pinned first, then feature groups
Admin :6008Foundations, Screens, ComponentsPrimitives 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 Flows because it has no flow components. Nothing else about the shape differs.
  • Components/Brand and Components/Offers in the admin tree are the SHARED @lantern/ui components, which the merchant portal really does render (apps/admin/src/components/offers/AdSlot.jsx wraps one). Their titles live in packages/ui and 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 as apps/web/src/design/: Colors, Typography, CTAs, Icons.
  • No Brand page. The mark is shared and web's Foundations/Brand already documents it; a second copy would be a second source of truth for one component.
  • Every value is read live from :root at 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-500 and --warning both 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, h1 and h2 rules that fire with no class, which is the other reason the previews cannot be shared.
  • CTAs renders the real .btn classes, 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-contrast measured 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. LaunchDashboard is AdminDashboard, LaunchCard is DashboardCard, launchFixtures.js is dashboardFixtures.js, and the launch/ subfolder is gone: the files sit in apps/admin/src/admin/dashboard/, which is now the only Dashboard there.
  • LaunchTimeline keeps 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 by harness/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.md measures 30 items across all three surfaces with options rather than verdicts.
  • merchant-design-brief.md measures 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 dev branch. 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: useDashboardRail never sets loading: true on 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 under Components/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/billing redirects 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 reports operational; and the Errors feed infers truncation from entries.length >= 200 rather 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.md has 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-2 fails contrast wherever it is used as text (14 nodes, 5 surfaces). --secondary-accent-blue-500 misses 4.5 by a whisker on a card (4 nodes, 3 surfaces). Both measured in axe-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. โ€‹

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.

FindingWhere it showsStatus
The uptime rollup reports operational when every endpoint times outSystem Health, Uptime tabOpen, not fixed
The Errors feed states a capped number as an exact countSystem Health, Errors tabOpen, not fixed
useDashboardRail never sets loading: true on a re-read, so a slow request can overwrite a newer snapshotAdmin Dashboard railOpen, 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 rootNo visual surfaceFixed 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.

What issue does each piece close? โ€‹

PieceIssueState
The dashboards themselves#320: Phase 3: Venue analytics, aggregate interest and vibe insights for merchantsOpen, epic
The merchant sidebar footer#988: bug(merchant): the sidebar profile footer is not pinned to the bottom of the rail, it rises up under the navFixed on branch
The merchant switcher naming#980: bug(admin): the merchant switcher shows 'Merchant' instead of the business name, because a dedupe ref outlives a StrictMode remountFixed on branch
Venue addresses with no commas#993: bug(data): seeded venue addresses have no commas, so the merchant Venues page reads as one run-on stringFixed forward at the importer
Uptime and server errors#164: Infra: Application Monitoring & ObservabilityOpen, tracked in application-monitoring
The admin Banned stat counting expired bans#995: bug(auth): the admin Banned stat counts expired bans, because it reads the raw flag instead of isActivelyBannedLocated, not fixed, auth territory
A rejected offer having no home#996: bug(merchant): a rejected offer has no home, and the reason the API sends for it is read by nothingReported, not fixed

Built with VitePress