fix: Prevent state machine from navigating before URL initialization
Fixed a race condition where the state machine would navigate to /chat before initializing from the URL, causing direct navigation to /galaxy URLs to redirect. **The Problem:** 1. Component mounts, state machine starts in 'convo' state (default) 2. State-to-URL effect fires: "state is convo → navigate to /chat" 3. URL-to-state initialization fires: "we're on /galaxy → NAVIGATE_TO_GALAXY" 4. State transitions to 'galaxy' 5. State-to-URL effect fires again: "state is galaxy → navigate to /galaxy" This caused a brief redirect to /chat before settling on /galaxy. **The Solution:** - Don't mark as initialized immediately after sending the initial event - Add a second effect that watches for state to match the URL - Only mark as initialized once state matches the target state for the URL - This prevents the state-to-URL effect from running before initialization **Changes:** - Modified URL-to-state initialization to not mark as initialized immediately - Added new effect to mark as initialized once state matches URL target - Added console log: "State initialized from URL, marking as ready" **Testing:** Verified with Playwright MCP that navigating directly to /galaxy?node=xxx no longer redirects to /chat. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -60,12 +60,32 @@ export function AppStateMachineProvider({ children }: { children: React.ReactNod
|
|||||||
if (initialEvent) {
|
if (initialEvent) {
|
||||||
console.log('[App Provider] Setting initial state:', initialEvent);
|
console.log('[App Provider] Setting initial state:', initialEvent);
|
||||||
send({ type: initialEvent as any });
|
send({ type: initialEvent as any });
|
||||||
|
} else {
|
||||||
|
// No specific route matched, we're probably on homepage or other route
|
||||||
|
// Mark as initialized immediately so we don't interfere
|
||||||
|
isInitializedRef.current = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark as initialized AFTER sending the event
|
// DON'T mark as initialized yet if we sent an event
|
||||||
isInitializedRef.current = true;
|
// Wait for the state change to complete
|
||||||
}, [pathname, send]); // Remove 'state' from dependencies!
|
}, [pathname, send]); // Remove 'state' from dependencies!
|
||||||
|
|
||||||
|
// Mark as initialized once state matches the target from URL
|
||||||
|
useEffect(() => {
|
||||||
|
if (isInitializedRef.current) return;
|
||||||
|
|
||||||
|
const targetStateForPath =
|
||||||
|
pathname === '/chat' ? 'convo' :
|
||||||
|
pathname === '/edit' ? 'edit' :
|
||||||
|
(pathname === '/galaxy' || pathname.startsWith('/galaxy/')) ? 'galaxy' :
|
||||||
|
null;
|
||||||
|
|
||||||
|
if (targetStateForPath && state.matches(targetStateForPath)) {
|
||||||
|
console.log('[App Provider] State initialized from URL, marking as ready');
|
||||||
|
isInitializedRef.current = true;
|
||||||
|
}
|
||||||
|
}, [pathname, state]);
|
||||||
|
|
||||||
// State machine is source of truth: sync state → URL only
|
// State machine is source of truth: sync state → URL only
|
||||||
// This effect ONLY runs when state changes, not when pathname changes
|
// This effect ONLY runs when state changes, not when pathname changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user