async function handleRecoveryActions(event, target, action, repository) { if (action === "repair-origin") { if (!repository?.localPath || !repository.sshUrl) return; if ( !confirm( `Replace origin with ${repository.sshUrl}? Local files and commits are not changed.`, ) ) return; setLoading(true, "Updating Git origin…"); try { await window.forgeflow.setOrigin(repository.localPath, repository.sshUrl); await refreshRepositories(false); showToast("Git origin updated", repository.sshUrl, "success"); } catch (error) { showToast("Could not update origin", error.message, "error"); } setLoading(false); } else if (action === "normalize-origins") { if ( !confirm( "Replace legacy origin URLs for every linked repository with the current Gitea SSH URL? Local files and commits are not changed.", ) ) return; setLoading(true, "Normalizing linked Git origins…"); try { const result = await window.forgeflow.normalizeOrigins(); ui.repositories = result.repositories; showToast( "Git origins normalized", `${result.changes.length} repository origin${result.changes.length === 1 ? "" : "s"} updated.`, "success", ); } catch (error) { showToast("Could not normalize origins", error.message, "error"); } setLoading(false); render(); } else if (action === "scan-git-recovery") { if (!repository?.localPath) return; setLoading(true, "Scanning Git directory and active processes…"); try { ui.gitRecovery = await window.forgeflow.gitRecoveryStatus( repository.localPath, ); ui.repositoryTab = "gittools"; showToast( "Git health scan complete", `${ui.gitRecovery.lockReport.locks.length} lock file(s) found.`, ui.gitRecovery.lockReport.locks.length ? "info" : "success", ); } catch (error) { showToast("Git health scan failed", error.message, "error"); } setLoading(false); render(); } else if (action === "repair-git-locks" || action === "repair-index-lock") { if ( !repository?.localPath || !confirm( "Repair stale Git lock files for this repository? ForgeFlow refuses while a matching Git process is active.", ) ) return; setLoading(true, "Safely repairing stale Git locks…"); try { const result = await window.forgeflow.repairGitLocks( repository.localPath, false, ); ui.gitRecovery = await window.forgeflow.gitRecoveryStatus( repository.localPath, ); await refreshRepositories(false); showToast( "Git locks repaired", `${result.removed.length} stale lock file(s) removed.`, "success", ); } catch (error) { if ( error.code === "GIT_PROCESS_PROBE_UNAVAILABLE" && confirm(`${error.message} Force repair after you have closed all Git tools for this repository?`) ) { try { const result = await window.forgeflow.repairGitLocks( repository.localPath, true, ); showToast( "Git locks force-repaired", `${result.removed.length} lock file(s) removed.`, "success", ); await refreshRepositories(false); } catch (forceError) { showToast("Could not repair Git locks", forceError.message, "error"); } } else showToast("Could not repair Git locks", error.message, "error"); } setLoading(false); render(); } else if (action === "reconcile-repository") { if (!repository?.localPath) return; setLoading(true, "Refreshing repository truth from Git…"); try { ui.gitRecovery = await window.forgeflow.reconcileRepository( repository.localPath, ); await refreshRepositories(false); showToast( "Repository reconciled", "Branch, upstream, lock and working-tree state were refreshed.", "success", ); } catch (error) { showToast("Could not reconcile repository", error.message, "error"); } setLoading(false); render(); } else if (action === "repair-repository-sync") { if (!repository?.localPath) return; const strategy = target.dataset.strategy; const destructive = strategy === "backup-reset"; const message = destructive ? "Create a safety branch from the current HEAD and reset this branch to its upstream? Uncommitted changes are never discarded." : `Run the repository-specific ${strategy} repair now?`; if (!confirm(message)) return; setLoading( true, destructive ? "Creating safety branch and repairing divergence…" : "Repairing repository synchronization…", ); try { const result = await window.forgeflow.repairRepositorySync( repository.localPath, strategy, ); ui.gitRecovery = await window.forgeflow.gitRecoveryStatus( repository.localPath, ); await refreshRepositories(false); showToast( "Repository synchronization repaired", result.backupBranch ? `Safety branch created: ${result.backupBranch}` : `Completed ${strategy}.`, "success", ); } catch (error) { showToast("Synchronization repair failed", error.message, "error"); } setLoading(false); render(); } else if (action === "run-troubleshooter") { setLoading( true, "Scanning repositories, Git operations and deployment servers…", ); try { ui.troubleshooter = await window.forgeflow.troubleshooterScan(); showToast( "Troubleshooter completed", ui.troubleshooter.summary.total ? `${ui.troubleshooter.summary.total} issue(s) found; ${ui.troubleshooter.summary.repairable} repairable.` : "No problems were detected.", ui.troubleshooter.summary.errors ? "error" : "success", ); } catch (error) { showToast("Troubleshooter failed", error.message, "error"); } setLoading(false); render(); } else if (action === "troubleshooter-auto-repair") { const safeIssues = (ui.troubleshooter?.issues || []).filter( (item) => item.repairable && item.safe, ); if ( !safeIssues.length || !confirm( `Repair ${safeIssues.length} safe issue(s) now? ForgeFlow will not run destructive reset actions automatically.`, ) ) return; setLoading(true, "Applying safe one-click repairs…"); try { const results = await window.forgeflow.troubleshooterAutoRepair(safeIssues); ui.troubleshooter = await window.forgeflow.troubleshooterScan(); await refreshRepositories(false); const failed = results.filter((item) => !item.ok); showToast( failed.length ? "Repairs partially completed" : "Safe repairs completed", `${results.length - failed.length} repaired, ${failed.length} failed.`, failed.length ? "error" : "success", ); } catch (error) { showToast("Automatic repair failed", error.message, "error"); } setLoading(false); render(); } else if (action === "troubleshooter-repair") { const issue = ui.troubleshooter?.issues?.[Number(target.dataset.issueIndex)]; if (!issue) return; const warning = issue.safe ? `Repair “${issue.title}” now?` : `“${issue.title}” requires a safety branch or another potentially destructive change. Continue?`; if (!confirm(warning)) return; setLoading(true, `Repairing ${issue.title}…`); try { const result = await window.forgeflow.troubleshooterRepair(issue); ui.troubleshooter = await window.forgeflow.troubleshooterScan(); await refreshRepositories(false); showToast( "Problem repaired", result?.backupBranch ? `Safety branch created: ${result.backupBranch}` : issue.title, "success", ); } catch (error) { showToast("Repair failed", error.message, "error"); } setLoading(false); render(); } else if (action === "run-system-preflight") await runSystemPreflight(); else if (action === "load-audit-log") { try { ui.auditEvents = await window.forgeflow.listAuditEvents(500); render(); } catch (error) { showToast("Could not load audit log", error.message, "error"); } } else if (action === "export-audit-json" || action === "export-audit-csv") { try { const result = await window.forgeflow.exportAuditLog( action.endsWith("csv") ? "csv" : "json", ); if (result) showToast( "Audit log exported", `${result.count} records exported.`, "success", ); } catch (error) { showToast("Could not export audit log", error.message, "error"); } } else if (action === "save-diagnostics-preferences") { const preferences = { diagnosticsEnabled: document.querySelector("#diagnostics-enabled").value === "true", diagnosticLevel: document.querySelector("#diagnostic-level").value, logRetentionDays: Number( document.querySelector("#diagnostic-retention").value, ), maxLogFileMb: Number( document.querySelector("#diagnostic-max-file").value, ), }; setLoading(true, "Saving diagnostic policy…"); try { ui.boot.state = await window.forgeflow.setPreferences(preferences); ui.diagnosticsStatus = await window.forgeflow.diagnosticsStatus(); showToast( "Diagnostic policy saved", "New events now use the updated retention and logging level.", "success", ); } catch (error) { showToast("Could not save diagnostics", error.message, "error"); } setLoading(false); } else if (action === "export-diagnostics") { const privacyMode = document.querySelector("#diagnostic-privacy")?.value || "standard"; setLoading(true, "Creating redacted diagnostic bundle…"); try { const bundle = await window.forgeflow.exportDiagnostics(privacyMode); if (bundle) { ui.lastDiagnosticBundle = bundle; ui.diagnosticsStatus = await window.forgeflow.diagnosticsStatus(); showToast( "Diagnostic bundle created", `${bundle.size} · SHA-256 ${shortSha(bundle.sha256)}`, "success", ); } } catch (error) { showToast("Could not export diagnostics", error.message, "error"); } setLoading(false); } else if (action === "show-diagnostic-bundle") { if (!ui.lastDiagnosticBundle?.path) return; await window.forgeflow .showDiagnosticBundle(ui.lastDiagnosticBundle.path) .catch((error) => showToast("Could not show bundle", error.message, "error"), ); } else if (action === "open-diagnostics-folder") await window.forgeflow .openDiagnosticsFolder() .catch((error) => showToast("Could not open diagnostic folder", error.message, "error"), ); else if (action === "clear-diagnostics") { if ( !confirm( "Clear local ForgeFlow diagnostic logs? This does not affect repositories or configuration.", ) ) return; try { ui.diagnosticsStatus = await window.forgeflow.clearDiagnostics(); showToast( "Diagnostic logs cleared", "A new session marker was created.", "success", ); render(); } catch (error) { showToast("Could not clear logs", error.message, "error"); } } else if (action === "reset-app") { if ( !confirm( "Reset ForgeFlow configuration? Your Git repositories and Gitea data are not modified.", ) ) return; ui.boot.state = await window.forgeflow.reset(); ui.repositories = []; ui.setupStep = 0; ui.setupValidation = null; ui.systemPreflight = null; ui.deploymentPreflight = null; ui.lastDiagnosticBundle = null; ui.setupDraft = { baseUrl: "https://", token: "", user: null, roots: [], discovered: [], }; render(); } else return false; return true; }