An expired or orphaned session left the app on a half-rendered page instead of the login screen: a properly signed token naming a user who no longer exists answered 404, which the client does not treat as an authentication failure. All failures in get_current_user are 401 now, and the client stops retrying them, so a dead session goes straight back to the login screen. - Clicking an edge names the two nodes it runs between, not just the message. - A node with a problem shows one badge carrying the whole explanation, rather than a corner badge and a status dot saying the same thing twice. The dot is back to what it is good at: whether the node ran. - The sidebar's collapse control sits in the sidebar, where a phone still finds one in the chrome because there is no sidebar on screen to hold it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WzrvW7rjQbynnhF6pxh6i
70 lines
1.9 KiB
TypeScript
70 lines
1.9 KiB
TypeScript
import {
|
|
MutationCache,
|
|
QueryCache,
|
|
QueryClient,
|
|
QueryClientProvider,
|
|
} from "@tanstack/react-query"
|
|
import { createRouter, RouterProvider } from "@tanstack/react-router"
|
|
import { MotionConfig } from "motion/react"
|
|
import { StrictMode } from "react"
|
|
import ReactDOM from "react-dom/client"
|
|
import { ApiError, OpenAPI } from "./client"
|
|
import { ThemeProvider } from "./components/theme-provider"
|
|
import { Toaster } from "./components/ui/sonner"
|
|
import "./index.css"
|
|
import { routeTree } from "./routeTree.gen"
|
|
|
|
OpenAPI.BASE = import.meta.env.VITE_API_URL
|
|
OpenAPI.TOKEN = async () => {
|
|
return localStorage.getItem("access_token") || ""
|
|
}
|
|
|
|
/** A session the server will not accept, whatever we do next. */
|
|
const isAuthFailure = (error: unknown) =>
|
|
error instanceof ApiError && [401, 403].includes(error.status)
|
|
|
|
const handleApiError = (error: Error) => {
|
|
if (isAuthFailure(error)) {
|
|
localStorage.removeItem("access_token")
|
|
window.location.href = "/login"
|
|
}
|
|
}
|
|
|
|
const queryClient = new QueryClient({
|
|
queryCache: new QueryCache({
|
|
onError: handleApiError,
|
|
}),
|
|
mutationCache: new MutationCache({
|
|
onError: handleApiError,
|
|
}),
|
|
defaultOptions: {
|
|
queries: {
|
|
// Retrying an expired session only delays the trip to the login screen.
|
|
retry: (count, error) => !isAuthFailure(error) && count < 3,
|
|
},
|
|
mutations: {
|
|
retry: false,
|
|
},
|
|
},
|
|
})
|
|
|
|
const router = createRouter({ routeTree })
|
|
declare module "@tanstack/react-router" {
|
|
interface Register {
|
|
router: typeof router
|
|
}
|
|
}
|
|
|
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
<StrictMode>
|
|
<ThemeProvider defaultTheme="system" storageKey="fluksio-ui-theme">
|
|
<MotionConfig reducedMotion="user">
|
|
<QueryClientProvider client={queryClient}>
|
|
<RouterProvider router={router} />
|
|
<Toaster richColors closeButton />
|
|
</QueryClientProvider>
|
|
</MotionConfig>
|
|
</ThemeProvider>
|
|
</StrictMode>,
|
|
)
|