From a263a202d9aa39d960a7d53f37d3e6655ef5aa2d Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 21 May 2026 19:18:22 +0200 Subject: [PATCH] docs(backlog): per-port branded login (section K) + next-env regen Section K documents the recommended path for multi-tenant branded auth screens: a single Next.js app behind `*.crm.example.com` wildcard DNS that derives the active portSlug from the Host header (instead of the current "first active port wins" fallback in resolveAuthShellBranding). Includes the open work: wildcard cert, parent-domain cookie scope, middleware host-resolver, switcher UI, and bootstrap seed. next-env.d.ts is auto-regenerated by Next typegen with double-quote formatting; included so the diff stays clean for the next dev session. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/BACKLOG.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 04f7549a..06ca08aa 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -351,6 +351,45 @@ Acceptance: spot-check the timeline tab on a recently-edited interest, client, y --- +## K. Per-port branded login (multi-tenant UX) + +The login / forgot-password / set-password screens currently show the +"first active port" branding via `resolveAuthShellBranding()`, because +those surfaces have no portId in the URL. With two unrelated ports +(Port Nimara + Port Amador, no umbrella company) this means whichever +port was created first wins the login screen for everyone. + +**Recommended path: shared instance, Host-header branding.** Run a +wildcard subdomain (`*.crm.example.com`) into the same Next.js app and +have middleware derive the active portSlug from the `Host` header. +`resolveAuthShellBranding()` then takes an optional host argument and +resolves by slug instead of "first port". Switcher becomes a +`window.location.assign('https://other-port.crm.example.com/dashboard')`; +session cookies are scoped to the parent domain so super-admins don't +re-auth when hopping. + +Open work: + +- Wildcard DNS + TLS cert (Cloudflare DNS-01 with `*.crm.example.com`). +- Cookie domain change: `pn-crm.session_token` needs `Domain=.example.com` + set in better-auth config. +- Middleware: read host, resolve portSlug, attach to request headers so + the auth-shell branding resolver can use it. +- Update `resolveAuthShellBranding()` to prefer host-derived port over + "first port" fallback. +- Port-switcher UI: dropdown in topbar that lists ports the user has + access to and navigates cross-subdomain. +- Bootstrap seed: populate `branding_logo_url` / `_email_background_url` + / `_app_name` for the default port so fresh deploys aren't blank. + +Alternative considered: **N instances, one per port.** Cleaner data / +deploy isolation but no UX gain over the shared-instance path. Defer +unless an operator demands independent migrations or data residency. + +Size: medium (1–2 days incl. cert + cookie work + seed + switcher). + +--- + ## I. Dashboard widget wishlist User-driven enhancements to the customizable main dashboard