diff --git a/src/lib/schema/fileOps.ts b/src/lib/schema/fileOps.ts index 17d40907..969e7e82 100644 --- a/src/lib/schema/fileOps.ts +++ b/src/lib/schema/fileOps.ts @@ -34,6 +34,7 @@ import { nodeRegistry } from '$lib/nodes'; import { NODE_TYPES } from '$lib/constants/nodeTypes'; import { AUTOSAVE_KEY, + LAST_FILE_KEY, kvDelete, kvGet, kvHas, @@ -85,6 +86,40 @@ export function getCurrentFileName(): string | null { export function clearCurrentFile(): void { currentFileHandle = null; currentFileNameStore.set(null); + void kvDelete(LAST_FILE_KEY); +} + +/** + * Persist the current file reference (name + handle, when one exists) so a + * restored session keeps saving to the file the user was working on. Handles + * are structured-cloneable, so IDB stores them as-is (same mechanism as the + * recents list); writing resumes after the browser's permission re-prompt. + */ +function persistFileRef(): void { + const name = getCurrentFileName(); + if (!name && !currentFileHandle) return; + void kvSet(LAST_FILE_KEY, { + name, + handle: currentFileHandle ?? undefined + }).catch(() => {}); +} + +/** + * Restore the persisted file reference after an autosave snapshot was loaded. + * `fallbackName` (e.g. the snapshot's metadata name) applies when no + * reference was persisted. No-op when nothing is known. + */ +export async function restoreFileRef(fallbackName?: string): Promise { + try { + const ref = await kvGet<{ name: string | null; handle?: FileSystemFileHandle }>(LAST_FILE_KEY); + const name = ref?.name || fallbackName; + if (ref?.handle && hasFileSystemAccess()) { + currentFileHandle = ref.handle; + } + if (name) currentFileNameStore.set(name); + } catch { + if (fallbackName) currentFileNameStore.set(fallbackName); + } } /** @@ -355,7 +390,9 @@ export async function installToolboxesForCurrentGraph(): Promise { */ export async function autoSave(): Promise { try { - const file = createGraphFile('Autosave'); + // Keep the working file's name in the snapshot so a restored session + // suggests it again ('Autosave' only for never-saved graphs). + const file = createGraphFile(getCurrentFileName() || 'Autosave'); await kvSet(AUTOSAVE_KEY, file); } catch (error) { console.warn('Autosave failed:', error); @@ -412,6 +449,7 @@ export async function loadAutoSave(): Promise { } await loadGraphFile(file); + await restoreFileRef(file.metadata?.name !== 'Autosave' ? file.metadata?.name : undefined); return true; } catch (error) { console.warn('Failed to restore autosave, clearing:', error); @@ -488,6 +526,7 @@ export async function saveAsFile(): Promise { // Update current file reference currentFileHandle = handle; currentFileNameStore.set(name); + persistFileRef(); void rememberRecent(handle); return true; } catch (error: any) { @@ -514,6 +553,7 @@ function downloadGraphFile(filename: string): void { // Set current file name for subsequent saves currentFileNameStore.set(name); + persistFileRef(); } /** @@ -748,6 +788,7 @@ async function importModel( componentFile.metadata.name || null ); + persistFileRef(); if (currentFileHandle) void rememberRecent(currentFileHandle); return { success: true, type: 'model' }; diff --git a/src/lib/schema/handleStore.ts b/src/lib/schema/handleStore.ts index e6114225..b7661b5d 100644 --- a/src/lib/schema/handleStore.ts +++ b/src/lib/schema/handleStore.ts @@ -17,6 +17,9 @@ const RECENTS_STORE = 'recents'; const RECENTS_LIMIT = 10; export const AUTOSAVE_KEY = 'autosave'; +// Name + handle of the file the user is working on, persisted next to the +// autosave blob so a restored session keeps saving to the same file. +export const LAST_FILE_KEY = 'lastFile'; export interface RecentFile { id: string; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index b9ec91cb..b9a8a9fa 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -12,7 +12,7 @@ import { themeStore, type Theme } from '$lib/stores/theme'; import { toggleThemeWithTransition } from '$lib/utils/themeTransition'; import { AUTOSAVE_KEY, kvGet, hasFileSystemAccess, type RecentFile } from '$lib/schema/handleStore'; - import { loadGraphFile, listRecentFiles, openRecentFile, removeRecentFile } from '$lib/schema/fileOps'; + import { loadGraphFile, restoreFileRef, listRecentFiles, openRecentFile, removeRecentFile } from '$lib/schema/fileOps'; import type { GraphFile } from '$lib/nodes/types'; import { triggerFitView } from '$lib/stores/viewActions'; import { consoleStore } from '$lib/stores/console'; @@ -105,6 +105,11 @@ // Python backend) deferred; missing blocks render as // placeholders in the preview and upgrade in the editor. await loadGraphFile(snapshot, { deferToolboxInstall: true, backendReady: new Promise(() => {}) }); + // Re-attach the session's file reference (name + handle): + // clicking through to the editor skips the restore prompt + // (graph already populated), so this is where Save gets its + // target back. + await restoreFileRef(snapshot.metadata?.name !== 'Autosave' ? snapshot.metadata?.name : undefined); preview = 'session'; } else { const res = await fetch(`${base}/examples/${DEFAULT_EXAMPLE.filename}`); diff --git a/src/routes/editor/+page.svelte b/src/routes/editor/+page.svelte index f1ee3bc5..ab1ad719 100644 --- a/src/routes/editor/+page.svelte +++ b/src/routes/editor/+page.svelte @@ -48,7 +48,7 @@ import { resolveBackend } from '$lib/pyodide/backend'; import { runGraphStreamingSimulation, validateGraphSimulation, exportToPython } from '$lib/pyodide/pathsimRunner'; import { consoleStore } from '$lib/stores/console'; - import { newGraph, saveFile, saveAsFile, setupAutoSave, clearAutoSave, debouncedAutoSave, openImportDialog, importFromUrl, currentFileName, loadGraphFile, listRecentFiles, openRecentFile, removeRecentFile, installToolboxesForCurrentGraph } from '$lib/schema/fileOps'; + import { newGraph, saveFile, saveAsFile, setupAutoSave, clearAutoSave, debouncedAutoSave, openImportDialog, importFromUrl, currentFileName, loadGraphFile, restoreFileRef, listRecentFiles, openRecentFile, removeRecentFile, installToolboxesForCurrentGraph } from '$lib/schema/fileOps'; import { AUTOSAVE_KEY, kvGet, hasFileSystemAccess, type RecentFile } from '$lib/schema/handleStore'; import type { GraphFile } from '$lib/nodes/types'; import { confirmationStore } from '$lib/stores/confirmation'; @@ -730,6 +730,9 @@ if (ok) { try { await loadGraphFile(snapshot); + // Re-attach the file the session was working on (name + + // handle), so Save targets it again instead of a default. + await restoreFileRef(snapshot.metadata?.name !== 'Autosave' ? snapshot.metadata?.name : undefined); setTimeout(() => triggerFitView(), 100); } catch (e) { console.warn('Failed to restore autosave:', e);