Add multi-diagram management

- DiagramRepository interface redesigned around multiple diagrams:
  list()/loadById()/deleteById() replace the old single-diagram
  load()/clear(). Both implementations updated to match.
- SupabaseDiagramRepository.save() now does an explicit update-or-insert
  instead of a blind upsert, so owner_id is only ever set at creation --
  an upsert would resend it on every save and let whoever saves last
  silently reassign ownership. Not reachable yet (no collaborator UI),
  but a real landmine once diagram sharing (organized-ideas.md §8) lands,
  and cheap to avoid now.
- LocalStorageDiagramRepository now stores diagrams keyed by id (was a
  single fixed key), keeping it a genuine working fallback rather than
  a stale reference implementing an old interface.
- Store: loadInitialDiagram (renamed from loadFromStorage) opens the
  last diagram you had open (tracked in localStorage -- a UI
  preference, not app data), falling back to the most recently updated
  one, falling back to a fresh empty diagram. New actions:
  refreshDiagramList, switchToDiagram, deleteDiagram. newProject and
  importProject now persist immediately (not just via the debounced
  autosave) so a new/imported diagram shows up in the list right away;
  importProject also assigns a fresh id so it can't collide with an
  existing diagram.
- New DiagramManagerModal (list/open/delete/+New), opened from a
  "Diagrams" button in TopBar that replaces the old single-diagram
  "New" button and its now-unnecessary confirmation dialog -- nothing
  is lost by creating a new diagram anymore, since the old one stays
  saved and reachable from the list.

Verified insert/list/update(-preserves-owner)/loadById/delete against
the real local stack; RLS test suite still 23/23 after a fresh reset.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
This commit is contained in:
2026-09-07 21:19:24 -05:00
co-authored by Claude Sonnet 5
parent 027455cc7d
commit 2f1b83e0a9
7 changed files with 284 additions and 75 deletions
+10 -13
View File
@@ -1,22 +1,15 @@
import { useRef } from 'react'
import { useRef, useState } from 'react'
import { downloadProjectFile, readProjectFile, ProjectImportError } from '../../data/exportImport'
import { supabase } from '../../data/supabaseClient'
import { useProjectStore } from '../../state/projectStore'
import DiagramManagerModal from './DiagramManagerModal'
export default function TopBar() {
const project = useProjectStore((s) => s.project)
const renameProject = useProjectStore((s) => s.renameProject)
const newProject = useProjectStore((s) => s.newProject)
const importProject = useProjectStore((s) => s.importProject)
const fileInputRef = useRef<HTMLInputElement>(null)
const handleNewProject = () => {
if (project.devices.length > 0 || project.connections.length > 0) {
const confirmed = window.confirm('Start a new project? Unsaved changes to the current one will be lost (unless exported).')
if (!confirmed) return
}
newProject()
}
const [diagramManagerOpen, setDiagramManagerOpen] = useState(false)
const handleExport = () => downloadProjectFile(project)
@@ -28,7 +21,7 @@ export default function TopBar() {
if (!file) return
try {
const imported = await readProjectFile(file)
importProject(imported)
await importProject(imported)
} catch (err) {
const message = err instanceof ProjectImportError ? err.message : 'Could not read that file.'
window.alert(`Import failed: ${message}`)
@@ -46,8 +39,11 @@ export default function TopBar() {
/>
</div>
<div className="flex items-center gap-1.5">
<button onClick={handleNewProject} className="rounded px-2.5 py-1.5 text-xs text-slate-600 hover:bg-slate-100">
New
<button
onClick={() => setDiagramManagerOpen(true)}
className="rounded px-2.5 py-1.5 text-xs text-slate-600 hover:bg-slate-100"
>
Diagrams
</button>
<button onClick={handleImportClick} className="rounded px-2.5 py-1.5 text-xs text-slate-600 hover:bg-slate-100">
Import
@@ -66,6 +62,7 @@ export default function TopBar() {
Sign out
</button>
</div>
{diagramManagerOpen && <DiagramManagerModal onClose={() => setDiagramManagerOpen(false)} />}
</header>
)
}