fix(stores): hold the estate fetch until there is a session
`GET /api/sites 401 (Unauthorized)` fired on the sign-in page for every visitor who had not signed in yet. WorkspaceProvider sits above the whole route tree, /login included, and it already carried a comment saying the estate was "gated on the session ... asking for the estate before anyone has signed in would put a guaranteed 401 in the console on every visit to the sign-in page". The gate was never applied. `useSites()` was called unconditionally and `isAuthenticated` only guarded the derived `stores` value below it — and a gate on a derived value cannot hold a fetch that has already gone out. useResource has taken `Endpoint<T> | null` for exactly this all along; the effect returns early on a null key, so nothing is sent. The gate goes in useSites rather than in one consumer because the rule belongs to the endpoint — no session, no estate — and /stores calls it too. Costs a signed-in user nothing: SessionProvider resolves `status` synchronously from the server-rendered `initialSession`, so there is no 'loading' pass to wait through before the request goes out. /stores is unaffected in the other direction too — AuthGuard renders a spinner instead of children once status is 'unauthenticated', so no consumer sits on a permanently held resource. Verified in the browser against a clean network buffer: with the gate reverted, /login issues GET /api/sites → 401; with it in place, /login issues 31 requests and none of them are /api/*. Worth being explicit about what this does NOT change: platform.loyaly.ai/api/* is the correct address for these calls. It is this app's own BFF, same-origin by design, and the hop to mcp.loyaly.ai happens server-side where the token lives. The 401 was a request that should never have been made, not a request made to the wrong host. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -75,10 +75,11 @@ export function WorkspaceProvider({children}: {children: React.ReactNode}) {
|
||||
* five Bengaluru shops, which meant every scoped request in the app was
|
||||
* filtered by a store id that only existed in this file.
|
||||
*
|
||||
* Gated on the session because this provider sits above the whole route
|
||||
* tree, including /login: asking for the estate before anyone has signed in
|
||||
* would put a guaranteed 401 in the console on every visit to the sign-in
|
||||
* page.
|
||||
* The session gate that keeps this off /login lives inside useSites, not
|
||||
* here. It used to be written here as prose and applied only to `stores`
|
||||
* below, so the request still went out on the sign-in page and every
|
||||
* anonymous visitor got `GET /api/sites 401` in their console. A gate on the
|
||||
* derived value cannot hold a fetch — only a null endpoint can.
|
||||
*/
|
||||
const {status} = useSession();
|
||||
const sites = useSites();
|
||||
|
||||
Reference in New Issue
Block a user