From 7a282d3a2fbde12686e30854058fe88f99b8193a Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 17 Jul 2025 12:26:28 -0400 Subject: [PATCH 1/7] fix(tui): render a smaller logo on splash screen When we have a smaller window, we render a smaller version of the Crush logo. --- .../tui/components/chat/sidebar/sidebar.go | 32 +++++++------------ internal/tui/components/chat/splash/splash.go | 21 ++++++++---- internal/tui/components/logo/logo.go | 14 ++++++++ 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/internal/tui/components/chat/sidebar/sidebar.go b/internal/tui/components/chat/sidebar/sidebar.go index ecb88e7d60dbaf09dfc2e88f789ed0057ee756fc..058b238fb6a070dcbc833673006aa44f43cd97c0 100644 --- a/internal/tui/components/chat/sidebar/sidebar.go +++ b/internal/tui/components/chat/sidebar/sidebar.go @@ -114,12 +114,22 @@ func (m *sidebarCmp) View() string { t := styles.CurrentTheme() parts := []string{} + style := t.S().Base. + Width(m.width). + Height(m.height). + Padding(1) + if m.compactMode { + style = style.PaddingTop(0) + } + if !m.compactMode { if m.height > LogoHeightBreakpoint { parts = append(parts, m.logo) } else { // Use a smaller logo for smaller screens - parts = append(parts, m.smallerScreenLogo(), "") + parts = append(parts, + logo.SmallRender(m.width-style.GetHorizontalFrameSize()), + "") } } @@ -159,13 +169,6 @@ func (m *sidebarCmp) View() string { ) } - style := t.S().Base. - Width(m.width). - Height(m.height). - Padding(1) - if m.compactMode { - style = style.PaddingTop(0) - } return style.Render( lipgloss.JoinVertical(lipgloss.Left, parts...), ) @@ -934,19 +937,6 @@ func (s *sidebarCmp) currentModelBlock() string { ) } -func (m *sidebarCmp) smallerScreenLogo() string { - t := styles.CurrentTheme() - title := t.S().Base.Foreground(t.Secondary).Render("Charm™") - title += " " + styles.ApplyBoldForegroundGrad("CRUSH", t.Secondary, t.Primary) - remainingWidth := m.width - lipgloss.Width(title) - 3 - if remainingWidth > 0 { - char := "╱" - lines := strings.Repeat(char, remainingWidth) - title += " " + t.S().Base.Foreground(t.Primary).Render(lines) - } - return title -} - // SetSession implements Sidebar. func (m *sidebarCmp) SetSession(session session.Session) tea.Cmd { m.session = session diff --git a/internal/tui/components/chat/splash/splash.go b/internal/tui/components/chat/splash/splash.go index 0ba04f6d16f2b93ac5556cd204fe63bcca5594e2..8dd30ec0258b83e52e80cf3af2baa05727857ea1 100644 --- a/internal/tui/components/chat/splash/splash.go +++ b/internal/tui/components/chat/splash/splash.go @@ -134,10 +134,8 @@ func (s *splashCmp) Init() tea.Cmd { // SetSize implements SplashPage. func (s *splashCmp) SetSize(width int, height int) tea.Cmd { s.height = height - if width != s.width { - s.width = width - s.logoRendered = s.logoBlock() - } + s.width = width + s.logoRendered = s.logoBlock() // remove padding, logo height, gap, title space s.listHeight = s.height - lipgloss.Height(s.logoRendered) - (SplashScreenPaddingY * 2) - s.logoGap() - 2 listWidth := min(60, width) @@ -467,14 +465,25 @@ func (s *splashCmp) infoSection() string { func (s *splashCmp) logoBlock() string { t := styles.CurrentTheme() - return t.S().Base.Padding(0, 2).Width(s.width).Render( + logoStyle := t.S().Base.Padding(0, 2).Width(s.width) + if s.width < 40 || s.height < 20 { + // If the width is too small, render a smaller version of the logo + // NOTE: 20 is not correct because [splashCmp.height] is not the + // *actual* window height, instead, it is the height of the splash + // component and that depends on other variables like compact mode and + // the height of the editor. + return logoStyle.Render( + logo.SmallRender(s.width - logoStyle.GetHorizontalFrameSize()), + ) + } + return logoStyle.Render( logo.Render(version.Version, false, logo.Opts{ FieldColor: t.Primary, TitleColorA: t.Secondary, TitleColorB: t.Primary, CharmColor: t.Secondary, VersionColor: t.Primary, - Width: s.width - 4, + Width: s.width - logoStyle.GetHorizontalFrameSize(), }), ) } diff --git a/internal/tui/components/logo/logo.go b/internal/tui/components/logo/logo.go index 195830632f1af0a681bb3075042a76a24d814155..a4c9ac455405f6eb912ac46a5a1de83c5cd6fb53 100644 --- a/internal/tui/components/logo/logo.go +++ b/internal/tui/components/logo/logo.go @@ -110,6 +110,20 @@ func Render(version string, compact bool, o Opts) string { return logo } +// SmallRender renders a smaller version of the Crush logo, suitable for +// smaller windows or sidebar usage. +func SmallRender(width int) string { + t := styles.CurrentTheme() + title := t.S().Base.Foreground(t.Secondary).Render("Charm™") + title = fmt.Sprintf("%s %s", title, styles.ApplyBoldForegroundGrad("Crush", t.Secondary, t.Primary)) + remainingWidth := width - lipgloss.Width(title) - 1 // 1 for the space after "Crush" + if remainingWidth > 0 { + lines := strings.Repeat("╱", remainingWidth) + title = fmt.Sprintf("%s %s", title, t.S().Base.Foreground(t.Primary).Render(lines)) + } + return title +} + // renderWord renders letterforms to fork a word. func renderWord(spacing int, stretchRandomLetter bool, letterforms ...letterform) string { if spacing < 0 { From 2a8998ef00f6b1e00d4e6dab13bac50eff543938 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 17 Jul 2025 12:34:55 -0400 Subject: [PATCH 2/7] fix(tui): logo: simplify logo word stretching --- internal/tui/components/logo/logo.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/internal/tui/components/logo/logo.go b/internal/tui/components/logo/logo.go index a4c9ac455405f6eb912ac46a5a1de83c5cd6fb53..c5902477b944602bd9b70398541631b3362b2e5f 100644 --- a/internal/tui/components/logo/logo.go +++ b/internal/tui/components/logo/logo.go @@ -44,13 +44,19 @@ func Render(version string, compact bool, o Opts) string { // Title. const spacing = 1 - crush := renderWord(spacing, !compact, + letterforms := []letterform{ letterC, letterR, letterU, letterSStylized, letterH, - ) + } + stretchIndex := -1 // -1 means no stretching. + if !compact { + stretchIndex = rand.IntN(len(letterforms)) + } + + crush := renderWord(spacing, stretchIndex, letterforms...) crushWidth := lipgloss.Width(crush) b := new(strings.Builder) for r := range strings.SplitSeq(crush, "\n") { @@ -124,8 +130,9 @@ func SmallRender(width int) string { return title } -// renderWord renders letterforms to fork a word. -func renderWord(spacing int, stretchRandomLetter bool, letterforms ...letterform) string { +// renderWord renders letterforms to fork a word. stretchIndex is the index of +// the letter to stretch, or -1 if no letter should be stretched. +func renderWord(spacing int, stretchIndex int, letterforms ...letterform) string { if spacing < 0 { spacing = 0 } @@ -133,11 +140,6 @@ func renderWord(spacing int, stretchRandomLetter bool, letterforms ...letterform renderedLetterforms := make([]string, len(letterforms)) // pick one letter randomly to stretch - stretchIndex := -1 - if stretchRandomLetter { - stretchIndex = rand.IntN(len(letterforms)) //nolint:gosec - } - for i, letter := range letterforms { renderedLetterforms[i] = letter(i == stretchIndex) } From 85b13fcd8100d9752b10865d97f672eb25e7f63e Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 17 Jul 2025 13:27:49 -0400 Subject: [PATCH 3/7] fix(tui): splash: cache logo rendering on resize Only re-render the logo when the width changes. --- internal/tui/components/chat/splash/splash.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/tui/components/chat/splash/splash.go b/internal/tui/components/chat/splash/splash.go index 8dd30ec0258b83e52e80cf3af2baa05727857ea1..60fd5e42bcdd2f608d596590accb9c9734c0435c 100644 --- a/internal/tui/components/chat/splash/splash.go +++ b/internal/tui/components/chat/splash/splash.go @@ -133,9 +133,12 @@ func (s *splashCmp) Init() tea.Cmd { // SetSize implements SplashPage. func (s *splashCmp) SetSize(width int, height int) tea.Cmd { + rerenderLogo := width != s.width s.height = height s.width = width - s.logoRendered = s.logoBlock() + if rerenderLogo { + s.logoRendered = s.logoBlock() + } // remove padding, logo height, gap, title space s.listHeight = s.height - lipgloss.Height(s.logoRendered) - (SplashScreenPaddingY * 2) - s.logoGap() - 2 listWidth := min(60, width) From 2929b21f4cfa8a23ed4199282e29bfc68becb4ec Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 17 Jul 2025 15:02:43 -0400 Subject: [PATCH 4/7] fix(tui): splash: make sure we have a margin after the logo on small screens --- internal/tui/components/chat/splash/splash.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/tui/components/chat/splash/splash.go b/internal/tui/components/chat/splash/splash.go index 60fd5e42bcdd2f608d596590accb9c9734c0435c..2a6e3be3aa498fd23dfe06f663dd8c8859e6dbef 100644 --- a/internal/tui/components/chat/splash/splash.go +++ b/internal/tui/components/chat/splash/splash.go @@ -453,9 +453,19 @@ func (s *splashCmp) Cursor() *tea.Cursor { return nil } +func (s *splashCmp) isSmallScreen() bool { + // Consider a screen small if either the width is less than 40 or if the + // height is less than 20 + return s.width < 40 || s.height < 20 +} + func (s *splashCmp) infoSection() string { t := styles.CurrentTheme() - return t.S().Base.PaddingLeft(2).Render( + infoStyle := t.S().Base.PaddingLeft(2) + if s.isSmallScreen() { + infoStyle = infoStyle.MarginTop(1) + } + return infoStyle.Render( lipgloss.JoinVertical( lipgloss.Left, s.cwd(), @@ -469,7 +479,7 @@ func (s *splashCmp) infoSection() string { func (s *splashCmp) logoBlock() string { t := styles.CurrentTheme() logoStyle := t.S().Base.Padding(0, 2).Width(s.width) - if s.width < 40 || s.height < 20 { + if s.isSmallScreen() { // If the width is too small, render a smaller version of the logo // NOTE: 20 is not correct because [splashCmp.height] is not the // *actual* window height, instead, it is the height of the splash From 3196bb37d64f6c4ed8068029a0a5b70812f958ac Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 17 Jul 2025 15:31:33 -0400 Subject: [PATCH 5/7] fix(tui): splash: re-render logo when screen size changes --- internal/tui/components/chat/splash/splash.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/tui/components/chat/splash/splash.go b/internal/tui/components/chat/splash/splash.go index 2a6e3be3aa498fd23dfe06f663dd8c8859e6dbef..163e8be44765404ce2f1f92e9371c2dcd6f67dd0 100644 --- a/internal/tui/components/chat/splash/splash.go +++ b/internal/tui/components/chat/splash/splash.go @@ -133,10 +133,11 @@ func (s *splashCmp) Init() tea.Cmd { // SetSize implements SplashPage. func (s *splashCmp) SetSize(width int, height int) tea.Cmd { + wasSmallScreen := s.isSmallScreen() rerenderLogo := width != s.width s.height = height s.width = width - if rerenderLogo { + if rerenderLogo || wasSmallScreen != s.isSmallScreen() { s.logoRendered = s.logoBlock() } // remove padding, logo height, gap, title space From 8a16b8e38536d94119183985e49bdb4f5f752cdb Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 17 Jul 2025 15:43:40 -0400 Subject: [PATCH 6/7] fix(tui): chat: adjust padding for message list The gap between the message list and the editor was too large. 3 lines of padding can be too much. 1 line gets added after each message list item as a "gap", there's the padding at the bottom of the message list, and there's the editor top padding. In this commit, we removed the bottom padding of the message list, keeping both the gap that gets added after each message list item and the padding at the top of the editor. --- internal/tui/components/chat/chat.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/tui/components/chat/chat.go b/internal/tui/components/chat/chat.go index 091231039c71e24b918a755d56ba0a0de27ae509..8d857ea38463e9d61dc25794e492b33cab0b487b 100644 --- a/internal/tui/components/chat/chat.go +++ b/internal/tui/components/chat/chat.go @@ -107,7 +107,7 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *messageListCmp) View() string { t := styles.CurrentTheme() return t.S().Base. - Padding(1). + Padding(1, 1, 0, 1). Width(m.width). Height(m.height). Render( @@ -508,7 +508,7 @@ func (m *messageListCmp) GetSize() (int, int) { func (m *messageListCmp) SetSize(width int, height int) tea.Cmd { m.width = width m.height = height - return m.listCmp.SetSize(width-2, height-2) // for padding + return m.listCmp.SetSize(width-2, height-1) // for padding } // Blur implements MessageListCmp. From a5b76d3a3fffbcc78e3f3aae823cf62dc1410b50 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 21 Jul 2025 10:04:41 -0400 Subject: [PATCH 7/7] fix(tui): splash: trigger smaller logo on 55 columns Co-authored-by: Andrey Nering --- internal/tui/components/chat/splash/splash.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/tui/components/chat/splash/splash.go b/internal/tui/components/chat/splash/splash.go index 163e8be44765404ce2f1f92e9371c2dcd6f67dd0..58fb48c2b2e290525ff88e02bb2354c5e2e2a6f6 100644 --- a/internal/tui/components/chat/splash/splash.go +++ b/internal/tui/components/chat/splash/splash.go @@ -457,7 +457,7 @@ func (s *splashCmp) Cursor() *tea.Cursor { func (s *splashCmp) isSmallScreen() bool { // Consider a screen small if either the width is less than 40 or if the // height is less than 20 - return s.width < 40 || s.height < 20 + return s.width < 55 || s.height < 20 } func (s *splashCmp) infoSection() string {