@@ -61,6 +61,7 @@ const FAST_RECONNECT_MIN_INTERVAL_MS = 2_000;
6161const MARKDOWN_DIRTY_SETTLE_MS = 350 ;
6262const OPEN_FILE_EXTERNAL_EDIT_IDLE_GRACE_MS = 1200 ;
6363const BOUND_RECOVERY_LOCK_MS = 1500 ;
64+ const CAPABILITY_REFRESH_INTERVAL_MS = 30_000 ;
6465
6566export default class VaultCrdtSyncPlugin extends Plugin {
6667 settings : VaultSyncSettings = DEFAULT_SETTINGS ;
@@ -161,6 +162,8 @@ export default class VaultCrdtSyncPlugin extends Plugin {
161162 private traceServerInFlight = false ;
162163 private recentServerTrace : unknown [ ] = [ ] ;
163164 private serverCapabilities : ServerCapabilities | null = null ;
165+ private capabilityRefreshPromise : Promise < void > | null = null ;
166+ private lastCapabilityRefreshAt = 0 ;
164167 private commandsRegistered = false ;
165168 private idbDegradedHandled = false ;
166169
@@ -312,6 +315,13 @@ export default class VaultCrdtSyncPlugin extends Plugin {
312315 void this . clearSavedBlobQueue ( ) ;
313316 }
314317 }
318+ const capabilityState = this . serverCapabilities ;
319+ const waitingForR2 =
320+ ! ! this . settings . host &&
321+ ( ! capabilityState || ! capabilityState . attachments || ! capabilityState . snapshots ) ;
322+ if ( waitingForR2 && Date . now ( ) - this . lastCapabilityRefreshAt >= CAPABILITY_REFRESH_INTERVAL_MS ) {
323+ void this . refreshServerCapabilities ( "background-poll" ) ;
324+ }
315325 } , 3000 ) ;
316326 this . register ( ( ) => {
317327 if ( this . statusInterval ) clearInterval ( this . statusInterval ) ;
@@ -501,6 +511,7 @@ export default class VaultCrdtSyncPlugin extends Plugin {
501511 if ( ! this . vaultSync ) return ;
502512
503513 this . log ( `Running reconnect reconciliation (gen ${ generation } )` ) ;
514+ await this . refreshServerCapabilities ( "provider-sync" ) ;
504515 this . validateAllOpenBindings ( `reconnect-pre:${ generation } ` ) ;
505516
506517 // Also import any untracked files from a previous conservative run
@@ -542,6 +553,7 @@ export default class VaultCrdtSyncPlugin extends Plugin {
542553 if ( ! this . vaultSync ) return ;
543554 if ( this . vaultSync . fatalAuthError ) return ;
544555
556+ void this . refreshServerCapabilities ( "app-foregrounded" ) ;
545557 this . requestFastReconnect ( "app-foregrounded" ) ;
546558 } ;
547559
@@ -564,6 +576,7 @@ export default class VaultCrdtSyncPlugin extends Plugin {
564576 this . onlineHandler = ( ) => {
565577 this . log ( "Network online event — requesting fast reconnect" ) ;
566578 this . scheduleTraceStateSnapshot ( "network-online" ) ;
579+ void this . refreshServerCapabilities ( "network-online" ) ;
567580 this . requestFastReconnect ( "network-online" ) ;
568581 } ;
569582
@@ -2303,6 +2316,14 @@ export default class VaultCrdtSyncPlugin extends Plugin {
23032316 DEFAULT_SETTINGS ,
23042317 data as Partial < VaultSyncSettings > ,
23052318 ) ;
2319+ let migratedSettings = false ;
2320+ if ( typeof data ?. attachmentSyncExplicitlyConfigured !== "boolean" ) {
2321+ this . settings . attachmentSyncExplicitlyConfigured = data ?. enableAttachmentSync === true ;
2322+ if ( data ?. enableAttachmentSync !== true ) {
2323+ this . settings . enableAttachmentSync = true ;
2324+ }
2325+ migratedSettings = true ;
2326+ }
23062327 // Load disk index from plugin data (stored under _diskIndex key)
23072328 if ( data && typeof data . _diskIndex === "object" && data . _diskIndex !== null ) {
23082329 this . diskIndex = data . _diskIndex as DiskIndex ;
@@ -2316,6 +2337,9 @@ export default class VaultCrdtSyncPlugin extends Plugin {
23162337 this . savedBlobQueue = data . _blobQueue as BlobQueueSnapshot ;
23172338 }
23182339 this . refreshPersistedState ( ) ;
2340+ if ( migratedSettings ) {
2341+ await this . persistPluginState ( ) ;
2342+ }
23192343 }
23202344
23212345 async saveSettings ( ) {
@@ -2451,9 +2475,25 @@ export default class VaultCrdtSyncPlugin extends Plugin {
24512475 this . refreshStatusBar ( ) ;
24522476 }
24532477
2454- async refreshServerCapabilities ( ) : Promise < void > {
2478+ async refreshServerCapabilities ( reason = "manual" ) : Promise < void > {
2479+ if ( this . capabilityRefreshPromise ) {
2480+ return await this . capabilityRefreshPromise ;
2481+ }
2482+
2483+ this . capabilityRefreshPromise = this . refreshServerCapabilitiesInner ( reason )
2484+ . finally ( ( ) => {
2485+ this . capabilityRefreshPromise = null ;
2486+ } ) ;
2487+ return await this . capabilityRefreshPromise ;
2488+ }
2489+
2490+ private async refreshServerCapabilitiesInner ( reason : string ) : Promise < void > {
2491+ this . lastCapabilityRefreshAt = Date . now ( ) ;
2492+ const previous = this . serverCapabilities ;
2493+
24552494 if ( ! this . settings . host ) {
24562495 this . serverCapabilities = null ;
2496+ await this . handleCapabilityChange ( previous , null , reason ) ;
24572497 return ;
24582498 }
24592499
@@ -2463,6 +2503,55 @@ export default class VaultCrdtSyncPlugin extends Plugin {
24632503 this . serverCapabilities = null ;
24642504 this . log ( `Server capability probe failed: ${ err } ` ) ;
24652505 }
2506+
2507+ await this . handleCapabilityChange ( previous , this . serverCapabilities , reason ) ;
2508+ }
2509+
2510+ private async handleCapabilityChange (
2511+ previous : ServerCapabilities | null ,
2512+ next : ServerCapabilities | null ,
2513+ reason : string ,
2514+ ) : Promise < void > {
2515+ const prevAttachments = previous ?. attachments ?? null ;
2516+ const prevSnapshots = previous ?. snapshots ?? null ;
2517+ const nextAttachments = next ?. attachments ?? null ;
2518+ const nextSnapshots = next ?. snapshots ?? null ;
2519+ const changed =
2520+ prevAttachments !== nextAttachments ||
2521+ prevSnapshots !== nextSnapshots ||
2522+ previous ?. authMode !== next ?. authMode ||
2523+ previous ?. claimed !== next ?. claimed ;
2524+ if ( ! changed ) return ;
2525+
2526+ this . log (
2527+ `Server capabilities updated (${ reason } ): ` +
2528+ `claimed=${ next ?. claimed ?? "unknown" } auth=${ next ?. authMode ?? "unknown" } ` +
2529+ `attachments=${ nextAttachments ?? "unknown" } snapshots=${ nextSnapshots ?? "unknown" } ` ,
2530+ ) ;
2531+ this . scheduleTraceStateSnapshot ( `capabilities:${ reason } ` ) ;
2532+
2533+ if ( this . vaultSync ) {
2534+ await this . refreshAttachmentSyncRuntime ( `capability-change:${ reason } ` ) ;
2535+ }
2536+
2537+ const gainedR2 = prevAttachments === false && nextAttachments === true ;
2538+ const lostR2 = prevAttachments === true && nextAttachments === false ;
2539+ if ( gainedR2 ) {
2540+ new Notice (
2541+ this . settings . enableAttachmentSync
2542+ ? "YAOS: R2 backend detected. Attachments and snapshots are now available."
2543+ : "YAOS: R2 backend detected. Attachments and snapshots are available if you enable them in settings." ,
2544+ 7000 ,
2545+ ) ;
2546+ if ( this . vaultSync ?. connected && this . vaultSync . providerSynced && this . serverSupportsSnapshots ) {
2547+ void this . triggerDailySnapshot ( ) ;
2548+ }
2549+ } else if ( lostR2 ) {
2550+ new Notice (
2551+ "YAOS: R2 backend is unavailable. Attachment transfers are paused and snapshots are unavailable." ,
2552+ 7000 ,
2553+ ) ;
2554+ }
24662555 }
24672556
24682557 private async handleSetupLink ( params : Record < string , string > ) : Promise < void > {
0 commit comments