@@ -412,6 +412,7 @@ function renderLockedAccessCard() {
412412// Actions
413413async function loadUnlockedState ( ) {
414414 await api . runtime . sendMessage ( { kind : 'resetAutoLock' } ) ;
415+ await deduplicateProfiles ( ) ;
415416 await loadNames ( ) ;
416417 await loadProfileIndex ( ) ;
417418 await loadProfileType ( ) ;
@@ -421,6 +422,44 @@ async function loadUnlockedState() {
421422 render ( ) ;
422423}
423424
425+ /**
426+ * Remove duplicate profiles (same nsec/npub). Keeps the first occurrence,
427+ * deletes copies. Runs on every unlock to auto-heal from rapid-click duplicates.
428+ */
429+ async function deduplicateProfiles ( ) {
430+ try {
431+ const profiles = await getProfiles ( ) ;
432+ if ( profiles . length <= 1 ) return ;
433+
434+ const seen = new Map ( ) ; // npub → first index
435+ const toDelete = [ ] ; // indices to remove (reverse order)
436+
437+ for ( let i = 0 ; i < profiles . length ; i ++ ) {
438+ const npub = await getNpub ( i ) ;
439+ if ( ! npub ) continue ;
440+
441+ if ( seen . has ( npub ) ) {
442+ toDelete . push ( i ) ;
443+ } else {
444+ seen . set ( npub , i ) ;
445+ }
446+ }
447+
448+ if ( toDelete . length === 0 ) return ;
449+
450+ console . log ( `[dedup] Found ${ toDelete . length } duplicate profile(s), removing...` ) ;
451+
452+ // Delete from highest index first so indices don't shift
453+ for ( let i = toDelete . length - 1 ; i >= 0 ; i -- ) {
454+ await deleteProfile ( toDelete [ i ] ) ;
455+ }
456+
457+ console . log ( `[dedup] Cleaned up ${ toDelete . length } duplicate(s)` ) ;
458+ } catch ( e ) {
459+ console . error ( '[dedup] Failed to deduplicate profiles:' , e ) ;
460+ }
461+ }
462+
424463async function loadNames ( ) {
425464 state . profileNames = await getProfileNames ( ) ;
426465}
0 commit comments