detached e59f877

test(app): cover rename prompt integration

commit
e59f8774b712ce2a8aae8960ee043f162587d911
author
brice · · 2026-07-03 09:29 UTC
parent
26d6300
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +38 -1
M README.md +5 -1
@@ -53,6 +53,8 @@ Some terminals encode Alt+Space as a non-breaking space instead of `ESC Space`;
5353
5454 Open the palette with Alt+Space, then type a command code or fuzzy-match its label. Use Up/Down or Ctrl-N/Ctrl-P to move through matches, Enter to run the selected command, and Esc to close the palette.
5555
56+Ephemeral sessions are marked with `*` in the status bar, for example `0*`. Renaming an ephemeral session makes it persistent and removes the marker.
57+
5658 | Code | Action |
5759 |---|---|
5860 | CNT | create new tab |
@@ -61,9 +63,11 @@ Open the palette with Alt+Space, then type a command code or fuzzy-match its lab
6163 | PVT | switch to previous tab |
6264 | SSP | switch session or tab |
6365 | CPY | enter scrollback mode |
64-| RNS | promote session |
66+| RNS | rename session |
6567 | DET | detach |
6668
69+Rename prompts are prefilled with the current name. Type text, use Backspace to edit, Enter to submit, or Esc/Ctrl-C to cancel. If validation fails, the prompt stays open and shows the error.
70+
6771 ## Scrollback and visual copy
6872
6973 Scrollback mode freezes a view over history while the program keeps running underneath. Mouse wheel up enters scrollback mode and shows `[SCROLL]`; scrolling back down to the live bottom exits. When a line selection is active, the selected rows are highlighted and the status bar shows `[VISUAL]`.
@@ -53,6 +53,8 @@ Some terminals encode Alt+Space as a non-breaking space instead of `ESC Space`;
53 53
54 Open the palette with Alt+Space, then type a command code or fuzzy-match its label. Use Up/Down or Ctrl-N/Ctrl-P to move through matches, Enter to run the selected command, and Esc to close the palette.54 Open the palette with Alt+Space, then type a command code or fuzzy-match its label. Use Up/Down or Ctrl-N/Ctrl-P to move through matches, Enter to run the selected command, and Esc to close the palette.
55 55
56+Ephemeral sessions are marked with `*` in the status bar, for example `0*`. Renaming an ephemeral session makes it persistent and removes the marker.
57+
56 | Code | Action |58 | Code | Action |
57 |---|---|59 |---|---|
58 | CNT | create new tab |60 | CNT | create new tab |
@@ -61,9 +63,11 @@ Open the palette with Alt+Space, then type a command code or fuzzy-match its lab
61 | PVT | switch to previous tab |63 | PVT | switch to previous tab |
62 | SSP | switch session or tab |64 | SSP | switch session or tab |
63 | CPY | enter scrollback mode |65 | CPY | enter scrollback mode |
64-| RNS | promote session |66+| RNS | rename session |
65 | DET | detach |67 | DET | detach |
66 68
69+Rename prompts are prefilled with the current name. Type text, use Backspace to edit, Enter to submit, or Esc/Ctrl-C to cancel. If validation fails, the prompt stays open and shows the error.
70+
67 ## Scrollback and visual copy71 ## Scrollback and visual copy
68 72
69 Scrollback mode freezes a view over history while the program keeps running underneath. Mouse wheel up enters scrollback mode and shows `[SCROLL]`; scrolling back down to the live bottom exits. When a line selection is active, the selected rows are highlighted and the status bar shows `[VISUAL]`.73 Scrollback mode freezes a view over history while the program keeps running underneath. Mouse wheel up enters scrollback mode and shows `[SCROLL]`; scrolling back down to the live bottom exits. When a line selection is active, the selected rows are highlighted and the status bar shows `[VISUAL]`.
M internal/app/daemon_integration_test.go +33 -0
@@ -190,6 +190,39 @@ func TestIntegration_CommandPaletteCreatesTab(t *testing.T) {
190190 require.Contains(t, text, " 1 2 ")
191191 }
192192
193+func TestIntegration_CommandPaletteRenamesEphemeralSession(t *testing.T) {
194+ sz := domain.Size{Cols: 80, Rows: 24}
195+ dir, _ := startDaemon(t, daemon.WithShell("/bin/sh", []string{"-c", "printf READY; sleep 30"}))
196+
197+ tr, p := attach(t, dir, ports.IntentEphemeral, "", sz)
198+ defer func() { _ = tr.Close() }()
199+ text := awaitScreenText(t, p, sz, "READY")
200+ require.Contains(t, text, " 0* ")
201+
202+ require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(ports.Input{Data: []byte("\x1b ")})}))
203+ awaitText(t, p, sz, "Commands")
204+
205+ require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(ports.Input{Data: []byte("RNS\r")})}))
206+ awaitText(t, p, sz, "Rename session")
207+
208+ require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(ports.Input{Data: []byte("\x7fwork\r")})}))
209+ text = awaitScreenText(t, p, sz, " work ")
210+ require.NotContains(t, text, "work*")
211+
212+ listTr, err := ipc.Dial(dir)
213+ require.NoError(t, err)
214+ defer func() { _ = listTr.Close() }()
215+ require.NoError(t, listTr.Send(ports.Frame{Type: ports.MsgList, Payload: ports.MarshalList(ports.List{})}))
216+ f, err := listTr.Recv()
217+ require.NoError(t, err)
218+ require.Equal(t, ports.MsgSessions, f.Type)
219+ sessions, err := ports.UnmarshalSessions(f.Payload)
220+ require.NoError(t, err)
221+ require.Len(t, sessions.Sessions, 1)
222+ require.Equal(t, "work", sessions.Sessions[0].Name)
223+ require.False(t, sessions.Sessions[0].Ephemeral)
224+}
225+
193226 func TestIntegration_AltCWithoutPaletteDoesNotCreateTab(t *testing.T) {
194227 sz := domain.Size{Cols: 80, Rows: 24}
195228 dir, _ := startDaemon(t, daemon.WithShell("/bin/sh", []string{"-c", "printf READY; sleep 30"}))
@@ -190,6 +190,39 @@ func TestIntegration_CommandPaletteCreatesTab(t *testing.T) {
190 require.Contains(t, text, " 1 2 ")190 require.Contains(t, text, " 1 2 ")
191 }191 }
192 192
193+func TestIntegration_CommandPaletteRenamesEphemeralSession(t *testing.T) {
194+ sz := domain.Size{Cols: 80, Rows: 24}
195+ dir, _ := startDaemon(t, daemon.WithShell("/bin/sh", []string{"-c", "printf READY; sleep 30"}))
196+
197+ tr, p := attach(t, dir, ports.IntentEphemeral, "", sz)
198+ defer func() { _ = tr.Close() }()
199+ text := awaitScreenText(t, p, sz, "READY")
200+ require.Contains(t, text, " 0* ")
201+
202+ require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(ports.Input{Data: []byte("\x1b ")})}))
203+ awaitText(t, p, sz, "Commands")
204+
205+ require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(ports.Input{Data: []byte("RNS\r")})}))
206+ awaitText(t, p, sz, "Rename session")
207+
208+ require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(ports.Input{Data: []byte("\x7fwork\r")})}))
209+ text = awaitScreenText(t, p, sz, " work ")
210+ require.NotContains(t, text, "work*")
211+
212+ listTr, err := ipc.Dial(dir)
213+ require.NoError(t, err)
214+ defer func() { _ = listTr.Close() }()
215+ require.NoError(t, listTr.Send(ports.Frame{Type: ports.MsgList, Payload: ports.MarshalList(ports.List{})}))
216+ f, err := listTr.Recv()
217+ require.NoError(t, err)
218+ require.Equal(t, ports.MsgSessions, f.Type)
219+ sessions, err := ports.UnmarshalSessions(f.Payload)
220+ require.NoError(t, err)
221+ require.Len(t, sessions.Sessions, 1)
222+ require.Equal(t, "work", sessions.Sessions[0].Name)
223+ require.False(t, sessions.Sessions[0].Ephemeral)
224+}
225+
193 func TestIntegration_AltCWithoutPaletteDoesNotCreateTab(t *testing.T) {226 func TestIntegration_AltCWithoutPaletteDoesNotCreateTab(t *testing.T) {
194 sz := domain.Size{Cols: 80, Rows: 24}227 sz := domain.Size{Cols: 80, Rows: 24}
195 dir, _ := startDaemon(t, daemon.WithShell("/bin/sh", []string{"-c", "printf READY; sleep 30"}))228 dir, _ := startDaemon(t, daemon.WithShell("/bin/sh", []string{"-c", "printf READY; sleep 30"}))

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