detached ca585a7

style(status): polish session recency bar

commit
ca585a7b7b27c54bac9a8eae45241a5a370565e9
author
brice · · 2026-07-05 13:36 UTC
parent
5eef83d
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +33 -11
M internal/usecase/daemon/status.go +5 -3
@@ -77,7 +77,7 @@ func drawTopBarSnapshot(row []renderer.Cell, status statusSnapshot, frame int, s
7777 func drawStatusBarState(row []renderer.Cell, state barState, styles themeStyles) {
7878 clearStatusRow(row)
7979 x := 0
80- writeStatusText(row, &x, " "+state.status.session+" ", styles.statusBar)
80+ writeStatusText(row, &x, " "+state.status.session+" ", styles.accent)
8181
8282 fittedMRU := fitMRU(state.mru, len(row), x, state.copyFeedback)
8383 for i, sess := range fittedMRU {
@@ -91,6 +91,7 @@ func drawStatusBarState(row []renderer.Cell, state barState, styles themeStyles)
9191 writeStatusText(row, &x, " ", style)
9292 writeBell(row, &x, state.attentionFrame)
9393 }
94+ writeStatusText(row, &x, " ", style)
9495 }
9596 drawRightPlainText(row, state.copyFeedback, x, styles.statusBar)
9697 }
@@ -209,7 +210,7 @@ func fitMRU(entries []mruSession, rowLen, leftUsed int, feedback string) []mruSe
209210 return nil
210211 }
211212 cost := func(e mruSession) int {
212- n := 1 + len([]rune(e.name))
213+ n := 2 + len([]rune(e.name))
213214 if e.ephemeral {
214215 n++
215216 }
@@ -229,11 +230,12 @@ func fitMRU(entries []mruSession, rowLen, leftUsed int, feedback string) []mruSe
229230 }
230231
231232 func mruStyle(base renderer.Style, t themeui.Theme, i, count int) renderer.Style {
232- if count <= 1 || !base.HasForegroundRGB {
233+ if count <= 1 || !base.HasForegroundRGB || !base.HasBackgroundRGB || !t.HasBG {
233234 return base
234235 }
235236 amount := (float64(i) / float64(count-1)) * 0.6
236237 base.ForegroundRGB = themeui.Blend(base.ForegroundRGB, t.Background, amount)
238+ base.BackgroundRGB = themeui.Blend(base.BackgroundRGB, t.Background, amount)
237239 return base
238240 }
239241
@@ -77,7 +77,7 @@ func drawTopBarSnapshot(row []renderer.Cell, status statusSnapshot, frame int, s
77 func drawStatusBarState(row []renderer.Cell, state barState, styles themeStyles) {77 func drawStatusBarState(row []renderer.Cell, state barState, styles themeStyles) {
78 clearStatusRow(row)78 clearStatusRow(row)
79 x := 079 x := 0
80- writeStatusText(row, &x, " "+state.status.session+" ", styles.statusBar)80+ writeStatusText(row, &x, " "+state.status.session+" ", styles.accent)
81 81
82 fittedMRU := fitMRU(state.mru, len(row), x, state.copyFeedback)82 fittedMRU := fitMRU(state.mru, len(row), x, state.copyFeedback)
83 for i, sess := range fittedMRU {83 for i, sess := range fittedMRU {
@@ -91,6 +91,7 @@ func drawStatusBarState(row []renderer.Cell, state barState, styles themeStyles)
91 writeStatusText(row, &x, " ", style)91 writeStatusText(row, &x, " ", style)
92 writeBell(row, &x, state.attentionFrame)92 writeBell(row, &x, state.attentionFrame)
93 }93 }
94+ writeStatusText(row, &x, " ", style)
94 }95 }
95 drawRightPlainText(row, state.copyFeedback, x, styles.statusBar)96 drawRightPlainText(row, state.copyFeedback, x, styles.statusBar)
96 }97 }
@@ -209,7 +210,7 @@ func fitMRU(entries []mruSession, rowLen, leftUsed int, feedback string) []mruSe
209 return nil210 return nil
210 }211 }
211 cost := func(e mruSession) int {212 cost := func(e mruSession) int {
212- n := 1 + len([]rune(e.name))213+ n := 2 + len([]rune(e.name))
213 if e.ephemeral {214 if e.ephemeral {
214 n++215 n++
215 }216 }
@@ -229,11 +230,12 @@ func fitMRU(entries []mruSession, rowLen, leftUsed int, feedback string) []mruSe
229 }230 }
230 231
231 func mruStyle(base renderer.Style, t themeui.Theme, i, count int) renderer.Style {232 func mruStyle(base renderer.Style, t themeui.Theme, i, count int) renderer.Style {
232- if count <= 1 || !base.HasForegroundRGB {233+ if count <= 1 || !base.HasForegroundRGB || !base.HasBackgroundRGB || !t.HasBG {
233 return base234 return base
234 }235 }
235 amount := (float64(i) / float64(count-1)) * 0.6236 amount := (float64(i) / float64(count-1)) * 0.6
236 base.ForegroundRGB = themeui.Blend(base.ForegroundRGB, t.Background, amount)237 base.ForegroundRGB = themeui.Blend(base.ForegroundRGB, t.Background, amount)
238+ base.BackgroundRGB = themeui.Blend(base.BackgroundRGB, t.Background, amount)
237 return base239 return base
238 }240 }
239 241
M internal/usecase/daemon/status_test.go +28 -8
@@ -334,7 +334,7 @@ func TestStatusBarRendersMRUNamesEphemeralAndInlineBell(t *testing.T) {
334334
335335 drawStatusBarState(row, state, resolveThemeStyles(nil))
336336
337- require.Equal(t, " cur fresh tmp*  ", rowText(row))
337+ require.Equal(t, " cur fresh tmp*  ", rowText(row))
338338 for _, c := range row {
339339 if c.Rune == attentionGlyph {
340340 require.True(t, c.Style.Bold)
@@ -344,6 +344,19 @@ func TestStatusBarRendersMRUNamesEphemeralAndInlineBell(t *testing.T) {
344344 t.Fatalf("inline bell not rendered: %q", rowText(row))
345345 }
346346
347+func TestStatusBarCurrentSessionUsesAccentStyle(t *testing.T) {
348+ theme := themeui.Theme{Foreground: renderer.RGB{R: 220, G: 220, B: 220}, Background: renderer.RGB{R: 10, G: 10, B: 10}, HasFG: true, HasBG: true, TrueColor: true, Known: true}
349+ styles := newThemeStyles(theme)
350+ row := make([]renderer.Cell, 16)
351+
352+ drawStatusBarState(row, barState{status: statusSnapshot{session: "cur"}, theme: theme}, styles)
353+
354+ for _, idx := range []int{0, 1, 2, 3, 4} {
355+ require.True(t, row[idx].Style.Equal(styles.accent), "cell %d should use accent style", idx)
356+ }
357+ require.NotEqual(t, styles.statusBar.BackgroundRGB, styles.accent.BackgroundRGB)
358+}
359+
347360 func TestStatusBarMRUGradientTruecolorAndPlainFallback(t *testing.T) {
348361 theme := themeui.Theme{Foreground: renderer.RGB{R: 200, G: 200, B: 200}, Background: renderer.RGB{R: 20, G: 20, B: 20}, HasFG: true, HasBG: true, TrueColor: true, Known: true}
349362 styles := newThemeStyles(theme)
@@ -352,12 +365,19 @@ func TestStatusBarMRUGradientTruecolorAndPlainFallback(t *testing.T) {
352365
353366 drawStatusBarState(row, state, styles)
354367
355- first := row[3].Style.ForegroundRGB.R
356- second := row[5].Style.ForegroundRGB.R
357- third := row[7].Style.ForegroundRGB.R
358- require.Equal(t, styles.statusBar.ForegroundRGB.R, first)
359- require.Greater(t, first, second)
360- require.Greater(t, second, third)
368+ firstFG := row[4].Style.ForegroundRGB.R
369+ secondFG := row[7].Style.ForegroundRGB.R
370+ thirdFG := row[10].Style.ForegroundRGB.R
371+ require.Equal(t, styles.statusBar.ForegroundRGB.R, firstFG)
372+ require.Greater(t, firstFG, secondFG)
373+ require.Greater(t, secondFG, thirdFG)
374+
375+ firstBG := row[4].Style.BackgroundRGB.R
376+ secondBG := row[7].Style.BackgroundRGB.R
377+ thirdBG := row[10].Style.BackgroundRGB.R
378+ require.Equal(t, styles.statusBar.BackgroundRGB.R, firstBG)
379+ require.Greater(t, firstBG, secondBG)
380+ require.Greater(t, secondBG, thirdBG)
361381
362382 plain := mruStyle(renderer.DefaultStyle(), themeui.Theme{}, 1, 3)
363383 require.False(t, plain.HasForegroundRGB)
@@ -365,7 +385,7 @@ func TestStatusBarMRUGradientTruecolorAndPlainFallback(t *testing.T) {
365385
366386 func TestStatusBarNarrowRowsDropWholeOldestMRUEntries(t *testing.T) {
367387 state := barState{status: statusSnapshot{session: "cur"}, mru: []mruSession{{name: "fresh"}, {name: "middle"}, {name: "old"}}}
368- row := make([]renderer.Cell, 19)
388+ row := make([]renderer.Cell, 21)
369389
370390 drawStatusBarState(row, state, resolveThemeStyles(nil))
371391
@@ -334,7 +334,7 @@ func TestStatusBarRendersMRUNamesEphemeralAndInlineBell(t *testing.T) {
334 334
335 drawStatusBarState(row, state, resolveThemeStyles(nil))335 drawStatusBarState(row, state, resolveThemeStyles(nil))
336 336
337- require.Equal(t, " cur fresh tmp*  ", rowText(row))337+ require.Equal(t, " cur fresh tmp*  ", rowText(row))
338 for _, c := range row {338 for _, c := range row {
339 if c.Rune == attentionGlyph {339 if c.Rune == attentionGlyph {
340 require.True(t, c.Style.Bold)340 require.True(t, c.Style.Bold)
@@ -344,6 +344,19 @@ func TestStatusBarRendersMRUNamesEphemeralAndInlineBell(t *testing.T) {
344 t.Fatalf("inline bell not rendered: %q", rowText(row))344 t.Fatalf("inline bell not rendered: %q", rowText(row))
345 }345 }
346 346
347+func TestStatusBarCurrentSessionUsesAccentStyle(t *testing.T) {
348+ theme := themeui.Theme{Foreground: renderer.RGB{R: 220, G: 220, B: 220}, Background: renderer.RGB{R: 10, G: 10, B: 10}, HasFG: true, HasBG: true, TrueColor: true, Known: true}
349+ styles := newThemeStyles(theme)
350+ row := make([]renderer.Cell, 16)
351+
352+ drawStatusBarState(row, barState{status: statusSnapshot{session: "cur"}, theme: theme}, styles)
353+
354+ for _, idx := range []int{0, 1, 2, 3, 4} {
355+ require.True(t, row[idx].Style.Equal(styles.accent), "cell %d should use accent style", idx)
356+ }
357+ require.NotEqual(t, styles.statusBar.BackgroundRGB, styles.accent.BackgroundRGB)
358+}
359+
347 func TestStatusBarMRUGradientTruecolorAndPlainFallback(t *testing.T) {360 func TestStatusBarMRUGradientTruecolorAndPlainFallback(t *testing.T) {
348 theme := themeui.Theme{Foreground: renderer.RGB{R: 200, G: 200, B: 200}, Background: renderer.RGB{R: 20, G: 20, B: 20}, HasFG: true, HasBG: true, TrueColor: true, Known: true}361 theme := themeui.Theme{Foreground: renderer.RGB{R: 200, G: 200, B: 200}, Background: renderer.RGB{R: 20, G: 20, B: 20}, HasFG: true, HasBG: true, TrueColor: true, Known: true}
349 styles := newThemeStyles(theme)362 styles := newThemeStyles(theme)
@@ -352,12 +365,19 @@ func TestStatusBarMRUGradientTruecolorAndPlainFallback(t *testing.T) {
352 365
353 drawStatusBarState(row, state, styles)366 drawStatusBarState(row, state, styles)
354 367
355- first := row[3].Style.ForegroundRGB.R368+ firstFG := row[4].Style.ForegroundRGB.R
356- second := row[5].Style.ForegroundRGB.R369+ secondFG := row[7].Style.ForegroundRGB.R
357- third := row[7].Style.ForegroundRGB.R370+ thirdFG := row[10].Style.ForegroundRGB.R
358- require.Equal(t, styles.statusBar.ForegroundRGB.R, first)371+ require.Equal(t, styles.statusBar.ForegroundRGB.R, firstFG)
359- require.Greater(t, first, second)372+ require.Greater(t, firstFG, secondFG)
360- require.Greater(t, second, third)373+ require.Greater(t, secondFG, thirdFG)
374+
375+ firstBG := row[4].Style.BackgroundRGB.R
376+ secondBG := row[7].Style.BackgroundRGB.R
377+ thirdBG := row[10].Style.BackgroundRGB.R
378+ require.Equal(t, styles.statusBar.BackgroundRGB.R, firstBG)
379+ require.Greater(t, firstBG, secondBG)
380+ require.Greater(t, secondBG, thirdBG)
361 381
362 plain := mruStyle(renderer.DefaultStyle(), themeui.Theme{}, 1, 3)382 plain := mruStyle(renderer.DefaultStyle(), themeui.Theme{}, 1, 3)
363 require.False(t, plain.HasForegroundRGB)383 require.False(t, plain.HasForegroundRGB)
@@ -365,7 +385,7 @@ func TestStatusBarMRUGradientTruecolorAndPlainFallback(t *testing.T) {
365 385
366 func TestStatusBarNarrowRowsDropWholeOldestMRUEntries(t *testing.T) {386 func TestStatusBarNarrowRowsDropWholeOldestMRUEntries(t *testing.T) {
367 state := barState{status: statusSnapshot{session: "cur"}, mru: []mruSession{{name: "fresh"}, {name: "middle"}, {name: "old"}}}387 state := barState{status: statusSnapshot{session: "cur"}, mru: []mruSession{{name: "fresh"}, {name: "middle"}, {name: "old"}}}
368- row := make([]renderer.Cell, 19)388+ row := make([]renderer.Cell, 21)
369 389
370 drawStatusBarState(row, state, resolveThemeStyles(nil))390 drawStatusBarState(row, state, resolveThemeStyles(nil))
371 391

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close