History

Connect card: pin the MCP install URL to the URL's org (fix wrong-org flash) (#1011)

A multi-org user loading /<orgB> while their session cookie still points at orgA saw orgA's slug flash into the copyable `npx add-mcp .../<slug>/mcp` box before /account/me resolved. On first paint `auth.organization` comes from the SSR auth-hint, which is scoped to the COOKIE's org — but the page is scoped to the URL's org. Seed OrganizationProvider's slug (which feeds the connect card via useOrganizationSlug) from the URL's `{-$orgSlug}` param, falling back to the session slug on a bare URL (which OrgSlugGate canonicalizes onto it anyway). The URL slug is the actual request scope and is correct on the very first paint, so the wrong slug never appears. OrgSlugGate keeps using the session slug for canonicalization; only the install-URL context changes. useOrganizationSlug is consumed solely by the connect card, so there's no other blast radius.

Rhys Sullivan committed 4 months ago

Commit 013fa381 parent be5e1bb1 file · +10 −1Browse files

Unified · Split

apps/cloud/src/routes/__root.tsx+10−1
@@ -275,6 +275,15 @@ function AuthGate({ ssrOrigin }: { ssrOrigin: string | null }) { · 274 unmodified lines
275275 // which is the same origin, so the key never changes and nothing remounts.
276276 const connection = ssrOrigin ? ({ kind: "http", origin: ssrOrigin } as const) : undefined;
277277 const activeSlug = auth.organization.slug;
278+ // The org context's slug feeds the connect card's `/<slug>/mcp` install URL.
279+ // Prefer the URL's slug over the session's: on first paint `auth.organization`
280+ // comes from the SSR auth-hint (the COOKIE's org), so a multi-org user viewing
281+ // /<orgB> while their cookie still points at orgA would briefly render orgA's
282+ // slug in the copyable URL before /account/me (URL-scoped) corrects it. The
283+ // URL slug is the actual request scope and is correct on the very first paint,
284+ // so sourcing it from there removes that flash. Falls back to the session slug
285+ // on a bare URL (which OrgSlugGate is about to canonicalize onto it anyway).
286+ const scopeSlug = urlOrgSlug ?? activeSlug;
278287
279288 return (
280289 <AutumnProvider pathPrefix="/api/billing">
@@ -284,7 +293,7 @@ function AuthGate({ ssrOrigin }: { ssrOrigin: string | null }) { · 3 unmodified lines
284293 <ExecutorPluginsProvider plugins={clientPlugins}>
285294 <OrganizationProvider
286295 organizationId={auth.organization.id}
287− organizationSlug={activeSlug}
296+ organizationSlug={scopeSlug}
288297 >
289298 {/* The org header scopes every request to the URL's org, so
290299 reaching here means the caller is a member of `activeSlug`