demo-auto-vendor-viewbreakdown.mdscope.md behind
this batch — a single fully-shaped ask, quoted in the stub.The demo host already auto signs a visitor in as the demo tenant's admin, and that is deliberate: admin is the only role that holds View As, which is what lets a presenter move between personas during a walkthrough. But admin is the wrong first screen — the story we sell opens on the vendor. So after the existing entry flow completes, the demo should enter View As on the first available vendor automatically, landing the visitor on the vendor dashboard rather than the admin one. The admin seat stays underneath: the View As banner and switcher are still there, so the presenter can hop to any other persona or exit back to admin exactly as today. This is demo-host-only behaviour — the platform host's admin sign-in is untouched.
Service-journey: none (a presentation-surface change, not a product step).
feature-role-matrix entity: users / View As. Lands entirely in apps/web — the demo entry
flow (app/(auth)/demo/, app/api/demo/enter/, lib/demo-sign-in.ts, lib/demo-host.ts)
and the View As entry path (lib/actions/view-as.ts, lib/view-as-switcher-data.ts).
apps/demo is frozen and is not involved.
_done/demo-auto-vendor-view.mdJamie's request (2026-08-21), verbatim:
"currently, the demo environment auto signs into admin. Which is good because the admin login has access to the view as feature. for specifically the demo url, after the admin auto sign in is done, we need to automatically load into the first available vendor role within the view as feature. Essentially when a person lands on the demo url, they should be automatically directed to the vendor view."
A visitor arriving at the demo today lands on /admin — the internal operator console — when
the story the demo is meant to tell opens on the vendor. Signing in as admin is still right
(it is the only role that holds View As, which is what lets a presenter move between personas
mid-walkthrough); it is the landing screen that is wrong.
On the demo host only, once the existing entry flow has produced a real Clerk session on the
demo admin, enter View As on the first available vendor before the visitor sees anything, and
land them on the vendor dashboard (ROLE_HOME.vendor = /vendor) instead of /admin. The
admin seat is unchanged underneath it: the View As banner and switcher render as they do
today, so a presenter can switch to any other persona or exit back to admin. If the demo
tenant has no vendor user, the visitor falls back to the admin landing rather than a broken
or empty screen.
/admin./admin with no View As
cookie set.not_demo_host, not_gated, disabled,
not_configured, rate_limited) behave exactly as before.DEMO_AUTO_SIGN_IN kill switch.groupByRole in lib/view-as-switcher-data.ts), so the cheapest deterministic answer is
the first of that sorted list. Alternative: a named vendor pinned by env var or by the demo
roster script, so the storyline always opens on the same seat regardless of directory
churn. Recommend the pinned seat with the sorted-first as fallback — the demo roster
(packages/services/scripts/demo-roster.ts) already curates who exists.DemoEntry), or client-side as a follow-up call
after setActive? Server-side avoids a visible flash of /admin and a second navigation;
it needs the View As cookie set on a request that already carries the new session cookie,
which the current flow only has after window.location.assign(redirectTo). Recommend
landing on a demo-only entry path that sets the cookie and redirects, so the visitor sees
one navigation.viewAsAuditService) like a manual enter, or skipped as machine-initiated? Recommend
recording it, flagged as automatic, so the trail has no gaps.Evidence gathered 2026-08-21; all of it is apps/web (apps/demo is frozen and irrelevant
here).
app/api/demo/enter/route.ts mints a single-use Clerk sign-in token for
DEMO_ADMIN_CLERK_USER_ID and returns it with DEMO_TENANT_CLERK_ORG_ID; the browser
finishes in app/(auth)/demo/_components/demo-entry.tsx
(signIn.ticket() → signIn.finalize({ navigate: noop }) → setActive({ organization })
→ window.location.assign(redirectTo)). redirectTo is already validated server-side —
that is the seam where the vendor landing most likely belongs.lib/demo-host.ts
(isDemoRequest, DEMO_OVERRIDE_COOKIE, DEMO_ENTRY_API_ROUTE); gate: lib/demo-access.ts;
config and refusals: lib/demo-sign-in.ts.lib/actions/view-as.ts → enterViewAs is admin-only, own-tenant only,
sets the httpOnly VIEW_AS_COOKIE, writes a dual-identity audit entry via
viewAsAuditService, and redirects to ROLE_HOME[role]. It takes exactly the
ViewAsSelection shape ({ kind: "user", clerkUserId, role }). Reuse it rather than
writing the cookie directly, so the audit entry and the org re-check stay on one path.getViewAsSwitcherData() (lib/view-as.ts) →
userService.listTenantDirectory(tenantId) → resolveSwitcherData, which name-sorts
within each role. Ordering logic is pure and already unit-tested
(lib/view-as-switcher-data.test.ts) — extend those tests rather than adding a new harness.ROLE_HOME and the View As cookie contract: lib/auth.ts. Route policy / proxy behaviour
for the demo host: proxy.ts.turbo.json → globalEnv in the same
PR (the turbo/no-undeclared-env-vars rule fails lint otherwise).