@@ -22,7 +22,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'
2222import * as Y from 'yjs'
2323import { NostrSyncService , CONNECTION_STATES } from '../services/nostr-sync'
2424import { retrieveLEK } from '../services/key-storage'
25- import { getYdocInstance } from './useYjs'
25+ import { getYdocInstance , getUndoManager } from './useYjs'
2626import { getNostrDiagnostics } from '../services/nostr-diagnostics'
2727import {
2828 SyncPerformanceManager ,
@@ -405,18 +405,36 @@ export function useNostrSync(options = {}) {
405405 if ( deletedBookmarkIds . has ( bookmarkId ) ) {
406406 return
407407 }
408- deletedBookmarkIds . add ( bookmarkId )
409- // Limit set size
410- if ( deletedBookmarkIds . size > 500 ) {
411- const idsToRemove = [ ...deletedBookmarkIds ] . slice ( 0 , 250 )
412- idsToRemove . forEach ( id => deletedBookmarkIds . delete ( id ) )
413- }
414408
415409 // Defer to not block main thread
416410 setTimeout ( ( ) => {
417411 const ydoc = getYdocInstance ( )
418412 if ( ydoc ) {
419413 const bookmarksMap = ydoc . getMap ( 'bookmarks' )
414+ const existing = bookmarksMap . get ( bookmarkId )
415+
416+ // Check if local bookmark exists and is newer than deletion event
417+ // This handles the case where user undid a deletion - the restored
418+ // bookmark will have a newer updatedAt than the deletion event
419+ if ( existing ) {
420+ const updatedAt = existing ?. get ? existing . get ( 'updatedAt' ) : existing ?. updatedAt
421+ // Nostr events use seconds, our timestamps use milliseconds
422+ const deletionTime = event ?. created_at ? event . created_at * 1000 : 0
423+
424+ if ( updatedAt && updatedAt > deletionTime ) {
425+ console . log ( '[useNostrSync] Skipping deletion - local bookmark is newer:' , bookmarkId , { updatedAt, deletionTime } )
426+ return
427+ }
428+ }
429+
430+ // Track this deletion
431+ deletedBookmarkIds . add ( bookmarkId )
432+ // Limit set size
433+ if ( deletedBookmarkIds . size > 500 ) {
434+ const idsToRemove = [ ...deletedBookmarkIds ] . slice ( 0 , 250 )
435+ idsToRemove . forEach ( id => deletedBookmarkIds . delete ( id ) )
436+ }
437+
420438 // Use transaction with 'nostr-sync' origin so observer knows not to re-publish
421439 ydoc . transact ( ( ) => {
422440 bookmarksMap . delete ( bookmarkId )
@@ -457,23 +475,44 @@ export function useNostrSync(options = {}) {
457475 return
458476 }
459477
478+ // Check if this is an undo/redo operation
479+ const undoManager = getUndoManager ( )
480+ const isUndoRedo = ymapEvent . transaction . origin === undoManager
481+
460482 ymapEvent . changes . keys . forEach ( ( change , key ) => {
461483 if ( nostrSyncService && nostrSyncService . isInitialized ) {
462484 if ( change . action === 'add' || change . action === 'update' ) {
463485 const bookmarkYMap = bookmarksMap . get ( key )
464486 if ( bookmarkYMap ) {
465487 // Convert Y.Map to plain object for publishing
466- const bookmarkData = bookmarkYMap . get ? {
488+ let bookmarkData = bookmarkYMap . get ? {
467489 url : bookmarkYMap . get ( 'url' ) ,
468490 title : bookmarkYMap . get ( 'title' ) ,
469491 description : bookmarkYMap . get ( 'description' ) || '' ,
470492 tags : bookmarkYMap . get ( 'tags' ) ?. toArray ( ) || [ ] ,
471493 readLater : bookmarkYMap . get ( 'readLater' ) || false ,
494+ inbox : bookmarkYMap . get ( 'inbox' ) || false ,
472495 favicon : bookmarkYMap . get ( 'favicon' ) || null ,
473496 preview : bookmarkYMap . get ( 'preview' ) || null ,
474497 createdAt : bookmarkYMap . get ( 'createdAt' ) ,
475498 updatedAt : bookmarkYMap . get ( 'updatedAt' ) ,
476- } : bookmarkYMap // Already a plain object
499+ } : { ...bookmarkYMap } // Already a plain object, clone it
500+
501+ // If this is an undo restoration (bookmark was deleted and now restored),
502+ // bump updatedAt to ensure it's newer than any deletion events on relays.
503+ // This prevents the deletion from being replayed on refresh.
504+ if ( isUndoRedo && change . action === 'add' ) {
505+ const newUpdatedAt = Date . now ( )
506+ console . log ( '[useNostrSync] Undo restoration detected, bumping updatedAt:' , key , { old : bookmarkData . updatedAt , new : newUpdatedAt } )
507+ bookmarkData = { ...bookmarkData , updatedAt : newUpdatedAt }
508+
509+ // Persist the updated timestamp to IndexedDB
510+ // Use 'nostr-sync' origin to avoid re-triggering this observer
511+ ydoc . transact ( ( ) => {
512+ bookmarksMap . set ( key , bookmarkData )
513+ } , 'nostr-sync' )
514+ }
515+
477516 // Queue for debounced publishing
478517 nostrSyncService . queueBookmarkUpdate ( key , bookmarkData )
479518 }
0 commit comments