detached c8d167e

fix(tabs): show renamed tabs consistently

commit
c8d167ec624f8909365962f14e44816edd969af8
author
brice · · 2026-07-09 18:12 UTC
parent
4b56f28
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +16 -5
M internal/usecase/daemon/input_test.go +5 -2
@@ -176,10 +176,13 @@ func TestRNTOpensPromptAndRenamesActiveTab(t *testing.T) {
176176 d.handleInput(sess, ac, []byte("\x1b "))
177177 awaitFrame(t, sends, ports.MsgOutput)
178178 d.handleInput(sess, ac, []byte("RNT\r"))
179- awaitFrame(t, sends, ports.MsgOutput)
179+ out := awaitFrame(t, sends, ports.MsgOutput)
180180 require.True(t, ac.promptActive())
181+ msg, err := ports.UnmarshalOutput(out.Payload)
182+ require.NoError(t, err)
183+ require.Contains(t, string(msg.Data), "> 2")
181184
182- d.handleInput(sess, ac, []byte("logs\r"))
185+ d.handleInput(sess, ac, []byte("\x7flogs\r"))
183186 awaitFrame(t, sends, ports.MsgOutput)
184187
185188 require.False(t, ac.promptActive())
@@ -176,10 +176,13 @@ func TestRNTOpensPromptAndRenamesActiveTab(t *testing.T) {
176 d.handleInput(sess, ac, []byte("\x1b "))176 d.handleInput(sess, ac, []byte("\x1b "))
177 awaitFrame(t, sends, ports.MsgOutput)177 awaitFrame(t, sends, ports.MsgOutput)
178 d.handleInput(sess, ac, []byte("RNT\r"))178 d.handleInput(sess, ac, []byte("RNT\r"))
179- awaitFrame(t, sends, ports.MsgOutput)179+ out := awaitFrame(t, sends, ports.MsgOutput)
180 require.True(t, ac.promptActive())180 require.True(t, ac.promptActive())
181+ msg, err := ports.UnmarshalOutput(out.Payload)
182+ require.NoError(t, err)
183+ require.Contains(t, string(msg.Data), "> 2")
181 184
182- d.handleInput(sess, ac, []byte("logs\r"))185+ d.handleInput(sess, ac, []byte("\x7flogs\r"))
183 awaitFrame(t, sends, ports.MsgOutput)186 awaitFrame(t, sends, ports.MsgOutput)
184 187
185 require.False(t, ac.promptActive())188 require.False(t, ac.promptActive())
M internal/usecase/daemon/palette.go +5 -0
@@ -1,6 +1,8 @@
11 package daemon
22
33 import (
4+ "strconv"
5+
46 "github.com/bnema/vev/internal/domain"
57 "github.com/bnema/vev/internal/usecase/command"
68 "github.com/bnema/vev/internal/usecase/palette"
@@ -154,6 +156,9 @@ func (e paletteExec) RenameTab() error {
154156 }
155157 e.sess.mu.Lock()
156158 currentName := tb.name
159+ if currentName == "" {
160+ currentName = strconv.Itoa(e.sess.active + 1)
161+ }
157162 e.sess.mu.Unlock()
158163 e.d.enterPrompt(e.sess, e.ac, " Rename tab ", currentName, func(name string) error {
159164 return e.d.renameTab(e.sess, tb, name)
@@ -1,6 +1,8 @@
1 package daemon1 package daemon
2 2
3 import (3 import (
4+ "strconv"
5+
4 "github.com/bnema/vev/internal/domain"6 "github.com/bnema/vev/internal/domain"
5 "github.com/bnema/vev/internal/usecase/command"7 "github.com/bnema/vev/internal/usecase/command"
6 "github.com/bnema/vev/internal/usecase/palette"8 "github.com/bnema/vev/internal/usecase/palette"
@@ -154,6 +156,9 @@ func (e paletteExec) RenameTab() error {
154 }156 }
155 e.sess.mu.Lock()157 e.sess.mu.Lock()
156 currentName := tb.name158 currentName := tb.name
159+ if currentName == "" {
160+ currentName = strconv.Itoa(e.sess.active + 1)
161+ }
157 e.sess.mu.Unlock()162 e.sess.mu.Unlock()
158 e.d.enterPrompt(e.sess, e.ac, " Rename tab ", currentName, func(name string) error {163 e.d.enterPrompt(e.sess, e.ac, " Rename tab ", currentName, func(name string) error {
159 return e.d.renameTab(e.sess, tb, name)164 return e.d.renameTab(e.sess, tb, name)
M internal/usecase/daemon/picker.go +4 -1
@@ -71,7 +71,10 @@ func (d *Daemon) pickerViews(cur *session) ([]picker.SessionView, int) {
7171 view := picker.SessionView{ID: s.id, Name: s.name, Active: s.active, Tabs: make([]string, len(s.tabs))}
7272 sessionAttention := false
7373 for i, tb := range s.tabs {
74- label := strconv.Itoa(i + 1)
74+ label := tb.name
75+ if label == "" {
76+ label = strconv.Itoa(i + 1)
77+ }
7578 if tb.attention {
7679 label = attentionSuffix(label)
7780 sessionAttention = true
@@ -71,7 +71,10 @@ func (d *Daemon) pickerViews(cur *session) ([]picker.SessionView, int) {
71 view := picker.SessionView{ID: s.id, Name: s.name, Active: s.active, Tabs: make([]string, len(s.tabs))}71 view := picker.SessionView{ID: s.id, Name: s.name, Active: s.active, Tabs: make([]string, len(s.tabs))}
72 sessionAttention := false72 sessionAttention := false
73 for i, tb := range s.tabs {73 for i, tb := range s.tabs {
74- label := strconv.Itoa(i + 1)74+ label := tb.name
75+ if label == "" {
76+ label = strconv.Itoa(i + 1)
77+ }
75 if tb.attention {78 if tb.attention {
76 label = attentionSuffix(label)79 label = attentionSuffix(label)
77 sessionAttention = true80 sessionAttention = true
M internal/usecase/daemon/picker_test.go +2 -2
@@ -38,7 +38,7 @@ func TestPickerViewsAddsBellSuffixForAttention(t *testing.T) {
3838 ctx, cancel := context.WithCancel(d.serveCtx)
3939 defer cancel()
4040 current := &session{id: "s1", name: "alpha", ctx: ctx, cancel: cancel, tabs: []*tab{{}, {}}}
41- ringing := &session{id: "s2", name: "beta", ctx: ctx, cancel: cancel, tabs: []*tab{{}, {}}}
41+ ringing := &session{id: "s2", name: "beta", ctx: ctx, cancel: cancel, tabs: []*tab{{name: "shell"}, {name: "logs"}}}
4242 ringing.mu.Lock()
4343 ringing.tabs[1].attention = true
4444 ringing.tabs[1].attentionAt = time.Unix(10, 0)
@@ -53,7 +53,7 @@ func TestPickerViewsAddsBellSuffixForAttention(t *testing.T) {
5353 require.Equal(t, "alpha", views[0].Name)
5454 require.Equal(t, []string{"1", "2"}, views[0].Tabs)
5555 require.Equal(t, "beta ", views[1].Name)
56- require.Equal(t, []string{"1", "2 "}, views[1].Tabs)
56+ require.Equal(t, []string{"shell", "logs "}, views[1].Tabs)
5757 }
5858
5959 func TestPickerResumesStoppedSessionWithPersistedTabNames(t *testing.T) {
@@ -38,7 +38,7 @@ func TestPickerViewsAddsBellSuffixForAttention(t *testing.T) {
38 ctx, cancel := context.WithCancel(d.serveCtx)38 ctx, cancel := context.WithCancel(d.serveCtx)
39 defer cancel()39 defer cancel()
40 current := &session{id: "s1", name: "alpha", ctx: ctx, cancel: cancel, tabs: []*tab{{}, {}}}40 current := &session{id: "s1", name: "alpha", ctx: ctx, cancel: cancel, tabs: []*tab{{}, {}}}
41- ringing := &session{id: "s2", name: "beta", ctx: ctx, cancel: cancel, tabs: []*tab{{}, {}}}41+ ringing := &session{id: "s2", name: "beta", ctx: ctx, cancel: cancel, tabs: []*tab{{name: "shell"}, {name: "logs"}}}
42 ringing.mu.Lock()42 ringing.mu.Lock()
43 ringing.tabs[1].attention = true43 ringing.tabs[1].attention = true
44 ringing.tabs[1].attentionAt = time.Unix(10, 0)44 ringing.tabs[1].attentionAt = time.Unix(10, 0)
@@ -53,7 +53,7 @@ func TestPickerViewsAddsBellSuffixForAttention(t *testing.T) {
53 require.Equal(t, "alpha", views[0].Name)53 require.Equal(t, "alpha", views[0].Name)
54 require.Equal(t, []string{"1", "2"}, views[0].Tabs)54 require.Equal(t, []string{"1", "2"}, views[0].Tabs)
55 require.Equal(t, "beta ", views[1].Name)55 require.Equal(t, "beta ", views[1].Name)
56- require.Equal(t, []string{"1", "2 "}, views[1].Tabs)56+ require.Equal(t, []string{"shell", "logs "}, views[1].Tabs)
57 }57 }
58 58
59 func TestPickerResumesStoppedSessionWithPersistedTabNames(t *testing.T) {59 func TestPickerResumesStoppedSessionWithPersistedTabNames(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 12

Tags 11

↑↓ moveenter openesc close