@@ -239,7 +239,10 @@ export class SyncEngine {
239239 // ---- queue draining ---------------------------------------------------
240240
241241 async flushQueue ( ) : Promise < void > {
242- if ( this . deps . queue . size === 0 ) return ;
242+ if ( this . deps . queue . size === 0 ) {
243+ await this . recoverStatusIfNeeded ( ) ;
244+ return ;
245+ }
243246 // Shared gate for every flush trigger so the first /sync can't race /connect.
244247 if ( ! this . deps . getSettings ( ) . connectorId ) {
245248 const connected = await this . ensureConnected ( ) ;
@@ -259,6 +262,31 @@ export class SyncEngine {
259262 this . setStatus ( this . queueStatusKind ( ) , this . statusDetail ( ) ) ;
260263 }
261264
265+ /**
266+ * Lightweight status recovery path used after network-change signals.
267+ * Clears stale offline/auth/error only when connectivity/auth is explicitly re-validated.
268+ */
269+ async recoverConnectivityStatus ( ) : Promise < void > {
270+ const settings = this . deps . getSettings ( ) ;
271+ if ( ! settings . apiToken ) {
272+ this . refreshStatus ( { force : true } ) ;
273+ return ;
274+ }
275+ if ( ! settings . searchSpaceId ) {
276+ try {
277+ const health = await this . deps . apiClient . health ( ) ;
278+ this . applyHealth ( health ) ;
279+ this . refreshStatus ( { force : true } ) ;
280+ } catch ( err ) {
281+ this . handleStartupError ( err ) ;
282+ }
283+ return ;
284+ }
285+ const connected = await this . ensureConnected ( ) ;
286+ if ( ! connected ) return ;
287+ this . refreshStatus ( { force : true } ) ;
288+ }
289+
262290 private async processBatch ( batch : QueueItem [ ] ) : Promise < BatchResult > {
263291 const settings = this . deps . getSettings ( ) ;
264292 const upserts = batch . filter ( ( b ) : b is QueueItem & { op : "upsert" } => b . op === "upsert" ) ;
@@ -510,6 +538,7 @@ export class SyncEngine {
510538 refreshStatus ( opts : { force ?: boolean } = { } ) : void {
511539 if ( ! opts . force ) {
512540 const last = this . lastAppliedKind ;
541+ if ( last === "syncing" ) return ;
513542 const isError =
514543 last === "auth-error" || last === "offline" || last === "error" ;
515544 const s = this . deps . getSettings ( ) ;
@@ -523,6 +552,18 @@ export class SyncEngine {
523552 this . setStatus ( "auth-error" , message ?? "API token expired or invalid" ) ;
524553 }
525554
555+ reportError ( err : unknown ) : void {
556+ if ( err instanceof AuthError ) {
557+ this . reportAuthError ( err . message ) ;
558+ return ;
559+ }
560+ if ( err instanceof TransientError ) {
561+ this . setStatus ( "offline" , err . message ) ;
562+ return ;
563+ }
564+ this . setStatus ( "error" , ( err as Error ) . message ?? "Unknown error" ) ;
565+ }
566+
526567 private setStatus ( kind : StatusKind , detail ?: string ) : void {
527568 const s = this . deps . getSettings ( ) ;
528569 if ( ! s . apiToken ) {
@@ -601,6 +642,19 @@ export class SyncEngine {
601642 this . setStatus ( this . queueStatusKind ( ) , `${ prefix } : ${ ( err as Error ) . message } ` ) ;
602643 }
603644
645+ private async recoverStatusIfNeeded ( ) : Promise < void > {
646+ if ( ! this . isRecoverableErrorState ( ) ) return ;
647+ await this . recoverConnectivityStatus ( ) ;
648+ }
649+
650+ private isRecoverableErrorState ( ) : boolean {
651+ return (
652+ this . lastAppliedKind === "offline" ||
653+ this . lastAppliedKind === "auth-error" ||
654+ this . lastAppliedKind === "error"
655+ ) ;
656+ }
657+
604658 // ---- predicates -------------------------------------------------------
605659
606660 private shouldTrack ( file : TAbstractFile ) : boolean {
0 commit comments