diff --git a/tui/main.go b/tui/main.go index a93e0ee..a18a419 100644 --- a/tui/main.go +++ b/tui/main.go @@ -1591,20 +1591,22 @@ func (m *model) syncViewport() { func (m *model) history(width int) string { if len(m.messages) == 0 { - if width < 28 { - return softStyle.Render(compactText("Ready — ask anything", width)) + if width < 44 || (m.height > 0 && m.historyHeight() < 9) { + return brandStyle.Render(compactText("◆ OPENKYROZEN · READY — ASK ANYTHING", width)) + } + bannerWidth := maxInt(1, minInt(72, width-8)) + rows := bannerRows(bannerWidth) + for index, row := range rows { + style := brandStyle + if index == 3 { + style = softStyle + } + rows[index] = style.Render(row) } - body := strings.Join([]string{ - brandStyle.Render("◆ WORKSPACE READY"), - titleStyle.Render("What will we build?"), - mutedStyle.Render("Ask naturally, or start with a focused workflow."), - "", - softStyle.Render("/plan") + mutedStyle.Render(" plan") + " " + - softStyle.Render("/attach") + mutedStyle.Render(" context") + " " + - softStyle.Render("/graph") + mutedStyle.Render(" inspect"), - }, "\n") - boxWidth := maxInt(1, minInt(68, width-8)) - return welcomeStyle.Copy().Width(boxWidth).MaxWidth(boxWidth).Render(body) + rows = append(rows, "", greenStyle.Render("● WORKSPACE READY"), + mutedStyle.Render(compactText("Ask anything · /plan · /attach · /graph", bannerWidth))) + content := strings.Join(rows, "\n") + return lipgloss.NewStyle().Width(width).MaxWidth(width).Height(m.historyHeight()).MaxHeight(m.historyHeight()).Align(lipgloss.Center, lipgloss.Center).Render(content) } var lines []string for index := range m.messages { diff --git a/tui/main_test.go b/tui/main_test.go index 543d03e..d975357 100644 --- a/tui/main_test.go +++ b/tui/main_test.go @@ -560,10 +560,39 @@ func TestFocusedCockpitRailBreakpointAndCompactGraph(t *testing.T) { func TestFocusedCockpitMessageHierarchyAndEmptyState(t *testing.T) { m := initialModel("", true) - empty := m.history(80) - if !strings.Contains(empty, "WORKSPACE READY") || !strings.Contains(empty, "/plan") || !strings.Contains(empty, "/graph") { + m.width, m.height = 120, 30 + mainWidth, _ := m.layoutWidths() + empty := m.history(mainWidth) + if !strings.Contains(empty, "OPENKYROZEN") || !strings.Contains(empty, "WORKSPACE READY") || !strings.Contains(empty, "/plan") || !strings.Contains(empty, "/graph") { t.Fatalf("empty state is incomplete: %s", empty) } + if lipgloss.Height(empty) != m.historyHeight() { + t.Fatalf("empty state height is %d, want %d", lipgloss.Height(empty), m.historyHeight()) + } + bannerLine := -1 + for index, line := range strings.Split(empty, "\n") { + if strings.Contains(line, "◈ OPENKYROZEN ◈") { + bannerLine = index + break + } + } + if bannerLine < 3 || bannerLine > m.historyHeight()-4 { + t.Fatalf("empty-state banner is not vertically centered: line %d of %d", bannerLine, m.historyHeight()) + } + for _, size := range [][2]int{{60, 16}, {80, 24}, {120, 30}, {140, 40}} { + sized := initialModel("", true) + sized.width, sized.height, sized.screen = size[0], size[1], screenChat + sized.resize() + content := sized.View().Content + if !strings.Contains(content, "OPENKYROZEN") || len(strings.Split(content, "\n")) != size[1] { + t.Fatalf("%dx%d empty state lost the banner or terminal bounds", size[0], size[1]) + } + for index, line := range strings.Split(content, "\n") { + if width := lipgloss.Width(line); width > size[0] { + t.Fatalf("%dx%d empty-state line %d is %d cells wide", size[0], size[1], index, width) + } + } + } m.messages = []chatMessage{ {role: "user", text: "Review this"}, {role: "assistant", text: "Working **carefully**.", streaming: true}, @@ -579,6 +608,9 @@ func TestFocusedCockpitMessageHierarchyAndEmptyState(t *testing.T) { if strings.Contains(history, "private details") { t.Fatalf("collapsed tool receipt exposed details: %s", history) } + if strings.Contains(history, "◈ OPENKYROZEN ◈") { + t.Fatal("empty-state banner remained after the conversation started") + } } func TestFocusedCockpitDoesNotDuplicateModelTelemetry(t *testing.T) { diff --git a/tui/theme.go b/tui/theme.go index c364275..fafd441 100644 --- a/tui/theme.go +++ b/tui/theme.go @@ -38,7 +38,6 @@ var ( contextStyle = lipgloss.NewStyle().Background(lipgloss.Color(deep)) badgeStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(cyan)).Background(lipgloss.Color(surfaceHi)).Bold(true).Padding(0, 1) sectionStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(muted)).Bold(true) - welcomeStyle = lipgloss.NewStyle().Background(lipgloss.Color(deep)).Border(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color(border)).Padding(1, 2) assistantStyle = lipgloss.NewStyle().BorderLeft(true).BorderForeground(lipgloss.Color(border)).PaddingLeft(1) userStyle = lipgloss.NewStyle().Background(lipgloss.Color(surface)).BorderLeft(true).BorderForeground(lipgloss.Color(cyan)).Padding(0, 1) thinkingStyle = lipgloss.NewStyle().BorderLeft(true).BorderForeground(lipgloss.Color(amber)).PaddingLeft(1)