Skip to Content

← All archived intake epics

Intake: demo-auto-vendor-view

breakdown.md

Breakdown: Demo lands in the vendor view

  • scope-slug: demo-auto-vendor-view · source: Jamie's request (2026-08-21); no scope.md behind this batch — a single fully-shaped ask, quoted in the stub.
  • initiative: Sell the platform (demo tenant as the sales surface)
  • personas: Vendor (the emulated view), Admin (the real seat behind it)

What I understood

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.

Where it sits

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.

Build order

  1. demo-auto-vendor-view — after the demo auto sign-in, auto-enter View As on the first available vendor and land on the vendor dashboard — depends-on: none

Out of scope (whole scope)

  • Any change to who the demo signs in as (still the demo tenant admin) or to the entry gate, rate limit, or kill switch.
  • View As behaviour on the platform host, or for real admins on real tenants.
  • Choosing the vendor by anything richer than the existing switcher's deterministic ordering (see the stub's open question on which vendor "first" means).
  • Auto-entering any persona other than vendor, or making the landing persona configurable.

_done/demo-auto-vendor-view.md

Stub: Demo lands in the vendor view

  • feature-slug: demo-auto-vendor-view
  • scope: demo-auto-vendor-view
  • personas: Vendor, Admin
  • initiative: Sell the platform (demo tenant as the sales surface)
  • depends-on: none
  • sequence: 1 of 1

Problem

Jamie'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.

Proposed change

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.

Acceptance criteria (rough)

  • A visitor who completes demo entry on the demo host lands on the vendor dashboard, in View As, without clicking anything.
  • The underlying session is still the demo admin: the View As banner shows the emulated vendor, the switcher offers every other emulatable persona, and exiting View As returns the presenter to /admin.
  • The vendor chosen is deterministic — the same visit order picks the same vendor every time, and it is the first vendor by the switcher's existing ordering.
  • With no vendor in the demo tenant's directory, entry still succeeds and lands on the admin dashboard; nothing errors and no half-set View As cookie is left behind.
  • Nothing changes on the platform host: a real admin signs in to /admin with no View As cookie set.
  • The existing entry refusals (not_demo_host, not_gated, disabled, not_configured, rate_limited) behave exactly as before.
  • Exiting View As and re-entering by hand still works, and a presenter who exits is not auto-pushed back into the vendor view on the next navigation.

Out of scope (this feature)

  • Changing which user the demo signs in as, or any part of the entry gate, token mint, rate limit or DEMO_AUTO_SIGN_IN kill switch.
  • View As on the platform host or for real tenants.
  • Making the auto-entered persona configurable, or auto-entering personas other than vendor.
  • Any change to the vendor dashboard itself or to what demo data it shows.

Open questions

  • Q1: What is "first available vendor"? The switcher already name-sorts users within a role (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.
  • Q2: Where does the auto-enter run — server-side as part of resolving the demo landing (before the redirect target is handed to 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.
  • Q3: Should the auto-entry be recorded in the View As audit trail (viewAsAuditService) like a manual enter, or skipped as machine-initiated? Recommend recording it, flagged as automatic, so the trail has no gaps.
  • Q4: Should a presenter who has exited View As be re-entered on their next visit to the demo root, or does exit stick for the session? Recommend exit sticks until a fresh entry.

Notes for Define

Evidence gathered 2026-08-21; all of it is apps/web (apps/demo is frozen and irrelevant here).

  • Entry today: 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.
  • Demo-host detection and the entry route constant: 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.
  • View As entry: lib/actions/view-as.tsenterViewAs 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.
  • Candidate vendors come from 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.
  • If a pinned-vendor env var comes out of Q1, add it to turbo.jsonglobalEnv in the same PR (the turbo/no-undeclared-env-vars rule fails lint otherwise).
  • touches: apps/web/app/(auth)/demo/, apps/web/app/api/demo/, apps/web/lib/{demo-sign-in, demo-host,view-as,view-as-switcher-data,auth}.ts, apps/web/lib/actions/view-as.ts, possibly turbo.json.