feat: disable localStorage persistence and add auto-load last project

**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 <noreply@anthropic.com>
This commit is contained in:
2025-11-19 09:51:27 +01:00
parent abd2a403cb
commit 0d86cff1b7
2 changed files with 29 additions and 72 deletions

View File

@@ -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 {

View File

@@ -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<Track[]>(() => {
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<Track[]>([]);
const addTrack = useCallback((name?: string) => {
const track = createTrack(name);