diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx
index 0eef1dce1..5632acf5c 100644
--- a/src/components/Layout/index.tsx
+++ b/src/components/Layout/index.tsx
@@ -16,6 +16,7 @@ import animationData from '@/assets/animation/onboarding_success.json';
import { AnimationJson } from '@/components/AnimationJson';
import { InstallDependencies } from '@/components/InstallStep/InstallDependencies';
import TopBar from '@/components/TopBar';
+import { useAuthHydration } from '@/hooks/useAuthHydration';
import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
import { useInstallationSetup } from '@/hooks/useInstallationSetup';
import { useAuthStore } from '@/store/authStore';
@@ -27,6 +28,7 @@ import HistorySidebar from '../HistorySidebar';
import InstallationErrorDialog from '../InstallStep/InstallationErrorDialog/InstallationErrorDialog';
const Layout = () => {
+ const hasHydrated = useAuthHydration();
const {
initState,
isFirstLaunch,
@@ -52,6 +54,8 @@ const Layout = () => {
useInstallationSetup();
useEffect(() => {
+ if (!chatStore) return;
+
const handleBeforeClose = () => {
const currentStatus =
chatStore.tasks[chatStore.activeTaskId as string]?.status;
@@ -67,7 +71,7 @@ const Layout = () => {
return () => {
window.ipcRenderer.removeAllListeners('before-close');
};
- }, [chatStore.tasks, chatStore.activeTaskId]);
+ }, [chatStore, chatStore?.tasks, chatStore?.activeTaskId]);
// Determine what to show based on states
const shouldShowOnboarding =
@@ -79,9 +83,10 @@ const Layout = () => {
installationState === 'waiting-backend';
const shouldShowMainContent = !actualShouldShowInstallScreen;
- if (!chatStore) {
- console.log(chatStore);
-
+ // Wait for auth store hydration so initState/isFirstLaunch are correct
+ // and we don't briefly show install/onboarding then switch to main content
+ if (!hasHydrated || !chatStore) {
+ if (!chatStore) console.log(chatStore);
return
Loading...
;
}
diff --git a/src/hooks/useAuthHydration.ts b/src/hooks/useAuthHydration.ts
new file mode 100644
index 000000000..888d9d89a
--- /dev/null
+++ b/src/hooks/useAuthHydration.ts
@@ -0,0 +1,43 @@
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+
+import { useAuthStore } from '@/store/authStore';
+import { useEffect, useState } from 'react';
+
+/**
+ * Waits for the persisted auth store to finish rehydrating from storage.
+ * Use this before reading auth state (token, initState, etc.) to avoid
+ * temporary redirects or UI flicker when persisted state loads after first render.
+ *
+ * @returns true once the auth store has been hydrated (sync or async)
+ */
+export function useAuthHydration(): boolean {
+ const [hydrated, setHydrated] = useState(false);
+
+ useEffect(() => {
+ // Subscribe first so we don't miss hydration completing between check and subscribe
+ const unsubFinish = useAuthStore.persist.onFinishHydration(() => {
+ setHydrated(true);
+ });
+ // Sync check: hydration may already be done (e.g. sync localStorage)
+ if (useAuthStore.persist.hasHydrated()) {
+ setHydrated(true);
+ }
+ return () => {
+ unsubFinish();
+ };
+ }, []);
+
+ return hydrated;
+}
diff --git a/src/routers/index.tsx b/src/routers/index.tsx
index 301a86188..8bb49f5c5 100644
--- a/src/routers/index.tsx
+++ b/src/routers/index.tsx
@@ -12,6 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+import { useAuthHydration } from '@/hooks/useAuthHydration';
import { useAuthStore } from '@/store/authStore';
import { lazy, useEffect, useReducer } from 'react';
import { Navigate, Outlet, Route, Routes } from 'react-router-dom';
@@ -53,8 +54,11 @@ const authReducer = (state: AuthState, action: AuthAction): AuthState => {
}
};
-// Route guard: Check if user is logged in
+// Route guard: Check if user is logged in.
+// Waits for persisted auth store to hydrate before reading token to avoid
+// temporary redirect to /login or flicker when the user is actually logged in.
const ProtectedRoute = () => {
+ const hasHydrated = useAuthHydration();
const [state, dispatch] = useReducer(authReducer, {
loading: false,
isAuthenticated: false,
@@ -62,7 +66,10 @@ const ProtectedRoute = () => {
});
const { token, localProxyValue, logout } = useAuthStore();
+
useEffect(() => {
+ if (!hasHydrated) return;
+
// Check VITE_USE_LOCAL_PROXY value on app startup
if (token) {
const currentProxyValue = import.meta.env.VITE_USE_LOCAL_PROXY || null;
@@ -78,9 +85,11 @@ const ProtectedRoute = () => {
}
dispatch({ type: 'INITIALIZE', payload: { isAuthenticated: !!token } });
- }, [token, localProxyValue, logout]);
+ }, [hasHydrated, token, localProxyValue, logout]);
- if (state.loading || !state.initialized) {
+ // Show loading until persisted auth is rehydrated so we don't redirect
+ // logged-in users to /login briefly
+ if (!hasHydrated || state.loading || !state.initialized) {
return (