From 2ebaa6b5c64227c65066459c09bf7f3228609afa Mon Sep 17 00:00:00 2001 From: MDietermann Date: Mon, 13 Apr 2026 22:26:54 +0200 Subject: [PATCH] fix: address Plan 2 final-review findings - tui/doc.go: remove teatest blank import (test harness in prod binary) - cmd: honor wizard HaltOnError toggle in addition to --halt-on-error flag - cmd: wire SIGINT/SIGTERM to root context via signal.NotifyContext - dashboard/run: derive engine context and cancel on tea.Program exit so the engine goroutine does not outlive Run --- cmd/archduino/main.go | 13 +++++++++++-- internal/tui/dashboard/run.go | 10 +++++++++- internal/tui/doc.go | 7 ------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/cmd/archduino/main.go b/cmd/archduino/main.go index 9641e24..36c336e 100644 --- a/cmd/archduino/main.go +++ b/cmd/archduino/main.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "os" + "os/signal" + "syscall" "time" "github.com/spf13/cobra" @@ -41,7 +43,13 @@ func main() { Version: version, } root.AddCommand(newInstallCmd()) - if err := root.Execute(); err != nil { + + // Wire SIGINT/SIGTERM to a cancellable context so long-running phases + // (engine.Run, sudo keepalive) observe the cancellation promptly. + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + if err := root.ExecuteContext(ctx); err != nil { fmt.Fprintln(os.Stderr, "error:", err) os.Exit(1) } @@ -140,7 +148,8 @@ func newInstallCmd() *cobra.Command { fmt.Fprintln(os.Stdout, "installation cancelled") return nil } - sum, runErr = dashboard.Run(ctx, Registry, finalState, halt) + // Either the CLI flag or the wizard toggle can force halt-on-error. + sum, runErr = dashboard.Run(ctx, Registry, finalState, halt || finalState.HaltOnError) } ended := time.Now().UTC() diff --git a/internal/tui/dashboard/run.go b/internal/tui/dashboard/run.go index 94e4521..79082cf 100644 --- a/internal/tui/dashboard/run.go +++ b/internal/tui/dashboard/run.go @@ -23,10 +23,18 @@ func Run(ctx context.Context, reg *module.Registry, state tui.WizardState, halt m := NewModel(selected, nameByID) p := tea.NewProgram(m, tea.WithContext(ctx)) + // Derived context so that when p.Run() returns (user pressed q, tea + // errored, parent ctx cancelled) the engine goroutine is cancelled + // too. Deferred cancel ensures no orphan goroutine outlives Run. + engineCtx, cancelEngine := context.WithCancel(ctx) + defer cancelEngine() + reporter := tui.NewTeaReporter(p) go func() { - summary, err := engine.Run(ctx, reg, engine.RunOptions{ + // TODO(plan-3): forward state.Parallelism once engine.RunOptions + // gains a Parallelism field and the engine supports concurrent Apply. + summary, err := engine.Run(engineCtx, reg, engine.RunOptions{ Selected: selected, HaltOnError: halt, Reporter: reporter, diff --git a/internal/tui/doc.go b/internal/tui/doc.go index e006423..3c29284 100644 --- a/internal/tui/doc.go +++ b/internal/tui/doc.go @@ -2,10 +2,3 @@ // that collects user choices and the Bubble Tea dashboard that renders // live module status during engine.Run. package tui - -import ( - _ "github.com/charmbracelet/bubbletea" - _ "github.com/charmbracelet/huh" - _ "github.com/charmbracelet/lipgloss" - _ "github.com/charmbracelet/x/exp/teatest" -)