Merge contextual Target actions with Profile workflows

This commit is contained in:
proxyctl coordinator
2026-07-17 23:18:59 +08:00
18 changed files with 1073 additions and 166 deletions
+32 -3
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"proxyctl/internal/app"
"proxyctl/internal/profile"
)
@@ -46,6 +47,13 @@ func (p terminalPresentation) mainView(model presentationState, width, height in
page.primary = model.picker.view(model.snapshot, width, max(5, height-9))
page.footer = "↑/↓ PgUp/PgDn: select Enter: activate Esc: cancel"
}
if model.driftResolutionTargetID != "" && model.confirmation == nil {
if selected, ok := newTargetCatalog(model.snapshot, true, model.driftResolutionTargetID).selected(model.driftResolutionTargetID); ok {
page.dashboard = nil
page.primary, page.context = p.driftResolutionView(selected)
page.footer = "r: Reapply Active Profile o: Restore Original\nEnter/Esc: Cancel"
}
}
if model.confirmation != nil {
dialog := newConfirmationDialog(*model.confirmation)
if model.confirmationDialog != nil {
@@ -58,6 +66,26 @@ func (p terminalPresentation) mainView(model presentationState, width, height in
return page
}
func (p terminalPresentation) driftResolutionView(selected app.TargetSnapshot) (string, string) {
var choices strings.Builder
fmt.Fprintf(&choices, "%s\nChoose how to resolve %s without an automatic recovery direction.", p.theme.section.Render("Drift Resolution — "+selected.Description.Name), selected.Description.Name)
for _, kind := range []app.TargetActionKind{app.ReapplyActiveProfileAction, app.RestoreOriginalAction} {
action, ok := driftResolutionAction(selected, kind)
label := targetActionLabel(kind)
if !ok || !action.Available {
reason := action.DisabledReason
if reason == "" {
reason = "unavailable"
}
fmt.Fprintf(&choices, "\n\n%s — unavailable: %s", label, reason)
continue
}
fmt.Fprintf(&choices, "\n\n%s", label)
}
context := "Reapply Active Profile writes the Active Profile again. Restore Original asks the Target Adapter to use its existing ownership and restoration safeguards; insufficient evidence fails safely without overwriting external changes."
return choices.String(), context
}
func (p terminalPresentation) dashboardFooter(model presentationState, catalog targetCatalog) string {
if model.exitDeferred {
return "Exit pending; browsing remains safe while the current mutation completes"
@@ -75,11 +103,12 @@ func (p terminalPresentation) dashboardFooter(model presentationState, catalog t
}
actions := "↑/k ↓/j: select Tab: focus details " + visibility
selected, ok := catalog.selected(model.selectedTargetID)
availability := targetMutationAvailability(selected, ok, model.snapshot.ActiveProfile, model.operationActive || model.exitDeferred)
availability := targetMutationAvailability(selected, ok, model.operationActive || model.exitDeferred)
label := targetActionLabel(selected.PrimaryAction.Kind)
if availability.available {
actions = "Space: toggle " + actions
actions = "Space: " + label + " " + actions
} else {
actions = "Space: disabled (" + availability.compactReason + ") " + actions
actions = "Space: " + label + " disabled (" + availability.compactReason + ") " + actions
}
footer := actions + "\np: switch Active Profile m: manage Proxy Profiles r: refresh ?: help q: quit"
if model.operationActive {