Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions tui/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
36 changes: 34 additions & 2 deletions tui/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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) {
Expand Down
1 change: 0 additions & 1 deletion tui/theme.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading