style(status): polish session recency bar
- commit
- ca585a7b7b27c54bac9a8eae45241a5a370565e9
- author
- brice · · 2026-07-05 13:36 UTC
- parent
- 5eef83d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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 | |||
| 77 | 77 | func drawStatusBarState(row []renderer.Cell, state barState, styles themeStyles) { | |
| 78 | 78 | clearStatusRow(row) | |
| 79 | 79 | x := 0 | |
| 80 | - | writeStatusText(row, &x, " "+state.status.session+" ", styles.statusBar) | |
| 80 | + | writeStatusText(row, &x, " "+state.status.session+" ", styles.accent) | |
| 81 | 81 | ||
| 82 | 82 | fittedMRU := fitMRU(state.mru, len(row), x, state.copyFeedback) | |
| 83 | 83 | for i, sess := range fittedMRU { | |
| @@ -91,6 +91,7 @@ func drawStatusBarState(row []renderer.Cell, state barState, styles themeStyles) | |||
| 91 | 91 | writeStatusText(row, &x, " ", style) | |
| 92 | 92 | writeBell(row, &x, state.attentionFrame) | |
| 93 | 93 | } | |
| 94 | + | writeStatusText(row, &x, " ", style) | |
| 94 | 95 | } | |
| 95 | 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 | 210 | return nil | |
| 210 | 211 | } | |
| 211 | 212 | cost := func(e mruSession) int { | |
| 212 | - | n := 1 + len([]rune(e.name)) | |
| 213 | + | n := 2 + len([]rune(e.name)) | |
| 213 | 214 | if e.ephemeral { | |
| 214 | 215 | n++ | |
| 215 | 216 | } | |
| @@ -229,11 +230,12 @@ func fitMRU(entries []mruSession, rowLen, leftUsed int, feedback string) []mruSe | |||
| 229 | 230 | } | |
| 230 | 231 | ||
| 231 | 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 | 234 | return base | |
| 234 | 235 | } | |
| 235 | 236 | amount := (float64(i) / float64(count-1)) * 0.6 | |
| 236 | 237 | base.ForegroundRGB = themeui.Blend(base.ForegroundRGB, t.Background, amount) | |
| 238 | + | base.BackgroundRGB = themeui.Blend(base.BackgroundRGB, t.Background, amount) | |
| 237 | 239 | return base | |
| 238 | 240 | } | |
| 239 | 241 | ||
| @@ -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 := 0 | 79 | 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 nil | 210 | 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 base | 234 | return base | ||
| 234 | } | 235 | } | ||
| 235 | amount := (float64(i) / float64(count-1)) * 0.6 | 236 | 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 base | 239 | return base | ||
| 238 | } | 240 | } | ||
| 239 | 241 | ||||
M internal/usecase/daemon/status_test.go +28 -8
| @@ -334,7 +334,7 @@ func TestStatusBarRendersMRUNamesEphemeralAndInlineBell(t *testing.T) { | |||
| 334 | 334 | ||
| 335 | 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 | 338 | for _, c := range row { | |
| 339 | 339 | if c.Rune == attentionGlyph { | |
| 340 | 340 | require.True(t, c.Style.Bold) | |
| @@ -344,6 +344,19 @@ func TestStatusBarRendersMRUNamesEphemeralAndInlineBell(t *testing.T) { | |||
| 344 | 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 | 360 | func TestStatusBarMRUGradientTruecolorAndPlainFallback(t *testing.T) { | |
| 348 | 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 | 362 | styles := newThemeStyles(theme) | |
| @@ -352,12 +365,19 @@ func TestStatusBarMRUGradientTruecolorAndPlainFallback(t *testing.T) { | |||
| 352 | 365 | ||
| 353 | 366 | drawStatusBarState(row, state, styles) | |
| 354 | 367 | ||
| 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) | |
| 361 | 381 | ||
| 362 | 382 | plain := mruStyle(renderer.DefaultStyle(), themeui.Theme{}, 1, 3) | |
| 363 | 383 | require.False(t, plain.HasForegroundRGB) | |
| @@ -365,7 +385,7 @@ func TestStatusBarMRUGradientTruecolorAndPlainFallback(t *testing.T) { | |||
| 365 | 385 | ||
| 366 | 386 | func TestStatusBarNarrowRowsDropWholeOldestMRUEntries(t *testing.T) { | |
| 367 | 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 | 390 | drawStatusBarState(row, state, resolveThemeStyles(nil)) | |
| 371 | 391 | ||
| @@ -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.R | 368 | + | firstFG := row[4].Style.ForegroundRGB.R |
| 356 | - | second := row[5].Style.ForegroundRGB.R | 369 | + | secondFG := row[7].Style.ForegroundRGB.R |
| 357 | - | third := row[7].Style.ForegroundRGB.R | 370 | + | 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 | ||||