diff --git a/frontend/editor/src/core/contexts/FolderContext.test.tsx b/frontend/editor/src/core/contexts/FolderContext.test.tsx index 46de8f6d7..1734bf350 100644 --- a/frontend/editor/src/core/contexts/FolderContext.test.tsx +++ b/frontend/editor/src/core/contexts/FolderContext.test.tsx @@ -1,6 +1,7 @@ import React from "react"; import { describe, expect, test, vi, beforeEach } from "vitest"; import { render, screen, waitFor, act } from "@testing-library/react"; +import { MemoryRouter } from "react-router-dom"; import { FolderProvider, useFolders } from "@app/contexts/FolderContext"; import { createFolderId, FolderId, FolderRecord } from "@app/types/folder"; @@ -110,9 +111,11 @@ function axiosError(status: number, message = "rejected"): Error { async function renderAndWaitForPull(): Promise { render( - - - , + + + + + , ); // The pull is fired from a mount effect; wait until it has resolved by // observing that `mockList` was called at least once and a tick has @@ -241,9 +244,11 @@ describe("FolderContext stale-folder 404 cleanup", () => { mockList.mockResolvedValueOnce(initial); const apiRef: { current: ProbeApi | null } = { current: null }; render( - - (apiRef.current = api)} /> - , + + + (apiRef.current = api)} /> + + , ); await waitFor(() => expect(screen.getByTestId("count").textContent).toBe( diff --git a/frontend/editor/src/core/contexts/FolderContext.tsx b/frontend/editor/src/core/contexts/FolderContext.tsx index e218072ec..65b746bd6 100644 --- a/frontend/editor/src/core/contexts/FolderContext.tsx +++ b/frontend/editor/src/core/contexts/FolderContext.tsx @@ -38,6 +38,8 @@ import { } from "@app/types/folder"; import { useIndexedDB } from "@app/contexts/IndexedDBContext"; import { useAppConfig } from "@app/contexts/AppConfigContext"; +import { useLocation } from "react-router-dom"; +import { isAuthRoute } from "@app/constants/routes"; interface FolderContextValue { folders: FolderRecord[]; @@ -363,10 +365,17 @@ export function FolderProvider({ children }: FolderProviderProps) { // guaranteed-to-403 round-trip per session. const { config: appConfig } = useAppConfig(); const storageBackedByServer = appConfig?.storageEnabled === true; + // Don't hit the authenticated storage API on auth routes (/login, /signup, + // /auth/*, ...). The global FolderProvider is mounted everywhere, including + // the login screen where the user has no session yet, so an unguarded pull + // fires a guaranteed-401 GET /api/v1/storage/folders before sign-in. Mirror + // the auth-route skip used by LicenseContext / AppConfigContext. + const location = useLocation(); + const onAuthRoute = isAuthRoute(location.pathname); useEffect(() => { - if (!storageBackedByServer) return; + if (!storageBackedByServer || onAuthRoute) return; void pullFromServer(); - }, [pullFromServer, storageBackedByServer]); + }, [pullFromServer, storageBackedByServer, onAuthRoute]); const foldersById = useMemo(() => { const map = new Map(); diff --git a/frontend/editor/vite.config.ts b/frontend/editor/vite.config.ts index 3f51f7ba3..0df5fbee6 100644 --- a/frontend/editor/vite.config.ts +++ b/frontend/editor/vite.config.ts @@ -243,8 +243,13 @@ export default defineConfig(async ({ mode }) => { // SPA fallback returns index.html as text/html and React never mounts. // VITE_BUILD_FOR_PREVIEW=1 (set by the CI playwright steps) overrides to // an absolute base so deep-route asset paths resolve to /assets/... + // Trailing slash is required: it becomes `` in index.html, and + // the browser resolves relative links (manifest.json, modern-logo/favicon.ico) + // against the base's *directory*. Without it, `/bpp` resolves relatives to + // the domain root → `/manifest.json` 404 instead of `/bpp/manifest.json`. + // getBasePath() strips the trailing slash, so BASE_PATH/routing are unchanged. base: env.RUN_SUBPATH - ? `/${env.RUN_SUBPATH}` + ? `/${env.RUN_SUBPATH}/` : process.env.VITE_BUILD_FOR_PREVIEW === "1" ? "/" : "./",