detached 26071da

fix(palette): validate previews and completion input

commit
26071daeaddc0c29423c6fba21dc613db86332d9
author
brice · · 2026-08-11 15:02 UTC
parent
bdf6220
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +8 -0
M internal/usecase/command/registry.go +3 -0
@@ -80,6 +80,9 @@ func paletteOptionalOne(slug, code, name, desc string, target TargetKind, prompt
8080 }
8181 return desc
8282 }
83+ if len(args) > 1 {
84+ return "Use one name"
85+ }
8386 switch slug {
8487 case "new-session":
8588 return "Create session “" + args[0] + "”"
@@ -80,6 +80,9 @@ func paletteOptionalOne(slug, code, name, desc string, target TargetKind, prompt
80 }80 }
81 return desc81 return desc
82 }82 }
83+ if len(args) > 1 {
84+ return "Use one name"
85+ }
83 switch slug {86 switch slug {
84 case "new-session":87 case "new-session":
85 return "Create session “" + args[0] + "”"88 return "Create session “" + args[0] + "”"
M internal/usecase/palette/model.go +2 -0
@@ -1,6 +1,7 @@
11 package palette
22
33 import (
4+ "strings"
45 "unicode"
56 "unicode/utf8"
67
@@ -124,6 +125,7 @@ func (m *Model) CompleteSelected() bool {
124125 query := m.Query()
125126 completed := cmd.Code
126127 if cmd.Arguments != command.ArgumentsNone {
128+ query = strings.TrimLeftFunc(query, unicode.IsSpace)
127129 token, args, hasSeparator := completionParts(query)
128130 if token == cmd.Code && hasSeparator && args != "" {
129131 return false
@@ -1,6 +1,7 @@
1 package palette1 package palette
2 2
3 import (3 import (
4+ "strings"
4 "unicode"5 "unicode"
5 "unicode/utf8"6 "unicode/utf8"
6 7
@@ -124,6 +125,7 @@ func (m *Model) CompleteSelected() bool {
124 query := m.Query()125 query := m.Query()
125 completed := cmd.Code126 completed := cmd.Code
126 if cmd.Arguments != command.ArgumentsNone {127 if cmd.Arguments != command.ArgumentsNone {
128+ query = strings.TrimLeftFunc(query, unicode.IsSpace)
127 token, args, hasSeparator := completionParts(query)129 token, args, hasSeparator := completionParts(query)
128 if token == cmd.Code && hasSeparator && args != "" {130 if token == cmd.Code && hasSeparator && args != "" {
129 return false131 return false
M internal/usecase/palette/model_test.go +3 -0
@@ -396,6 +396,8 @@ func TestPaletteDescriptionKeepsInactiveRowSurfaceAcrossFallbacks(t *testing.T)
396396 func TestModelCompleteSelected(t *testing.T) {
397397 jrs := cmd("JRS", "Jump", "Jump to recent session")
398398 jrs.Arguments = command.ArgumentsRequired
399+ optional := cmd("CNS", "New session", "Create named session")
400+ optional.Arguments = command.ArgumentsOptional
399401 registryFirst := command.Registry()[0]
400402
401403 tests := []struct {
@@ -419,6 +421,7 @@ func TestModelCompleteSelected(t *testing.T) {
419421 {name: "argument command appends required space", commands: []command.Command{jrs}, query: "jrs", want: "JRS ", wantChange: true},
420422 {name: "partial token preserves unicode whitespace argument bytes", commands: []command.Command{jrs}, query: "jr\u2003 \tα β", want: "JRS α β", wantChange: true},
421423 {name: "exact code and argument is unchanged", commands: []command.Command{jrs}, query: "JRS\u2003 α β", want: "JRS\u2003 α β", wantChange: false},
424+ {name: "leading whitespace optional argument does not duplicate token", commands: []command.Command{optional}, query: "\u2003CNS name", want: "\u2003CNS name", wantChange: false},
422425 {name: "no match", commands: []command.Command{cmd("CPY", "Copy", "Enter copy mode")}, query: "zzz", want: "zzz", wantChange: false},
423426 {name: "effective override code", commands: []command.Command{{Slug: "new-tab", Code: "NT", Desc: "Create tab"}}, query: "n", want: "NT", wantChange: true},
424427 }
@@ -396,6 +396,8 @@ func TestPaletteDescriptionKeepsInactiveRowSurfaceAcrossFallbacks(t *testing.T)
396 func TestModelCompleteSelected(t *testing.T) {396 func TestModelCompleteSelected(t *testing.T) {
397 jrs := cmd("JRS", "Jump", "Jump to recent session")397 jrs := cmd("JRS", "Jump", "Jump to recent session")
398 jrs.Arguments = command.ArgumentsRequired398 jrs.Arguments = command.ArgumentsRequired
399+ optional := cmd("CNS", "New session", "Create named session")
400+ optional.Arguments = command.ArgumentsOptional
399 registryFirst := command.Registry()[0]401 registryFirst := command.Registry()[0]
400 402
401 tests := []struct {403 tests := []struct {
@@ -419,6 +421,7 @@ func TestModelCompleteSelected(t *testing.T) {
419 {name: "argument command appends required space", commands: []command.Command{jrs}, query: "jrs", want: "JRS ", wantChange: true},421 {name: "argument command appends required space", commands: []command.Command{jrs}, query: "jrs", want: "JRS ", wantChange: true},
420 {name: "partial token preserves unicode whitespace argument bytes", commands: []command.Command{jrs}, query: "jr\u2003 \tα β", want: "JRS α β", wantChange: true},422 {name: "partial token preserves unicode whitespace argument bytes", commands: []command.Command{jrs}, query: "jr\u2003 \tα β", want: "JRS α β", wantChange: true},
421 {name: "exact code and argument is unchanged", commands: []command.Command{jrs}, query: "JRS\u2003 α β", want: "JRS\u2003 α β", wantChange: false},423 {name: "exact code and argument is unchanged", commands: []command.Command{jrs}, query: "JRS\u2003 α β", want: "JRS\u2003 α β", wantChange: false},
424+ {name: "leading whitespace optional argument does not duplicate token", commands: []command.Command{optional}, query: "\u2003CNS name", want: "\u2003CNS name", wantChange: false},
422 {name: "no match", commands: []command.Command{cmd("CPY", "Copy", "Enter copy mode")}, query: "zzz", want: "zzz", wantChange: false},425 {name: "no match", commands: []command.Command{cmd("CPY", "Copy", "Enter copy mode")}, query: "zzz", want: "zzz", wantChange: false},
423 {name: "effective override code", commands: []command.Command{{Slug: "new-tab", Code: "NT", Desc: "Create tab"}}, query: "n", want: "NT", wantChange: true},426 {name: "effective override code", commands: []command.Command{{Slug: "new-tab", Code: "NT", Desc: "Create tab"}}, query: "n", want: "NT", wantChange: true},
424 }427 }

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