diff --git a/cmd/root_test.go b/cmd/root_test.go index 0280455..5ecdfc2 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -392,18 +392,21 @@ func TestNoArgumentVersionAndCatalog(t *testing.T) { for _, required := range []string{ "████╗", "Structured documentation for codebases and AI agents", "GUIHO", "Cristóvão GUIHO", - "organization", "platform", "x64", "version", "v0.8.0", + "\n\nPlatform ", "x64", "\nVersion v0.8.0\nAuthor GUIHO (www.guiho.co)\n\n", "Run xdocs --help to see available commands.", } { if !strings.Contains(out, required) { t.Fatalf("welcome output omits %q:\n%s", required, out) } } - if !strings.HasPrefix(out, "\n\n") { - t.Fatalf("welcome output does not start with two blank lines: %q", out) + if strings.Contains(out, "organization") || strings.Contains(out, "Creator") { + t.Fatalf("welcome output still shows a creator/organization detail line:\n%s", out) } - if !strings.HasSuffix(out, "\n\n") { - t.Fatalf("welcome output does not end with two blank lines: %q", out) + if !strings.HasPrefix(out, "\n██") || strings.HasPrefix(out, "\n\n") { + t.Fatalf("welcome output does not start with exactly one blank line: %q", out) + } + if !strings.HasSuffix(out, "commands.\n\n") || strings.HasSuffix(out, "\n\n\n") { + t.Fatalf("welcome output does not end with exactly one blank line: %q", out) } if strings.Contains(out, "Hello Windows") || strings.Contains(out, "\x1b[") { t.Fatalf("welcome output should be beautiful borderless without legacy text or ANSI in non-terminal mode: %q", out) diff --git a/internal/welcome/welcome.go b/internal/welcome/welcome.go index 7e7e409..e1f6a33 100644 --- a/internal/welcome/welcome.go +++ b/internal/welcome/welcome.go @@ -19,6 +19,9 @@ const ( ansiCream = "\x1b[38;2;237;224;212m" ansiSage = "\x1b[38;2;101;109;74m" ansiForest = "\x1b[38;2;65;72;51m" + + // ansiAccent is the xdocs primary accent: the warm brown logo tone. + ansiAccent = ansiBrown + ansiBold ) var platformLabels = map[string]string{ @@ -83,45 +86,39 @@ func render(platform, architecture, version string, withColor bool) string { architecture = displayValue(archLabels, architecture) version = strings.TrimPrefix(strings.TrimSpace(version), "v") - innerWidth := 56 - for _, line := range xdocsLogo { - if w := len([]rune(line)); w+4 > innerWidth { - innerWidth = w + 4 - } - } tagline := "Structured documentation for codebases and AI agents" - if len([]rune(tagline))+4 > innerWidth { - innerWidth = len([]rune(tagline)) + 4 - } var out strings.Builder - // Two blank lines before — as requested - out.WriteString("\n\n") + // One blank line before, so the welcome never touches the prompt line + out.WriteString("\n") for i, line := range xdocsLogo { color := "" if i < len(logoColors) { color = logoColors[i] } - out.WriteString(colorize(withColor, center(line, innerWidth), color)) + out.WriteString(colorize(withColor, line, color)) out.WriteByte('\n') } - out.WriteString(colorize(withColor, center(tagline, innerWidth), ansiTan+ansiDim)) + out.WriteString(colorize(withColor, tagline, ansiTan+ansiDim)) out.WriteByte('\n') - out.WriteString(colorize(withColor, center("GUIHO · Cristóvão GUIHO", innerWidth), ansiSage)) - out.WriteString("\n\n\n") + out.WriteString(colorize(withColor, "GUIHO · Cristóvão GUIHO", ansiSage)) + out.WriteString("\n\n") - // Details — organization, platform, version - writeDetail(&out, withColor, "organization", "GUIHO", ansiCream+ansiBold) - writeDetail(&out, withColor, "platform", platform+" "+architecture, ansiCream) - writeDetail(&out, withColor, "version", "v"+version, ansiBrown+ansiBold) + // Details — platform, version, author + writeDetail(&out, withColor, "Platform", colorize(withColor, platform+" "+architecture, ansiCream)) + writeDetail(&out, withColor, "Version", colorize(withColor, "v"+version, ansiAccent)) + writeDetail(&out, withColor, "Author", + colorize(withColor, "GUIHO (", ansiCream)+ + colorize(withColor, "www.guiho.co", ansiAccent)+ + colorize(withColor, ")", ansiCream)) out.WriteByte('\n') out.WriteString("Run ") out.WriteString(colorize(withColor, "xdocs --help", ansiCream+ansiBold)) out.WriteString(" to see available commands.") out.WriteString("\n") - // Two blank lines after — as requested - out.WriteString("\n\n") + // One blank line after, so the welcome never touches the next prompt + out.WriteString("\n") return out.String() } @@ -132,17 +129,6 @@ func displayValue(labels map[string]string, value string) string { return value } -func center(value string, width int) string { - w := len([]rune(value)) - if w >= width { - return value - } - pad := width - w - // left pad half, right is implicit via center visual; we only left-pad like genius - left := pad / 2 - return strings.Repeat(" ", left) + value -} - func colorize(enabled bool, value, codes string) string { if !enabled || codes == "" { return value @@ -150,15 +136,12 @@ func colorize(enabled bool, value, codes string) string { return codes + value + ansiReset } -func writeDetail(out *strings.Builder, withColor bool, label, value, valueColor string) { - const labelWidth = 12 - if withColor { - // labels in forest (subtle earth), values in cream/brown accent - out.WriteString(colorize(true, fmt.Sprintf(" %-12s", label), ansiForest)) - out.WriteString(" ") - out.WriteString(colorize(true, value, valueColor)) - out.WriteByte('\n') - return - } - fmt.Fprintf(out, " %-12s %s\n", label, value) +// detailLabelWidth fits the longest detail label ("Platform") so values align. +const detailLabelWidth = 8 + +// writeDetail writes one detail row; value is already colourized by the caller. +func writeDetail(out *strings.Builder, withColor bool, label, value string) { + // labels in forest (subtle earth), values in cream/brown accent + padded := colorize(withColor, fmt.Sprintf("%-*s", detailLabelWidth, label), ansiForest) + fmt.Fprintf(out, "%s %s\n", padded, value) } diff --git a/internal/welcome/welcome.xdocs.md b/internal/welcome/welcome.xdocs.md index 2b15395..d6e2b61 100644 --- a/internal/welcome/welcome.xdocs.md +++ b/internal/welcome/welcome.xdocs.md @@ -4,7 +4,8 @@ description: Beautiful hello window rendering for the xdocs CLI with earth-tone parent: xdocs-internal children: [] files: - welcome.go: ANSI earth-tone rendering (7F5539, A68A64, EDE0D4, 656D4A, 414833), XDOCS block logo, centered tagline, organization/platform/version details, and two blank lines before and after the hello window. + welcome.go: ANSI earth-tone rendering (7F5539, A68A64, EDE0D4, 656D4A, 414833), XDOCS block logo, left-aligned tagline and author line, column-0 Platform/Version/Author details with aligned values, and one blank line before and after the hello window. + welcome_test.go: Exact left-aligned plain layout and coloured-output alignment regression tests, including absence of the organization line and one-blank-line framing. documents: {} tags: - welcome diff --git a/internal/welcome/welcome_test.go b/internal/welcome/welcome_test.go new file mode 100644 index 0000000..51397c4 --- /dev/null +++ b/internal/welcome/welcome_test.go @@ -0,0 +1,46 @@ +package welcome + +import ( + "strings" + "testing" +) + +func TestRenderIsLeftAlignedWithoutCreatorLine(t *testing.T) { + got := Render("linux", "amd64", "0.12.0") + want := "\n" + strings.Join(xdocsLogo, "\n") + "\n" + + "Structured documentation for codebases and AI agents\n" + + "GUIHO · Cristóvão GUIHO\n" + + "\n" + + "Platform Linux x64\n" + + "Version v0.12.0\n" + + "Author GUIHO (www.guiho.co)\n" + + "\n" + + "Run xdocs --help to see available commands.\n" + + "\n" + if got != want { + t.Fatalf("unexpected welcome layout:\ngot:\n%q\nwant:\n%q", got, want) + } +} + +func TestRenderWithColorKeepsLeftAlignment(t *testing.T) { + got := RenderWithColor("windows", "arm64", "v1.2.3", true) + for _, line := range strings.Split(got, "\n") { + if strings.HasPrefix(line, " ") { + t.Fatalf("coloured welcome line is indented: %q", line) + } + } + for _, required := range []string{ + logoColors[0] + xdocsLogo[0] + ansiReset + "\n", + ansiSage + "GUIHO · Cristóvão GUIHO" + ansiReset + "\n\n" + ansiForest + "Platform" + ansiReset, + ansiForest + "Version " + ansiReset + " " + ansiBrown + ansiBold + "v1.2.3" + ansiReset + "\n" + + ansiForest + "Author " + ansiReset + " " + + ansiCream + "GUIHO (" + ansiReset + ansiAccent + "www.guiho.co" + ansiReset + ansiCream + ")" + ansiReset + "\n\nRun ", + } { + if !strings.Contains(got, required) { + t.Fatalf("coloured welcome omits %q:\n%q", required, got) + } + } + if strings.Contains(got, "organization") { + t.Fatalf("coloured welcome still shows the organization line: %q", got) + } +}