@@ -10,6 +10,7 @@ import {
1010 useParams ,
1111} from "@tanstack/react-router" ;
1212import { AutumnProvider } from "autumn-js/react" ;
13+ import { isValidOrgSlug } from "@executor-js/api" ;
1314import posthog from "posthog-js" ;
1415import { PostHogProvider } from "posthog-js/react" ;
1516import type { FrontendErrorReporter } from "@executor-js/react/api/error-reporting" ;
@@ -214,6 +215,14 @@ function AuthGate({ ssrOrigin }: { ssrOrigin: string | null }) {
214215 // is scoped to it, so `auth.organization` IS this org when the caller is a
215216 // member — and `null` when the URL names an org they can't access.
216217 const urlOrgSlug = ( useParams ( { strict : false } ) as { orgSlug ?: string } ) . orgSlug ;
218+ // The same slug derived from the PATHNAME instead of the route params: the
219+ // params resolve asynchronously (a fresh load renders once with no orgSlug,
220+ // then again with it), and anything keyed on them remounts on that flap.
221+ // The pathname is synchronously correct on the very first render, and it is
222+ // exactly what the request header derives from (getActiveOrgSlug), so the
223+ // registry scope below can never disagree with the header scope.
224+ const firstSegment = location . pathname . split ( "/" ) [ 1 ] ?? "" ;
225+ const pathnameOrgSlug = isValidOrgSlug ( firstSegment ) ? firstSegment : null ;
217226
218227 // The SSR gate already bounced fresh org-less document requests to
219228 // /create-org; this catches the MID-SESSION transitions (org deleted,
@@ -282,26 +291,42 @@ function AuthGate({ ssrOrigin }: { ssrOrigin: string | null }) {
282291 // /<orgB> while their cookie still points at orgA would briefly render orgA's
283292 // slug in the copyable URL before /account/me (URL-scoped) corrects it. The
284293 // URL slug is the actual request scope and is correct on the very first paint,
285- // so sourcing it from there removes that flash. Falls back to the session slug
286- // on a bare URL (which OrgSlugGate is about to canonicalize onto it anyway).
287- const scopeSlug = urlOrgSlug ?? activeSlug ;
294+ // so sourcing it from there removes that flash. VALIDATED (pathnameOrgSlug,
295+ // not the raw route param): the `{-$orgSlug}` param also captures reserved
296+ // console roots ("/integrations" → orgSlug "integrations"), which are not
297+ // org scopes. Falls back to the auth org on a bare/reserved URL (which
298+ // OrgSlugGate canonicalizes onto it below).
299+ const scopeSlug = pathnameOrgSlug ?? activeSlug ;
288300 const billingHeaders = scopeSlug ? { [ EXECUTOR_ORG_HEADER ] : scopeSlug } : undefined ;
289301
290302 return (
291303 < AutumnProvider pathPrefix = "/api/billing" headers = { billingHeaders } >
292304 < Sentry . ErrorBoundary fallback = { < ShellErrorFallback /> } showDialog = { false } >
293- < ExecutorProvider connection = { connection } onHandledError = { captureFrontendError } >
305+ { /* scopeKey ties the atom registry to the URL's org: cached query
306+ results can never survive an org change, and the bare → slugged
307+ canonicalization remounts the registry so anything fetched
308+ header-less on first paint (rejected server-side) is refetched
309+ with the org header. */ }
310+ < ExecutorProvider
311+ connection = { connection }
312+ scopeKey = { pathnameOrgSlug }
313+ onHandledError = { captureFrontendError }
314+ >
294315 < React . Suspense fallback = { < BlankScreen /> } >
295316 < ExecutorPluginsProvider plugins = { clientPlugins } >
296317 < OrganizationProvider
297318 organizationId = { auth . organization . id }
298319 organizationSlug = { scopeSlug }
299320 >
300- { /* The org header scopes every request to the URL's org, so
301- reaching here means the caller is a member of `activeSlug`
302- (a foreign slug already 404'd above). The gate only keeps
303- the URL canonical — bare → /<slug>. */ }
304- < OrgSlugGate activeSlug = { activeSlug } >
321+ { /* Canonicalize onto the URL's org, not the auth org: on first
322+ paint `auth.organization` is the SSR hint (the COOKIE's
323+ org), and canonicalizing onto that would rewrite a
324+ multi-org user's /<orgB> URL to /<orgA> during the hint
325+ window. `scopeSlug` prefers the URL slug, so a slugged URL
326+ is already canonical (a foreign slug 404'd above) and only
327+ a bare URL gets rewritten — onto the auth org, the one
328+ thing it can mean. */ }
329+ < OrgSlugGate activeSlug = { scopeSlug } >
305330 < Shell />
306331 < Toaster />
307332 </ OrgSlugGate >
0 commit comments