From 937965b335e6ae9f507e6c36ed233299a5e8a9db Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Sat, 25 Apr 2026 03:24:17 +0530 Subject: [PATCH] feat: improve status handling and user feedback in SurfSense plugin - Added refreshStatus method to update connection status immediately after settings changes. - Enhanced error reporting with reportAuthError method for better authentication feedback. - Updated status visuals in the status modal to reflect the new centralized structure. - Improved connection handling in the settings tab to ensure accurate status representation. --- surfsense_obsidian/src/main.ts | 4 +-- surfsense_obsidian/src/settings.ts | 1 + surfsense_obsidian/src/status-modal.ts | 3 ++- surfsense_obsidian/src/status-visuals.ts | 7 +---- surfsense_obsidian/src/sync-engine.ts | 33 +++++++++++++++++------- 5 files changed, 30 insertions(+), 18 deletions(-) diff --git a/surfsense_obsidian/src/main.ts b/surfsense_obsidian/src/main.ts index db3ef8392..b3b585132 100644 --- a/surfsense_obsidian/src/main.ts +++ b/surfsense_obsidian/src/main.ts @@ -183,6 +183,7 @@ export default class SurfSensePlugin extends Plugin { this.settings.vaultId !== previousVaultId || this.settings.connectorId !== previousConnectorId; if (!changed) return; + this.engine?.refreshStatus(); this.notifyStatusChange(); if (this.settings.searchSpaceId !== null) { void this.engine.ensureConnected(); @@ -242,6 +243,7 @@ export default class SurfSensePlugin extends Plugin { } private notifyAuthError(): void { + this.engine?.reportAuthError(); const now = Date.now(); if (now - this.lastAuthToastAt < 10_000) return; this.lastAuthToastAt = now; @@ -265,8 +267,6 @@ export default class SurfSensePlugin extends Plugin { async saveSettings() { await this.saveData(this.settings); - // Ensures the indicator reacts to settings edits (token paste, search-space pick) - // without waiting for the next sync trigger. this.engine?.refreshStatus(); } diff --git a/surfsense_obsidian/src/settings.ts b/surfsense_obsidian/src/settings.ts index 0f2c0d2e7..646ac7dd0 100644 --- a/surfsense_obsidian/src/settings.ts +++ b/surfsense_obsidian/src/settings.ts @@ -91,6 +91,7 @@ export class SurfSenseSettingTab extends PluginSettingTab { try { await this.plugin.api.verifyToken(); new Notice("Surfsense: token verified."); + this.plugin.engine.refreshStatus({ force: true }); await this.refreshSearchSpaces(); this.display(); } catch (err) { diff --git a/surfsense_obsidian/src/status-modal.ts b/surfsense_obsidian/src/status-modal.ts index d17ad72a5..e05b3a5bc 100644 --- a/surfsense_obsidian/src/status-modal.ts +++ b/surfsense_obsidian/src/status-modal.ts @@ -1,5 +1,6 @@ import { type App, Modal, Notice, Setting } from "obsidian"; import type SurfSensePlugin from "./main"; +import { STATUS_VISUALS } from "./status-visuals"; /** Live status panel reachable from the status bar / command palette. */ export class StatusModal extends Modal { @@ -28,7 +29,7 @@ export class StatusModal extends Modal { const s = plugin.settings; const rows: Array<[string, string]> = [ - ["Status", plugin.lastStatus.kind], + ["Status", STATUS_VISUALS[plugin.lastStatus.kind].label], [ "Last sync", s.lastSyncAt ? new Date(s.lastSyncAt).toLocaleString() : "—", diff --git a/surfsense_obsidian/src/status-visuals.ts b/surfsense_obsidian/src/status-visuals.ts index 78a877587..96a3c8f34 100644 --- a/surfsense_obsidian/src/status-visuals.ts +++ b/surfsense_obsidian/src/status-visuals.ts @@ -1,11 +1,6 @@ import type { StatusKind } from "./types"; -/** - * Single source of truth for status icons + labels. Both the status bar - * and the settings "Connection" heading render from this table so a change - * here updates both surfaces. - */ - +/** Shared by the status bar and the settings "Connection" heading. */ export interface StatusVisual { icon: string; label: string; diff --git a/surfsense_obsidian/src/sync-engine.ts b/surfsense_obsidian/src/sync-engine.ts index e027b64c0..c98d2b354 100644 --- a/surfsense_obsidian/src/sync-engine.ts +++ b/surfsense_obsidian/src/sync-engine.ts @@ -71,6 +71,7 @@ export class SyncEngine { private idleReconcileStreak = 0; /** 2^streak is capped at this value (e.g. 8 → max ×8 backoff). */ private readonly maxBackoffMultiplier = 8; + private lastAppliedKind: StatusKind = "needs-setup"; constructor(deps: SyncEngineDeps) { this.deps = deps; @@ -502,24 +503,38 @@ export class SyncEngine { // ---- status helpers --------------------------------------------------- /** - * Recomputes status from settings + queue depth. Call from main.ts after - * settings change so the indicator reacts to token paste / search-space - * pick without waiting for the next sync trigger. + * Conservative by default: real errors are preserved while setup is + * complete, so unrelated edits don't optimistically clear the indicator. + * Pass `force: true` after an explicit verify/reconcile confirmation. */ - refreshStatus(): void { + refreshStatus(opts: { force?: boolean } = {}): void { + if (!opts.force) { + const last = this.lastAppliedKind; + const isError = + last === "auth-error" || last === "offline" || last === "error"; + const s = this.deps.getSettings(); + const setupComplete = !!(s.apiToken && s.searchSpaceId && s.connectorId); + if (isError && setupComplete) return; + } this.setStatus(this.queueStatusKind(), this.statusDetail()); } + reportAuthError(message?: string): void { + this.setStatus("auth-error", message ?? "API token expired or invalid"); + } + private setStatus(kind: StatusKind, detail?: string): void { - // Errors carry meaningful signal; only "happy" kinds get downgraded - // to needs-setup when prerequisites are missing. - if (kind !== "auth-error" && kind !== "offline" && kind !== "error") { - const s = this.deps.getSettings(); - if (!s.apiToken || !s.searchSpaceId || !s.connectorId) { + const s = this.deps.getSettings(); + if (!s.apiToken) { + kind = "needs-setup"; + detail = this.setupHint(s); + } else if (kind !== "auth-error" && kind !== "offline" && kind !== "error") { + if (!s.searchSpaceId || !s.connectorId) { kind = "needs-setup"; detail = this.setupHint(s); } } + this.lastAppliedKind = kind; this.deps.setStatus({ kind, detail, queueDepth: this.deps.queue.size }); }