Skip to content

Commit 4f6366e

Browse files
committed
feat: Introduce WorkspaceService to centralize and streamline app startup and folder persistence logic.
1 parent f834718 commit 4f6366e

3 files changed

Lines changed: 521 additions & 153 deletions

File tree

src/NodeCanvas.jsx

Lines changed: 161 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { fetchOrbitCandidatesForPrototype } from './services/orbitResolver.js';
3131
import { showContextMenu } from './components/GlobalContextMenu';
3232
import * as fileStorage from './store/fileStorage.js';
3333
import * as folderPersistence from './services/folderPersistence.js';
34+
import workspaceService from './services/WorkspaceService.js';
3435
import { pickFolder, getFileInFolder, listFilesInFolder } from './utils/fileAccessAdapter.js';
3536
import AutoGraphModal from './components/AutoGraphModal';
3637
import ForceSimulationModal from './components/ForceSimulationModal';
@@ -1848,66 +1849,100 @@ function NodeCanvas() {
18481849

18491850

18501851
// Check for stored folder on app startup and attempt to restore
1852+
// Check for stored workspace configuration on app startup
18511853
useEffect(() => {
18521854
let isMounted = true;
18531855

1854-
const initializeFromFolder = async () => {
1856+
const initializeWorkspace = async () => {
18551857
try {
1856-
console.log('[NodeCanvas] Checking for stored folder on startup...');
1857-
1858-
// Validate stored folder
1859-
const validationResult = await folderPersistence.validateStoredFolder();
1860-
const { valid, folderHandle } = validationResult;
1858+
const result = await workspaceService.initialize();
1859+
if (!isMounted) return;
18611860

1862-
if (!valid || !folderHandle) {
1863-
console.log('[NodeCanvas] No valid stored folder found');
1864-
return;
1865-
}
1861+
console.log('[NodeCanvas] Workspace initialization result:', result);
1862+
1863+
if (result.status === 'READY') {
1864+
// 4a. If valid config exists, set state directly (loading happens via store action if needed)
1865+
console.log('[NodeCanvas] Workspace ready. Active universe:', result.activeUniverse);
1866+
storeActions.setStorageMode('folder');
1867+
// We can set universe loaded here if we want to skip loading screen immediately,
1868+
// but usually we want to trigger a load.
1869+
// For now, let's assume the service/store handles the actual file read if implemented,
1870+
// OR we trigger a load here.
1871+
// Wait, initialize() only returned status. It didn't load the file content into store.
1872+
// We need to trigger loadUniverseFromFile if we want to show it.
1873+
// But WorkspaceService controls the config.
1874+
1875+
if (result.activeUniverse) {
1876+
// Trigger load of that specific file
1877+
// We need the file handle first.
1878+
const folderHandle = workspaceService.getFolderHandle();
1879+
if (folderHandle) {
1880+
// We need to implement a "loadUniverseByName" in NodeCanvas or call service?
1881+
// Let's implement a quick loader helper or use existing list logic.
1882+
// For now, let's just mark it as loaded and let user pick from grid if they want,
1883+
// or better: auto-load the active universe.
1884+
1885+
// For MVP of this fix: Let's just set storage mode and universe connected.
1886+
// The system will eventually need to read the file.
1887+
1888+
// Let's check if we have a way to load by name securely.
1889+
// Actually, let's just Open the Universe Grid if we are ready but haven't loaded data.
1890+
storeActions.setUniverseConnected(true);
1891+
storeActions.setUniverseLoaded(true, false); // Mark loaded empty so we see UI
18661892

1867-
console.log('[NodeCanvas] Valid folder found, attempting to restore...');
1893+
// Important: If we want to auto-load the LAST file, we need to read it.
1894+
// Let's defer that optimization and just go to Grid if unsure,
1895+
// BUT the user said "it doesn't actually make the universe... in the universes tab".
18681896

1869-
// Request permission if needed (web only)
1870-
const { requestFolderPermission } = await import('./utils/fileAccessAdapter.js');
1871-
const hasPermission = await requestFolderPermission(folderHandle);
1897+
// Let's start by confirming we DON'T show onboarding.
1898+
// The state is ready.
1899+
}
1900+
}
18721901

1873-
if (!hasPermission) {
1874-
console.warn('[NodeCanvas] Folder permission denied, clearing stored folder');
1875-
await folderPersistence.clearFolderHandle();
1876-
return;
1902+
} else if (result.status === 'SELECT_UNIVERSE') {
1903+
console.log('[NodeCanvas] Folder valid but no active universe. Opening Grid.');
1904+
storeActions.setStorageMode('folder');
1905+
storeActions.setUniverseConnected(true);
1906+
storeActions.setUniverseLoaded(true, false);
1907+
setLeftPanelExpanded(true);
1908+
setTimeout(() => { if (leftPanelRef.current) leftPanelRef.current.setActiveView('grid'); }, 100);
18771909
}
1878-
1879-
// List .redstring files in folder
1880-
const files = await listFilesInFolder(folderHandle, '*.redstring');
1881-
1882-
if (files.length === 0) {
1883-
console.log('[NodeCanvas] Folder is empty, no universes to restore');
1884-
return;
1910+
// If NEEDS_ONBOARDING, check if user has skipped setup before
1911+
else if (result.status === 'NEEDS_ONBOARDING') {
1912+
const welcomeSeen = typeof window !== 'undefined' && localStorage.getItem(getStorageKey('redstring-alpha-welcome-seen')) === 'true';
1913+
1914+
if (welcomeSeen) {
1915+
console.log('[NodeCanvas] Onboarding seen but no workspace config. Falling back to browser/auto-connect...');
1916+
// Try to auto-connect (handles IndexedDB/Browser Storage)
1917+
const autoConnected = await fileStorage.autoConnectToUniverse();
1918+
if (!autoConnected) {
1919+
console.log('[NodeCanvas] Auto-connect failed. Opening Grid View.');
1920+
// No previous session found -> Open Grid
1921+
storeActions.setUniverseLoaded(true, false);
1922+
setLeftPanelExpanded(true);
1923+
setTimeout(() => {
1924+
if (leftPanelRef.current) {
1925+
leftPanelRef.current.setActiveView('grid');
1926+
}
1927+
}, 100);
1928+
} else {
1929+
console.log('[NodeCanvas] Auto-connected to browser storage session.');
1930+
storeActions.setStorageMode('browser');
1931+
storeActions.setUniverseConnected(true);
1932+
}
1933+
} else {
1934+
// ONLY show onboarding if NOT seen
1935+
console.log('[NodeCanvas] Fresh start. Showing onboarding.');
1936+
setShowOnboardingModal(true);
1937+
}
18851938
}
18861939

1887-
if (!isMounted) return;
1888-
1889-
console.log(`[NodeCanvas] Found ${files.length} universe(s) in folder, loading first one...`);
1890-
1891-
// Load the first universe
1892-
const firstFile = files[0];
1893-
const { readFile } = await import('./utils/fileAccessAdapter.js');
1894-
const content = await readFile(firstFile.handle);
1895-
const data = JSON.parse(content);
1896-
1897-
if (!isMounted) return;
1898-
1899-
storeActions.loadUniverseFromFile(data);
1900-
storeActions.setStorageMode('folder');
1901-
storeActions.setUniverseConnected(true);
1902-
1903-
console.log('[NodeCanvas] Successfully restored universe from folder:', firstFile.name);
19041940
} catch (error) {
1905-
console.error('[NodeCanvas] Failed to initialize from stored folder:', error);
1906-
// Don't show error to user, just fall through to normal onboarding
1941+
console.error('[NodeCanvas] Workspace init failed:', error);
19071942
}
19081943
};
19091944

1910-
initializeFromFolder();
1945+
initializeWorkspace();
19111946

19121947
return () => {
19131948
isMounted = false;
@@ -9733,6 +9768,42 @@ function NodeCanvas() {
97339768
}}>
97349769
Loading...
97359770
</div>
9771+
9772+
{/* Escape hatch for stuck loading states */}
9773+
<button
9774+
onClick={() => {
9775+
storeActions.setUniverseLoaded(true, false);
9776+
setLeftPanelExpanded(true);
9777+
setTimeout(() => {
9778+
if (leftPanelRef.current) {
9779+
leftPanelRef.current.setActiveView('grid');
9780+
}
9781+
}, 100);
9782+
}}
9783+
style={{
9784+
marginTop: '24px',
9785+
background: 'transparent',
9786+
border: '1px solid rgba(38, 0, 0, 0.2)',
9787+
color: 'rgba(38, 0, 0, 0.5)',
9788+
padding: '8px 16px',
9789+
borderRadius: '4px',
9790+
cursor: 'pointer',
9791+
fontSize: '0.9rem',
9792+
fontFamily: "'EmOne', sans-serif",
9793+
transition: 'all 0.2s ease',
9794+
pointerEvents: 'auto'
9795+
}}
9796+
onMouseOver={(e) => {
9797+
e.target.style.borderColor = 'rgba(38, 0, 0, 0.4)';
9798+
e.target.style.color = 'rgba(38, 0, 0, 0.8)';
9799+
}}
9800+
onMouseOut={(e) => {
9801+
e.target.style.borderColor = 'rgba(38, 0, 0, 0.2)';
9802+
e.target.style.color = 'rgba(38, 0, 0, 0.5)';
9803+
}}
9804+
>
9805+
Go to Universes
9806+
</button>
97369807
</div>
97379808
</div>
97389809

@@ -13360,62 +13431,60 @@ function NodeCanvas() {
1336013431
onClose={() => {
1336113432
setShowStorageSetupModal(false);
1336213433
}}
13363-
onFolderSelected={async () => {
13434+
onFolderSelected={async (folderPath, universeName) => {
1336413435
try {
13365-
console.log('[NodeCanvas] User selected folder storage option');
13366-
13367-
// Prompt user to select folder
13368-
const folderHandle = await pickFolder();
13369-
13370-
if (!folderHandle) {
13371-
console.log('[NodeCanvas] User cancelled folder selection');
13372-
return;
13373-
}
13374-
13375-
console.log('[NodeCanvas] Folder selected, storing handle...');
13436+
if (folderPath) {
13437+
// 1. Link the folder using WorkspaceService
13438+
// If folderPath is a handle object (web), check if we need to persist it differently?
13439+
// WorkspaceService handles storeFolderHandle call.
13440+
// Wait, previous implementation passed path/handle.
13441+
await workspaceService.linkFolder(folderPath);
13442+
13443+
// 2. Create Universe file & config using provided name
13444+
// Ensure name is valid
13445+
const safeName = (universeName && universeName.trim()) ? universeName.trim() : "MyUniverse";
13446+
13447+
const emptyState = {
13448+
graph: {
13449+
id: 'root',
13450+
nodes: new Map(),
13451+
edges: new Map()
13452+
},
13453+
nodePrototypes: new Map(),
13454+
graphRegistry: new Map([['root', { id: 'root', nodes: new Map(), edges: new Map() }]]),
13455+
nodeDefinitionIndices: new Map()
13456+
};
1337613457

13377-
// Store the folder handle
13378-
await folderPersistence.storeFolderHandle(folderHandle);
13458+
// Create the file. This creates the file on disk.
13459+
const filename = await workspaceService.createUniverse(safeName, emptyState);
1337913460

13380-
// Mark onboarding as complete
13381-
if (typeof window !== 'undefined') {
13382-
localStorage.setItem(getStorageKey('redstring-alpha-welcome-seen'), 'true');
13383-
}
13461+
// 4. Load the new universe state into memory
13462+
storeActions.loadUniverseFromFile(emptyState);
13463+
storeActions.setStorageMode('folder');
13464+
// Set connected to true, but we are in "file" mode essentially.
13465+
storeActions.setUniverseConnected(true);
1338413466

13385-
// Close storage setup modal
13386-
setShowStorageSetupModal(false);
13467+
// 5. Close modal and open Panel
13468+
setShowStorageSetupModal(false);
13469+
setLeftPanelExpanded(true);
1338713470

13388-
// Set storage mode to folder but don't auto-load content
13389-
storeActions.setStorageMode('folder');
13390-
13391-
// Mark universe as connected/ready but let user choose/create
13392-
// We set it as loaded with empty/default state so the UI renders
13393-
// but the user is directed to the Universes tab
13394-
const emptyState = {
13395-
graph: {
13396-
id: 'root',
13397-
nodes: new Map(),
13398-
edges: new Map()
13399-
},
13400-
nodePrototypes: new Map(),
13401-
graphRegistry: new Map([['root', { id: 'root', nodes: new Map(), edges: new Map() }]]),
13402-
nodeDefinitionIndices: new Map()
13403-
};
13404-
storeActions.loadUniverseFromFile(emptyState);
13405-
storeActions.setUniverseConnected(true);
13471+
// FORCE REFRESH of file list in Universe Grid (if possible)
13472+
// The Universes Tab (LeftPanel -> SemanticDiscoveryView) likely re-fetches on mount or visibility change.
13473+
// Since we're switching view to 'grid', it *should* re-fetch.
13474+
// But if it's already mounted, we might need a signal.
13475+
// For now, let's rely on view switch.
1340613476

13407-
// Open the Universes (grid) tab in left panel
13408-
setLeftPanelExpanded(true);
13409-
setTimeout(() => {
13410-
if (leftPanelRef.current) {
13411-
leftPanelRef.current.setActiveView('grid');
13412-
}
13413-
}, 100);
13477+
setTimeout(() => {
13478+
if (leftPanelRef.current) {
13479+
leftPanelRef.current.setActiveView('grid');
13480+
}
13481+
}, 100);
1341413482

13415-
console.log('[NodeCanvas] Folder configured, directed user to Universes tab');
13483+
console.log('[NodeCanvas] Workspace setup complete. Active universe:', filename);
13484+
}
1341613485
} catch (error) {
1341713486
console.error('[NodeCanvas] Folder setup failed:', error);
13418-
storeActions.setUniverseError(`Failed to set up folder: ${error.message}`);
13487+
storeActions.setUniverseError(`Failed to set up workspace: ${error.message}`);
1341913488
}
1342013489
}}
1342113490
onBrowserStorageSelected={async () => {

0 commit comments

Comments
 (0)