Ad Network as a first class section โ
- Status: BUILT and pushed 2026-08-28 on
feat/admin-and-merchant-portals. Not merged; the operator reviews the branch on 2026-08-29. - Issue:
#986: feat(admin): promote Ad Network to a first class section with Delivery, Placements and Review tabs - Launch plan: UNDECIDED. Not guessed.
- Test plan:
docs/engineering/testing/runs/ad-network-first-class/README.md, filled from a live pass. 8 of 10 scenarios passed; 2 are blocked on data and say what would close them. - Captures:
../admin-venues-section/screenshots/live/(live, signed in) andscreenshots/(Storybook, including the 2026-08-30 tab-strip before and after). - Design source:
design-survey.md, section "Ad Network". The option matrix lives THERE and is deliberately not copied here.
What is this? โ
Ad Network becomes a top-level Platform item beside Venues and Merchants. โ
- Operator words, 2026-08-24: "It needs its own dedicated considerations."
- Today it is
/admin/analytics/dashboards/ads, one tab among five, two clicks deep. Venues by contrast is one click with no parent.
Option B was chosen on 2026-08-28: a section with tabs, not a bare promotion. โ
| Tab | What it is | Where it comes from |
|---|---|---|
| Delivery | The existing Ad Delivery dashboard | Moved from analytics/dashboards/ads |
| Placements | The inventory view | New. The concept already exists in the data (the chart segments Hero, Inline, Feed, Unknown) with no surface owning it |
| Review | The gate an offer passes to enter the network | Moved from merchants/offer-review |
What does this cost that a nav move does not show? โ
Merchants changes meaning. โ
- With Offer review gone, Merchants becomes accounts and applications only. The survey judged that cleaner, but it is a real change to a section nobody asked to change.
Offer review's own redesign is now entangled with this move. โ
- The operator named that redesign separately ("REALLY needs a redesign"). This project does the MOVE, not the redesign. Keeping those apart is what stops one build turning into three.
A defect was waiting at the destination. FIXED 2026-08-28, and the fix has two halves. โ
#981is closed.apps/admin/vite.config.mjsnow proxies/api/adminto merchants-api. Verified rather than inferred: an unauthenticated request went from200 text/html(the app shell answering) to401 application/json(merchants-api saying no), which proves it reaches the service.- The second half matters more than the first.
parseResponseturned a non-JSON 200 into{ message: '<html>' }, soresult.offers || []rendered a confident empty queue with no error and no failed request. A 2xx whose body is not JSON now throws. That is why#981survived four days, and why the 2026-08-24 design survey recorded the queue as empty and reasoned about it as a product state. - With both fixed, the queue reads
{"offers":[],"count":0}: genuinely empty in dev, which is a different claim from the one the survey made.
The page moved section and kept its old section's tab strip. FIXED 2026-08-30. โ
- Operator words: "the ad delivery dashboard in the ad network screens is incorrect ... It is showing the analytics dashboard links in ad delivery which is incorrect." She read it in the admin Storybook.
AdDeliveryDashboardrenders body-only when the section mounts it, and a full page shell when it is mounted on its own. The shell still renderedDashboardTabs, the ANALYTICS strip, so the page showed User Engagement, Ad Delivery, Platform & Device, Conversion Funnels and Venue Activity, with User Engagement lit rather than anything on the page you were looking at.- Both routes into that shell redirect, so Storybook is the only surface that mounts it. That is why it survived the live pass on 2026-08-28 and only turned up when someone opened the story.
- The shell now renders
PageTabswithAD_NETWORK_TABS, imported fromAdNetworkSectionrather than copied, and a breadcrumb reading Ad Network. Delivery is active. Verified in Storybook at 6008, before and after, inscreenshots/.
The same bug had a second direction, and a second page. โ
DashboardTabsstill LISTED Ad Delivery as a tab Analytics owned, so Analytics advertised a page it had given away and clicking it left the section with no warning. Ad Delivery is now acrossLink, the marking#985already gave Venue Activity: it can never go active, it sits at the end of the strip, and its tooltip says "Lives under Ad Network now".VenueActivityDashboardhad the identical defect from#985, found while checking the otherDashboardTabsconsumers. Its standalone shell wore the Analytics strip too. It now wears the Venues strip fromVENUES_TABS, with Activity active.DashboardPlaceholderis the third consumer and it is correct: its three routes are genuinely Analytics dashboards.
A strip that nothing renders is a strip nobody checks. โ
- After the two fixes, no story rendered
DashboardTabsat all, so the Analytics strip would have gone back to being invisible.DashboardPlaceholder.stories.jsxnow renders it, which is the only place the Analytics tab strip can be looked at.
The file did NOT move, deliberately. โ
AdDeliveryDashboard.jsxstill lives inapps/admin/src/admin/analytics/while the section that owns it lives inadmin/ad-network/. Moving it is the honest fix and it is still worth doing.- It was left because the move lands on more than the file:
AdminShell.jsx,analytics/index.js, the co-located CSS, and a path-keyed entry intooling/scripts/admin-zones-baseline.json, which reads a rename as a new violation. A large Storybook reorganization landed on this branch the same morning andtooling/storybook-admin-static/is a committed capture of it. Doing half the move beside that is worse than not starting it.
#1000 names the wrong component. โ
- The issue attributes the critical
aria-required-childrentoPageTabs.jsx:21. Measured with axe:PageTabsreports zero violations, because every child it renders is arole="tab"button. - The violation comes from the hand-rolled strips that copy the
page-tabsmarkup and put a bare<a>insiderole="tablist":DashboardTabs.jsx,EventTrackingTabs.jsxandBigQueryTabs.jsx. The two routes the issue cites both render one of those, notPageTabs. Not fixed here; the issue needs its target corrected first.
What is open? โ
What Placements actually shows. RESOLVED 2026-08-28. โ
- The inventory half is REAL and derived from
PLACEMENTSin@lantern/shared/ads: three surfaces, where each appears, how contended each is, with copy taken from the placement table indocs/economics/AD_PLACEMENT_ECONOMICS.mdso the portal and the economics model use the same words. - The three per-surface figures (fill rate, eligible offers, CTR) are NOT faked. They render through
Scaffold, dated and pointing at#998, which was filed BEFORE the surface was built so the number is real rather than a placeholder for a placeholder. - A zero would have lied. It reads as "nothing is delivering" when the truth is "nothing has been asked", and those look identical on a dashboard while meaning opposite things.
Unknownis deliberately absent. The Delivery chart segments Hero, Inline, Feed and Unknown, and Unknown reads like a fourth surface. It is a data-quality bucket for rollup rows whose placement did not match the vocabulary, so it is not inventory anyone can buy and it stays on the chart where it already is.
Whether pacing and fill rules belong here. โ
- The Ad Delivery copy already gestures at them with nothing behind it. Out of scope for the move; named here so it is not lost.