diff --git a/paperforge/plugin/main.js b/paperforge/plugin/main.js index c809da52..1b14de3a 100644 --- a/paperforge/plugin/main.js +++ b/paperforge/plugin/main.js @@ -1766,6 +1766,21 @@ class PaperForgeStatusView extends ItemView { const action = ACTIONS.find(a => a.id === 'paperforge-doctor'); if (action) this._runAction(action, globalDoctorBtn); }); + + const globalStatusBtn = btnsRow.createEl('button', { cls: 'paperforge-contextual-btn' }); + globalStatusBtn.createEl('span', { cls: 'paperforge-contextual-btn-icon', text: '\u2139' }); + globalStatusBtn.createEl('span', { text: 'Refresh Status' }); + globalStatusBtn.addEventListener('click', () => { + this._fetchStats(); + }); + + const globalRepairBtn = btnsRow.createEl('button', { cls: 'paperforge-contextual-btn' }); + globalRepairBtn.createEl('span', { cls: 'paperforge-contextual-btn-icon', text: '\u21BA' }); + globalRepairBtn.createEl('span', { text: 'Repair Issues' }); + globalRepairBtn.addEventListener('click', () => { + const action = ACTIONS.find(a => a.id === 'paperforge-repair'); + if (action) this._runAction(action, globalRepairBtn); + }); } /* ── System Status Row helper ── */ @@ -4837,7 +4852,9 @@ module.exports = class PaperForgePlugin extends Plugin { const pyResult = resolvePythonExecutable(vaultPath, this.settings); if (!pyResult.path) { this._autoSyncRunning = false; return; } - const cmd = `"${pyResult.path}" -m paperforge --vault "${vaultPath}" sync --key "${entry.name}"`; + // `paperforge sync` has no `--key` selector. Fall back to a full sync + // so OCR state changes still propagate into the canonical index. + const cmd = `"${pyResult.path}" -m paperforge --vault "${vaultPath}" sync`; exec(cmd, { timeout: 30000, encoding: 'utf-8' }, () => { this._autoSyncRunning = false; this._memoryStatusText = null; diff --git a/paperforge/services/sync_service.py b/paperforge/services/sync_service.py index 1f058e28..cdd03058 100644 --- a/paperforge/services/sync_service.py +++ b/paperforge/services/sync_service.py @@ -257,6 +257,7 @@ class SyncService: index_count = 0 orphaned = 0 flat_cleaned = 0 + memory_rebuild_error: Exception | None = None if not selection_only: from paperforge.worker._domain import load_domain_config @@ -297,8 +298,9 @@ class SyncService: if get_memory_db_path(self.vault).exists(): build_from_index(self.vault) - except Exception: - pass + except Exception as exc: + memory_rebuild_error = exc + logger.error("Failed to rebuild memory DB after sync: %s", exc) if not json_output: print(f"index-refresh: {index_count} entries in index") @@ -306,6 +308,8 @@ class SyncService: print(f"index-refresh: {total} formal note(s) in literature") all_errors = list(selection_result.get("errors", [])) + if memory_rebuild_error is not None: + all_errors.append(f"memory rebuild failed: {memory_rebuild_error}") is_ok = selection_result.get("failed", 0) == 0 and len(all_errors) == 0 result = PFResult( @@ -321,6 +325,18 @@ class SyncService: if _export_code != "ok": result.warnings.append(f"BBT exports: {_export_code}") + if memory_rebuild_error is not None: + result.error = PFError( + code=ErrorCode.SYNC_FAILED, + message=f"Sync completed but memory rebuild failed: {memory_rebuild_error}", + details={"phase": "memory_rebuild", "exception_type": type(memory_rebuild_error).__name__}, + suggestions=["Run paperforge memory build to rebuild the memory DB from the cleaned canonical index."], + ) + result.next_actions.append({ + "command": "paperforge memory build", + "reason": "Rebuild memory DB so it matches the cleaned canonical index", + }) + return result # ── Legacy passthrough (backward-compat for commands/sync.py) ──