From 24a8bd5840d8dc0db8fcd95201933f0366ddabbe Mon Sep 17 00:00:00 2001 From: "m.kindritskiy" Date: Wed, 8 Jul 2026 22:24:32 +0300 Subject: [PATCH 1/3] Fix styled error indentation --- docs/docs/changelog.md | 1 + internal/cmd/error.go | 8 ++++---- internal/cmd/help_test.go | 13 +++++++++++-- .../TestErrorGolden/command_not_found.golden | 10 +++++----- .../command_not_found_with_suggestion.golden | 18 +++++++++--------- .../TestErrorGolden/dependency_chain.golden | 16 ++++++++-------- .../TestErrorGolden/dependency_single.golden | 12 ++++++------ internal/executor/execute_test.go | 2 +- 8 files changed, 45 insertions(+), 35 deletions(-) diff --git a/docs/docs/changelog.md b/docs/docs/changelog.md index f50b0b88..a2825980 100644 --- a/docs/docs/changelog.md +++ b/docs/docs/changelog.md @@ -5,6 +5,7 @@ title: Changelog ## [Unreleased](https://github.com/lets-cli/lets/releases/tag/v0.0.X) +* `[Fixed]` Align styled error output to the left while preserving the padded error badge. * `[Docs]` Convert design specs into ADRs. * `[Refactoring]` Rename the internal download progress package to progressbar. * `[Added]` Show interactive download progress for `lets self upgrade`. Issue [#367](https://github.com/lets-cli/lets/issues/367) diff --git a/internal/cmd/error.go b/internal/cmd/error.go index 5768fe25..a1b35d16 100644 --- a/internal/cmd/error.go +++ b/internal/cmd/error.go @@ -52,12 +52,12 @@ func (o errorOutput) blank() { // header writes the styled error heading. func (o errorOutput) header() { - o.writeln(o.styles.ErrorHeader.String()) + o.writeln(o.styles.ErrorHeader.UnsetMarginLeft().String()) } // commandTreeTitle writes the dependency tree section heading. func (o errorOutput) commandTreeTitle() { - title := o.styles.Title.Margin(0, 0).MarginLeft(2).Padding(0, 0) + title := o.styles.Title.Margin(0, 0).Padding(0, 0) o.writeln(title.Render("command tree:")) } @@ -68,7 +68,7 @@ func (r errorRenderer) Render() { return } - errorText := r.styles.ErrorText + errorText := r.styles.ErrorText.UnsetMarginLeft() r.out.header() r.renderMessage(errorText) @@ -132,7 +132,7 @@ func (r errorRenderer) renderDependencyTree(depErr *executor.DependencyError) { r.out.commandTreeTitle() for i, name := range depErr.Chain { - line := strings.Repeat(" ", i+2) + joint + r.styles.Program.Command.Render(name) + line := strings.Repeat(" ", i+1) + joint + r.styles.Program.Command.Render(name) if i == len(depErr.Chain)-1 { line += " " + failed } diff --git a/internal/cmd/help_test.go b/internal/cmd/help_test.go index e0c28b51..bf18770f 100644 --- a/internal/cmd/help_test.go +++ b/internal/cmd/help_test.go @@ -177,11 +177,11 @@ func TestHelpRendererDoesNotDuplicateDocoptUsageCommandPath(t *testing.T) { } } -func TestErrorHandlerRemovesErrorHeaderLeftPadding(t *testing.T) { +func TestErrorHandlerRemovesErrorTextLeftMargin(t *testing.T) { var stderr bytes.Buffer styles := fang.Styles{ ErrorHeader: lipgloss.NewStyle().Padding(0, 1).SetString("ERROR"), - ErrorText: lipgloss.NewStyle(), + ErrorText: lipgloss.NewStyle().MarginLeft(2), Program: fang.Program{ Flag: lipgloss.NewStyle(), }, @@ -193,9 +193,18 @@ func TestErrorHandlerRemovesErrorHeaderLeftPadding(t *testing.T) { if !strings.Contains(out, "ERROR") { t.Fatalf("expected error header in output: %q", out) } + if !strings.HasPrefix(out, " ERROR") && !strings.Contains(out, "\n ERROR") { + t.Fatalf("expected error header padding to start at column zero: %q", out) + } + if strings.Contains(out, "\n ERROR") { + t.Fatalf("expected error header outer margin to be removed: %q", out) + } if !strings.Contains(out, "\nTry --help for usage.") { t.Fatalf("expected usage hint in output: %q", out) } + if strings.Contains(out, "\n unknown command") || strings.Contains(out, "\n Try --help") { + t.Fatalf("expected error text to start at column zero: %q", out) + } } func TestErrorHandlerSplitsExecuteErrorCause(t *testing.T) { diff --git a/internal/cmd/testdata/TestErrorGolden/command_not_found.golden b/internal/cmd/testdata/TestErrorGolden/command_not_found.golden index 537faec8..2fe1b99b 100644 --- a/internal/cmd/testdata/TestErrorGolden/command_not_found.golden +++ b/internal/cmd/testdata/TestErrorGolden/command_not_found.golden @@ -1,7 +1,7 @@ - - ERROR - - Unknown command "zzzznotacommand" for "lets". + + ERROR + +Unknown command "zzzznotacommand" for "lets". - Try --help for usage. +Try --help for usage. diff --git a/internal/cmd/testdata/TestErrorGolden/command_not_found_with_suggestion.golden b/internal/cmd/testdata/TestErrorGolden/command_not_found_with_suggestion.golden index 9fb08427..97806d4e 100644 --- a/internal/cmd/testdata/TestErrorGolden/command_not_found_with_suggestion.golden +++ b/internal/cmd/testdata/TestErrorGolden/command_not_found_with_suggestion.golden @@ -1,11 +1,11 @@ - - ERROR - - Unknown command "slef" for "lets" - - Did you mean this? - self - . + + ERROR + +Unknown command "slef" for "lets" + +Did you mean this? + self +. - Try --help for usage. +Try --help for usage. diff --git a/internal/cmd/testdata/TestErrorGolden/dependency_chain.golden b/internal/cmd/testdata/TestErrorGolden/dependency_chain.golden index 0d77b6f4..4f701270 100644 --- a/internal/cmd/testdata/TestErrorGolden/dependency_chain.golden +++ b/internal/cmd/testdata/TestErrorGolden/dependency_chain.golden @@ -1,10 +1,10 @@ - - ERROR - - Exit status 1. + + ERROR + +Exit status 1. - COMMAND TREE: - └─ deploy - └─ build - └─ lint <-- failed here +COMMAND TREE: + └─ deploy + └─ build + └─ lint <-- failed here diff --git a/internal/cmd/testdata/TestErrorGolden/dependency_single.golden b/internal/cmd/testdata/TestErrorGolden/dependency_single.golden index 985bc6c7..f9193a68 100644 --- a/internal/cmd/testdata/TestErrorGolden/dependency_single.golden +++ b/internal/cmd/testdata/TestErrorGolden/dependency_single.golden @@ -1,8 +1,8 @@ - - ERROR - - Exit status 1. + + ERROR + +Exit status 1. - COMMAND TREE: - └─ lint <-- failed here +COMMAND TREE: + └─ lint <-- failed here diff --git a/internal/executor/execute_test.go b/internal/executor/execute_test.go index fa21d64e..732bfd9e 100644 --- a/internal/executor/execute_test.go +++ b/internal/executor/execute_test.go @@ -300,7 +300,7 @@ func TestAfterScriptErrorDoesNotOverrideMainError(t *testing.T) { mainErr := fmt.Errorf("main script failed") r := newRecordingRunner() - r.failOn(0, mainErr) // main script fails + r.failOn(0, mainErr) // main script fails r.failOn(1, fmt.Errorf("after script failed")) // after script also fails (logged, not returned) ex := NewExecutor(cfg, r.run) From f7f85912cdd2197ddd31a0d2a687aa8f9eff8322 Mon Sep 17 00:00:00 2001 From: "m.kindritskiy" Date: Wed, 8 Jul 2026 22:26:17 +0300 Subject: [PATCH 2/3] Add examples help spacing --- docs/docs/changelog.md | 1 + internal/cmd/help.go | 1 + internal/cmd/help_test.go | 6 ++++++ 3 files changed, 8 insertions(+) diff --git a/docs/docs/changelog.md b/docs/docs/changelog.md index a2825980..562383f2 100644 --- a/docs/docs/changelog.md +++ b/docs/docs/changelog.md @@ -5,6 +5,7 @@ title: Changelog ## [Unreleased](https://github.com/lets-cli/lets/releases/tag/v0.0.X) +* `[Fixed]` Add spacing between the `EXAMPLES` help title and its examples. * `[Fixed]` Align styled error output to the left while preserving the padded error badge. * `[Docs]` Convert design specs into ADRs. * `[Refactoring]` Rename the internal download progress package to progressbar. diff --git a/internal/cmd/help.go b/internal/cmd/help.go index c963e040..93f253bf 100644 --- a/internal/cmd/help.go +++ b/internal/cmd/help.go @@ -160,6 +160,7 @@ func (r helpRenderer) renderUsageAndExamples() { cw := blockStyle.GetWidth() - blockStyle.GetHorizontalPadding() r.out.sectionTitle("examples") + r.out.blank() for i, example := range examples { if lipgloss.Width(example) > cw { diff --git a/internal/cmd/help_test.go b/internal/cmd/help_test.go index bf18770f..3fd56179 100644 --- a/internal/cmd/help_test.go +++ b/internal/cmd/help_test.go @@ -96,6 +96,12 @@ Example: if !strings.Contains(out, "OPTIONS") { t.Fatalf("expected options title in output: %q", out) } + if !strings.Contains(out, "EXAMPLES\n\n") { + t.Fatalf("expected blank line after examples title in output: %q", out) + } + if !strings.Contains(out, `lets release 1.0.0 -m "Release 1.0.0"`) { + t.Fatalf("expected example in output: %q", out) + } argIdx := strings.Index(out, "") flagIdx := strings.Index(out, "--message=, -m") if argIdx == -1 { From ba33c7714048c436fd53424974da244071e86642 Mon Sep 17 00:00:00 2001 From: "m.kindritskiy" Date: Wed, 8 Jul 2026 22:26:27 +0300 Subject: [PATCH 3/3] update theme colors --- internal/theme/theme.go | 15 +++++++++------ internal/theme/theme_test.go | 10 ++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/internal/theme/theme.go b/internal/theme/theme.go index 4f6b9a68..599c9381 100644 --- a/internal/theme/theme.go +++ b/internal/theme/theme.go @@ -73,14 +73,15 @@ func ProgressColors(scheme fang.ColorScheme) (color.Color, color.Color) { // DefaultColorScheme is the default colorscheme. func DefaultColorScheme(c lipgloss.LightDarkFunc) fang.ColorScheme { baseCyan := charmtone.Turtle + baseCyanLighter := lipgloss.Color("#0BF4F1") baseWhite := charmtone.Ash baseGray := charmtone.Oyster return fang.ColorScheme{ - Base: c(charmtone.Charcoal, baseCyan), - Title: baseWhite, + Base: c(charmtone.Charcoal, baseCyanLighter), + Title: baseCyanLighter, Codeblock: c(charmtone.Salt, lipgloss.Color("#2F2E36")), - Program: c(charmtone.Malibu, baseWhite), + Program: c(charmtone.Malibu, baseCyan), Command: c(charmtone.Pony, baseCyan), DimmedArgument: c(charmtone.Squid, baseGray), Comment: c(charmtone.Squid, lipgloss.Color("#747282")), @@ -118,15 +119,17 @@ func AnsiColorScheme(c lipgloss.LightDarkFunc) fang.ColorScheme { } func SynthwaveColorScheme(c lipgloss.LightDarkFunc) fang.ColorScheme { + basePink := lipgloss.Color("#f24db8") + return fang.ColorScheme{ - Base: c(charmtone.Charcoal, charmtone.Cheeky), + Base: c(charmtone.Charcoal, basePink), Title: charmtone.Grape, Codeblock: c(charmtone.Salt, lipgloss.Color("#2F2E36")), Program: c(charmtone.Malibu, charmtone.Grape), - Command: c(charmtone.Pony, charmtone.Cheeky), + Command: c(charmtone.Pony, basePink), DimmedArgument: c(charmtone.Squid, charmtone.Oyster), Comment: c(charmtone.Squid, lipgloss.Color("#747282")), - Flag: c(lipgloss.Color("#0CB37F"), charmtone.Cheeky), + Flag: c(lipgloss.Color("#0CB37F"), basePink), Argument: c(charmtone.Charcoal, charmtone.Ash), Description: c(charmtone.Charcoal, charmtone.Ash), FlagDefault: c(charmtone.Smoke, charmtone.Squid), diff --git a/internal/theme/theme_test.go b/internal/theme/theme_test.go index 69356260..8707086a 100644 --- a/internal/theme/theme_test.go +++ b/internal/theme/theme_test.go @@ -7,6 +7,8 @@ import ( "github.com/charmbracelet/x/exp/charmtone" ) +var defaultTitleColor = lipgloss.Color("#0BF4F1") + func TestValidName(t *testing.T) { for _, name := range []string{DefaultName, ANSIName, SynthwaveName} { if !ValidName(name) { @@ -20,8 +22,8 @@ func TestValidName(t *testing.T) { } func TestColorSchemeByName(t *testing.T) { - if got := ColorSchemeByName(DefaultName)(lipgloss.LightDark(true)).Title; got != charmtone.Ash { - t.Fatalf("expected default theme title color %v, got %v", charmtone.Ash, got) + if got := ColorSchemeByName(DefaultName)(lipgloss.LightDark(true)).Title; got != defaultTitleColor { + t.Fatalf("expected default theme title color %v, got %v", defaultTitleColor, got) } if got := ColorSchemeByName(ANSIName)(lipgloss.LightDark(true)).ErrorDetails; got != lipgloss.Red { @@ -32,8 +34,8 @@ func TestColorSchemeByName(t *testing.T) { t.Fatalf("expected synthwave theme title color %v, got %v", charmtone.Grape, got) } - if got := ColorSchemeByName("unknown")(lipgloss.LightDark(true)).Title; got != charmtone.Ash { - t.Fatalf("expected unknown theme to fall back to %v, got %v", charmtone.Ash, got) + if got := ColorSchemeByName("unknown")(lipgloss.LightDark(true)).Title; got != defaultTitleColor { + t.Fatalf("expected unknown theme to fall back to %v, got %v", defaultTitleColor, got) } }