Shadow mode
How Href admins view a tenant's CRM read-only without disturbing data.
When you click Open CRM (shadow) from a tenant detail page in the admin console, you launch a shadow session: an authenticated, read-only view of that tenant's CRM as if you were one of their team members.
How it works
- The action ensures a
tenant_membersrow exists for you on that tenant withrole = 'shadow'. If you're already a real member (admin/manager/member), the existing role is preserved and shadow mode does not activate (you'd see the CRM normally). - Supabase Auth generates a one-time magic link aimed at the tenant's
/auth/callback. The admin console opens that link in a new tab. - Once the magic link lands, you have a session on the tenant subdomain. Cookies are scoped to that subdomain only; your admin-console session stays put.
- The CRM layout detects
role = 'shadow'and renders an amber Shadow mode banner at the top with your admin email and an Exit shadow button.
What "read-only" means
Every server action that mutates tenant data (stage moves, note adds, task changes, settings edits, invites, API key rotations, etc.) calls requireWriteAccess(). That helper enforces role !== 'shadow'. If you click an edit control in shadow mode, you'll get a toast:
Read-only: you're shadowing this workspace as an Href admin.
UI controls aren't hidden — they're visible so you can see what the user sees — but they won't do anything. Phase 2 polish will hide them entirely.
Exiting
Click Exit shadow in the banner. You're signed out of the tenant subdomain; your admin-console session is unaffected (different cookie scope). Back to admin.
The shadow tenant_members row stays so re-entering shadow on the same tenant is instant. To fully clean up after support is done, ask an admin to remove the row via SQL:
DELETE FROM tenant_members
WHERE user_id = '<admin-user-id>' AND role = 'shadow';
Audit notes
Shadow sessions show up the same way as regular sessions in Supabase Auth logs. There is no separate "shadow event" table yet. v2 will add an admin_audit_log that records every shadow entry, every edit attempted (and rejected), and every tenant rename / delete from the admin console.
Implementation reference
- Schema:
0004_shadow_role.sqladds'shadow'to thetenant_members.roleCHECK. - Server-side gate:
src/lib/auth/session.ts → requireWriteAccess(). - Launch action:
src/lib/actions/shadow.ts → startShadowSession(). - Banner:
src/components/shadow-banner.tsx, wired intosrc/app/(auth)/crm/layout.tsx.