From 3b4cb868ebbebd95817d6e292585c7e8236a28ef Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Fri, 13 Mar 2026 15:05:35 +0200 Subject: [PATCH 1/2] Use checkmark for success message --- internal/output/plain_format.go | 6 +++--- internal/output/plain_format_test.go | 4 ++-- internal/output/plain_sink_test.go | 4 ++-- internal/output/style.go | 13 +++++++++++++ internal/ui/app_test.go | 2 +- internal/ui/components/message.go | 3 ++- internal/ui/styles/styles.go | 7 +++++-- 7 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 internal/output/style.go diff --git a/internal/output/plain_format.go b/internal/output/plain_format.go index 763fa64..0bedd71 100644 --- a/internal/output/plain_format.go +++ b/internal/output/plain_format.go @@ -49,9 +49,9 @@ func formatStatusLine(e ContainerStatusEvent) (string, bool) { return "Waiting for LocalStack to be ready...", true case "ready": if e.Detail != "" { - return fmt.Sprintf("LocalStack ready (%s)", e.Detail), true + return fmt.Sprintf("%s LocalStack ready (%s)", SuccessMarkerText(), e.Detail), true } - return "LocalStack ready", true + return SuccessMarkerText() + " LocalStack ready", true default: if e.Detail != "" { return fmt.Sprintf("LocalStack: %s (%s)", e.Phase, e.Detail), true @@ -116,7 +116,7 @@ func formatAuthEvent(e AuthEvent) string { func formatMessageEvent(e MessageEvent) string { switch e.Severity { case SeveritySuccess: - return "> Success: " + e.Text + return SuccessMarkerText() + " " + e.Text case SeverityNote: return "> Note: " + e.Text case SeverityWarning: diff --git a/internal/output/plain_format_test.go b/internal/output/plain_format_test.go index 42168c5..9124fa3 100644 --- a/internal/output/plain_format_test.go +++ b/internal/output/plain_format_test.go @@ -24,7 +24,7 @@ func TestFormatEventLine(t *testing.T) { { name: "message event success", event: MessageEvent{Severity: SeveritySuccess, Text: "done"}, - want: "> Success: done", + want: SuccessMarkerText() + " done", wantOK: true, }, { @@ -60,7 +60,7 @@ func TestFormatEventLine(t *testing.T) { { name: "status ready with detail", event: ContainerStatusEvent{Phase: "ready", Container: "localstack-aws", Detail: "abc123"}, - want: "LocalStack ready (abc123)", + want: SuccessMarkerText() + " LocalStack ready (abc123)", wantOK: true, }, { diff --git a/internal/output/plain_sink_test.go b/internal/output/plain_sink_test.go index 38dcf8c..ee9a275 100644 --- a/internal/output/plain_sink_test.go +++ b/internal/output/plain_sink_test.go @@ -61,12 +61,12 @@ func TestPlainSink_EmitsStatusEvent(t *testing.T) { { name: "ready phase with detail", event: ContainerStatusEvent{Phase: "ready", Container: "localstack-aws", Detail: "abc123"}, - expected: "LocalStack ready (abc123)\n", + expected: fmt.Sprintf("%s LocalStack ready (abc123)\n", SuccessMarkerText()), }, { name: "ready phase without detail", event: ContainerStatusEvent{Phase: "ready", Container: "localstack-aws"}, - expected: "LocalStack ready\n", + expected: fmt.Sprintf("%s LocalStack ready\n", SuccessMarkerText()), }, { name: "unknown phase with detail", diff --git a/internal/output/style.go b/internal/output/style.go new file mode 100644 index 0000000..a24a5e9 --- /dev/null +++ b/internal/output/style.go @@ -0,0 +1,13 @@ +package output + +import "fmt" + +const SuccessColorHex = "#B7C95C" + +func SuccessMarker() string { + return fmt.Sprintf("\x1b[38;2;183;201;92m%s\x1b[0m", SuccessMarkerText()) +} + +func SuccessMarkerText() string { + return "✔︎" +} diff --git a/internal/ui/app_test.go b/internal/ui/app_test.go index 0949fa2..06a3d20 100644 --- a/internal/ui/app_test.go +++ b/internal/ui/app_test.go @@ -176,7 +176,7 @@ func TestAppMessageEventRendering(t *testing.T) { if len(app.lines) != 1 { t.Fatalf("expected 1 line, got %d", len(app.lines)) } - if !strings.Contains(app.lines[0].text, "Success:") || !strings.Contains(app.lines[0].text, "Done") { + if !strings.Contains(app.lines[0].text, output.SuccessMarkerText()) || !strings.Contains(app.lines[0].text, "Done") { t.Fatalf("expected rendered success message, got: %q", app.lines[0].text) } } diff --git a/internal/ui/components/message.go b/internal/ui/components/message.go index 48d01fa..412adc9 100644 --- a/internal/ui/components/message.go +++ b/internal/ui/components/message.go @@ -41,7 +41,8 @@ func messagePrefix(e output.MessageEvent) (string, string) { prefix := styles.Secondary.Render("> ") switch e.Severity { case output.SeveritySuccess: - return "> Success:", prefix + styles.Success.Render("Success:") + checkmark := output.SuccessMarkerText() + return "> " + checkmark, prefix + styles.Success.Render(checkmark) case output.SeverityNote: return "> Note:", prefix + styles.Note.Render("Note:") case output.SeverityWarning: diff --git a/internal/ui/styles/styles.go b/internal/ui/styles/styles.go index 792ffb7..30be467 100644 --- a/internal/ui/styles/styles.go +++ b/internal/ui/styles/styles.go @@ -1,6 +1,9 @@ package styles -import "github.com/charmbracelet/lipgloss" +import ( + "github.com/charmbracelet/lipgloss" + "github.com/localstack/lstk/internal/output" +) const ( NimboDarkColor = "#3F51C7" @@ -39,7 +42,7 @@ var ( // Message severity styles Success = lipgloss.NewStyle(). - Foreground(lipgloss.Color("42")) + Foreground(lipgloss.Color(output.SuccessColorHex)) Note = lipgloss.NewStyle(). Foreground(lipgloss.Color("33")) From 5e1991c1bda95420145fd43fee9deaecaedfe69a Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Mon, 16 Mar 2026 22:00:32 +0200 Subject: [PATCH 2/2] Remove raw ANSI color from non-TUI success marker --- internal/output/style.go | 6 ------ internal/ui/app.go | 3 +++ internal/ui/components/message.go | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/internal/output/style.go b/internal/output/style.go index a24a5e9..33e1f39 100644 --- a/internal/output/style.go +++ b/internal/output/style.go @@ -1,13 +1,7 @@ package output -import "fmt" - const SuccessColorHex = "#B7C95C" -func SuccessMarker() string { - return fmt.Sprintf("\x1b[38;2;183;201;92m%s\x1b[0m", SuccessMarkerText()) -} - func SuccessMarkerText() string { return "✔︎" } diff --git a/internal/ui/app.go b/internal/ui/app.go index 9804ed5..740fb30 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -182,6 +182,9 @@ func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) { a.pullProgress = a.pullProgress.Hide() } if line, ok := output.FormatEventLine(msg); ok { + if msg.Phase == "ready" { + line = strings.Replace(line, output.SuccessMarkerText(), styles.Success.Render(output.SuccessMarkerText()), 1) + } a.lines = appendLine(a.lines, styledLine{text: line}) } return a, nil diff --git a/internal/ui/components/message.go b/internal/ui/components/message.go index 412adc9..7497302 100644 --- a/internal/ui/components/message.go +++ b/internal/ui/components/message.go @@ -42,7 +42,7 @@ func messagePrefix(e output.MessageEvent) (string, string) { switch e.Severity { case output.SeveritySuccess: checkmark := output.SuccessMarkerText() - return "> " + checkmark, prefix + styles.Success.Render(checkmark) + return checkmark, styles.Success.Render(checkmark) case output.SeverityNote: return "> Note:", prefix + styles.Note.Render("Note:") case output.SeverityWarning: