@@ -323,6 +323,9 @@ let ipcHandlers = null;
323323let cliBridge = null ;
324324let globeKeyAlertShown = false ;
325325let authBridgeServer = null ;
326+ let pendingNoteCloudId = null ;
327+ let pendingNoteRetryTimer = null ;
328+ let pendingNoteRetryCount = 0 ;
326329const WHISPER_WAKE_REWARM_DELAY_MS = 3000 ;
327330let wakeRewarmTimer = null ;
328331
@@ -517,6 +520,11 @@ app.on("open-url", (event, url) => {
517520 return ;
518521 }
519522
523+ if ( isNoteDeepLink ( url ) ) {
524+ void handleNoteDeepLink ( url ) ;
525+ return ;
526+ }
527+
520528 if ( isInvitationDeepLink ( url ) ) {
521529 handleInvitationDeepLink ( url ) ;
522530 return ;
@@ -535,6 +543,79 @@ function isInvitationDeepLink(url) {
535543 return url . slice ( `${ OAUTH_PROTOCOL } ://` . length ) . startsWith ( "invitations/" ) ;
536544}
537545
546+ function isNoteDeepLink ( url ) {
547+ return url . slice ( `${ OAUTH_PROTOCOL } ://` . length ) . startsWith ( "notes/" ) ;
548+ }
549+
550+ function parseNoteCloudId ( deepLinkUrl ) {
551+ try {
552+ const match = deepLinkUrl . match ( / n o t e s \/ ( [ ^ / ? # ] + ) / ) ;
553+ const cloudId = match ?. [ 1 ] ? decodeURIComponent ( match [ 1 ] ) : "" ;
554+ return / ^ [ 0 - 9 a - f ] { 8 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 12 } $ / i. test ( cloudId )
555+ ? cloudId
556+ : null ;
557+ } catch {
558+ return null ;
559+ }
560+ }
561+
562+ function clearPendingNoteDeepLink ( ) {
563+ clearTimeout ( pendingNoteRetryTimer ) ;
564+ pendingNoteRetryTimer = null ;
565+ pendingNoteCloudId = null ;
566+ pendingNoteRetryCount = 0 ;
567+ }
568+
569+ async function flushPendingNoteDeepLink ( ) {
570+ if ( ! pendingNoteCloudId || ! windowManager || ! databaseManager ) return ;
571+
572+ try {
573+ // Surface the panel on the first attempt only; retries just poll the
574+ // database so they can't repeatedly steal focus.
575+ if ( pendingNoteRetryCount === 0 ) {
576+ await windowManager . createControlPanelWindow ( ) ;
577+ }
578+
579+ const note = databaseManager . getNoteByCloudId ( pendingNoteCloudId ) ;
580+ if ( ! note ) {
581+ // Cloud sync may still be hydrating during a cold launch. Retry briefly so
582+ // the handoff can resolve a note pulled after the protocol event arrived.
583+ pendingNoteRetryCount += 1 ;
584+ if ( pendingNoteRetryCount <= 10 ) {
585+ clearTimeout ( pendingNoteRetryTimer ) ;
586+ pendingNoteRetryTimer = setTimeout ( ( ) => {
587+ void flushPendingNoteDeepLink ( ) ;
588+ } , 1000 ) ;
589+ } else {
590+ console . warn ( "Note deep link could not resolve a local note" , {
591+ cloudId : pendingNoteCloudId ,
592+ } ) ;
593+ clearPendingNoteDeepLink ( ) ;
594+ }
595+ return ;
596+ }
597+
598+ const payload = { noteId : note . id , folderId : note . folder_id ?? null } ;
599+ clearPendingNoteDeepLink ( ) ;
600+ await windowManager . queueNoteNavigation ( payload ) ;
601+ } catch ( error ) {
602+ console . error ( "Note deep link failed:" , error ) ;
603+ clearPendingNoteDeepLink ( ) ;
604+ }
605+ }
606+
607+ async function handleNoteDeepLink ( deepLinkUrl ) {
608+ const cloudId = parseNoteCloudId ( deepLinkUrl ) ;
609+ if ( ! cloudId ) {
610+ console . warn ( "Invalid note deep link" ) ;
611+ return ;
612+ }
613+
614+ clearPendingNoteDeepLink ( ) ;
615+ pendingNoteCloudId = cloudId ;
616+ await flushPendingNoteDeepLink ( ) ;
617+ }
618+
538619function handleInvitationDeepLink ( deepLinkUrl ) {
539620 try {
540621 const match = deepLinkUrl . match ( / i n v i t a t i o n s \/ ( [ ^ / ? # ] + ) / ) ;
@@ -863,6 +944,13 @@ async function startApp() {
863944 await windowManager . createControlPanelWindow ( ) ;
864945 }
865946
947+ const initialProtocolUrl = process . argv . find ( ( arg ) => arg . startsWith ( `${ OAUTH_PROTOCOL } ://` ) ) ;
948+ if ( initialProtocolUrl && isNoteDeepLink ( initialProtocolUrl ) ) {
949+ await handleNoteDeepLink ( initialProtocolUrl ) ;
950+ } else {
951+ await flushPendingNoteDeepLink ( ) ;
952+ }
953+
866954 // Create agent window (hidden) and set up agent hotkey
867955 await windowManager . createAgentWindow ( ) ;
868956
@@ -1557,6 +1645,8 @@ if (gotSingleInstanceLock) {
15571645 if ( url ) {
15581646 if ( url . includes ( "upgrade-success" ) ) {
15591647 handleUpgradeDeepLink ( ) ;
1648+ } else if ( isNoteDeepLink ( url ) ) {
1649+ await handleNoteDeepLink ( url ) ;
15601650 } else if ( isInvitationDeepLink ( url ) ) {
15611651 handleInvitationDeepLink ( url ) ;
15621652 } else {
@@ -1677,6 +1767,7 @@ function performSyncTeardown() {
16771767 clearTimeout ( wakeRewarmTimer ) ;
16781768 wakeRewarmTimer = null ;
16791769 }
1770+ clearPendingNoteDeepLink ( ) ;
16801771 if ( authBridgeServer ) {
16811772 authBridgeServer . close ( ) ;
16821773 authBridgeServer = null ;
0 commit comments