Skip to content

Commit b3deb16

Browse files
committed
fix: show context window percentage in statusbar
- Replace meaningless token count (@721) with ctx: N% - Color-code: muted <75%, amber 75-89%, red 90%+ - Fix integer division rounding to zero for small percentages - Remove SetContextCount, keep only SetContextPercent - Toast always reserves 1 line to prevent layout shifts - Remove gapBeforeToast padding
1 parent 68c2401 commit b3deb16

3 files changed

Lines changed: 31 additions & 43 deletions

File tree

‎internal/app/app.go‎

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ const (
4848
horizontalPadding = 1
4949
verticalPadding = 1
5050
gapAfterStatusBar = 1
51-
gapBeforeToast = 1
5251

5352
minWidth = 50
5453
minHeight = 25
@@ -233,11 +232,9 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
233232
}()
234233
}
235234
// Update context window usage in statusbar
236-
if msg.InputTokens > 0 {
237-
m.statusBar.SetContextCount(msg.InputTokens + msg.OutputTokens)
238-
}
239235
if msg.InputTokens > 0 && msg.ContextWindow > 0 {
240-
m.statusBar.SetContextPercent(msg.InputTokens * 100 / msg.ContextWindow)
236+
pct := (msg.InputTokens*100 + msg.ContextWindow - 1) / msg.ContextWindow // round up
237+
m.statusBar.SetContextPercent(pct)
241238
}
242239
}
243240

@@ -285,10 +282,8 @@ func (m *Model) updateLayout() {
285282
keyBarHeight := m.keyBar.Height()
286283

287284
// Page is flexible - gets remaining height
288-
pageHeight := contentHeight - statusBarHeight - gapAfterStatusBar - keyBarHeight
289-
if toastHeight > 0 {
290-
pageHeight -= toastHeight + gapBeforeToast
291-
}
285+
// Toast always reserves its line to prevent layout shifts
286+
pageHeight := contentHeight - statusBarHeight - gapAfterStatusBar - toastHeight - keyBarHeight
292287

293288
switch m.state {
294289
case stateOnboarding:
@@ -450,10 +445,8 @@ func (m *Model) renderContent() string {
450445
}
451446

452447
// Calculate page height (flexible component gets remaining space)
453-
pageHeight := contentHeight - statusBarHeight - gapAfterStatusBar - keyBarHeight
454-
if toastHeight > 0 {
455-
pageHeight -= toastHeight + gapBeforeToast
456-
}
448+
// Toast always reserves its line to prevent layout shifts
449+
pageHeight := contentHeight - statusBarHeight - gapAfterStatusBar - toastHeight - keyBarHeight
457450

458451
// Style page to fill its allocated space
459452
styledPage := lipgloss.NewStyle().
@@ -467,10 +460,7 @@ func (m *Model) renderContent() string {
467460
sections = append(sections, statusBarView)
468461
sections = append(sections, "") // gapAfterStatusBar
469462
sections = append(sections, styledPage)
470-
if toastHeight > 0 {
471-
sections = append(sections, "") // gapBeforeToast
472-
sections = append(sections, toastView)
473-
}
463+
sections = append(sections, toastView)
474464
if keyBarHeight > 0 {
475465
sections = append(sections, keyBarView)
476466
}

‎internal/app/statusbar/statusbar.go‎

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package statusbar
33

44
import (
55
"fmt"
6+
"image/color"
67
"strings"
78

89
tea "charm.land/bubbletea/v2"
@@ -31,9 +32,6 @@ type Model struct {
3132
// Conversation
3233
title string
3334

34-
// Context
35-
contextCount int
36-
3735
// Context window usage (0-100)
3836
contextPercent int
3937
}
@@ -88,11 +86,6 @@ func (m *Model) SetTitle(title string) {
8886
m.title = title
8987
}
9088

91-
// SetContextCount sets the number of entities in context.
92-
func (m *Model) SetContextCount(count int) {
93-
m.contextCount = count
94-
}
95-
9689
// SetContextPercent sets the context window usage percentage.
9790
func (m *Model) SetContextPercent(percent int) {
9891
m.contextPercent = percent
@@ -112,7 +105,6 @@ func (m *Model) View() string {
112105
colors := m.theme.Colors
113106
diagStyle := lipgloss.NewStyle().Foreground(colors.Brand.GradientEnd)
114107
sepStyle := lipgloss.NewStyle().Foreground(colors.Page.TextMuted)
115-
mutedStyle := lipgloss.NewStyle().Foreground(colors.Page.TextMuted)
116108

117109
sep := sepStyle.Render(" │ ")
118110

@@ -150,18 +142,9 @@ func (m *Model) View() string {
150142
}
151143
}
152144

153-
// 5. Context count (if fits)
154-
if m.contextCount > 0 {
155-
ctxSeg := mutedStyle.Render(fmt.Sprintf("@%d", m.contextCount))
156-
testContent := strings.Join(segments, sep) + sep + ctxSeg
157-
if lipgloss.Width(testContent) < m.width-8 {
158-
segments = append(segments, ctxSeg)
159-
}
160-
}
161-
162-
// 6. Context percent (if fits)
145+
// 5. Context window usage (if fits)
163146
if m.contextPercent > 0 {
164-
pctSeg := mutedStyle.Render(fmt.Sprintf("%d%%", m.contextPercent))
147+
pctSeg := m.renderContextPercent()
165148
testContent := strings.Join(segments, sep) + sep + pctSeg
166149
if lipgloss.Width(testContent) < m.width-3 {
167150
segments = append(segments, pctSeg)
@@ -224,3 +207,21 @@ func (m *Model) renderTitle(maxWidth int) string {
224207

225208
return style.Render("\"" + title + "\"")
226209
}
210+
211+
// renderContextPercent renders "ctx: N%" with color based on usage level.
212+
func (m *Model) renderContextPercent() string {
213+
colors := m.theme.Colors
214+
215+
var fg color.Color
216+
switch {
217+
case m.contextPercent >= 90:
218+
fg = colors.Error.Fg
219+
case m.contextPercent >= 75:
220+
fg = colors.Warning.Fg
221+
default:
222+
fg = colors.Page.TextMuted
223+
}
224+
225+
style := lipgloss.NewStyle().Foreground(fg)
226+
return style.Render(fmt.Sprintf("ctx: %d%%", m.contextPercent))
227+
}

‎internal/app/toast/toast.go‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,8 @@ func (m *Model) SetWidth(width int) {
5656
}
5757

5858
// Height returns the number of lines this component renders.
59-
// Returns 0 when no toast is showing, 1 when a toast is visible.
59+
// Always returns 1 to reserve space and prevent layout shifts.
6060
func (m *Model) Height() int {
61-
if m.current == nil {
62-
return 0
63-
}
6461
return 1
6562
}
6663

@@ -91,10 +88,10 @@ func (m *Model) show(t toast) tea.Cmd {
9188
return clearAfter(DefaultTTL)
9289
}
9390

94-
// View renders the toast. Returns empty string if no toast.
91+
// View renders the toast. Returns an empty line if no toast to reserve space.
9592
func (m *Model) View() string {
9693
if m.current == nil {
97-
return ""
94+
return " "
9895
}
9996

10097
colors := m.theme.Colors

0 commit comments

Comments
 (0)