@@ -947,6 +947,44 @@ export default class SyncProcess {
947947 return
948948 }
949949
950+ // The scan flagged this as a creation, but a surviving mapping may already point it at an item
951+ // that still exists on the target. This happens when the common-ancestor cache was reset
952+ // (account.init() wipes cache+mappings) or rolled back (an interrupted atomic sync persists
953+ // cache+mappings while the server discards its working copy): the scan re-sees an
954+ // already-synced item as "new". Re-creating it would duplicate it on the target and the later
955+ // addMapping would evict the surviving mapping, orphaning one copy → a silent drop. Instead,
956+ // reconcile the source subtree against the existing target item and only create genuinely-new
957+ // descendants.
958+ const mappedTargetId = Mappings . mapId ( mappingsSnapshot , action . payload , targetLocation )
959+ const existingTargetItem = ( typeof mappedTargetId !== 'undefined' && mappedTargetId !== null )
960+ ? targetTree . findItem ( action . payload . type , mappedTargetId )
961+ : null
962+ // Only treat it as already-present if it is still genuinely mergeable. A URL change, for
963+ // instance, keeps the local id (so the mapping survives) but is intentionally recreated
964+ // rather than merged (Scanner refuses to merge bookmarks whose url changed) — that must fall
965+ // through to a real CREATE, not be swallowed here.
966+ if ( existingTargetItem && action . payload . canMergeWith ( existingTargetItem ) ) {
967+ const subScanner = new Scanner (
968+ this . mappings ,
969+ existingTargetItem , // target tree
970+ action . payload , // source tree
971+ ( oldItem , newItem ) => (
972+ oldItem . type === newItem . type &&
973+ oldItem . canMergeWith ( newItem ) &&
974+ ! Mappings . wouldEvictUnrelatedMapping ( mappingsSnapshot , oldItem , newItem )
975+ ) ,
976+ this . hashSettings ,
977+ false ,
978+ true ,
979+ true ,
980+ )
981+ const scanResult = await subScanner . run ( )
982+ scanResult . CREATE . getActions ( ) . forEach ( a =>
983+ targetPlan . CREATE . commit ( { type : a . type , payload : a . payload } )
984+ )
985+ return
986+ }
987+
950988 const concurrentRemoval = targetScanResult . REMOVE . getActions ( ) . find ( targetRemoval =>
951989 // target removal removed this creation's target (via some chain)
952990 Diff . findChain ( mappingsSnapshot , allCreateAndMoveActions , sourceTree , action . payload , targetRemoval , findChainCacheForCreations )
0 commit comments