diff --git a/internal/output/plain_format.go b/internal/output/plain_format.go index daecb731..2c1c0a84 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)", SuccessMarker(), e.Detail), true } - return "LocalStack ready", true + return SuccessMarker() + " 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 SuccessMarker() + " " + e.Text case SeverityNote: return "> Note: " + e.Text case SeverityWarning: @@ -151,7 +151,7 @@ func formatErrorEvent(e ErrorEvent) string { func formatInstanceInfo(e InstanceInfoEvent) string { var sb strings.Builder - sb.WriteString("✓ " + e.EmulatorName + " is running (" + e.Host + ")") + sb.WriteString(SuccessMarker() + " " + e.EmulatorName + " is running (" + e.Host + ")") var meta []string if e.Uptime > 0 { meta = append(meta, "UPTIME: "+formatUptime(e.Uptime)) diff --git a/internal/output/plain_format_test.go b/internal/output/plain_format_test.go index a508e7d9..84fcb8ba 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: SuccessMarker() + " 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: SuccessMarker() + " LocalStack ready (abc123)", wantOK: true, }, { @@ -120,7 +120,7 @@ func TestFormatEventLine(t *testing.T) { ContainerName: "localstack-aws", Uptime: 4*time.Minute + 23*time.Second, }, - want: "✓ LocalStack AWS Emulator is running (localhost.localstack.cloud:4566)\n UPTIME: 4m 23s · CONTAINER: localstack-aws · VERSION: 4.14.1", + want: SuccessMarker() + " LocalStack AWS Emulator is running (localhost.localstack.cloud:4566)\n UPTIME: 4m 23s · CONTAINER: localstack-aws · VERSION: 4.14.1", wantOK: true, }, { @@ -129,7 +129,7 @@ func TestFormatEventLine(t *testing.T) { EmulatorName: "LocalStack AWS Emulator", Host: "127.0.0.1:4566", }, - want: "✓ LocalStack AWS Emulator is running (127.0.0.1:4566)", + want: SuccessMarker() + " LocalStack AWS Emulator is running (127.0.0.1:4566)", wantOK: true, }, { @@ -237,4 +237,3 @@ func TestFormatTableWidth(t *testing.T) { } }) } - diff --git a/internal/output/plain_sink_test.go b/internal/output/plain_sink_test.go index 38dcf8c2..f6908d94 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", SuccessMarker()), }, { name: "ready phase without detail", event: ContainerStatusEvent{Phase: "ready", Container: "localstack-aws"}, - expected: "LocalStack ready\n", + expected: fmt.Sprintf("%s LocalStack ready\n", SuccessMarker()), }, { name: "unknown phase with detail", @@ -163,7 +163,7 @@ func TestPlainSink_EmitsInstanceInfoEvent(t *testing.T) { Uptime: 4*time.Minute + 23*time.Second, }) - expected := "✓ LocalStack AWS Emulator is running (localhost.localstack.cloud:4566)\n UPTIME: 4m 23s · CONTAINER: localstack-aws · VERSION: 4.14.1\n" + expected := SuccessMarker() + " LocalStack AWS Emulator is running (localhost.localstack.cloud:4566)\n UPTIME: 4m 23s · CONTAINER: localstack-aws · VERSION: 4.14.1\n" assert.Equal(t, expected, out.String()) assert.NoError(t, sink.Err()) }) @@ -177,7 +177,7 @@ func TestPlainSink_EmitsInstanceInfoEvent(t *testing.T) { Host: "127.0.0.1:4566", }) - expected := "✓ LocalStack AWS Emulator is running (127.0.0.1:4566)\n" + expected := SuccessMarker() + " LocalStack AWS Emulator is running (127.0.0.1:4566)\n" assert.Equal(t, expected, out.String()) assert.NoError(t, sink.Err()) }) diff --git a/internal/output/symbols.go b/internal/output/symbols.go new file mode 100644 index 00000000..94834287 --- /dev/null +++ b/internal/output/symbols.go @@ -0,0 +1,5 @@ +package output + +func SuccessMarker() string { + return "✔︎" +} diff --git a/internal/ui/app.go b/internal/ui/app.go index 3dc7fe8b..4185bd3b 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.SuccessMarker(), styles.Success.Render(output.SuccessMarker()), 1) + } a.lines = appendLine(a.lines, styledLine{text: line}) } return a, nil diff --git a/internal/ui/app_test.go b/internal/ui/app_test.go index 0949fa26..b2da93d8 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.SuccessMarker()) || !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 1b60b6a8..4bf19e96 100644 --- a/internal/ui/components/message.go +++ b/internal/ui/components/message.go @@ -45,7 +45,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.SuccessMarker() + return checkmark, 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 c32e9867..a3daa4b3 100644 --- a/internal/ui/styles/styles.go +++ b/internal/ui/styles/styles.go @@ -1,11 +1,14 @@ package styles -import "github.com/charmbracelet/lipgloss" +import ( + "github.com/charmbracelet/lipgloss" +) const ( NimboDarkColor = "#3F51C7" NimboMidColor = "#5E6AD2" NimboLightColor = "#7E88EC" + SuccessColor = "#B7C95C" ) var ( @@ -39,7 +42,7 @@ var ( // Message severity styles Success = lipgloss.NewStyle(). - Foreground(lipgloss.Color("42")) + Foreground(lipgloss.Color(SuccessColor)) Note = lipgloss.NewStyle(). Foreground(lipgloss.Color("33"))