Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds a new "Hosted Components" frontend app plus Vite/TypeScript/ESLint config, TanStack Router routes (root, index, handler), React client entry, Stack client integration, vitest exclusion, and a dev-launchpad registration entry. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser as Browser/Client
participant Client as client.tsx
participant Router as TanStack Router
participant Root as __root Route
participant Stack as StackClientApp
Browser->>Client: load & hydrate
Client->>Router: initialize/getRouter
Router->>Root: render RootComponent (route match)
Root->>Stack: compute projectId & instantiate StackClientApp
Root->>Stack: wrap app with StackProvider/StackTheme -> Outlet
Router->>Client: render matched page (index / handler)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryAdds a new Key changes:
Minor issue:
Confidence Score: 5/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User visits subdomain] --> B{Extract project ID from subdomain}
B --> C{Project ID exists?}
C -->|No| D[Show 'Invalid URL' error]
C -->|Yes| E{Valid UUID format?}
E -->|No| F[Show 'Invalid project ID' error]
E -->|Yes| G[Initialize StackClientApp]
G --> H{User route}
H -->|/handler/*| I[StackHandler component<br/>Sign-in/Sign-up flows]
H -->|/| J{User authenticated?}
J -->|No| K[Redirect to sign-in]
J -->|Yes| L[Show welcome page<br/>with UserButton]
Last reviewed commit: 07b9be1 |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
apps/hosted-components/src/routes/__root.tsx (1)
115-117: Render an explicit loading state instead of a blank screen.Line [116] returns an empty fragment while initialization is in progress; use a visible loading UI so state transitions are explicit.
Based on learnings: "When building frontend code, always carefully deal with loading and error states. Be very explicit with these."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/hosted-components/src/routes/__root.tsx` around lines 115 - 117, The early return for projectId (if (projectId === undefined) return <></>;) shows a blank screen; replace it with an explicit loading UI so users see progress. In the if (projectId === undefined) branch in __root.tsx, return a visible loading state (for example a <Loading /> component or a simple accessible element like a div with role="status" and a spinner/text) instead of an empty fragment, ensuring any existing Loading component or CSS spinner is used for consistency with the app.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/hosted-components/package.json`:
- Line 7: The dev script in package.json sets Vite to port 8109 ("dev": "vite
dev --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}09") which conflicts with the
app’s expected 8105; update the dev script to default to 8105 instead (adjust
the NEXT_PUBLIC_STACK_PORT_PREFIX fallback or the appended digits) so the "dev"
npm script, the Vite server, and the local URL guidance in src/routes/__root.tsx
all use port 8105 consistently.
In `@apps/hosted-components/src/routes/__root.tsx`:
- Around line 97-113: Replace the type bypass and non-null assertion: create a
properly typed adapter for redirectMethod that calls the React Router
useNavigate hook (e.g., a small function that captures const navigate =
useNavigate(); and returns a function (to: string) => void) and pass that
adapter to new StackClientApp instead of `useNavigate as any`; and replace the
`stackApp!` usage with a defensive null-coalescing pattern (e.g., `stackApp ??
throwErr('stackApp was unexpectedly null despite validation checks')` or an
explicit guard that throws) so the code is type-safe and fails with an explicit
error if stackApp is null.
---
Nitpick comments:
In `@apps/hosted-components/src/routes/__root.tsx`:
- Around line 115-117: The early return for projectId (if (projectId ===
undefined) return <></>;) shows a blank screen; replace it with an explicit
loading UI so users see progress. In the if (projectId === undefined) branch in
__root.tsx, return a visible loading state (for example a <Loading /> component
or a simple accessible element like a div with role="status" and a spinner/text)
instead of an empty fragment, ensuring any existing Loading component or CSS
spinner is used for consistency with the app.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (12)
apps/dev-launchpad/public/index.htmlapps/hosted-components/.env.developmentapps/hosted-components/.eslintrc.cjsapps/hosted-components/package.jsonapps/hosted-components/src/client.tsxapps/hosted-components/src/routeTree.gen.tsapps/hosted-components/src/router.tsxapps/hosted-components/src/routes/__root.tsxapps/hosted-components/src/routes/handler/$.tsxapps/hosted-components/src/routes/index.tsxapps/hosted-components/tsconfig.jsonapps/hosted-components/vite.config.ts
| @@ -0,0 +1 @@ | |||
| VITE_STACK_API_URL=http://localhost:8102 | |||
There was a problem hiding this comment.
needs to respect ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}
| } | ||
|
|
||
| if (!projectId) { | ||
| return <FullPageError title="Invalid URL" message={`Could not determine project ID from subdomain. Visit <projectId>.localhost:8105.`} />; |
There was a problem hiding this comment.
port should be ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}09
There was a problem hiding this comment.
or honestly the entire message should adapt based on the current URL (if it's not hosted locally it should show that URL)
There was a problem hiding this comment.
I get this error about half the time when running pnpm run dev
[2] @stackframe/hosted-components:dev: Error: Failed to resolve entry for package "@stackframe/react". The package may have incorrect main/module/exports specified in its package.json.
[2] @stackframe/hosted-components:dev: at packageEntryFailure (file:///Users/konstantinwohlwend/Documents/stack/node_modules/.pnpm/vite@7.3.1_@types+node@22.19.0_jiti@2.6.1_lightningcss@1.30.1_terser@5.44.0_tsx@4.21.0_yaml@2.8.0/node_modules/vite/dist/node/chunks/config.js:32816:32)
[2] @stackframe/hosted-components:dev: ... 8 lines matching cause stack trace ...
[2] @stackframe/hosted-components:dev: at async TransformPluginContext.resolve (file:///Users/konstantinwohlwend/Documents/stack/node_modules/.pnpm/vite@7.3.1_@types+node@22.19.0_jiti@2.6.1_lightningcss@1.30.1_terser@5.44.0_tsx@4.21.0_yaml@2.8.0/node_modules/vite/dist/node/chunks/config.js:28929:13) {
[2] @stackframe/hosted-components:dev: cause: Error: Failed to resolve entry for package "@stackframe/react". The package may have incorrect main/module/exports specified in its package.json.
[2] @stackframe/hosted-components:dev: at packageEntryFailure (file:///Users/konstantinwohlwend/Documents/stack/node_modules/.pnpm/vite@7.3.1_@types+node@22.19.0_jiti@2.6.1_lightningcss@1.30.1_terser@5.44.0_tsx@4.21.0_yaml@2.8.0/node_modules/vite/dist/node/chunks/config.js:32816:32)
[2] @stackframe/hosted-components:dev: at resolvePackageEntry (file:///Users/konstantinwohlwend/Documents/stack/node_modules/.pnpm/vite@7.3.1_@types+node@22.19.0_jiti@2.6.1_lightningcss@1.30.1_terser@5.44.0_tsx@4.21.0_yaml@2.8.0/node_modules/vite/dist/node/chunks/config.js:32813:2)
[2] @stackframe/hosted-components:dev: at tryNodeResolve (file:///Users/konstantinwohlwend/Documents/stack/node_modules/.pnpm/vite@7.3.1_@types+node@22.19.0_jiti@2.6.1_lightningcss@1.30.1_terser@5.44.0_tsx@4.21.0_yaml@2.8.0/node_modules/vite/dist/node/chunks/config.js:32716:70)
[2] @stackframe/hosted-components:dev: at ResolveIdContext.handler (file:///Users/konstantinwohlwend/Documents/stack/node_modules/.pnpm/vite@7.3.1_@types+node@22.19.0_jiti@2.6.1_lightningcss@1.30.1_terser@5.44.0_tsx@4.21.0_yaml@2.8.0/node_modules/vite/dist/node/chunks/config.js:32555:16)
[2] @stackframe/hosted-components:dev: at EnvironmentPluginContainer.resolveId (file:///Users/konstantinwohlwend/Documents/stack/node_modules/.pnpm/vite@7.3.1_@types+node@22.19.0_jiti@2.6.1_lightningcss@1.30.1_terser@5.44.0_tsx@4.21.0_yaml@2.8.0/node_modules/vite/dist/node/chunks/config.js:28717:56)
[2] @stackframe/hosted-components:dev: at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
[2] @stackframe/hosted-components:dev: at async ResolveIdContext.resolve (file:///Users/konstantinwohlwend/Documents/stack/node_modules/.pnpm/vite@7.3.1_@types+node@22.19.0_jiti@2.6.1_lightningcss@1.30.1_terser@5.44.0_tsx@4.21.0_yaml@2.8.0/node_modules/vite/dist/node/chunks/config.js:28929:13)
[2] @stackframe/hosted-components:dev: at async ResolveIdContext.resolveId (file:///Users/konstantinwohlwend/Documents/stack/node_modules/.pnpm/@tanstack+start-plugin-core@1.163.2_@tanstack+react-router@1.163.2_react-dom@19.2.3_rea_c17f8a37508f9eb0a0349e723ec00ade/node_modules/@tanstack/start-plugin-core/dist/esm/import-protection-plugin/plugin.js:719:26)
[2] @stackframe/hosted-components:dev: at async EnvironmentPluginContainer.resolveId (file:///Users/konstantinwohlwend/Documents/stack/node_modules/.pnpm/vite@7.3.1_@types+node@22.19.0_jiti@2.6.1_lightningcss@1.30.1_terser@5.44.0_tsx@4.21.0_yaml@2.8.0/node_modules/vite/dist/node/chunks/config.js:28717:19)
[2] @stackframe/hosted-components:dev: at async TransformPluginContext.resolve (file:///Users/konstantinwohlwend/Documents/stack/node_modules/.pnpm/vite@7.3.1_@types+node@22.19.0_jiti@2.6.1_lightningcss@1.30.1_terser@5.44.0_tsx@4.21.0_yaml@2.8.0/node_modules/vite/dist/node/chunks/config.js:28929:13) {
[2] @stackframe/hosted-components:dev: code: 'ERR_RESOLVE_PACKAGE_ENTRY_FAIL',
[2] @stackframe/hosted-components:dev: pos: 117,
[2] @stackframe/hosted-components:dev: plugin: 'vite:import-analysis',
[2] @stackframe/hosted-components:dev: id: '/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx',
[2] @stackframe/hosted-components:dev: pluginCode: 'import { Fragment, jsxDEV } from "react/jsx-dev-runtime";\n' +
[2] @stackframe/hosted-components:dev: 'import { StackClientApp, StackProvider, StackTheme } from "@stackframe/react";\n' +
[2] @stackframe/hosted-components:dev: 'import {\n' +
[2] @stackframe/hosted-components:dev: ' HeadContent,\n' +
[2] @stackframe/hosted-components:dev: ' Outlet,\n' +
[2] @stackframe/hosted-components:dev: ' Scripts,\n' +
[2] @stackframe/hosted-components:dev: ' createRootRoute,\n' +
[2] @stackframe/hosted-components:dev: ' useNavigate\n' +
[2] @stackframe/hosted-components:dev: '} from "@tanstack/react-router";\n' +
[2] @stackframe/hosted-components:dev: 'import { Component, useEffect, useMemo, useState } from "react";\n' +
[2] @stackframe/hosted-components:dev: 'export function getProjectId() {\n' +
[2] @stackframe/hosted-components:dev: ' const hostname = window.location.hostname;\n' +
[2] @stackframe/hosted-components:dev: ' const parts = hostname.split(".");\n' +
[2] @stackframe/hosted-components:dev: ' if (parts.length >= 2) {\n' +
[2] @stackframe/hosted-components:dev: ' return parts[0];\n' +
[2] @stackframe/hosted-components:dev: ' }\n' +
[2] @stackframe/hosted-components:dev: ' return null;\n' +
[2] @stackframe/hosted-components:dev: '}\n' +
[2] @stackframe/hosted-components:dev: 'function FullPageError({ title, message }) {\n' +
[2] @stackframe/hosted-components:dev: ' return /* @__PURE__ */ jsxDEV("div", { style: { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "100vh" }, children: /* @__PURE__ */ jsxDEV("div", { style: { textAlign: "center", maxWidth: 480, padding: 24 }, children: [\n' +
[2] @stackframe/hosted-components:dev: ' /* @__PURE__ */ jsxDEV("h1", { style: { fontSize: 24, marginBottom: 8 }, children: title }, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 30,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 9\n' +
[2] @stackframe/hosted-components:dev: ' }, this),\n' +
[2] @stackframe/hosted-components:dev: ' /* @__PURE__ */ jsxDEV("p", { style: { color: "#666" }, children: message }, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 31,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 9\n' +
[2] @stackframe/hosted-components:dev: ' }, this)\n' +
[2] @stackframe/hosted-components:dev: ' ] }, void 0, true, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 29,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 7\n' +
[2] @stackframe/hosted-components:dev: ' }, this) }, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 28,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 5\n' +
[2] @stackframe/hosted-components:dev: ' }, this);\n' +
[2] @stackframe/hosted-components:dev: '}\n' +
[2] @stackframe/hosted-components:dev: 'class ErrorBoundary extends Component {\n' +
[2] @stackframe/hosted-components:dev: ' constructor(props) {\n' +
[2] @stackframe/hosted-components:dev: ' super(props);\n' +
[2] @stackframe/hosted-components:dev: ' this.state = { error: null };\n' +
[2] @stackframe/hosted-components:dev: ' }\n' +
[2] @stackframe/hosted-components:dev: ' static getDerivedStateFromError(error) {\n' +
[2] @stackframe/hosted-components:dev: ' return { error };\n' +
[2] @stackframe/hosted-components:dev: ' }\n' +
[2] @stackframe/hosted-components:dev: ' componentDidCatch(error, errorInfo) {\n' +
[2] @stackframe/hosted-components:dev: ' console.error("Hosted components error:", error, errorInfo);\n' +
[2] @stackframe/hosted-components:dev: ' }\n' +
[2] @stackframe/hosted-components:dev: ' render() {\n' +
[2] @stackframe/hosted-components:dev: ' if (this.state.error) {\n' +
[2] @stackframe/hosted-components:dev: ' return /* @__PURE__ */ jsxDEV(FullPageError, { title: "Something went wrong", message: this.state.error.message }, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 53,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 14\n' +
[2] @stackframe/hosted-components:dev: ' }, this);\n' +
[2] @stackframe/hosted-components:dev: ' }\n' +
[2] @stackframe/hosted-components:dev: ' return this.props.children;\n' +
[2] @stackframe/hosted-components:dev: ' }\n' +
[2] @stackframe/hosted-components:dev: '}\n' +
[2] @stackframe/hosted-components:dev: 'export const Route = createRootRoute({\n' +
[2] @stackframe/hosted-components:dev: ' head: () => ({\n' +
[2] @stackframe/hosted-components:dev: ' meta: [\n' +
[2] @stackframe/hosted-components:dev: ' { charSet: "utf-8" },\n' +
[2] @stackframe/hosted-components:dev: ' { name: "viewport", content: "width=device-width, initial-scale=1" }\n' +
[2] @stackframe/hosted-components:dev: ' ]\n' +
[2] @stackframe/hosted-components:dev: ' }),\n' +
[2] @stackframe/hosted-components:dev: ' shellComponent: RootDocument,\n' +
[2] @stackframe/hosted-components:dev: ' component: RootComponent\n' +
[2] @stackframe/hosted-components:dev: '});\n' +
[2] @stackframe/hosted-components:dev: 'function RootDocument({ children }) {\n' +
[2] @stackframe/hosted-components:dev: ' return /* @__PURE__ */ jsxDEV("html", { children: [\n' +
[2] @stackframe/hosted-components:dev: ' /* @__PURE__ */ jsxDEV("head", { children: [\n' +
[2] @stackframe/hosted-components:dev: ' /* @__PURE__ */ jsxDEV(HeadContent, {}, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 75,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 9\n' +
[2] @stackframe/hosted-components:dev: ' }, this),\n' +
[2] @stackframe/hosted-components:dev: ' /* @__PURE__ */ jsxDEV("link", { rel: "preconnect", href: "https://fonts.googleapis.com" }, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 76,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 9\n' +
[2] @stackframe/hosted-components:dev: ' }, this),\n' +
[2] @stackframe/hosted-components:dev: ' /* @__PURE__ */ jsxDEV("link", { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" }, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 77,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 9\n' +
[2] @stackframe/hosted-components:dev: ' }, this),\n' +
[2] @stackframe/hosted-components:dev: ' /* @__PURE__ */ jsxDEV("link", { href: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap", rel: "stylesheet" }, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 78,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 9\n' +
[2] @stackframe/hosted-components:dev: ' }, this)\n' +
[2] @stackframe/hosted-components:dev: ' ] }, void 0, true, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 74,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 7\n' +
[2] @stackframe/hosted-components:dev: ' }, this),\n' +
[2] @stackframe/hosted-components:dev: ` /* @__PURE__ */ jsxDEV("body", { style: { fontFamily: "'Inter', sans-serif", margin: 0 }, children: [\n` +
[2] @stackframe/hosted-components:dev: ' children,\n' +
[2] @stackframe/hosted-components:dev: ' /* @__PURE__ */ jsxDEV(Scripts, {}, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 82,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 9\n' +
[2] @stackframe/hosted-components:dev: ' }, this)\n' +
[2] @stackframe/hosted-components:dev: ' ] }, void 0, true, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 80,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 7\n' +
[2] @stackframe/hosted-components:dev: ' }, this)\n' +
[2] @stackframe/hosted-components:dev: ' ] }, void 0, true, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 73,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 5\n' +
[2] @stackframe/hosted-components:dev: ' }, this);\n' +
[2] @stackframe/hosted-components:dev: '}\n' +
[2] @stackframe/hosted-components:dev: 'function RootComponent() {\n' +
[2] @stackframe/hosted-components:dev: ' const [projectId, setProjectId] = useState(void 0);\n' +
[2] @stackframe/hosted-components:dev: ' useEffect(() => {\n' +
[2] @stackframe/hosted-components:dev: ' setProjectId(getProjectId());\n' +
[2] @stackframe/hosted-components:dev: ' }, []);\n' +
[2] @stackframe/hosted-components:dev: ' const isValidProjectId = projectId ? projectId === "internal" || /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(projectId) : false;\n' +
[2] @stackframe/hosted-components:dev: ' const stackApp = useMemo(() => {\n' +
[2] @stackframe/hosted-components:dev: ' if (!projectId || !isValidProjectId) return null;\n' +
[2] @stackframe/hosted-components:dev: ' return new StackClientApp({\n' +
[2] @stackframe/hosted-components:dev: ' projectId,\n' +
[2] @stackframe/hosted-components:dev: ' tokenStore: "cookie",\n' +
[2] @stackframe/hosted-components:dev: ' baseUrl: import.meta.env.VITE_STACK_API_URL || void 0,\n' +
[2] @stackframe/hosted-components:dev: ' urls: {\n' +
[2] @stackframe/hosted-components:dev: ' handler: "/handler",\n' +
[2] @stackframe/hosted-components:dev: ' signIn: "/handler/sign-in",\n' +
[2] @stackframe/hosted-components:dev: ' signUp: "/handler/sign-up",\n' +
[2] @stackframe/hosted-components:dev: ' afterSignIn: "/",\n' +
[2] @stackframe/hosted-components:dev: ' afterSignUp: "/",\n' +
[2] @stackframe/hosted-components:dev: ' afterSignOut: "/handler/sign-in"\n' +
[2] @stackframe/hosted-components:dev: ' },\n' +
[2] @stackframe/hosted-components:dev: ' redirectMethod: { useNavigate }\n' +
[2] @stackframe/hosted-components:dev: ' });\n' +
[2] @stackframe/hosted-components:dev: ' }, [projectId]);\n' +
[2] @stackframe/hosted-components:dev: ' if (projectId === void 0) {\n' +
[2] @stackframe/hosted-components:dev: ' return /* @__PURE__ */ jsxDEV(Fragment, {}, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 116,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 12\n' +
[2] @stackframe/hosted-components:dev: ' }, this);\n' +
[2] @stackframe/hosted-components:dev: ' }\n' +
[2] @stackframe/hosted-components:dev: ' if (!projectId) {\n' +
[2] @stackframe/hosted-components:dev: ' return /* @__PURE__ */ jsxDEV(FullPageError, { title: "Invalid URL", message: `Could not determine project ID from subdomain. Visit <projectId>.localhost:8105.` }, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 120,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 12\n' +
[2] @stackframe/hosted-components:dev: ' }, this);\n' +
[2] @stackframe/hosted-components:dev: ' }\n' +
[2] @stackframe/hosted-components:dev: ' if (!isValidProjectId) {\n' +
[2] @stackframe/hosted-components:dev: ' return /* @__PURE__ */ jsxDEV(FullPageError, { title: "Something went wrong", message: `Invalid project ID: ${projectId}. Project IDs must be UUIDs.` }, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 124,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 12\n' +
[2] @stackframe/hosted-components:dev: ' }, this);\n' +
[2] @stackframe/hosted-components:dev: ' }\n' +
[2] @stackframe/hosted-components:dev: ' return /* @__PURE__ */ jsxDEV(ErrorBoundary, { children: /* @__PURE__ */ jsxDEV(StackProvider, { app: stackApp, children: /* @__PURE__ */ jsxDEV(StackTheme, { children: /* @__PURE__ */ jsxDEV(Outlet, {}, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 131,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 11\n' +
[2] @stackframe/hosted-components:dev: ' }, this) }, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 130,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 9\n' +
[2] @stackframe/hosted-components:dev: ' }, this) }, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 129,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 7\n' +
[2] @stackframe/hosted-components:dev: ' }, this) }, void 0, false, {\n' +
[2] @stackframe/hosted-components:dev: ' fileName: "/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx",\n' +
[2] @stackframe/hosted-components:dev: ' lineNumber: 128,\n' +
[2] @stackframe/hosted-components:dev: ' columnNumber: 5\n' +
[2] @stackframe/hosted-components:dev: ' }, this);\n' +
[2] @stackframe/hosted-components:dev: '}\n',
[2] @stackframe/hosted-components:dev: loc: {
[2] @stackframe/hosted-components:dev: file: '/Users/konstantinwohlwend/Documents/stack/apps/hosted-components/src/routes/__root.tsx',
[2] @stackframe/hosted-components:dev: line: 2,
[2] @stackframe/hosted-components:dev: column: 58
[2] @stackframe/hosted-components:dev: },
[2] @stackframe/hosted-components:dev: frame: '1 | import { Fragment, jsxDEV } from "react/jsx-dev-runtime";\n' +
[2] @stackframe/hosted-components:dev: '2 | import { StackClientApp, StackProvider, StackTheme } from "@stackframe/react";\n' +
[2] @stackframe/hosted-components:dev: ' | ^\n' +
[2] @stackframe/hosted-components:dev: '3 | import {\n' +
[2] @stackframe/hosted-components:dev: '4 | HeadContent,',
There was a problem hiding this comment.
assumably because the package is still building when the hosted components dev starts, and the hosted components dev hot reload does not look at node_modules
There was a problem hiding this comment.
can you also create a vercel project for this? domain built-with-stack-auth.com is on cloudflare
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
apps/hosted-components/package.json (1)
7-7:⚠️ Potential issue | 🟠 MajorUse the same default port as the rest of this app.
This still defaults Vite to
8109, whileapps/hosted-components/src/routes/__root.tsxpoints users to<projectId>.localhost:8105. The default dev flow and the in-app guidance will disagree until these match.Proposed fix
- "dev": "vite dev --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}09", + "dev": "vite dev --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}05",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/hosted-components/package.json` at line 7, The dev script in package.json currently defaults Vite to port 8109 via "dev": "vite dev --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}09"; change the default to match the rest of the app (8105) by updating the dev script to use the same fallback prefix so Vite will start on 8105 when NEXT_PUBLIC_STACK_PORT_PREFIX is not set; edit the "dev" script entry (symbol: "dev") and adjust the fallback port expression (symbol: NEXT_PUBLIC_STACK_PORT_PREFIX) so the resolved default is 8105 instead of 8109.
🧹 Nitpick comments (1)
apps/hosted-components/src/routes/__root.tsx (1)
14-24: GuardgetProjectId()against SSR callers.This route module is loaded on the server too, and
getProjectId()is exported. Any future SSR/test caller will crash onwindow. Make the browser-only assumption explicit inside the helper.Suggested change
export function getProjectId(): string | null { + if (typeof window === "undefined") { + return null; + } + // Extract from subdomain: <projectId>.built-with-stack-auth.com // Also works with <projectId>.localhost for local dev const hostname = window.location.hostname;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/hosted-components/src/routes/__root.tsx` around lines 14 - 24, getProjectId() currently assumes a browser environment and directly accesses window, which will crash during SSR/testing; update the function to first guard for non-browser callers by checking typeof window === "undefined" (and/or typeof window.location === "undefined") and returning null in that case, then continue with the existing hostname parsing logic (hostname.split and parts[0]) when running in the browser so server-side imports won't throw.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/hosted-components/package.json`:
- Around line 7-15: The dev script in hosted-components fails because
`@stackframe/react`'s dist files aren't guaranteed to exist; update the task
wiring so hosted-components' dev depends on the package build: add a dependsOn
entry (e.g., dependsOn: ["^build"]) to the hosted-components "dev" task in
turbo.json so the workspace runs the dependent package build first, or
alternatively add a Vite alias in hosted-components' Vite config to resolve
"@stackframe/react" directly to its source (src) to avoid needing dist files
during dev.
In `@apps/hosted-components/src/routes/__root.tsx`:
- Around line 89-93: The initial state for projectId is incorrectly bootstrapped
to the string "internal", causing the app to create a StackClientApp for the
internal project before the real hostname is known; change the initialization so
projectId starts as undefined (or null) instead of "internal", keep the existing
useEffect that calls getProjectId() and setProjectId(), and ensure any code that
constructs/bootstraps StackClientApp (or reads projectId) guards against
undefined and only creates the client after projectId is set to the real value.
In `@apps/hosted-components/src/routes/index.tsx`:
- Around line 6-10: The pendingComponent render lacks accessible status
announcement for screen readers; update the pendingComponent function to include
an accessible status (e.g., role="status" or aria-live="polite") and provide
visible or visually-hidden text such as "Loading, please wait" so assistive tech
is informed during auth loading/redirect; keep the existing spinner for visual
users and add the announcement text inside the same wrapper (or as a
screen-reader-only element) to ensure both visual and non-visual users receive
feedback.
---
Duplicate comments:
In `@apps/hosted-components/package.json`:
- Line 7: The dev script in package.json currently defaults Vite to port 8109
via "dev": "vite dev --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}09"; change the
default to match the rest of the app (8105) by updating the dev script to use
the same fallback prefix so Vite will start on 8105 when
NEXT_PUBLIC_STACK_PORT_PREFIX is not set; edit the "dev" script entry (symbol:
"dev") and adjust the fallback port expression (symbol:
NEXT_PUBLIC_STACK_PORT_PREFIX) so the resolved default is 8105 instead of 8109.
---
Nitpick comments:
In `@apps/hosted-components/src/routes/__root.tsx`:
- Around line 14-24: getProjectId() currently assumes a browser environment and
directly accesses window, which will crash during SSR/testing; update the
function to first guard for non-browser callers by checking typeof window ===
"undefined" (and/or typeof window.location === "undefined") and returning null
in that case, then continue with the existing hostname parsing logic
(hostname.split and parts[0]) when running in the browser so server-side imports
won't throw.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 02535b65-2167-4f30-a751-ce9a02098417
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (7)
apps/hosted-components/.env.developmentapps/hosted-components/package.jsonapps/hosted-components/src/routes/__root.tsxapps/hosted-components/src/routes/index.tsxapps/hosted-components/vite.config.tspackages/stack-shared/src/utils/monkey-patch.tsxpackages/stack-shared/src/utils/react.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/hosted-components/.env.development
- apps/hosted-components/vite.config.ts
| "dev": "vite dev --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}09", | ||
| "build": "vite build", | ||
| "start": "node .output/server/index.mjs", | ||
| "lint": "eslint --ext .ts,.tsx .", | ||
| "typecheck": "tsc --noEmit", | ||
| "clean": "rimraf .output && rimraf node_modules" | ||
| }, | ||
| "dependencies": { | ||
| "@stackframe/react": "workspace:*", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
react_pkg=""
while IFS= read -r pkg; do
if jq -e '.name == "@stackframe/react"' "$pkg" >/dev/null; then
react_pkg="$pkg"
break
fi
done < <(fd '^package.json$' packages)
if [ -z "$react_pkg" ]; then
echo "Could not find package.json for `@stackframe/react`"
exit 1
fi
pkg_dir="$(dirname "$react_pkg")"
echo "== `@stackframe/react` package metadata =="
jq '{name, main, module, types, exports, scripts}' "$react_pkg"
echo
echo "== Export targets and whether they exist in the checkout =="
jq -r '
[.main, .module, .types] | map(select(type=="string"))[] ,
(.exports? // {} | .. | strings)
' "$react_pkg" | sort -u | while IFS= read -r rel; do
[ -z "$rel" ] && continue
case "$rel" in
.*|dist/*|lib/*|src/*)
if [ -e "$pkg_dir/$rel" ]; then
echo "OK $rel"
else
echo "MISS $rel"
fi
;;
esac
done
echo
echo "== Hosted-components / Turbo wiring =="
rg -n -C2 '"dev"|"build"|"predev"|"\^build"|@stackframe/react|@stackframe/hosted-components' \
turbo.json package.json pnpm-workspace.yaml "$react_pkg"Repository: stack-auth/stack-auth
Length of output: 122
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find `@stackframe/react` package.json
react_pkg=$(find packages -name 'package.json' -exec grep -l '"name": "@stackframe/react"' {} \;)
if [ -z "$react_pkg" ]; then
echo "Could not find package.json for `@stackframe/react`"
exit 1
fi
pkg_dir=$(dirname "$react_pkg")
echo "== `@stackframe/react` package metadata =="
jq '{name, main, module, types, exports, scripts}' "$react_pkg"
echo
echo "== Export targets and whether they exist in the checkout =="
jq -r '.main // empty, .module // empty, .types // empty, (.exports? // {} | to_entries[] | .value | select(type=="string"))' "$react_pkg" | sort -u | while read -r rel; do
[ -z "$rel" ] && continue
case "$rel" in
.*|dist/*|lib/*|src/*)
if [ -e "$pkg_dir/$rel" ]; then
echo "OK $rel"
else
echo "MISS $rel"
fi
;;
esac
done
echo
echo "== Check turbo.json for task wiring =="
if [ -f turbo.json ]; then
jq '.tasks[] | select(.cache==false or .cache==true) | {task: .task, dependsOn: .dependsOn, outputs: .outputs}' turbo.json 2>/dev/null | head -100
fi
echo
echo "== hosted-components package.json =="
if [ -f apps/hosted-components/package.json ]; then
jq '.scripts' apps/hosted-components/package.json
fi
echo
echo "== Search for build dependencies related to react/hosted-components =="
grep -r "dependsOn\|@stackframe/react" turbo.json 2>/dev/null || echo "No matches found"Repository: stack-auth/stack-auth
Length of output: 315
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find `@stackframe/react` package.json
react_pkg=$(find packages -name 'package.json' -exec grep -l '"name": "@stackframe/react"' {} \;)
if [ -z "$react_pkg" ]; then
echo "Could not find package.json for `@stackframe/react`"
exit 1
fi
pkg_dir=$(dirname "$react_pkg")
echo "== `@stackframe/react` package metadata =="
jq '.name, .main, .module, .types, .exports' "$react_pkg"
echo
echo "== Export targets and whether they exist in the checkout =="
jq -r '.main // empty, .module // empty, .types // empty, (.exports | to_entries[]? | .value | strings)' "$react_pkg" 2>/dev/null | sort -u | while read -r rel; do
[ -z "$rel" ] && continue
if [ -e "$pkg_dir/$rel" ]; then
echo "✓ EXISTS: $rel"
else
echo "✗ MISSING: $rel"
fi
done
echo
echo "== hosted-components package.json location and scripts =="
cat apps/hosted-components/package.json | jq '.scripts'
echo
echo "== turbo.json tasks =="
jq '.tasks | keys[]' turbo.json | head -20
echo
echo "== Searching turbo.json for hosted-components task wiring =="
jq '.tasks | to_entries[] | select(.value | tostring | contains("hosted-components"))' turbo.jsonRepository: stack-auth/stack-auth
Length of output: 1566
🏁 Script executed:
#!/bin/bash
echo "== Full turbo.json content =="
cat turbo.json | jq '.' | head -200
echo
echo "== `@stackframe/react` build task in turbo.json =="
jq '.tasks."@stackframe/react#build"' turbo.json
echo
echo "== hosted-components build and dev tasks in turbo.json =="
jq '.tasks."hosted-components#build", .tasks."hosted-components#dev"' turbo.json
echo
echo "== All `@stackframe` tasks =="
jq '.tasks | keys[] | select(startswith("@stackframe"))' turbo.json
echo
echo "== Check react package.json for build script =="
jq '.scripts' packages/react/package.jsonRepository: stack-auth/stack-auth
Length of output: 3303
🏁 Script executed:
#!/bin/bash
echo "== Check if `@stackframe/react` is listed in hosted-components dependencies =="
jq '.dependencies, .devDependencies' apps/hosted-components/package.json
echo
echo "== Check if there's any pnpm filter or Turbo configuration for hosted-components =="
cat pnpm-workspace.yaml | grep -A5 hosted-components || echo "No workspace config found"
echo
echo "== Does hosted-components reference `@stackframe/react` in source =="
grep -r "@stackframe/react" apps/hosted-components/src --include="*.ts" --include="*.tsx" 2>/dev/null | head -5 || echo "No direct references found"Repository: stack-auth/stack-auth
Length of output: 1289
Ensure @stackframe/react builds before hosted-components dev starts.
pnpm run dev fails intermittently with ERR_RESOLVE_PACKAGE_ENTRY_FAIL for @stackframe/react because Vite attempts to resolve the package's exports during import analysis. The package declares main: "./dist/index.js" and types: "./dist/index.d.ts", but these files do not exist in the checkout—they only exist after running pnpm run build in the @stackframe/react package.
No task wiring currently guarantees the react package builds before hosted-components dev starts. The dev task in turbo.json has no dependsOn entries, so running pnpm run dev in hosted-components will fail if react's dist files haven't been built in a prior step.
Add explicit build dependency wiring (e.g., add dependsOn: ["^build"] to the dev task for hosted-components, or configure a Vite alias to resolve @stackframe/react directly to source files) to eliminate the flakiness.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/hosted-components/package.json` around lines 7 - 15, The dev script in
hosted-components fails because `@stackframe/react`'s dist files aren't guaranteed
to exist; update the task wiring so hosted-components' dev depends on the
package build: add a dependsOn entry (e.g., dependsOn: ["^build"]) to the
hosted-components "dev" task in turbo.json so the workspace runs the dependent
package build first, or alternatively add a Vite alias in hosted-components'
Vite config to resolve "@stackframe/react" directly to its source (src) to avoid
needing dist files during dev.
| const [projectId, setProjectId] = useState<string | null | undefined>("internal"); | ||
|
|
||
| useEffect(() => { | ||
| setProjectId(getProjectId()); | ||
| }, []); |
There was a problem hiding this comment.
Don't bootstrap every request as internal.
Starting projectId as "internal" means the server render and first client render both create a StackClientApp for the internal project before the hostname is read. That can trigger auth/navigation against the wrong project and then flip after mount.
Suggested change
-function RootComponent() {
- const [projectId, setProjectId] = useState<string | null | undefined>("internal");
+function RootComponent() {
+ const [projectId, setProjectId] = useState<string | null | undefined>(undefined);
useEffect(() => {
setProjectId(getProjectId());
}, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/hosted-components/src/routes/__root.tsx` around lines 89 - 93, The
initial state for projectId is incorrectly bootstrapped to the string
"internal", causing the app to create a StackClientApp for the internal project
before the real hostname is known; change the initialization so projectId starts
as undefined (or null) instead of "internal", keep the existing useEffect that
calls getProjectId() and setProjectId(), and ensure any code that
constructs/bootstraps StackClientApp (or reads projectId) guards against
undefined and only creates the client after projectId is set to the real value.
| pendingComponent: () => ( | ||
| <div style={{ display: "flex", alignItems: "center", justifyContent: "center", minHeight: "100vh" }}> | ||
| <div style={{ width: 24, height: 24, border: "2px solid #e5e5e5", borderTop: "2px solid #333", borderRadius: "50%", animation: "spin 0.6s linear infinite" }} /> | ||
| <style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style> | ||
| </div> |
There was a problem hiding this comment.
Announce the pending state to assistive tech.
This is the only UI users get while auth is loading/redirecting, but it has no status role or text. Screen readers will just get silence here.
Suggested change
pendingComponent: () => (
- <div style={{ display: "flex", alignItems: "center", justifyContent: "center", minHeight: "100vh" }}>
- <div style={{ width: 24, height: 24, border: "2px solid `#e5e5e5`", borderTop: "2px solid `#333`", borderRadius: "50%", animation: "spin 0.6s linear infinite" }} />
+ <div
+ role="status"
+ aria-live="polite"
+ style={{ display: "flex", alignItems: "center", justifyContent: "center", minHeight: "100vh" }}
+ >
+ <div
+ aria-hidden="true"
+ style={{ width: 24, height: 24, border: "2px solid `#e5e5e5`", borderTop: "2px solid `#333`", borderRadius: "50%", animation: "spin 0.6s linear infinite" }}
+ />
+ <span style={{ position: "absolute", width: 1, height: 1, padding: 0, margin: -1, overflow: "hidden", clip: "rect(0, 0, 0, 0)", whiteSpace: "nowrap", border: 0 }}>
+ Loading…
+ </span>
<style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style>
</div>
),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pendingComponent: () => ( | |
| <div style={{ display: "flex", alignItems: "center", justifyContent: "center", minHeight: "100vh" }}> | |
| <div style={{ width: 24, height: 24, border: "2px solid #e5e5e5", borderTop: "2px solid #333", borderRadius: "50%", animation: "spin 0.6s linear infinite" }} /> | |
| <style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style> | |
| </div> | |
| pendingComponent: () => ( | |
| <div | |
| role="status" | |
| aria-live="polite" | |
| style={{ display: "flex", alignItems: "center", justifyContent: "center", minHeight: "100vh" }} | |
| > | |
| <div | |
| aria-hidden="true" | |
| style={{ width: 24, height: 24, border: "2px solid `#e5e5e5`", borderTop: "2px solid `#333`", borderRadius: "50%", animation: "spin 0.6s linear infinite" }} | |
| /> | |
| <span style={{ position: "absolute", width: 1, height: 1, padding: 0, margin: -1, overflow: "hidden", clip: "rect(0, 0, 0, 0)", whiteSpace: "nowrap", border: 0 }}> | |
| Loading… | |
| </span> | |
| <style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style> | |
| </div> | |
| ), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/hosted-components/src/routes/index.tsx` around lines 6 - 10, The
pendingComponent render lacks accessible status announcement for screen readers;
update the pendingComponent function to include an accessible status (e.g.,
role="status" or aria-live="polite") and provide visible or visually-hidden text
such as "Loading, please wait" so assistive tech is informed during auth
loading/redirect; keep the existing spinner for visual users and add the
announcement text inside the same wrapper (or as a screen-reader-only element)
to ensure both visual and non-visual users receive feedback.
Summary by CodeRabbit