@@ -10,6 +10,8 @@ import {
1010 unlockWithMasterPassword ,
1111} from '../lib/encryption' ;
1212import { logger } from '../lib/logger' ;
13+ import { getCacheDecryptedContentPreference } from '../lib/preferences' ;
14+ import { useApiService } from '../services/api' ;
1315
1416// Global function to force all hook instances to refresh
1517export function forceGlobalMasterPasswordRefresh ( ) {
@@ -23,6 +25,9 @@ export function useMasterPassword() {
2325 const [ isNewSetup , setIsNewSetup ] = useState ( false ) ;
2426 const [ isChecking , setIsChecking ] = useState ( true ) ;
2527 const [ , setLastCheckTime ] = useState < number > ( 0 ) ;
28+ const [ loadingStage , setLoadingStage ] = useState < 'securing' | 'caching' > ( 'securing' ) ;
29+ const [ cacheMode , setCacheMode ] = useState < 'encrypted' | 'decrypted' > ( 'encrypted' ) ;
30+ const api = useApiService ( ) ;
2631
2732 const userId = user ?. id ;
2833
@@ -111,6 +116,9 @@ export function useMasterPassword() {
111116 }
112117
113118 try {
119+ // Stage 1: Securing (PBKDF2 key derivation)
120+ setLoadingStage ( 'securing' ) ;
121+
114122 if ( isNewSetup ) {
115123 // Setting up new master password
116124 await setupMasterPassword ( password , userId ) ;
@@ -133,7 +141,60 @@ export function useMasterPassword() {
133141 } ) ;
134142 }
135143
136- // Successfully authenticated - update state
144+ // Stage 2: Caching (prefetch notes and folders)
145+ setLoadingStage ( 'caching' ) ;
146+
147+ // Check cache mode preference
148+ const cacheDecrypted = await getCacheDecryptedContentPreference ( ) ;
149+ setCacheMode ( cacheDecrypted ? 'decrypted' : 'encrypted' ) ;
150+
151+ if ( __DEV__ ) {
152+ console . log ( `[PREFETCH] Starting prefetch with ${ cacheDecrypted ? 'DECRYPTED' : 'ENCRYPTED' } cache mode` ) ;
153+ }
154+
155+ try {
156+ const prefetchStart = performance . now ( ) ;
157+
158+ // Prefetch folders first (faster)
159+ if ( __DEV__ ) console . log ( '[PREFETCH] Step 1: Fetching folders...' ) ;
160+ await api . getFolders ( ) ;
161+ if ( __DEV__ ) console . log ( '[PREFETCH] Step 1 complete: Folders fetched' ) ;
162+
163+ // Then prefetch notes (slower)
164+ if ( __DEV__ ) console . log ( '[PREFETCH] Step 2: Fetching notes...' ) ;
165+
166+ // Add 15 second timeout for notes fetch
167+ // IMPORTANT: Skip background refresh during prefetch to prevent hanging
168+ const notesFetchPromise = api . getNotes ( { } , { skipBackgroundRefresh : true } ) ;
169+ const timeoutPromise = new Promise < never > ( ( _ , reject ) => {
170+ setTimeout ( ( ) => reject ( new Error ( 'Notes fetch timeout after 15 seconds' ) ) , 15000 ) ;
171+ } ) ;
172+
173+ await Promise . race ( [ notesFetchPromise , timeoutPromise ] ) ;
174+ if ( __DEV__ ) console . log ( '[PREFETCH] Step 2 complete: Notes fetched' ) ;
175+
176+ const prefetchEnd = performance . now ( ) ;
177+ if ( __DEV__ ) {
178+ console . log ( `[PREFETCH] ✅ Completed in ${ ( prefetchEnd - prefetchStart ) . toFixed ( 2 ) } ms - cache ready!` ) ;
179+ }
180+
181+ logger . recordEvent ( 'notes_prefetch_completed' , {
182+ durationMs : prefetchEnd - prefetchStart ,
183+ cacheMode : cacheDecrypted ? 'decrypted' : 'encrypted' ,
184+ } ) ;
185+ } catch ( error ) {
186+ // Log error but continue - user can still use app, they'll just fetch on demand
187+ if ( __DEV__ ) {
188+ console . error ( '[PREFETCH] Failed to prefetch notes (non-critical):' , error ) ;
189+ }
190+ logger . warn ( '[PREFETCH] Failed to prefetch notes' , {
191+ attributes : {
192+ error : error instanceof Error ? error . message : 'Unknown error' ,
193+ } ,
194+ } ) ;
195+ }
196+
197+ // Successfully authenticated and cached - update state
137198 setNeedsUnlock ( false ) ;
138199 setIsNewSetup ( false ) ;
139200 setIsChecking ( false ) ;
@@ -162,6 +223,8 @@ export function useMasterPassword() {
162223 isNewSetup,
163224 isChecking,
164225 userId,
226+ loadingStage,
227+ cacheMode,
165228 onPasswordSuccess,
166229 signOut,
167230 refreshStatus : checkMasterPasswordStatus ,
0 commit comments