detached 9d03f4b

refactor(daemon): tidy palette helpers and review nitpicks

Move palette generation-guard helpers onto attachedClient since they
never touch Daemon state, drop a redundant nil check and stray blank
line, hoist an argument-command lookup out of the palette render loop,
and clarify the JRS/BSK README wording.
commit
9d03f4bb4662d015d8f88eca88dd1e37a3fa74f7
author
brice · · 2026-07-11 14:04 UTC
parent
9d575ec
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +6 -9
M README.md +1 -1
@@ -180,7 +180,7 @@ Some terminals encode Alt+Space as a non-breaking space instead of `ESC Space`;
180180
181181 Open the palette with Alt+Space. Type a command code or fuzzy-match the label/description. Use Up/Down or Ctrl-N/Ctrl-P to move, Enter to run, and Esc/Ctrl-C to close.
182182
183-Successful commands are promoted near the top the next time the palette opens. `BSK` toggles only this client's previously active session. Type an exact `JRS` token to show the current interaction's immutable recent-session ranks; `JRS N` jumps to rank N. Invalid or stale ranks leave the palette open.
183+Successful commands are promoted near the top the next time the palette opens. `BSK` toggles back to your previously active session. Typing `JRS` lists recent sessions by number in the status bar, and `JRS N` jumps to number N; the numbers are captured when you start typing, so a busy MRU list never shifts under you. An invalid or stale number leaves the palette open.
184184
185185 | Code | Action |
186186 |---|---|
@@ -180,7 +180,7 @@ Some terminals encode Alt+Space as a non-breaking space instead of `ESC Space`;
180 180
181 Open the palette with Alt+Space. Type a command code or fuzzy-match the label/description. Use Up/Down or Ctrl-N/Ctrl-P to move, Enter to run, and Esc/Ctrl-C to close.181 Open the palette with Alt+Space. Type a command code or fuzzy-match the label/description. Use Up/Down or Ctrl-N/Ctrl-P to move, Enter to run, and Esc/Ctrl-C to close.
182 182
183-Successful commands are promoted near the top the next time the palette opens. `BSK` toggles only this client's previously active session. Type an exact `JRS` token to show the current interaction's immutable recent-session ranks; `JRS N` jumps to rank N. Invalid or stale ranks leave the palette open.183+Successful commands are promoted near the top the next time the palette opens. `BSK` toggles back to your previously active session. Typing `JRS` lists recent sessions by number in the status bar, and `JRS N` jumps to number N; the numbers are captured when you start typing, so a busy MRU list never shifts under you. An invalid or stale number leaves the palette open.
184 184
185 | Code | Action |185 | Code | Action |
186 |---|---|186 |---|---|
M internal/usecase/daemon/picker.go +0 -1
@@ -378,7 +378,6 @@ func (d *Daemon) switchToTarget(sess *session, ac *attachedClient, target picker
378378 return false
379379 }
380380 if old != nil && old != ac {
381-
382381 d.unregisterPreview(old)
383382 old.clearPreviousSession()
384383 old.setSession(nil)
@@ -378,7 +378,6 @@ func (d *Daemon) switchToTarget(sess *session, ac *attachedClient, target picker
378 return false378 return false
379 }379 }
380 if old != nil && old != ac {380 if old != nil && old != ac {
381-
382 d.unregisterPreview(old)381 d.unregisterPreview(old)
383 old.clearPreviousSession()382 old.clearPreviousSession()
384 old.setSession(nil)383 old.setSession(nil)
M internal/usecase/daemon/status.go +1 -3
@@ -281,9 +281,7 @@ func (d *Daemon) barStateForContextual(cur *session, copyFeedback string, ranked
281281
282282 func (d *Daemon) barStateFor(cur *session, copyFeedback string) barState {
283283 state := d.barStateBase(cur, copyFeedback)
284- if d != nil {
285- state.mru = d.recentSessions(cur)
286- }
284+ state.mru = d.recentSessions(cur)
287285 return state
288286 }
289287
@@ -281,9 +281,7 @@ func (d *Daemon) barStateForContextual(cur *session, copyFeedback string, ranked
281 281
282 func (d *Daemon) barStateFor(cur *session, copyFeedback string) barState {282 func (d *Daemon) barStateFor(cur *session, copyFeedback string) barState {
283 state := d.barStateBase(cur, copyFeedback)283 state := d.barStateBase(cur, copyFeedback)
284- if d != nil {284+ state.mru = d.recentSessions(cur)
285- state.mru = d.recentSessions(cur)
286- }
287 return state285 return state
288 }286 }
289 287
M internal/usecase/palette/model.go +4 -4
@@ -93,6 +93,7 @@ func (m *Model) ArgumentCommand() (command.Command, bool) {
9393 }
9494 return ArgumentCommand(m.commands, m.Query())
9595 }
96+
9697 func (m *Model) Matches() []Match {
9798 if m == nil {
9899 return nil
@@ -178,6 +179,7 @@ func (m *Model) Render(inner domain.Size, opts RenderOptions) renderer.Frame {
178179 codeWidth = len([]rune(match.Command.Code))
179180 }
180181 }
182+ activeCmd, activeOK := m.ArgumentCommand()
181183 for y := range visible {
182184 idx := m.scroll + y
183185 if idx >= len(m.matches) {
@@ -211,10 +213,8 @@ func (m *Model) Render(inner domain.Size, opts RenderOptions) renderer.Frame {
211213 x++
212214 }
213215 description := match.Command.Desc
214- if opts.Guidance != "" && match.Command.ContextHint != command.ContextHintNone && m.Query() != "" {
215- if active, ok := m.ArgumentCommand(); ok && active.Code == match.Command.Code {
216- description = opts.Guidance
217- }
216+ if opts.Guidance != "" && activeOK && match.Command.ContextHint != command.ContextHintNone && activeCmd.Code == match.Command.Code {
217+ description = opts.Guidance
218218 }
219219 ui.DrawText(frame, x, y+start, frame.Width, description, mergePaletteDescStyle(style, desc))
220220 }
@@ -93,6 +93,7 @@ func (m *Model) ArgumentCommand() (command.Command, bool) {
93 }93 }
94 return ArgumentCommand(m.commands, m.Query())94 return ArgumentCommand(m.commands, m.Query())
95 }95 }
96+
96 func (m *Model) Matches() []Match {97 func (m *Model) Matches() []Match {
97 if m == nil {98 if m == nil {
98 return nil99 return nil
@@ -178,6 +179,7 @@ func (m *Model) Render(inner domain.Size, opts RenderOptions) renderer.Frame {
178 codeWidth = len([]rune(match.Command.Code))179 codeWidth = len([]rune(match.Command.Code))
179 }180 }
180 }181 }
182+ activeCmd, activeOK := m.ArgumentCommand()
181 for y := range visible {183 for y := range visible {
182 idx := m.scroll + y184 idx := m.scroll + y
183 if idx >= len(m.matches) {185 if idx >= len(m.matches) {
@@ -211,10 +213,8 @@ func (m *Model) Render(inner domain.Size, opts RenderOptions) renderer.Frame {
211 x++213 x++
212 }214 }
213 description := match.Command.Desc215 description := match.Command.Desc
214- if opts.Guidance != "" && match.Command.ContextHint != command.ContextHintNone && m.Query() != "" {216+ if opts.Guidance != "" && activeOK && match.Command.ContextHint != command.ContextHintNone && activeCmd.Code == match.Command.Code {
215- if active, ok := m.ArgumentCommand(); ok && active.Code == match.Command.Code {217+ description = opts.Guidance
216- description = opts.Guidance
217- }
218 }218 }
219 ui.DrawText(frame, x, y+start, frame.Width, description, mergePaletteDescStyle(style, desc))219 ui.DrawText(frame, x, y+start, frame.Width, description, mergePaletteDescStyle(style, desc))
220 }220 }

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