fix: prevent TLDraw topOffset crash — decouple store init from editor ready

Three changes to singlePlayerPage:

1. Store creation no longer waits for isEditorReady. The editor ref
   is already passed as optional to NavigationSnapshotService and
   loadNodeSnapshotFromDatabase, so we can create and populate the
   store without needing the editor to be mounted first.

2. <Tldraw> only renders when store is defined: {store && <Tldraw>}.
   Previously it rendered immediately with store={undefined}, which
   caused TLDraw to initialize with no backing store and crash
   reading topOffset on an unresolved internal object.

3. Loading guard also checks !user so the component never renders
   with a null profile before the redirect effect fires.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kcar 2026-05-21 18:17:52 +00:00
parent b1681d86fb
commit e27d1f5c8d

View File

@ -85,23 +85,13 @@ export default function SinglePlayerPage() {
setUserPreferences: setTldrawPreferences setUserPreferences: setTldrawPreferences
}); });
// Initialize store // Initialize store — runs as soon as user is ready, no editor needed for store creation
useEffect(() => { useEffect(() => {
if (!isEditorReady) {
logger.debug('single-player-page', '⏳ Waiting for editor to be ready');
return;
}
if (!user) { if (!user) {
logger.debug('single-player-page', '⏳ Waiting for user data'); logger.debug('single-player-page', '⏳ Waiting for user data');
return; return;
} }
if (!editorRef.current) {
logger.debug('single-player-page', '⏳ Waiting for editor ref');
return;
}
logger.info('single-player-page', '🔄 Starting store initialization', { logger.info('single-player-page', '🔄 Starting store initialization', {
isEditorReady, isEditorReady,
hasUser: !!user, hasUser: !!user,
@ -221,7 +211,7 @@ export default function SinglePlayerPage() {
}; };
initializeStoreAndSnapshot(); initializeStoreAndSnapshot();
}, [isEditorReady, user, context.node]); }, [user, context.node]);
// Handle initial node placement // Handle initial node placement
useEffect(() => { useEffect(() => {
@ -451,7 +441,7 @@ export default function SinglePlayerPage() {
const uiComponents = getUiComponents(presentationMode); const uiComponents = getUiComponents(presentationMode);
// Show loading state if user context is still loading // Show loading state if user context is still loading
if (userLoading) { if (userLoading || !user) {
return ( return (
<div style={{ <div style={{
position: 'fixed', position: 'fixed',
@ -505,7 +495,7 @@ export default function SinglePlayerPage() {
</Alert> </Alert>
</Snackbar> </Snackbar>
<Tldraw {store && <Tldraw
user={tldrawUser} user={tldrawUser}
store={store} store={store}
tools={singlePlayerTools} tools={singlePlayerTools}
@ -549,7 +539,7 @@ export default function SinglePlayerPage() {
logger.error('single-player-page', '❌ Error in onMount', error); logger.error('single-player-page', '❌ Error in onMount', error);
} }
}} }}
/> />}
</div> </div>
); );
} }