mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 10:08:46 +02:00
Auth probe retries network failures; lazy context init; CK repair logging
A thrown fetch in the auth probe is a network failure, not a 401 — it now retries once before treating the user as logged out, and logs the real cause (FE-15). Theme and privacy state initialize lazily from localStorage so neither the wrong theme nor unmasked sensitive values flash on reload (FE-07). The CopilotKit orphan-tool-call repair logs when it has to skip instead of failing silently (FE-11). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -382,7 +382,10 @@ app.all("/api/copilotkit/*", async (c) => {
|
||||
headers: outbound.headers,
|
||||
body: JSON.stringify({ ...body, messages: paired }),
|
||||
});
|
||||
} catch {
|
||||
} catch (err) {
|
||||
// Skipping the repair is survivable; doing it silently is not —
|
||||
// orphan tool calls then fail downstream with no trace of why.
|
||||
console.error("[copilotkit] orphan-tool-call repair skipped:", err);
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -20,12 +20,27 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Probe auth state by hitting a protected endpoint.
|
||||
// If the ws_token cookie is valid, the server returns 200; else 401.
|
||||
fetch("/api/v1/auth/me", { credentials: "include" })
|
||||
.then((r) => setAuthenticated(r.ok))
|
||||
.catch(() => setAuthenticated(false))
|
||||
.finally(() => setIsLoading(false));
|
||||
// Probe auth state by hitting a protected endpoint. An HTTP status is an
|
||||
// authoritative answer (200 = in, 401 = out); a thrown fetch is a NETWORK
|
||||
// failure and must not log the user out — retry once before giving up.
|
||||
let cancelled = false;
|
||||
const probe = () => fetch("/api/v1/auth/me", { credentials: "include" });
|
||||
probe()
|
||||
.catch(() => new Promise((r) => setTimeout(r, 1500)).then(probe))
|
||||
.then((r) => {
|
||||
if (!cancelled) setAuthenticated((r as Response).ok);
|
||||
})
|
||||
.catch(() => {
|
||||
// Network still down: leave unauthenticated but log the real cause.
|
||||
console.warn("Auth probe failed twice (network) — backend unreachable");
|
||||
if (!cancelled) setAuthenticated(false);
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setIsLoading(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const logout = async () => {
|
||||
|
||||
@@ -8,11 +8,10 @@ const PrivacyContext = createContext<{
|
||||
}>({ privacyMode: false, togglePrivacy: () => {} });
|
||||
|
||||
export function PrivacyProvider({ children }: { children: ReactNode }) {
|
||||
const [privacyMode, setPrivacyMode] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
setPrivacyMode(localStorage.getItem("privacyMode") === "true");
|
||||
}, []);
|
||||
// Lazy initializer: sensitive values must never flash unmasked on reload.
|
||||
const [privacyMode, setPrivacyMode] = useState<boolean>(
|
||||
() => localStorage.getItem("privacyMode") === "true",
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.classList.toggle("privacy", privacyMode);
|
||||
|
||||
@@ -10,18 +10,15 @@ const ThemeContext = createContext<{
|
||||
}>({ theme: "dark", toggleTheme: () => {} });
|
||||
|
||||
export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
const [theme, setTheme] = useState<Theme>("dark");
|
||||
|
||||
// Initialize once on mount (localStorage + prefers-color-scheme).
|
||||
useEffect(() => {
|
||||
// Lazy initializer: resolving the theme before first paint avoids the
|
||||
// flash of wrong theme an effect-based init caused.
|
||||
const [theme, setTheme] = useState<Theme>(() => {
|
||||
const saved = localStorage.getItem("theme") as Theme | null;
|
||||
const initial: Theme = saved
|
||||
? saved
|
||||
: window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
setTheme(initial);
|
||||
}, []);
|
||||
if (saved) return saved;
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.classList.toggle("dark", theme === "dark");
|
||||
|
||||
Reference in New Issue
Block a user