A customer admin who opens an Admin link lands on the dashboard with a short notice instead of a silent bounce.
2 Oct 2026 · branch fix/admin-deeplink-unavailable-message · captured against propflow-stage · at 8b950826215
Data note. Every name and balance below comes from propflow-stage, which is anonymized by construction (ADR-0097/0110 — phones in the impossible +1000 NPA, emails @example.test). The identical capture against prod would carry real tenants' names and balances and is not safe to attach anywhere, which is why the generator refuses to run against one.
Signed in as a real customer admin (role org_admin, not platform staff). In production, the Admin gate sends a non-admin who opens /admin, /admin/dev/atlas or /settings/admin to /dashboard?accessDenied=admin. This PR adds the accessDenied part, and middleware.test.ts, page-gate.test.ts and gate-page.surfaces.test.ts pin it. A local dev server skips the page gate on purpose (middleware.ts and gate-page.ts do nothing outside production), so a deep link can't be bounced here. This capture opens the landing address directly, and the browser renders it exactly as it would after the bounce.
The notice is in the top right: a title saying what happened, then one sentence saying where they are now. It shows no page address, role name or internal term. Before this PR, nothing rendered for a normal session, so the link looked broken.
Asserted in the captured DOM — That page isn't available. · We brought you back to your dashboard. · Dismiss. The notice is in the DOM, and the component has already removed accessDenied from the address bar.
At 390px the notice spans the screen inside the 16px side margins, and Dismiss stays on the same row.
Asserted in the captured DOM — That page isn't available. · We brought you back to your dashboard.
After a click on Dismiss, the notice is gone and the dashboard is unobstructed. A refresh won't bring it back, because the component removes accessDenied from the address on first read.
Asserted in the captured DOM — Dashboard
Without accessDenied, no notice. The notice is keyed on the redirect's reason, not shown on every visit.
Round 1 (against docs/runbooks/brand-and-design.md):
Round 2: I added the Dismiss capture above to prove the notice closes, and the no-param control to prove it is keyed on the redirect reason. No code change came out of either round.
Unchanged:
AccessDeniedToast.test.tsx).