Pass session preset explicitly to runCommand

Amolith created

Replace the package-level presetResolved bool with an explicit
sessionPreset *string parameter on runCommand. This makes the dependency
visible in the function signature and eliminates shared mutable state
that required manual cleanup in tests.

When sessionPreset is non-nil, its value is used as the preset and the
standalone preset prompt is skipped. A nil value means no TUI session
ran.

Change summary

cmd/root.go        | 60 ++++++++++++++++-------------------------------
cmd/root_test.go   |  7 +----
cmd/subcommands.go |  5 +--
3 files changed, 25 insertions(+), 47 deletions(-)

Detailed changes

cmd/root.go 🔗

@@ -23,7 +23,6 @@ var (
 	flagPreset     string
 	flagShowCmd    bool
 	flagConfigFile string
-	presetResolved bool
 )
 
 const overrideArgumentsKey = "_arguments"
@@ -54,14 +53,7 @@ var rootCmd = &cobra.Command{
 			return nil
 		}
 
-		// Preset was resolved by the session; pass it directly
-		// so runCommand skips its own preset prompt.
-		flagPreset = preset
-		// Mark that the session already handled preset selection
-		// so runCommand does not re-prompt even when preset is "".
-		presetResolved = true
-
-		return runCommand(commandName, cmd, nil, overrides)
+		return runCommand(commandName, cmd, nil, overrides, &preset)
 	},
 }
 
@@ -72,7 +64,14 @@ func registerRootFlags() {
 	flags.StringVarP(&flagConfigFile, "config", "C", "", "path to keld config file")
 }
 
-func runCommand(commandName string, cmd *cobra.Command, rawArgs []string, sessionOverrides map[string][]string) error {
+// runCommand resolves config and executes restic.
+//
+// sessionPreset signals that a TUI session already handled preset
+// selection. When non-nil, its value is used as the preset and the
+// standalone preset prompt is skipped. When nil, the subcommand was
+// invoked non-interactively (with arguments) and no prompting
+// occurs.
+func runCommand(commandName string, cmd *cobra.Command, rawArgs []string, sessionOverrides map[string][]string, sessionPreset *string) error {
 	if flagConfigFile != "" {
 		if err := os.Setenv("KELD_CONFIG_FILE", flagConfigFile); err != nil {
 			return fmt.Errorf("setting KELD_CONFIG_FILE: %w", err)
@@ -86,10 +85,15 @@ func runCommand(commandName string, cmd *cobra.Command, rawArgs []string, sessio
 
 	preset := flagPreset
 	// Interactive mode when launched from root command or when a
-	// TUI session ran (presetResolved). The session handles preset
-	// selection, but runCommand may still need to collect values
-	// for commands without dedicated TUI screens (e.g. backup paths).
-	interactive := cmd == cmd.Root() || presetResolved
+	// TUI session ran (sessionPreset != nil). The session handles
+	// preset selection, but runCommand may still need to collect
+	// values for commands without dedicated TUI screens (e.g.
+	// backup paths).
+	interactive := cmd == cmd.Root() || sessionPreset != nil
+
+	if sessionPreset != nil {
+		preset = *sessionPreset
+	}
 
 	if preset != "" {
 		if err := validatePreset(preset); err != nil {
@@ -99,18 +103,10 @@ func runCommand(commandName string, cmd *cobra.Command, rawArgs []string, sessio
 
 	overrides := mergeOverrides(parsePassthrough(rawArgs), sessionOverrides)
 
-	// In interactive mode, fill missing preset/command inputs via prompts.
-	// When the unified session has already resolved the preset (presetResolved),
-	// skip the standalone preset prompt.
+	// In interactive mode, fill missing command-specific inputs
+	// via prompts. The session already handles preset selection,
+	// so only command-level prompts are needed here.
 	if interactive {
-		if !presetResolved && preset == "" {
-			p, err := promptPreset()
-			if err != nil {
-				return err
-			}
-			preset = p
-		}
-
 		// Resolve config before prompting so we can skip questions for values
 		// already provided by presets.
 		peek, err := config.Resolve(preset, commandName, overrides)
@@ -473,20 +469,6 @@ func validatePreset(preset string) error {
 	return fmt.Errorf("unknown preset %q; available presets: %s", preset, strings.Join(known, ", "))
 }
 
-// promptPreset shows an interactive preset selector when presets are defined
-// in the config. Returns "" (global-only) if no presets exist.
-func promptPreset() (string, error) {
-	presets := config.Presets()
-	if len(presets) == 0 {
-		return "", nil
-	}
-	selected, err := form.SelectPreset(presets)
-	if err != nil {
-		return "", fmt.Errorf("preset selection: %w", err)
-	}
-	return selected, nil
-}
-
 // promptForCommand collects required inputs for commands that need them.
 // The resolved config is checked first so prompts are skipped for values
 // the preset already provides.

cmd/root_test.go 🔗

@@ -108,7 +108,7 @@ func TestRunCommandAppliesRootFlagsAndPassthrough(t *testing.T) {
 
 	backup := lookupSubcommand(t, "backup")
 	out, err := captureStdout(t, func() error {
-		return runCommand("backup", backup, []string{"--tag", "daily", "/src"}, nil)
+		return runCommand("backup", backup, []string{"--tag", "daily", "/src"}, nil, nil)
 	})
 	if err != nil {
 		t.Fatalf("runCommand returned error: %v", err)
@@ -133,7 +133,7 @@ func TestRunCommandRejectsUnknownPreset(t *testing.T) {
 	setRootFlagValuesForTest(t, "missing", true, configFile)
 
 	backup := lookupSubcommand(t, "backup")
-	err := runCommand("backup", backup, nil, nil)
+	err := runCommand("backup", backup, nil, nil, nil)
 	if err == nil {
 		t.Fatal("expected unknown preset error")
 	}
@@ -199,16 +199,13 @@ func setRootFlagValuesForTest(t *testing.T, preset string, showCommand bool, con
 	t.Helper()
 
 	prevPreset, prevShowCommand, prevConfigFile := flagPreset, flagShowCmd, flagConfigFile
-	prevPresetResolved := presetResolved
 	flagPreset = preset
 	flagShowCmd = showCommand
 	flagConfigFile = configFile
-	presetResolved = false
 	t.Cleanup(func() {
 		flagPreset = prevPreset
 		flagShowCmd = prevShowCommand
 		flagConfigFile = prevConfigFile
-		presetResolved = prevPresetResolved
 	})
 }
 

cmd/subcommands.go 🔗

@@ -58,11 +58,10 @@ func registerSubcommands() {
 						return nil
 					}
 					flagPreset = preset
-					presetResolved = true
-					return runCommand(cmdName, cmd, nil, overrides)
+					return runCommand(cmdName, cmd, nil, overrides, &preset)
 				}
 
-				return runCommand(commandName, cmd, args, nil)
+				return runCommand(commandName, cmd, args, nil, nil)
 			},
 		}