style(status): reserve room beside recent sessions
- commit
- f92d01ca2db0e68f8b7555df9d79eaf705166706
- author
- brice · · 2026-07-05 14:31 UTC
- parent
- dae2682
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +78 -2
M internal/usecase/daemon/status.go +29 -2
| @@ -207,8 +207,8 @@ func fitMRU(entries []mruSession, rowLen, leftUsed int, feedback string) []mruSe | |||
| 207 | 207 | if feedback != "" { | |
| 208 | 208 | copyReserve = len([]rune(feedback)) + 2 | |
| 209 | 209 | } | |
| 210 | - | budget := rowLen - leftUsed - copyReserve | |
| 211 | - | if budget <= 0 || len(entries) == 0 { | |
| 210 | + | physicalBudget := rowLen - leftUsed - copyReserve | |
| 211 | + | if physicalBudget <= 0 || len(entries) == 0 { | |
| 212 | 212 | return nil | |
| 213 | 213 | } | |
| 214 | 214 | cost := func(e mruSession) int { | |
| @@ -221,6 +221,19 @@ func fitMRU(entries []mruSession, rowLen, leftUsed int, feedback string) []mruSe | |||
| 221 | 221 | } | |
| 222 | 222 | return n | |
| 223 | 223 | } | |
| 224 | + | ||
| 225 | + | budget := physicalBudget | |
| 226 | + | if feedback == "" { | |
| 227 | + | budget -= mruFutureRightReserve(rowLen) | |
| 228 | + | // Keep at least one recent session when it physically fits; the reserved | |
| 229 | + | // right side is only a budget preference, not a reason to hide all recents. | |
| 230 | + | if firstCost := cost(entries[0]); firstCost <= physicalBudget && budget < firstCost { | |
| 231 | + | budget = firstCost | |
| 232 | + | } | |
| 233 | + | } | |
| 234 | + | if budget <= 0 { | |
| 235 | + | return nil | |
| 236 | + | } | |
| 224 | 237 | used := 0 | |
| 225 | 238 | for i, e := range entries { | |
| 226 | 239 | used += cost(e) | |
| @@ -231,6 +244,20 @@ func fitMRU(entries []mruSession, rowLen, leftUsed int, feedback string) []mruSe | |||
| 231 | 244 | return entries | |
| 232 | 245 | } | |
| 233 | 246 | ||
| 247 | + | func mruFutureRightReserve(rowLen int) int { | |
| 248 | + | if rowLen < 40 { | |
| 249 | + | return 0 | |
| 250 | + | } | |
| 251 | + | reserve := rowLen / 4 | |
| 252 | + | if reserve < 12 { | |
| 253 | + | return 12 | |
| 254 | + | } | |
| 255 | + | if reserve > 24 { | |
| 256 | + | return 24 | |
| 257 | + | } | |
| 258 | + | return reserve | |
| 259 | + | } | |
| 260 | + | ||
| 234 | 261 | func mruStyle(base renderer.Style, t themeui.Theme, i, count int) renderer.Style { | |
| 235 | 262 | if count <= 1 || !base.HasForegroundRGB || !base.HasBackgroundRGB || !t.HasBG { | |
| 236 | 263 | return base | |
| @@ -207,8 +207,8 @@ func fitMRU(entries []mruSession, rowLen, leftUsed int, feedback string) []mruSe | |||||
| 207 | if feedback != "" { | 207 | if feedback != "" { | ||
| 208 | copyReserve = len([]rune(feedback)) + 2 | 208 | copyReserve = len([]rune(feedback)) + 2 | ||
| 209 | } | 209 | } | ||
| 210 | - | budget := rowLen - leftUsed - copyReserve | 210 | + | physicalBudget := rowLen - leftUsed - copyReserve |
| 211 | - | if budget <= 0 || len(entries) == 0 { | 211 | + | if physicalBudget <= 0 || len(entries) == 0 { |
| 212 | return nil | 212 | return nil | ||
| 213 | } | 213 | } | ||
| 214 | cost := func(e mruSession) int { | 214 | cost := func(e mruSession) int { | ||
| @@ -221,6 +221,19 @@ func fitMRU(entries []mruSession, rowLen, leftUsed int, feedback string) []mruSe | |||||
| 221 | } | 221 | } | ||
| 222 | return n | 222 | return n | ||
| 223 | } | 223 | } | ||
| 224 | + | ||||
| 225 | + | budget := physicalBudget | |||
| 226 | + | if feedback == "" { | |||
| 227 | + | budget -= mruFutureRightReserve(rowLen) | |||
| 228 | + | // Keep at least one recent session when it physically fits; the reserved | |||
| 229 | + | // right side is only a budget preference, not a reason to hide all recents. | |||
| 230 | + | if firstCost := cost(entries[0]); firstCost <= physicalBudget && budget < firstCost { | |||
| 231 | + | budget = firstCost | |||
| 232 | + | } | |||
| 233 | + | } | |||
| 234 | + | if budget <= 0 { | |||
| 235 | + | return nil | |||
| 236 | + | } | |||
| 224 | used := 0 | 237 | used := 0 | ||
| 225 | for i, e := range entries { | 238 | for i, e := range entries { | ||
| 226 | used += cost(e) | 239 | used += cost(e) | ||
| @@ -231,6 +244,20 @@ func fitMRU(entries []mruSession, rowLen, leftUsed int, feedback string) []mruSe | |||||
| 231 | return entries | 244 | return entries | ||
| 232 | } | 245 | } | ||
| 233 | 246 | ||||
| 247 | + | func mruFutureRightReserve(rowLen int) int { | |||
| 248 | + | if rowLen < 40 { | |||
| 249 | + | return 0 | |||
| 250 | + | } | |||
| 251 | + | reserve := rowLen / 4 | |||
| 252 | + | if reserve < 12 { | |||
| 253 | + | return 12 | |||
| 254 | + | } | |||
| 255 | + | if reserve > 24 { | |||
| 256 | + | return 24 | |||
| 257 | + | } | |||
| 258 | + | return reserve | |||
| 259 | + | } | |||
| 260 | + | ||||
| 234 | func mruStyle(base renderer.Style, t themeui.Theme, i, count int) renderer.Style { | 261 | func mruStyle(base renderer.Style, t themeui.Theme, i, count int) renderer.Style { | ||
| 235 | if count <= 1 || !base.HasForegroundRGB || !base.HasBackgroundRGB || !t.HasBG { | 262 | if count <= 1 || !base.HasForegroundRGB || !base.HasBackgroundRGB || !t.HasBG { | ||
| 236 | return base | 263 | return base | ||
M internal/usecase/daemon/status_test.go +49 -0
| @@ -395,6 +395,55 @@ func TestStatusBarNarrowRowsDropWholeOldestMRUEntries(t *testing.T) { | |||
| 395 | 395 | require.NotContains(t, text, "old") | |
| 396 | 396 | } | |
| 397 | 397 | ||
| 398 | + | func TestStatusBarMRUWidthAwareBudget(t *testing.T) { | |
| 399 | + | mru := make([]mruSession, 0, maxMRUSessions) | |
| 400 | + | for i := 1; i <= maxMRUSessions; i++ { | |
| 401 | + | mru = append(mru, mruSession{name: "recent" + strconv.Itoa(i)}) | |
| 402 | + | } | |
| 403 | + | state := barState{status: statusSnapshot{session: "cur"}, mru: mru} | |
| 404 | + | ||
| 405 | + | tests := []struct { | |
| 406 | + | name string | |
| 407 | + | cols int | |
| 408 | + | wantShown []string | |
| 409 | + | wantHidden []string | |
| 410 | + | }{ | |
| 411 | + | { | |
| 412 | + | name: "narrow keeps first recent when it physically fits", | |
| 413 | + | cols: 15, | |
| 414 | + | wantShown: []string{"cur", "recent1"}, | |
| 415 | + | wantHidden: []string{"recent2"}, | |
| 416 | + | }, | |
| 417 | + | { | |
| 418 | + | name: "medium reserves right-side room instead of filling all recents", | |
| 419 | + | cols: 80, | |
| 420 | + | wantShown: []string{"recent1", "recent6"}, | |
| 421 | + | wantHidden: []string{"recent7", "recent9"}, | |
| 422 | + | }, | |
| 423 | + | { | |
| 424 | + | name: "wide shows more recents while still keeping a right-side reserve", | |
| 425 | + | cols: 120, | |
| 426 | + | wantShown: []string{"recent1", "recent9"}, | |
| 427 | + | wantHidden: nil, | |
| 428 | + | }, | |
| 429 | + | } | |
| 430 | + | for _, tt := range tests { | |
| 431 | + | t.Run(tt.name, func(t *testing.T) { | |
| 432 | + | row := make([]renderer.Cell, tt.cols) | |
| 433 | + | ||
| 434 | + | drawStatusBarState(row, state, resolveThemeStyles(nil)) | |
| 435 | + | ||
| 436 | + | text := rowText(row) | |
| 437 | + | for _, want := range tt.wantShown { | |
| 438 | + | require.Contains(t, text, want) | |
| 439 | + | } | |
| 440 | + | for _, hidden := range tt.wantHidden { | |
| 441 | + | require.NotContains(t, text, hidden) | |
| 442 | + | } | |
| 443 | + | }) | |
| 444 | + | } | |
| 445 | + | } | |
| 446 | + | ||
| 398 | 447 | func TestStatusBarCopyFeedbackFullyRenderedAlongsideMRU(t *testing.T) { | |
| 399 | 448 | state := barState{status: statusSnapshot{session: "cur"}, copyFeedback: "copied", mru: []mruSession{{name: "a"}, {name: "b"}, {name: "c"}}} | |
| 400 | 449 | row := make([]renderer.Cell, 20) | |
| @@ -395,6 +395,55 @@ func TestStatusBarNarrowRowsDropWholeOldestMRUEntries(t *testing.T) { | |||||
| 395 | require.NotContains(t, text, "old") | 395 | require.NotContains(t, text, "old") | ||
| 396 | } | 396 | } | ||
| 397 | 397 | ||||
| 398 | + | func TestStatusBarMRUWidthAwareBudget(t *testing.T) { | |||
| 399 | + | mru := make([]mruSession, 0, maxMRUSessions) | |||
| 400 | + | for i := 1; i <= maxMRUSessions; i++ { | |||
| 401 | + | mru = append(mru, mruSession{name: "recent" + strconv.Itoa(i)}) | |||
| 402 | + | } | |||
| 403 | + | state := barState{status: statusSnapshot{session: "cur"}, mru: mru} | |||
| 404 | + | ||||
| 405 | + | tests := []struct { | |||
| 406 | + | name string | |||
| 407 | + | cols int | |||
| 408 | + | wantShown []string | |||
| 409 | + | wantHidden []string | |||
| 410 | + | }{ | |||
| 411 | + | { | |||
| 412 | + | name: "narrow keeps first recent when it physically fits", | |||
| 413 | + | cols: 15, | |||
| 414 | + | wantShown: []string{"cur", "recent1"}, | |||
| 415 | + | wantHidden: []string{"recent2"}, | |||
| 416 | + | }, | |||
| 417 | + | { | |||
| 418 | + | name: "medium reserves right-side room instead of filling all recents", | |||
| 419 | + | cols: 80, | |||
| 420 | + | wantShown: []string{"recent1", "recent6"}, | |||
| 421 | + | wantHidden: []string{"recent7", "recent9"}, | |||
| 422 | + | }, | |||
| 423 | + | { | |||
| 424 | + | name: "wide shows more recents while still keeping a right-side reserve", | |||
| 425 | + | cols: 120, | |||
| 426 | + | wantShown: []string{"recent1", "recent9"}, | |||
| 427 | + | wantHidden: nil, | |||
| 428 | + | }, | |||
| 429 | + | } | |||
| 430 | + | for _, tt := range tests { | |||
| 431 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 432 | + | row := make([]renderer.Cell, tt.cols) | |||
| 433 | + | ||||
| 434 | + | drawStatusBarState(row, state, resolveThemeStyles(nil)) | |||
| 435 | + | ||||
| 436 | + | text := rowText(row) | |||
| 437 | + | for _, want := range tt.wantShown { | |||
| 438 | + | require.Contains(t, text, want) | |||
| 439 | + | } | |||
| 440 | + | for _, hidden := range tt.wantHidden { | |||
| 441 | + | require.NotContains(t, text, hidden) | |||
| 442 | + | } | |||
| 443 | + | }) | |||
| 444 | + | } | |||
| 445 | + | } | |||
| 446 | + | ||||
| 398 | func TestStatusBarCopyFeedbackFullyRenderedAlongsideMRU(t *testing.T) { | 447 | func TestStatusBarCopyFeedbackFullyRenderedAlongsideMRU(t *testing.T) { | ||
| 399 | state := barState{status: statusSnapshot{session: "cur"}, copyFeedback: "copied", mru: []mruSession{{name: "a"}, {name: "b"}, {name: "c"}}} | 448 | state := barState{status: statusSnapshot{session: "cur"}, copyFeedback: "copied", mru: []mruSession{{name: "a"}, {name: "b"}, {name: "c"}}} | ||
| 400 | row := make([]renderer.Cell, 20) | 449 | row := make([]renderer.Cell, 20) | ||