From 529d8332118119351f652e6cfbfa5081d3397712 Mon Sep 17 00:00:00 2001 From: Amolith Date: Sun, 21 Dec 2025 15:01:07 -0700 Subject: [PATCH] refactor(init): wizard UX improvements - Token validation uses errTokenRequired instead of errKeyRequired - Access-token step offers 'Skip for now' on errors instead of discarding config - Ctrl+C on save prompt shows clearer message - Color summary shows 'auto' when config value is empty - Extract printConfigSummary helper to reduce function length - Add colorAuto constant to avoid magic string duplication Assisted-by: Claude Opus 4.5 via Amp --- cmd/init/apikey.go | 6 ++++-- cmd/init/init.go | 28 +++++++++++++++++++--------- cmd/init/steps.go | 28 +++++++++++++++++++++++++--- cmd/init/ui.go | 6 ++++-- 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/cmd/init/apikey.go b/cmd/init/apikey.go index f2a71fe16c2c5ee22c7ddd87aced779783e75b25..b2a383928a26434e65cca7fbaa37491e8d87efb8 100644 --- a/cmd/init/apikey.go +++ b/cmd/init/apikey.go @@ -20,6 +20,8 @@ import ( "git.secluded.site/lune/internal/ui" ) +const tokenValidationTimeout = 10 * time.Second + func configureAccessToken(cmd *cobra.Command) error { out := cmd.OutOrStdout() @@ -110,7 +112,7 @@ func promptForToken(out io.Writer) error { Value(&token). Validate(func(s string) error { if s == "" { - return errKeyRequired + return errTokenRequired } return nil @@ -167,7 +169,7 @@ func validateWithSpinner(token string) error { func validateTokenWithPing(token string) error { c := lunatask.NewClient(token, lunatask.UserAgent("lune/init")) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), tokenValidationTimeout) defer cancel() _, err := c.Ping(ctx) diff --git a/cmd/init/init.go b/cmd/init/init.go index 2fe4bcc87fac89966eee374db7d5b4c0f48db207..bff532057681813b746a7430fcadca693f377651 100644 --- a/cmd/init/init.go +++ b/cmd/init/init.go @@ -8,6 +8,7 @@ package init import ( "errors" "fmt" + "io" "github.com/charmbracelet/huh" "github.com/spf13/cobra" @@ -177,18 +178,12 @@ func runReconfigure(cmd *cobra.Command, cfg *config.Config) error { } } -func saveWithSummary(cmd *cobra.Command, cfg *config.Config) error { - path, err := config.Path() - if err != nil { - return err - } - +func printConfigSummary(out io.Writer, cfg *config.Config) { goalCount := 0 for _, area := range cfg.Areas { goalCount += len(area.Goals) } - out := cmd.OutOrStdout() fmt.Fprintln(out) fmt.Fprintln(out, ui.Bold.Render("Configuration summary:")) fmt.Fprintf(out, " Areas: %d (%d goals)\n", len(cfg.Areas), goalCount) @@ -203,8 +198,23 @@ func saveWithSummary(cmd *cobra.Command, cfg *config.Config) error { fmt.Fprintf(out, " Default notebook: %s\n", cfg.Defaults.Notebook) } - fmt.Fprintf(out, " Color: %s\n", cfg.UI.Color) + color := cfg.UI.Color + if color == "" { + color = colorAuto + } + + fmt.Fprintf(out, " Color: %s\n", color) fmt.Fprintln(out) +} + +func saveWithSummary(cmd *cobra.Command, cfg *config.Config) error { + path, err := config.Path() + if err != nil { + return err + } + + out := cmd.OutOrStdout() + printConfigSummary(out, cfg) var save bool @@ -216,7 +226,7 @@ func saveWithSummary(cmd *cobra.Command, cfg *config.Config) error { Run() if err != nil { if errors.Is(err, huh.ErrUserAborted) { - fmt.Fprintln(out, ui.Warning.Render("Changes discarded.")) + fmt.Fprintln(out, ui.Warning.Render("Setup cancelled; configuration was not saved.")) return errQuit } diff --git a/cmd/init/steps.go b/cmd/init/steps.go index 4d878a5e71123e330c9e13a8b2d4a64c0a3ba0ec..01a7e577947cd490375898800e0a13393d49c07a 100644 --- a/cmd/init/steps.go +++ b/cmd/init/steps.go @@ -6,25 +6,27 @@ package init import ( "errors" + "fmt" "github.com/charmbracelet/huh" "github.com/spf13/cobra" "git.secluded.site/lune/internal/config" + "git.secluded.site/lune/internal/ui" ) // runUIPrefsStep runs the UI preferences configuration step. func runUIPrefsStep(cfg *config.Config) wizardNav { color := cfg.UI.Color if color == "" { - color = "auto" + color = colorAuto } err := huh.NewSelect[string](). Title("Color output"). Description("When should lune use colored output?"). Options( - huh.NewOption("Auto (detect terminal capability)", "auto"), + huh.NewOption("Auto (detect terminal capability)", colorAuto), huh.NewOption("Always", "always"), huh.NewOption("Never", "never"), ). @@ -89,7 +91,27 @@ func runAccessTokenStep(cmd *cobra.Command) wizardNav { } if err != nil { - return navQuit + out := cmd.OutOrStdout() + fmt.Fprintln(out, ui.Error.Render("Token configuration failed: "+err.Error())) + + var skip bool + + skipErr := huh.NewConfirm(). + Title("Skip token configuration?"). + Description("You can configure it later with 'lune init'."). + Affirmative("Skip for now"). + Negative("Go back"). + Value(&skip). + Run() + if skipErr != nil { + return navQuit + } + + if skip { + return navNext + } + + return navBack } return navNext diff --git a/cmd/init/ui.go b/cmd/init/ui.go index 209a99cd544f2aea2b06e8bb66501eaee861f57b..6d7cf49941a9d79f5a5b1b1c44d14e513b52f5d9 100644 --- a/cmd/init/ui.go +++ b/cmd/init/ui.go @@ -28,11 +28,13 @@ const ( actionEdit = "edit" actionDelete = "delete" actionGoals = "goals" + colorAuto = "auto" ) var ( errNameRequired = errors.New("name is required") errKeyRequired = errors.New("key is required") + errTokenRequired = errors.New("access token is required") errKeyFormat = errors.New("key must be lowercase letters, numbers, and hyphens (e.g. 'work' or 'q1-goals')") errKeyDuplicate = errors.New("this key is already in use") errRefRequired = errors.New("reference is required") @@ -44,14 +46,14 @@ var ( func configureUIPrefs(cfg *config.Config) error { color := cfg.UI.Color if color == "" { - color = "auto" + color = colorAuto } err := huh.NewSelect[string](). Title("Color output"). Description("When should lune use colored output?"). Options( - huh.NewOption("Auto (detect terminal capability)", "auto"), + huh.NewOption("Auto (detect terminal capability)", colorAuto), huh.NewOption("Always", "always"), huh.NewOption("Never", "never"), ).