@@ -222,7 +222,8 @@ type FilterMode = 'all' | 'encrypted' | 'plain'
222222type SortMode = 'updated' | 'created' | 'title'
223223
224224interface LocalNote {
225- id : string
225+ id : string // stable local key — never changes after creation
226+ dbId ?: string // real Supabase UUID — set after first INSERT
226227 title : string
227228 body : string
228229 tags : string [ ]
@@ -232,7 +233,6 @@ interface LocalNote {
232233 createdAt : number
233234 updatedAt : number
234235 pinned ?: boolean
235- dbId ?: string
236236}
237237
238238export default function Dashboard ( ) {
@@ -271,6 +271,7 @@ export default function Dashboard() {
271271
272272 const saveTimerRef = useRef < ReturnType < typeof setTimeout > | null > ( null )
273273 const isSavingRef = useRef ( false )
274+ // Maps local note id → real Supabase dbId (or '' for unsaved-but-known)
274275 const persistedRef = useRef < Record < string , string > > ( { } )
275276 const currentIdRef = useRef < string | null > ( null )
276277 const notesRef = useRef < LocalNote [ ] > ( [ ] )
@@ -364,6 +365,7 @@ export default function Dashboard() {
364365 }
365366 if ( data && data . length > 0 ) {
366367 const mapped : LocalNote [ ] = data . map ( ( n : Note ) => ( {
368+ // For persisted notes, local id == dbId (stable UUID from Supabase)
367369 id : n . id ,
368370 dbId : n . id ,
369371 title : n . title ,
@@ -390,10 +392,6 @@ export default function Dashboard() {
390392 } )
391393 } , [ router ] )
392394
393- // FIX: use stable constant IDs for sample notes so they don't
394- // reappear after the user deletes them (uid() would generate a new
395- // random ID on every page load, making every load look like brand-new
396- // notes that were never deleted).
397395 function sampleNotes ( ) : LocalNote [ ] {
398396 return [
399397 {
@@ -461,12 +459,16 @@ export default function Dashboard() {
461459 const titleVal = titleRef . current . trim ( ) || 'Untitled'
462460 const encrypted_body = note . encrypted ? note . body : bodyRef . current
463461 const currentTags = tagsRef . current
464- // persistedRef[id] is '' (empty string) for brand-new unsaved notes,
465- // undefined for notes that were never touched, and the real dbId once
466- // they've been inserted. The falsy check handles both '' and undefined .
462+
463+ // persistedRef[id] is '' for brand-new unsaved notes (falsy → INSERT),
464+ // and the real dbId string once persisted (truthy → UPDATE) .
467465 const existingDbId = persistedRef . current [ id ]
468466
469467 if ( ! existingDbId ) {
468+ // ── INSERT ──
469+ // Keep the local `id` stable in React state. Only store the real
470+ // Supabase UUID in persistedRef and note.dbId so subsequent saves
471+ // can UPDATE correctly — without ever changing note.id.
470472 const { data : inserted , error } = await supabase . from ( 'notes' ) . insert ( {
471473 user_id : user . id ,
472474 title : titleVal ,
@@ -482,18 +484,21 @@ export default function Dashboard() {
482484
483485 if ( ! error && inserted ) {
484486 const dbId : string = inserted . id
485- // Clean up the temp local id before replacing with real dbId
486- delete persistedRef . current [ id ]
487- persistedRef . current [ dbId ] = dbId
487+ // Map local id → real dbId so future saves take the UPDATE path
488+ persistedRef . current [ id ] = dbId
489+ // Patch note. dbId in state (no id change — sidebar key stays stable)
488490 setNotes ( prev => prev . map ( n =>
489491 n . id === id
490- ? { ...n , title : titleVal , body : encrypted_body , tags : currentTags , updatedAt : Date . now ( ) , dbId, id : dbId }
492+ ? { ...n , title : titleVal , body : encrypted_body , tags : currentTags , updatedAt : Date . now ( ) , dbId }
491493 : n
492494 ) )
493- setCurrentId ( dbId )
494- currentIdRef . current = dbId
495+ // Also update the ref immediately so any in-flight save sees dbId
496+ notesRef . current = notesRef . current . map ( n =>
497+ n . id === id ? { ...n , dbId } : n
498+ )
495499 }
496500 } else {
501+ // ── UPDATE ──
497502 await supabase . from ( 'notes' ) . update ( {
498503 title : titleVal ,
499504 encrypted_body,
@@ -526,16 +531,13 @@ export default function Dashboard() {
526531 } , [ title , body , tags , currentId , isLocked , doSave ] )
527532
528533 // ── CREATE NOTE ──
529- // FIX: seed persistedRef immediately with an empty string so doSave()
530- // always finds a defined (falsy) entry and reliably takes the INSERT path.
531534 const createNote = ( ) => {
532535 const note : LocalNote = {
533536 id : uid ( ) , title : '' , body : '' , tags : [ ] ,
534537 encrypted : false , iv : null , salt : null , pinned : false ,
535538 createdAt : Date . now ( ) , updatedAt : Date . now ( ) ,
536539 }
537- // Mark as "known but not yet persisted" — empty string is falsy so
538- // doSave() will INSERT on the first save attempt.
540+ // '' is falsy → doSave will take the INSERT path on first save
539541 persistedRef . current [ note . id ] = ''
540542 setNotes ( prev => [ note , ...prev ] )
541543 setCurrentId ( note . id )
@@ -559,7 +561,6 @@ export default function Dashboard() {
559561 updatedAt : Date . now ( ) ,
560562 pinned : false ,
561563 }
562- // Seed as unsaved so it gets INSERTed on first autosave
563564 persistedRef . current [ copy . id ] = ''
564565 setNotes ( prev => [ copy , ...prev ] )
565566 setCurrentId ( copy . id )
@@ -573,7 +574,8 @@ export default function Dashboard() {
573574 if ( ! currentNote ) return
574575 const newPinned = ! currentNote . pinned
575576 setNotes ( prev => prev . map ( n => n . id === currentId ? { ...n , pinned : newPinned } : n ) )
576- const dbId = persistedRef . current [ currentId ! ]
577+ // Use note.dbId (reliable) or fall back to persistedRef
578+ const dbId = currentNote . dbId || persistedRef . current [ currentId ! ]
577579 if ( dbId ) {
578580 const supabase = createClient ( )
579581 await supabase . from ( 'notes' ) . update ( { pinned : newPinned } ) . eq ( 'id' , dbId )
@@ -584,16 +586,13 @@ export default function Dashboard() {
584586 const deleteNote = async ( ) => {
585587 if ( ! currentId || ! currentNote ) return
586588 if ( ! confirm ( t ( 'deleteConfirm' ) ) ) return
587- const dbId = persistedRef . current [ currentId ]
589+ // Use note.dbId (reliable) or fall back to persistedRef
590+ const dbId = currentNote . dbId || persistedRef . current [ currentId ]
588591 if ( dbId ) {
589592 const supabase = createClient ( )
590593 await supabase . from ( 'notes' ) . delete ( ) . eq ( 'id' , dbId )
591- delete persistedRef . current [ currentId ]
592- delete persistedRef . current [ dbId ]
593- } else {
594- // Remove the unsaved-marker entry too
595- delete persistedRef . current [ currentId ]
596594 }
595+ delete persistedRef . current [ currentId ]
597596 setNotes ( prev => prev . filter ( n => n . id !== currentId ) )
598597 setCurrentId ( null )
599598 setTitle ( '' )
@@ -720,7 +719,6 @@ export default function Dashboard() {
720719 }
721720
722721 if ( imported . length === 0 ) throw new Error ( 'empty' )
723- // Seed each imported note as unsaved so autosave INSERTs them
724722 imported . forEach ( n => { persistedRef . current [ n . id ] = '' } )
725723 setNotes ( prev => [ ...imported , ...prev ] )
726724 showToast ( t ( 'importSuccess' , { n : imported . length } ) )
0 commit comments