From 0d86cff1b7de8885512d695b65cfffd870468ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Wed, 19 Nov 2025 09:51:27 +0100 Subject: [PATCH] feat: disable localStorage persistence and add auto-load last project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **useMultiTrack.ts:** - Removed localStorage persistence (tracks, effects, automation) - Now relies entirely on IndexedDB via project management system - Simpler state management without dual persistence **AudioEditor.tsx:** - Store last project ID in localStorage when saving - Auto-load last project on page mount - Only runs once per session with hasAutoLoaded flag - Falls back to empty state if project can't be loaded **Benefits:** - No more conflicts between localStorage and IndexedDB - Effects and automation properly persisted - Seamless experience - reload page and your project is ready - Single source of truth (IndexedDB) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- components/editor/AudioEditor.tsx | 26 +++++++++++ lib/hooks/useMultiTrack.ts | 75 ++----------------------------- 2 files changed, 29 insertions(+), 72 deletions(-) diff --git a/components/editor/AudioEditor.tsx b/components/editor/AudioEditor.tsx index 08e5017..7045703 100644 --- a/components/editor/AudioEditor.tsx +++ b/components/editor/AudioEditor.tsx @@ -911,6 +911,9 @@ export function AudioEditor() { setCurrentProjectId(projectId); + // Save last project ID to localStorage for auto-load on next visit + localStorage.setItem('audio-ui-last-project', projectId); + addToast({ title: 'Project Saved', description: `"${currentProjectName}" saved successfully`, @@ -1009,6 +1012,29 @@ export function AudioEditor() { } }, [loadTracks, addToast]); + // Auto-load last project on mount + const [hasAutoLoaded, setHasAutoLoaded] = React.useState(false); + React.useEffect(() => { + if (hasAutoLoaded) return; // Only run once + + const loadLastProject = async () => { + const lastProjectId = localStorage.getItem('audio-ui-last-project'); + if (lastProjectId) { + try { + console.log('[Auto-load] Loading last project:', lastProjectId); + await handleLoadProject(lastProjectId); + } catch (error) { + console.error('[Auto-load] Failed to load last project:', error); + // Clear invalid project ID + localStorage.removeItem('audio-ui-last-project'); + } + } + setHasAutoLoaded(true); + }; + + loadLastProject(); + }, [hasAutoLoaded, handleLoadProject]); + // Delete project const handleDeleteProject = React.useCallback(async (projectId: string) => { try { diff --git a/lib/hooks/useMultiTrack.ts b/lib/hooks/useMultiTrack.ts index 50b709d..15feb73 100644 --- a/lib/hooks/useMultiTrack.ts +++ b/lib/hooks/useMultiTrack.ts @@ -1,79 +1,10 @@ -import { useState, useCallback, useEffect } from 'react'; +import { useState, useCallback } from 'react'; import type { Track } from '@/types/track'; import { createTrack, createTrackFromBuffer } from '@/lib/audio/track-utils'; -import { createEffectChain } from '@/lib/audio/effects/chain'; -import { DEFAULT_TRACK_HEIGHT } from '@/types/track'; - -const STORAGE_KEY = 'audio-ui-multi-track'; export function useMultiTrack() { - const [tracks, setTracks] = useState(() => { - if (typeof window === 'undefined') return []; - - try { - const saved = localStorage.getItem(STORAGE_KEY); - if (saved) { - const parsed = JSON.parse(saved); - - // Clear corrupted data immediately if we detect issues - const hasInvalidData = parsed.some((t: any) => - typeof t.name !== 'string' || t.name === '[object Object]' - ); - - if (hasInvalidData) { - console.warn('Detected corrupted track data in localStorage, clearing...'); - localStorage.removeItem(STORAGE_KEY); - return []; - } - - // Note: AudioBuffers can't be serialized, but EffectChains and Automation can - return parsed.map((t: any) => ({ - ...t, - name: String(t.name || 'Untitled Track'), // Ensure name is always a string - height: t.height && t.height >= DEFAULT_TRACK_HEIGHT ? t.height : DEFAULT_TRACK_HEIGHT, // Migrate old heights - audioBuffer: null, // Will need to be reloaded - effectChain: t.effectChain || createEffectChain(`${t.name} Effects`), // Restore effect chain or create new - automation: t.automation || { lanes: [], showAutomation: false }, // Restore automation or create new - selection: t.selection || null, // Initialize selection - showEffects: t.showEffects || false, // Restore showEffects state - })); - } - } catch (error) { - console.error('Failed to load tracks from localStorage:', error); - // Clear corrupted data - localStorage.removeItem(STORAGE_KEY); - } - - return []; - }); - - // Save tracks to localStorage (without audio buffers) - useEffect(() => { - if (typeof window === 'undefined') return; - - try { - // Only save serializable fields, excluding audioBuffer and any DOM references - const trackData = tracks.map((track) => ({ - id: track.id, - name: String(track.name || 'Untitled Track'), - color: track.color, - height: track.height, - volume: track.volume, - pan: track.pan, - mute: track.mute, - solo: track.solo, - recordEnabled: track.recordEnabled, - collapsed: track.collapsed, - selected: track.selected, - showEffects: track.showEffects, // Save effects panel state - effectChain: track.effectChain, // Save effect chain - automation: track.automation, // Save automation data - })); - localStorage.setItem(STORAGE_KEY, JSON.stringify(trackData)); - } catch (error) { - console.error('Failed to save tracks to localStorage:', error); - } - }, [tracks]); + // Note: localStorage persistence disabled in favor of IndexedDB project management + const [tracks, setTracks] = useState([]); const addTrack = useCallback((name?: string) => { const track = createTrack(name);