Skip to content

Commit 42b6bd9

Browse files
authored
Merge pull request #100 from beNative/codex/add-feature-to-memorize-active-document-3s8mah
Fix ensureNodeVisible initialization order
2 parents ea33917 + 900edb9 commit 42b6bd9

3 files changed

Lines changed: 121 additions & 30 deletions

File tree

App.tsx

Lines changed: 118 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ interface DatabaseStatusState {
102102

103103
const MainApp: React.FC = () => {
104104
const { settings, saveSettings, loaded: settingsLoaded } = useSettings();
105-
const { items, addDocument, addFolder, updateItem, commitVersion, deleteItems, moveItems, getDescendantIds, duplicateItems, addDocumentsFromFiles, importNodesFromTransfer } = useDocuments();
105+
const { items, addDocument, addFolder, updateItem, commitVersion, deleteItems, moveItems, getDescendantIds, duplicateItems, addDocumentsFromFiles, importNodesFromTransfer, isLoading: areDocumentsLoading } = useDocuments();
106106
const { templates, addTemplate, updateTemplate, deleteTemplate, deleteTemplates } = useTemplates();
107107
const { theme } = useTheme();
108108

@@ -111,6 +111,7 @@ const MainApp: React.FC = () => {
111111
const [lastClickedId, setLastClickedId] = useState<string | null>(null);
112112
const [activeTemplateId, setActiveTemplateId] = useState<string | null>(null);
113113
const [expandedFolderIds, setExpandedFolderIds] = useState(new Set<string>());
114+
const [hasLoadedExpandedFolders, setHasLoadedExpandedFolders] = useState(false);
114115
const [pendingRevealId, setPendingRevealId] = useState<string | null>(null);
115116
const [renamingNodeId, setRenamingNodeId] = useState<string | null>(null);
116117

@@ -141,9 +142,12 @@ const MainApp: React.FC = () => {
141142
const [databasePath, setDatabasePath] = useState<string | null>(null);
142143
const [databaseStatus, setDatabaseStatus] = useState<DatabaseStatusState | null>(null);
143144
const [isDatabaseBusy, setIsDatabaseBusy] = useState(false);
145+
const [isRestoringActiveDocument, setIsRestoringActiveDocument] = useState(true);
146+
const [hasRestoredActiveDocument, setHasRestoredActiveDocument] = useState(false);
144147

145148
const activeNodeId = tabState.activeId;
146149
const openDocumentIds = tabState.order;
150+
const storedActiveDocumentIdRef = useRef<string | null>(null);
147151

148152
const activateDocumentTab = useCallback((documentId: string) => {
149153
setTabState(prev => {
@@ -224,6 +228,27 @@ const MainApp: React.FC = () => {
224228
const dragCounter = useRef(0);
225229
const ensureNodeVisibleRef = useRef<(node: Pick<DocumentOrFolder, 'id' | 'type' | 'parentId'>) => void>();
226230

231+
const ensureNodeVisible = useCallback((node: Pick<DocumentOrFolder, 'id' | 'type' | 'parentId'>) => {
232+
const ancestry = new Map(items.map(item => [item.id, item.parentId ?? null]));
233+
setExpandedFolderIds(prev => {
234+
const next = new Set(prev);
235+
let current = node.parentId;
236+
while (current) {
237+
next.add(current);
238+
current = ancestry.get(current) ?? null;
239+
}
240+
if (node.type === 'folder') {
241+
next.add(node.id);
242+
}
243+
return next;
244+
});
245+
setPendingRevealId(node.id);
246+
}, [items, setPendingRevealId]);
247+
248+
useEffect(() => {
249+
ensureNodeVisibleRef.current = ensureNodeVisible;
250+
}, [ensureNodeVisible]);
251+
227252
const llmStatus = useLLMStatus(settings.llmProviderUrl);
228253
const { logs, addLog } = useLogger();
229254
const lastLogRef = useRef<LogMessage | null>(null);
@@ -331,6 +356,68 @@ const MainApp: React.FC = () => {
331356
const documentItems = useMemo(() => items.filter(item => item.type === 'document'), [items]);
332357
const activeDocumentId = activeDocument?.id ?? null;
333358

359+
useEffect(() => {
360+
let isCancelled = false;
361+
storageService.load<string | null>(LOCAL_STORAGE_KEYS.ACTIVE_DOCUMENT_ID, null).then(savedId => {
362+
if (isCancelled) {
363+
return;
364+
}
365+
storedActiveDocumentIdRef.current = savedId;
366+
setIsRestoringActiveDocument(false);
367+
});
368+
369+
return () => {
370+
isCancelled = true;
371+
};
372+
}, []);
373+
374+
useEffect(() => {
375+
if (isRestoringActiveDocument || hasRestoredActiveDocument || !hasLoadedExpandedFolders) {
376+
return;
377+
}
378+
379+
const savedId = storedActiveDocumentIdRef.current;
380+
if (!savedId) {
381+
storedActiveDocumentIdRef.current = null;
382+
setHasRestoredActiveDocument(true);
383+
return;
384+
}
385+
386+
if (items.length === 0) {
387+
if (areDocumentsLoading) {
388+
return;
389+
}
390+
storedActiveDocumentIdRef.current = null;
391+
setHasRestoredActiveDocument(true);
392+
return;
393+
}
394+
395+
const target = items.find(item => item.id === savedId && item.type === 'document');
396+
if (!target) {
397+
storedActiveDocumentIdRef.current = null;
398+
setHasRestoredActiveDocument(true);
399+
return;
400+
}
401+
402+
ensureNodeVisibleRef.current?.(target);
403+
setActiveTemplateId(null);
404+
setView('editor');
405+
setDocumentView('editor');
406+
activateDocumentTab(savedId);
407+
setSelectedIds(new Set([savedId]));
408+
setLastClickedId(savedId);
409+
storedActiveDocumentIdRef.current = null;
410+
setHasRestoredActiveDocument(true);
411+
}, [isRestoringActiveDocument, hasRestoredActiveDocument, items, activateDocumentTab, areDocumentsLoading, hasLoadedExpandedFolders, ensureNodeVisibleRef]);
412+
413+
useEffect(() => {
414+
if (!hasRestoredActiveDocument) {
415+
return;
416+
}
417+
418+
storageService.save(LOCAL_STORAGE_KEYS.ACTIVE_DOCUMENT_ID, activeDocumentId);
419+
}, [activeDocumentId, hasRestoredActiveDocument]);
420+
334421

335422
useEffect(() => {
336423
const term = searchTerm.trim();
@@ -841,18 +928,42 @@ const MainApp: React.FC = () => {
841928
storageService.load(LOCAL_STORAGE_KEYS.LOGGER_PANEL_HEIGHT, DEFAULT_LOGGER_HEIGHT).then(height => {
842929
if (typeof height === 'number') setLoggerPanelHeight(height);
843930
});
844-
storageService.load<string[]>(LOCAL_STORAGE_KEYS.EXPANDED_FOLDERS, []).then(ids => {
845-
setExpandedFolderIds(new Set(ids));
846-
});
931+
932+
let isCancelled = false;
933+
934+
storageService
935+
.load<string[]>(LOCAL_STORAGE_KEYS.EXPANDED_FOLDERS, [])
936+
.then(ids => {
937+
if (isCancelled) {
938+
return;
939+
}
940+
setExpandedFolderIds(new Set(ids));
941+
})
942+
.catch(() => {
943+
// Loading failed; we'll fall back to the default empty set.
944+
})
945+
.finally(() => {
946+
if (!isCancelled) {
947+
setHasLoadedExpandedFolders(true);
948+
}
949+
});
950+
951+
return () => {
952+
isCancelled = true;
953+
};
847954
}, []);
848955

849956
useEffect(() => {
850-
if (settingsLoaded) {
957+
if (settingsLoaded && hasLoadedExpandedFolders) {
851958
storageService.save(LOCAL_STORAGE_KEYS.EXPANDED_FOLDERS, Array.from(expandedFolderIds));
852959
}
853-
}, [expandedFolderIds, settingsLoaded]);
960+
}, [expandedFolderIds, settingsLoaded, hasLoadedExpandedFolders]);
854961

855962
useEffect(() => {
963+
if (isRestoringActiveDocument || !hasRestoredActiveDocument) {
964+
return;
965+
}
966+
856967
if (items.length === 0) {
857968
if (openDocumentIds.length > 0 || activeNodeId !== null) {
858969
setTabState({ activeId: null, order: [] });
@@ -876,7 +987,7 @@ const MainApp: React.FC = () => {
876987
setSelectedIds(new Set([firstItem.id]));
877988
setLastClickedId(firstItem.id);
878989
}
879-
}, [items, activeNodeId, activeTemplateId, openDocumentIds.length, activateDocumentTab, setActiveItem]);
990+
}, [items, activeNodeId, activeTemplateId, openDocumentIds.length, activateDocumentTab, setActiveItem, isRestoringActiveDocument, hasRestoredActiveDocument]);
880991

881992
useEffect(() => {
882993
const documentIds = new Set(items.filter(item => item.type === 'document').map(item => item.id));
@@ -1121,27 +1232,6 @@ const MainApp: React.FC = () => {
11211232
return activeNode.type === 'folder' ? activeNode.id : activeNode.parentId;
11221233
}, [activeNode]);
11231234

1124-
const ensureNodeVisible = useCallback((node: Pick<DocumentOrFolder, 'id' | 'type' | 'parentId'>) => {
1125-
const ancestry = new Map(items.map(item => [item.id, item.parentId ?? null]));
1126-
setExpandedFolderIds(prev => {
1127-
const next = new Set(prev);
1128-
let current = node.parentId;
1129-
while (current) {
1130-
next.add(current);
1131-
current = ancestry.get(current) ?? null;
1132-
}
1133-
if (node.type === 'folder') {
1134-
next.add(node.id);
1135-
}
1136-
return next;
1137-
});
1138-
setPendingRevealId(node.id);
1139-
}, [items, setPendingRevealId]);
1140-
1141-
useEffect(() => {
1142-
ensureNodeVisibleRef.current = ensureNodeVisible;
1143-
}, [ensureNodeVisible]);
1144-
11451235
const handleNavigateToNode = useCallback((nodeId: string) => {
11461236
const target = items.find(item => item.id === nodeId);
11471237
if (!target) {

constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const LOCAL_STORAGE_KEYS = {
77
EXPANDED_FOLDERS: 'docforge_expanded_folders',
88
SIDEBAR_TEMPLATES_COLLAPSED: 'docforge_sidebar_templates_collapsed',
99
SIDEBAR_TEMPLATES_PANEL_HEIGHT: 'docforge_sidebar_templates_panel_height',
10+
ACTIVE_DOCUMENT_ID: 'docforge_active_document_id',
1011

1112
// Legacy keys used for migration check:
1213
LEGACY_PROMPTS: 'promptforge_prompts',

hooks/usePrompts.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const getDescendantIdsRecursive = (nodeId: string, allNodes: Node[]): Set<string
5252
* This allows the UI to function without a full refactor of all components.
5353
*/
5454
export const useDocuments = () => {
55-
const { nodes, addNode, updateNode, deleteNode, deleteNodes, moveNodes, updateDocumentContent, refreshNodes, duplicateNodes, importFiles, importNodesFromTransfer, addLog } = useNodes();
55+
const { nodes, addNode, updateNode, deleteNode, deleteNodes, moveNodes, updateDocumentContent, refreshNodes, duplicateNodes, importFiles, importNodesFromTransfer, addLog, isLoading } = useNodes();
5656

5757
const allNodesFlat = useMemo(() => flattenNodes(nodes), [nodes]);
5858
const items: DocumentOrFolder[] = useMemo(() => allNodesFlat.map(nodeToDocumentOrFolder), [allNodesFlat]);
@@ -162,5 +162,5 @@ export const useDocuments = () => {
162162
return getDescendantIdsRecursive(nodeId, allNodesFlat);
163163
}, [allNodesFlat]);
164164

165-
return { items, addDocument, addFolder, updateItem, commitVersion, deleteItem, deleteItems, moveItems, getDescendantIds, refresh: refreshNodes, duplicateItems, addDocumentsFromFiles, importNodesFromTransfer };
165+
return { items, addDocument, addFolder, updateItem, commitVersion, deleteItem, deleteItems, moveItems, getDescendantIds, refresh: refreshNodes, duplicateItems, addDocumentsFromFiles, importNodesFromTransfer, isLoading };
166166
};

0 commit comments

Comments
 (0)