detached 8f95b0e

fix(palette): integrate wrap-up findings

commit
8f95b0e103b06a470df04b5d02e2620d9a888006
author
brice · · 2026-07-15 07:20 UTC
committer
brice · · 2026-07-15 07:49 UTC
parent
77cef84
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +27 -22
M internal/usecase/daemon/config_test.go +4 -5
@@ -8,7 +8,6 @@ import (
88 "github.com/bnema/vev/internal/domain"
99 "github.com/bnema/vev/internal/ports"
1010 "github.com/bnema/vev/internal/usecase/keys"
11- "github.com/bnema/vev/internal/usecase/palette"
1211 )
1312
1413 type captureKeyHandler struct {
@@ -98,12 +97,12 @@ func TestApplyConfigRepaintsActivePaletteWithoutReplacingModel(t *testing.T) {
9897 require.Equal(t, query, ac.overlays.palette.Query())
9998 selectedAfter, ok := ac.overlays.palette.Selected()
10099 require.True(t, ok)
101- selectedCommand, ok := selected.(palette.CommandResult)
100+ selectedCommand, ok := selected.Command()
102101 require.True(t, ok)
103- selectedAfterCommand, ok := selectedAfter.(palette.CommandResult)
102+ selectedAfterCommand, ok := selectedAfter.Command()
104103 require.True(t, ok)
105- require.Equal(t, selectedCommand.Command().Slug, selectedAfterCommand.Command().Slug)
106- require.Equal(t, selectedCommand.Command().Code, selectedAfterCommand.Command().Code)
104+ require.Equal(t, selectedCommand.Slug, selectedAfterCommand.Slug)
105+ require.Equal(t, selectedCommand.Code, selectedAfterCommand.Code)
107106 }
108107
109108 func TestApplyConfigPublishesImmutableFloatingSnapshot(t *testing.T) {
@@ -8,7 +8,6 @@ import (
8 "github.com/bnema/vev/internal/domain"8 "github.com/bnema/vev/internal/domain"
9 "github.com/bnema/vev/internal/ports"9 "github.com/bnema/vev/internal/ports"
10 "github.com/bnema/vev/internal/usecase/keys"10 "github.com/bnema/vev/internal/usecase/keys"
11- "github.com/bnema/vev/internal/usecase/palette"
12 )11 )
13 12
14 type captureKeyHandler struct {13 type captureKeyHandler struct {
@@ -98,12 +97,12 @@ func TestApplyConfigRepaintsActivePaletteWithoutReplacingModel(t *testing.T) {
98 require.Equal(t, query, ac.overlays.palette.Query())97 require.Equal(t, query, ac.overlays.palette.Query())
99 selectedAfter, ok := ac.overlays.palette.Selected()98 selectedAfter, ok := ac.overlays.palette.Selected()
100 require.True(t, ok)99 require.True(t, ok)
101- selectedCommand, ok := selected.(palette.CommandResult)100+ selectedCommand, ok := selected.Command()
102 require.True(t, ok)101 require.True(t, ok)
103- selectedAfterCommand, ok := selectedAfter.(palette.CommandResult)102+ selectedAfterCommand, ok := selectedAfter.Command()
104 require.True(t, ok)103 require.True(t, ok)
105- require.Equal(t, selectedCommand.Command().Slug, selectedAfterCommand.Command().Slug)104+ require.Equal(t, selectedCommand.Slug, selectedAfterCommand.Slug)
106- require.Equal(t, selectedCommand.Command().Code, selectedAfterCommand.Command().Code)105+ require.Equal(t, selectedCommand.Code, selectedAfterCommand.Code)
107 }106 }
108 107
109 func TestApplyConfigPublishesImmutableFloatingSnapshot(t *testing.T) {108 func TestApplyConfigPublishesImmutableFloatingSnapshot(t *testing.T) {
M internal/usecase/daemon/palette.go +14 -8
@@ -67,7 +67,7 @@ func (d *Daemon) paletteResults(current *session, commands []command.Command) []
6767 for _, cmd := range commands {
6868 results = append(results, palette.NewCommandResult(cmd))
6969 }
70- active := make([]palette.SessionResult, 0, len(sessions))
70+ active := make([]palette.Result, 0, len(sessions))
7171 for _, candidate := range sessions {
7272 if candidate == current {
7373 continue
@@ -80,7 +80,11 @@ func (d *Daemon) paletteResults(current *session, commands []command.Command) []
8080 active = append(active, palette.NewActiveSessionResult(name, time.Unix(0, createdAt), id))
8181 }
8282 }
83- sort.Slice(active, func(i, j int) bool { return active[i].Name() < active[j].Name() })
83+ sort.Slice(active, func(i, j int) bool {
84+ left, _ := active[i].SessionName()
85+ right, _ := active[j].SessionName()
86+ return left < right
87+ })
8488 sort.Slice(stopped, func(i, j int) bool { return stopped[i].name < stopped[j].name })
8589 for _, candidate := range active {
8690 results = append(results, candidate)
@@ -150,7 +154,7 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) {
150154 return
151155 }
152156 var cmd command.Command
153- var sessionTarget palette.SessionResult
157+ var sessionTarget palette.Result
154158 var hasSessionTarget bool
155159 var args []string
156160 var recent []recentSession
@@ -209,7 +213,7 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) {
209213 return
210214 }
211215 rawQuery = ac.overlays.palette.Query()
212- if selectedCommand, ok := selected.CommandInfo(); ok {
216+ if selectedCommand, ok := selected.Command(); ok {
213217 cmd = selectedCommand
214218 // JRS is contextual: fuzzy selection cannot turn an unrelated
215219 // query into a jump.
@@ -222,8 +226,8 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) {
222226 args = action.Args
223227 recent = append([]recentSession(nil), ac.overlays.paletteRecent...)
224228 }
225- } else if selectedSession, ok := selected.Session(); ok {
226- sessionTarget = selectedSession
229+ } else if _, ok := selected.SessionName(); ok {
230+ sessionTarget = selected
227231 hasSessionTarget = true
228232 } else {
229233 changed = true
@@ -258,8 +262,10 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) {
258262 }
259263
260264 if hasSessionTarget {
261- createdAt := sessionTarget.CreatedAt().UnixNano()
262- target := picker.Target{Name: sessionTarget.Name(), TabIndex: -1, ExpectedCreatedAt: &createdAt}
265+ createdAt, _ := sessionTarget.SessionCreatedAt()
266+ expectedCreatedAt := createdAt.UnixNano()
267+ name, _ := sessionTarget.SessionName()
268+ target := picker.Target{Name: name, TabIndex: -1, ExpectedCreatedAt: &expectedCreatedAt}
263269 if id, active := sessionTarget.SessionID(); active {
264270 target.Session = id
265271 } else {
@@ -67,7 +67,7 @@ func (d *Daemon) paletteResults(current *session, commands []command.Command) []
67 for _, cmd := range commands {67 for _, cmd := range commands {
68 results = append(results, palette.NewCommandResult(cmd))68 results = append(results, palette.NewCommandResult(cmd))
69 }69 }
70- active := make([]palette.SessionResult, 0, len(sessions))70+ active := make([]palette.Result, 0, len(sessions))
71 for _, candidate := range sessions {71 for _, candidate := range sessions {
72 if candidate == current {72 if candidate == current {
73 continue73 continue
@@ -80,7 +80,11 @@ func (d *Daemon) paletteResults(current *session, commands []command.Command) []
80 active = append(active, palette.NewActiveSessionResult(name, time.Unix(0, createdAt), id))80 active = append(active, palette.NewActiveSessionResult(name, time.Unix(0, createdAt), id))
81 }81 }
82 }82 }
83- sort.Slice(active, func(i, j int) bool { return active[i].Name() < active[j].Name() })83+ sort.Slice(active, func(i, j int) bool {
84+ left, _ := active[i].SessionName()
85+ right, _ := active[j].SessionName()
86+ return left < right
87+ })
84 sort.Slice(stopped, func(i, j int) bool { return stopped[i].name < stopped[j].name })88 sort.Slice(stopped, func(i, j int) bool { return stopped[i].name < stopped[j].name })
85 for _, candidate := range active {89 for _, candidate := range active {
86 results = append(results, candidate)90 results = append(results, candidate)
@@ -150,7 +154,7 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) {
150 return154 return
151 }155 }
152 var cmd command.Command156 var cmd command.Command
153- var sessionTarget palette.SessionResult157+ var sessionTarget palette.Result
154 var hasSessionTarget bool158 var hasSessionTarget bool
155 var args []string159 var args []string
156 var recent []recentSession160 var recent []recentSession
@@ -209,7 +213,7 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) {
209 return213 return
210 }214 }
211 rawQuery = ac.overlays.palette.Query()215 rawQuery = ac.overlays.palette.Query()
212- if selectedCommand, ok := selected.CommandInfo(); ok {216+ if selectedCommand, ok := selected.Command(); ok {
213 cmd = selectedCommand217 cmd = selectedCommand
214 // JRS is contextual: fuzzy selection cannot turn an unrelated218 // JRS is contextual: fuzzy selection cannot turn an unrelated
215 // query into a jump.219 // query into a jump.
@@ -222,8 +226,8 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) {
222 args = action.Args226 args = action.Args
223 recent = append([]recentSession(nil), ac.overlays.paletteRecent...)227 recent = append([]recentSession(nil), ac.overlays.paletteRecent...)
224 }228 }
225- } else if selectedSession, ok := selected.Session(); ok {229+ } else if _, ok := selected.SessionName(); ok {
226- sessionTarget = selectedSession230+ sessionTarget = selected
227 hasSessionTarget = true231 hasSessionTarget = true
228 } else {232 } else {
229 changed = true233 changed = true
@@ -258,8 +262,10 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) {
258 }262 }
259 263
260 if hasSessionTarget {264 if hasSessionTarget {
261- createdAt := sessionTarget.CreatedAt().UnixNano()265+ createdAt, _ := sessionTarget.SessionCreatedAt()
262- target := picker.Target{Name: sessionTarget.Name(), TabIndex: -1, ExpectedCreatedAt: &createdAt}266+ expectedCreatedAt := createdAt.UnixNano()
267+ name, _ := sessionTarget.SessionName()
268+ target := picker.Target{Name: name, TabIndex: -1, ExpectedCreatedAt: &expectedCreatedAt}
263 if id, active := sessionTarget.SessionID(); active {269 if id, active := sessionTarget.SessionID(); active {
264 target.Session = id270 target.Session = id
265 } else {271 } else {
M internal/usecase/daemon/palette_test.go +9 -9
@@ -63,8 +63,8 @@ func TestPaletteEntryPublishesEligibleNamedSessionResults(t *testing.T) {
6363
6464 got := map[string]palette.ResultKind{}
6565 for _, match := range ac.overlays.palette.Matches() {
66- if result, ok := match.Result.(palette.SessionResult); ok {
67- got[result.Name()] = result.Kind()
66+ if name, ok := match.Result.SessionName(); ok {
67+ got[name] = match.Result.Kind()
6868 }
6969 }
7070 require.Equal(t, map[string]palette.ResultKind{
@@ -85,7 +85,7 @@ func TestPaletteSessionFailureFeedbackClearsOnQueryChange(t *testing.T) {
8585 ac.overlays.paletteMu.Lock()
8686 selected, ok := ac.overlays.palette.Selected()
8787 ac.overlays.paletteMu.Unlock()
88- _, isSession := selected.(palette.SessionResult)
88+ _, isSession := selected.SessionName()
8989 require.True(t, ok)
9090 require.True(t, isSession)
9191 d.mu.Lock()
@@ -455,9 +455,9 @@ func TestPaletteReopensWithSuccessfulCommandFirst(t *testing.T) {
455455 awaitFrame(t, sends, ports.MsgOutput)
456456 result, ok := ac.overlays.palette.Selected()
457457 require.True(t, ok)
458- cmd, ok := result.(palette.CommandResult)
458+ cmd, ok := result.Command()
459459 require.True(t, ok)
460- require.Equal(t, "NXT", cmd.Command().Code)
460+ require.Equal(t, "NXT", cmd.Code)
461461 }
462462
463463 func TestPaletteRecentCommandsNewestFirstThenRegistryOrder(t *testing.T) {
@@ -568,17 +568,17 @@ func TestPaletteCtrlNAndCtrlPNavigate(t *testing.T) {
568568 awaitFrame(t, sends, ports.MsgOutput)
569569 result, ok := ac.overlays.palette.Selected()
570570 require.True(t, ok)
571- cmd, ok := result.(palette.CommandResult)
571+ cmd, ok := result.Command()
572572 require.True(t, ok)
573- require.Equal(t, "CNS", cmd.Command().Code)
573+ require.Equal(t, "CNS", cmd.Code)
574574
575575 d.handlePaletteInput(ac, []byte{0x10})
576576 awaitFrame(t, sends, ports.MsgOutput)
577577 result, ok = ac.overlays.palette.Selected()
578578 require.True(t, ok)
579- cmd, ok = result.(palette.CommandResult)
579+ cmd, ok = result.Command()
580580 require.True(t, ok)
581- require.Equal(t, "CNT", cmd.Command().Code)
581+ require.Equal(t, "CNT", cmd.Code)
582582 }
583583
584584 func TestPaletteModalGeometry(t *testing.T) {
@@ -63,8 +63,8 @@ func TestPaletteEntryPublishesEligibleNamedSessionResults(t *testing.T) {
63 63
64 got := map[string]palette.ResultKind{}64 got := map[string]palette.ResultKind{}
65 for _, match := range ac.overlays.palette.Matches() {65 for _, match := range ac.overlays.palette.Matches() {
66- if result, ok := match.Result.(palette.SessionResult); ok {66+ if name, ok := match.Result.SessionName(); ok {
67- got[result.Name()] = result.Kind()67+ got[name] = match.Result.Kind()
68 }68 }
69 }69 }
70 require.Equal(t, map[string]palette.ResultKind{70 require.Equal(t, map[string]palette.ResultKind{
@@ -85,7 +85,7 @@ func TestPaletteSessionFailureFeedbackClearsOnQueryChange(t *testing.T) {
85 ac.overlays.paletteMu.Lock()85 ac.overlays.paletteMu.Lock()
86 selected, ok := ac.overlays.palette.Selected()86 selected, ok := ac.overlays.palette.Selected()
87 ac.overlays.paletteMu.Unlock()87 ac.overlays.paletteMu.Unlock()
88- _, isSession := selected.(palette.SessionResult)88+ _, isSession := selected.SessionName()
89 require.True(t, ok)89 require.True(t, ok)
90 require.True(t, isSession)90 require.True(t, isSession)
91 d.mu.Lock()91 d.mu.Lock()
@@ -455,9 +455,9 @@ func TestPaletteReopensWithSuccessfulCommandFirst(t *testing.T) {
455 awaitFrame(t, sends, ports.MsgOutput)455 awaitFrame(t, sends, ports.MsgOutput)
456 result, ok := ac.overlays.palette.Selected()456 result, ok := ac.overlays.palette.Selected()
457 require.True(t, ok)457 require.True(t, ok)
458- cmd, ok := result.(palette.CommandResult)458+ cmd, ok := result.Command()
459 require.True(t, ok)459 require.True(t, ok)
460- require.Equal(t, "NXT", cmd.Command().Code)460+ require.Equal(t, "NXT", cmd.Code)
461 }461 }
462 462
463 func TestPaletteRecentCommandsNewestFirstThenRegistryOrder(t *testing.T) {463 func TestPaletteRecentCommandsNewestFirstThenRegistryOrder(t *testing.T) {
@@ -568,17 +568,17 @@ func TestPaletteCtrlNAndCtrlPNavigate(t *testing.T) {
568 awaitFrame(t, sends, ports.MsgOutput)568 awaitFrame(t, sends, ports.MsgOutput)
569 result, ok := ac.overlays.palette.Selected()569 result, ok := ac.overlays.palette.Selected()
570 require.True(t, ok)570 require.True(t, ok)
571- cmd, ok := result.(palette.CommandResult)571+ cmd, ok := result.Command()
572 require.True(t, ok)572 require.True(t, ok)
573- require.Equal(t, "CNS", cmd.Command().Code)573+ require.Equal(t, "CNS", cmd.Code)
574 574
575 d.handlePaletteInput(ac, []byte{0x10})575 d.handlePaletteInput(ac, []byte{0x10})
576 awaitFrame(t, sends, ports.MsgOutput)576 awaitFrame(t, sends, ports.MsgOutput)
577 result, ok = ac.overlays.palette.Selected()577 result, ok = ac.overlays.palette.Selected()
578 require.True(t, ok)578 require.True(t, ok)
579- cmd, ok = result.(palette.CommandResult)579+ cmd, ok = result.Command()
580 require.True(t, ok)580 require.True(t, ok)
581- require.Equal(t, "CNT", cmd.Command().Code)581+ require.Equal(t, "CNT", cmd.Code)
582 }582 }
583 583
584 func TestPaletteModalGeometry(t *testing.T) {584 func TestPaletteModalGeometry(t *testing.T) {

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 2

Tags 13

↑↓ moveenter openesc close