feat(picker): show sort mode in modal title
- commit
- d9b9a0afe38df5ab668689c68e92249fe344ae89
- author
- brice · · 2026-07-29 13:57 UTC
- parent
- 18c1193
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +33 -3
M internal/usecase/daemon/move_picker.go +1 -0
| @@ -179,6 +179,7 @@ func (d *Daemon) refreshPickerOpts(ac *attachedClient, opts pickerRefreshOptions | |||
| 179 | 179 | updated := rt.picker != nil && rt.pickerIntent == intent && rt.pickerSource == source | |
| 180 | 180 | if updated { | |
| 181 | 181 | rt.picker = model | |
| 182 | + | rt.pickerTitle = pickerTitle(pickerSortMode(d.pickerSort.Load())) | |
| 182 | 183 | } | |
| 183 | 184 | rt.pickerMu.Unlock() | |
| 184 | 185 | if updated { | |
| @@ -179,6 +179,7 @@ func (d *Daemon) refreshPickerOpts(ac *attachedClient, opts pickerRefreshOptions | |||||
| 179 | updated := rt.picker != nil && rt.pickerIntent == intent && rt.pickerSource == source | 179 | updated := rt.picker != nil && rt.pickerIntent == intent && rt.pickerSource == source | ||
| 180 | if updated { | 180 | if updated { | ||
| 181 | rt.picker = model | 181 | rt.picker = model | ||
| 182 | + | rt.pickerTitle = pickerTitle(pickerSortMode(d.pickerSort.Load())) | |||
| 182 | } | 183 | } | ||
| 183 | rt.pickerMu.Unlock() | 184 | rt.pickerMu.Unlock() | ||
| 184 | if updated { | 185 | if updated { | ||
M internal/usecase/daemon/overlay_runtime.go +3 -0
| @@ -18,6 +18,7 @@ type overlayRuntime struct { | |||
| 18 | 18 | ||
| 19 | 19 | pickerMu sync.Mutex | |
| 20 | 20 | picker *picker.Model | |
| 21 | + | pickerTitle string | |
| 21 | 22 | pickerIntent pickerIntent | |
| 22 | 23 | pickerSource moveSourceLocator | |
| 23 | 24 | pickerPreview *tab | |
| @@ -381,6 +382,7 @@ type overlayRenderSnapshot struct { | |||
| 381 | 382 | ||
| 382 | 383 | pickerActive bool | |
| 383 | 384 | pickerModel *picker.Model | |
| 385 | + | pickerTitle string | |
| 384 | 386 | previewTab *tab | |
| 385 | 387 | ||
| 386 | 388 | noticesOverlayActive bool | |
| @@ -451,6 +453,7 @@ func (rt *overlayRuntime) SnapshotForRender() *overlayRenderSnapshot { | |||
| 451 | 453 | rt.pickerMu.Lock() | |
| 452 | 454 | snap.pickerActive = rt.picker != nil | |
| 453 | 455 | snap.pickerModel = rt.picker.Clone() | |
| 456 | + | snap.pickerTitle = rt.pickerTitle | |
| 454 | 457 | snap.previewTab = rt.pickerPreview | |
| 455 | 458 | rt.pickerMu.Unlock() | |
| 456 | 459 | ||
| @@ -18,6 +18,7 @@ type overlayRuntime struct { | |||||
| 18 | 18 | ||||
| 19 | pickerMu sync.Mutex | 19 | pickerMu sync.Mutex | ||
| 20 | picker *picker.Model | 20 | picker *picker.Model | ||
| 21 | + | pickerTitle string | |||
| 21 | pickerIntent pickerIntent | 22 | pickerIntent pickerIntent | ||
| 22 | pickerSource moveSourceLocator | 23 | pickerSource moveSourceLocator | ||
| 23 | pickerPreview *tab | 24 | pickerPreview *tab | ||
| @@ -381,6 +382,7 @@ type overlayRenderSnapshot struct { | |||||
| 381 | 382 | ||||
| 382 | pickerActive bool | 383 | pickerActive bool | ||
| 383 | pickerModel *picker.Model | 384 | pickerModel *picker.Model | ||
| 385 | + | pickerTitle string | |||
| 384 | previewTab *tab | 386 | previewTab *tab | ||
| 385 | 387 | ||||
| 386 | noticesOverlayActive bool | 388 | noticesOverlayActive bool | ||
| @@ -451,6 +453,7 @@ func (rt *overlayRuntime) SnapshotForRender() *overlayRenderSnapshot { | |||||
| 451 | rt.pickerMu.Lock() | 453 | rt.pickerMu.Lock() | ||
| 452 | snap.pickerActive = rt.picker != nil | 454 | snap.pickerActive = rt.picker != nil | ||
| 453 | snap.pickerModel = rt.picker.Clone() | 455 | snap.pickerModel = rt.picker.Clone() | ||
| 456 | + | snap.pickerTitle = rt.pickerTitle | |||
| 454 | snap.previewTab = rt.pickerPreview | 457 | snap.previewTab = rt.pickerPreview | ||
| 455 | rt.pickerMu.Unlock() | 458 | rt.pickerMu.Unlock() | ||
| 456 | 459 | ||||
M internal/usecase/daemon/picker.go +2 -0
| @@ -48,6 +48,7 @@ func (d *Daemon) publishPicker(sess *session, ac *attachedClient, model *picker. | |||
| 48 | 48 | rt.pickerPreviewSession = nil | |
| 49 | 49 | rt.pickerPreview = nil | |
| 50 | 50 | rt.picker = model | |
| 51 | + | rt.pickerTitle = pickerTitle(pickerSortMode(d.pickerSort.Load())) | |
| 51 | 52 | rt.pickerIntent = intent | |
| 52 | 53 | rt.pickerSource = source | |
| 53 | 54 | rt.pickerPending = nil | |
| @@ -452,6 +453,7 @@ func (d *Daemon) clearDestroyedTabPreview(tb *tab) { | |||
| 452 | 453 | func (d *Daemon) closePicker(ac *attachedClient) { | |
| 453 | 454 | ac.overlays.pickerMu.Lock() | |
| 454 | 455 | ac.overlays.picker = nil | |
| 456 | + | ac.overlays.pickerTitle = "" | |
| 455 | 457 | ac.overlays.pickerIntent = pickerNavigate | |
| 456 | 458 | ac.overlays.pickerSource = moveSourceLocator{} | |
| 457 | 459 | ac.overlays.pickerPending = nil | |
| @@ -48,6 +48,7 @@ func (d *Daemon) publishPicker(sess *session, ac *attachedClient, model *picker. | |||||
| 48 | rt.pickerPreviewSession = nil | 48 | rt.pickerPreviewSession = nil | ||
| 49 | rt.pickerPreview = nil | 49 | rt.pickerPreview = nil | ||
| 50 | rt.picker = model | 50 | rt.picker = model | ||
| 51 | + | rt.pickerTitle = pickerTitle(pickerSortMode(d.pickerSort.Load())) | |||
| 51 | rt.pickerIntent = intent | 52 | rt.pickerIntent = intent | ||
| 52 | rt.pickerSource = source | 53 | rt.pickerSource = source | ||
| 53 | rt.pickerPending = nil | 54 | rt.pickerPending = nil | ||
| @@ -452,6 +453,7 @@ func (d *Daemon) clearDestroyedTabPreview(tb *tab) { | |||||
| 452 | func (d *Daemon) closePicker(ac *attachedClient) { | 453 | func (d *Daemon) closePicker(ac *attachedClient) { | ||
| 453 | ac.overlays.pickerMu.Lock() | 454 | ac.overlays.pickerMu.Lock() | ||
| 454 | ac.overlays.picker = nil | 455 | ac.overlays.picker = nil | ||
| 456 | + | ac.overlays.pickerTitle = "" | |||
| 455 | ac.overlays.pickerIntent = pickerNavigate | 457 | ac.overlays.pickerIntent = pickerNavigate | ||
| 456 | ac.overlays.pickerSource = moveSourceLocator{} | 458 | ac.overlays.pickerSource = moveSourceLocator{} | ||
| 457 | ac.overlays.pickerPending = nil | 459 | ac.overlays.pickerPending = nil | ||
M internal/usecase/daemon/picker_test.go +20 -0
| @@ -237,9 +237,11 @@ func TestPickerSortToggleFlipsModeAndKeepsSelection(t *testing.T) { | |||
| 237 | 237 | require.True(t, ac.overlays.pickerActive()) | |
| 238 | 238 | ac.overlays.pickerMu.Lock() | |
| 239 | 239 | selected, ok := ac.overlays.picker.Selected() | |
| 240 | + | title := ac.overlays.pickerTitle | |
| 240 | 241 | ac.overlays.pickerMu.Unlock() | |
| 241 | 242 | require.True(t, ok) | |
| 242 | 243 | require.Equal(t, before, selected) | |
| 244 | + | require.Equal(t, " Sessions · grouped ", title) | |
| 243 | 245 | ||
| 244 | 246 | d.handleInput(sess, ac, []byte("s")) | |
| 245 | 247 | awaitFrame(t, sends, ports.MsgOutput) | |
| @@ -248,9 +250,27 @@ func TestPickerSortToggleFlipsModeAndKeepsSelection(t *testing.T) { | |||
| 248 | 250 | require.True(t, ac.overlays.pickerActive()) | |
| 249 | 251 | ac.overlays.pickerMu.Lock() | |
| 250 | 252 | selected, ok = ac.overlays.picker.Selected() | |
| 253 | + | title = ac.overlays.pickerTitle | |
| 251 | 254 | ac.overlays.pickerMu.Unlock() | |
| 252 | 255 | require.True(t, ok) | |
| 253 | 256 | require.Equal(t, before, selected) | |
| 257 | + | require.Equal(t, " Sessions · recent ", title) | |
| 258 | + | }) | |
| 259 | + | } | |
| 260 | + | } | |
| 261 | + | ||
| 262 | + | func TestPickerTitleReflectsSortMode(t *testing.T) { | |
| 263 | + | tests := []struct { | |
| 264 | + | name string | |
| 265 | + | mode pickerSortMode | |
| 266 | + | want string | |
| 267 | + | }{ | |
| 268 | + | {name: "recent", mode: pickerSortRecent, want: " Sessions · recent "}, | |
| 269 | + | {name: "grouped", mode: pickerSortGrouped, want: " Sessions · grouped "}, | |
| 270 | + | } | |
| 271 | + | for _, tc := range tests { | |
| 272 | + | t.Run(tc.name, func(t *testing.T) { | |
| 273 | + | require.Equal(t, tc.want, pickerTitle(tc.mode)) | |
| 254 | 274 | }) | |
| 255 | 275 | } | |
| 256 | 276 | } | |
| @@ -237,9 +237,11 @@ func TestPickerSortToggleFlipsModeAndKeepsSelection(t *testing.T) { | |||||
| 237 | require.True(t, ac.overlays.pickerActive()) | 237 | require.True(t, ac.overlays.pickerActive()) | ||
| 238 | ac.overlays.pickerMu.Lock() | 238 | ac.overlays.pickerMu.Lock() | ||
| 239 | selected, ok := ac.overlays.picker.Selected() | 239 | selected, ok := ac.overlays.picker.Selected() | ||
| 240 | + | title := ac.overlays.pickerTitle | |||
| 240 | ac.overlays.pickerMu.Unlock() | 241 | ac.overlays.pickerMu.Unlock() | ||
| 241 | require.True(t, ok) | 242 | require.True(t, ok) | ||
| 242 | require.Equal(t, before, selected) | 243 | require.Equal(t, before, selected) | ||
| 244 | + | require.Equal(t, " Sessions · grouped ", title) | |||
| 243 | 245 | ||||
| 244 | d.handleInput(sess, ac, []byte("s")) | 246 | d.handleInput(sess, ac, []byte("s")) | ||
| 245 | awaitFrame(t, sends, ports.MsgOutput) | 247 | awaitFrame(t, sends, ports.MsgOutput) | ||
| @@ -248,9 +250,27 @@ func TestPickerSortToggleFlipsModeAndKeepsSelection(t *testing.T) { | |||||
| 248 | require.True(t, ac.overlays.pickerActive()) | 250 | require.True(t, ac.overlays.pickerActive()) | ||
| 249 | ac.overlays.pickerMu.Lock() | 251 | ac.overlays.pickerMu.Lock() | ||
| 250 | selected, ok = ac.overlays.picker.Selected() | 252 | selected, ok = ac.overlays.picker.Selected() | ||
| 253 | + | title = ac.overlays.pickerTitle | |||
| 251 | ac.overlays.pickerMu.Unlock() | 254 | ac.overlays.pickerMu.Unlock() | ||
| 252 | require.True(t, ok) | 255 | require.True(t, ok) | ||
| 253 | require.Equal(t, before, selected) | 256 | require.Equal(t, before, selected) | ||
| 257 | + | require.Equal(t, " Sessions · recent ", title) | |||
| 258 | + | }) | |||
| 259 | + | } | |||
| 260 | + | } | |||
| 261 | + | ||||
| 262 | + | func TestPickerTitleReflectsSortMode(t *testing.T) { | |||
| 263 | + | tests := []struct { | |||
| 264 | + | name string | |||
| 265 | + | mode pickerSortMode | |||
| 266 | + | want string | |||
| 267 | + | }{ | |||
| 268 | + | {name: "recent", mode: pickerSortRecent, want: " Sessions · recent "}, | |||
| 269 | + | {name: "grouped", mode: pickerSortGrouped, want: " Sessions · grouped "}, | |||
| 270 | + | } | |||
| 271 | + | for _, tc := range tests { | |||
| 272 | + | t.Run(tc.name, func(t *testing.T) { | |||
| 273 | + | require.Equal(t, tc.want, pickerTitle(tc.mode)) | |||
| 254 | }) | 274 | }) | ||
| 255 | } | 275 | } | ||
| 256 | } | 276 | } | ||
M internal/usecase/daemon/render_pipeline.go +5 -1
| @@ -230,7 +230,11 @@ func captureOverlayLayers(state *capturedRenderState, snap *overlayRenderSnapsho | |||
| 230 | 230 | } | |
| 231 | 231 | if snap.pickerActive && snap.pickerModel != nil { | |
| 232 | 232 | presentation := pickerModal.Resolve(size) | |
| 233 | - | o.picker = capturedModal{active: true, title: pickerModal.Title, presentation: presentation, focused: true} | |
| 233 | + | title := snap.pickerTitle | |
| 234 | + | if title == "" { | |
| 235 | + | title = pickerModal.Title | |
| 236 | + | } | |
| 237 | + | o.picker = capturedModal{active: true, title: title, presentation: presentation, focused: true} | |
| 234 | 238 | renderStyles := picker.RenderStyles{Background: styles.PickerBase, Selection: styles.PickerSelection, SelectionName: styles.PickerSelectionName, SelectionMuted: styles.PickerSelectionMuted, Name: styles.PickerName, Detail: styles.PickerDescription, Base: styles.PickerBase, Separator: styles.PickerSeparator} | |
| 235 | 239 | o.picker.inner = snap.pickerModel.Render(rectSize(presentation.Inner), state.preview, renderStyles) | |
| 236 | 240 | } | |
| @@ -230,7 +230,11 @@ func captureOverlayLayers(state *capturedRenderState, snap *overlayRenderSnapsho | |||||
| 230 | } | 230 | } | ||
| 231 | if snap.pickerActive && snap.pickerModel != nil { | 231 | if snap.pickerActive && snap.pickerModel != nil { | ||
| 232 | presentation := pickerModal.Resolve(size) | 232 | presentation := pickerModal.Resolve(size) | ||
| 233 | - | o.picker = capturedModal{active: true, title: pickerModal.Title, presentation: presentation, focused: true} | 233 | + | title := snap.pickerTitle |
| 234 | + | if title == "" { | |||
| 235 | + | title = pickerModal.Title | |||
| 236 | + | } | |||
| 237 | + | o.picker = capturedModal{active: true, title: title, presentation: presentation, focused: true} | |||
| 234 | renderStyles := picker.RenderStyles{Background: styles.PickerBase, Selection: styles.PickerSelection, SelectionName: styles.PickerSelectionName, SelectionMuted: styles.PickerSelectionMuted, Name: styles.PickerName, Detail: styles.PickerDescription, Base: styles.PickerBase, Separator: styles.PickerSeparator} | 238 | renderStyles := picker.RenderStyles{Background: styles.PickerBase, Selection: styles.PickerSelection, SelectionName: styles.PickerSelectionName, SelectionMuted: styles.PickerSelectionMuted, Name: styles.PickerName, Detail: styles.PickerDescription, Base: styles.PickerBase, Separator: styles.PickerSeparator} | ||
| 235 | o.picker.inner = snap.pickerModel.Render(rectSize(presentation.Inner), state.preview, renderStyles) | 239 | o.picker.inner = snap.pickerModel.Render(rectSize(presentation.Inner), state.preview, renderStyles) | ||
| 236 | } | 240 | } | ||
M internal/usecase/daemon/render_test.go +2 -2
| @@ -107,7 +107,7 @@ func TestPaletteBackdropDimsSimultaneousPicker(t *testing.T) { | |||
| 107 | 107 | ||
| 108 | 108 | d.enterPicker(sess, ac) | |
| 109 | 109 | mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput)) | |
| 110 | - | pickerTitle := client.Frame.At(36, 2) | |
| 110 | + | pickerTitle := client.Frame.At(31, 2) | |
| 111 | 111 | require.Equal(t, 'S', pickerTitle.Rune, "fixture must address the picker title") | |
| 112 | 112 | undimmedPane := client.Frame.At(0, 1) | |
| 113 | 113 | ||
| @@ -115,7 +115,7 @@ func TestPaletteBackdropDimsSimultaneousPicker(t *testing.T) { | |||
| 115 | 115 | mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput)) | |
| 116 | 116 | dimmedPickerTitle := pickerTitle | |
| 117 | 117 | dimmedPickerTitle.Style = themeui.NewDimmer(backdropTheme()).Dim(pickerTitle.Style) | |
| 118 | - | require.Equal(t, dimmedPickerTitle, client.Frame.At(36, 2), "the lower-priority picker must be part of the palette backdrop") | |
| 118 | + | require.Equal(t, dimmedPickerTitle, client.Frame.At(31, 2), "the lower-priority picker must be part of the palette backdrop") | |
| 119 | 119 | require.Equal(t, themeui.NewDimmer(backdropTheme()).Dim(undimmedPane.Style), client.Frame.At(0, 1).Style, "pane content outside overlays must use the theme dim style") | |
| 120 | 120 | } | |
| 121 | 121 | ||
| @@ -107,7 +107,7 @@ func TestPaletteBackdropDimsSimultaneousPicker(t *testing.T) { | |||||
| 107 | 107 | ||||
| 108 | d.enterPicker(sess, ac) | 108 | d.enterPicker(sess, ac) | ||
| 109 | mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput)) | 109 | mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput)) | ||
| 110 | - | pickerTitle := client.Frame.At(36, 2) | 110 | + | pickerTitle := client.Frame.At(31, 2) |
| 111 | require.Equal(t, 'S', pickerTitle.Rune, "fixture must address the picker title") | 111 | require.Equal(t, 'S', pickerTitle.Rune, "fixture must address the picker title") | ||
| 112 | undimmedPane := client.Frame.At(0, 1) | 112 | undimmedPane := client.Frame.At(0, 1) | ||
| 113 | 113 | ||||
| @@ -115,7 +115,7 @@ func TestPaletteBackdropDimsSimultaneousPicker(t *testing.T) { | |||||
| 115 | mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput)) | 115 | mustApplyOutput(t, client, awaitFrame(t, sends, ports.MsgOutput)) | ||
| 116 | dimmedPickerTitle := pickerTitle | 116 | dimmedPickerTitle := pickerTitle | ||
| 117 | dimmedPickerTitle.Style = themeui.NewDimmer(backdropTheme()).Dim(pickerTitle.Style) | 117 | dimmedPickerTitle.Style = themeui.NewDimmer(backdropTheme()).Dim(pickerTitle.Style) | ||
| 118 | - | require.Equal(t, dimmedPickerTitle, client.Frame.At(36, 2), "the lower-priority picker must be part of the palette backdrop") | 118 | + | require.Equal(t, dimmedPickerTitle, client.Frame.At(31, 2), "the lower-priority picker must be part of the palette backdrop") |
| 119 | require.Equal(t, themeui.NewDimmer(backdropTheme()).Dim(undimmedPane.Style), client.Frame.At(0, 1).Style, "pane content outside overlays must use the theme dim style") | 119 | require.Equal(t, themeui.NewDimmer(backdropTheme()).Dim(undimmedPane.Style), client.Frame.At(0, 1).Style, "pane content outside overlays must use the theme dim style") | ||
| 120 | } | 120 | } | ||
| 121 | 121 | ||||