fix(status): exclude ephemeral sessions from recent history
- commit
- 795382872361375a63f14425a165290df0d5c6e4
- author
- brice · · 2026-07-15 16:41 UTC
- parent
- 71431fb
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +38 -2
M internal/usecase/daemon/picker_test.go +14 -0
| @@ -77,6 +77,20 @@ func TestPickerViewsAddsBellSuffixForAttention(t *testing.T) { | |||
| 77 | 77 | require.NotNil(t, target.ExpectedCreatedAt) | |
| 78 | 78 | } | |
| 79 | 79 | ||
| 80 | + | func TestPickerViewsIncludesEphemeralSessions(t *testing.T) { | |
| 81 | + | d := newTestDaemon(t, nil, stubClock{}) | |
| 82 | + | current := &session{id: "current", name: "current", tabs: []*tab{{}}} | |
| 83 | + | ephemeral := &session{id: "ephemeral", name: "1", ephemeral: true, tabs: []*tab{{}}} | |
| 84 | + | d.sessions[current.id] = current | |
| 85 | + | d.sessions[ephemeral.id] = ephemeral | |
| 86 | + | ||
| 87 | + | views, _ := d.pickerViews(current) | |
| 88 | + | ||
| 89 | + | require.Len(t, views, 2) | |
| 90 | + | require.Equal(t, ephemeral.id, views[0].ID) | |
| 91 | + | require.Equal(t, "1", views[0].Name) | |
| 92 | + | } | |
| 93 | + | ||
| 80 | 94 | func TestPickerViewsCarryNamedLifecycleIdentity(t *testing.T) { | |
| 81 | 95 | d := newTestDaemon(t, nil, stubClock{}) | |
| 82 | 96 | ctx, cancel := context.WithCancel(d.serveCtx) | |
| @@ -77,6 +77,20 @@ func TestPickerViewsAddsBellSuffixForAttention(t *testing.T) { | |||||
| 77 | require.NotNil(t, target.ExpectedCreatedAt) | 77 | require.NotNil(t, target.ExpectedCreatedAt) | ||
| 78 | } | 78 | } | ||
| 79 | 79 | ||||
| 80 | + | func TestPickerViewsIncludesEphemeralSessions(t *testing.T) { | |||
| 81 | + | d := newTestDaemon(t, nil, stubClock{}) | |||
| 82 | + | current := &session{id: "current", name: "current", tabs: []*tab{{}}} | |||
| 83 | + | ephemeral := &session{id: "ephemeral", name: "1", ephemeral: true, tabs: []*tab{{}}} | |||
| 84 | + | d.sessions[current.id] = current | |||
| 85 | + | d.sessions[ephemeral.id] = ephemeral | |||
| 86 | + | ||||
| 87 | + | views, _ := d.pickerViews(current) | |||
| 88 | + | ||||
| 89 | + | require.Len(t, views, 2) | |||
| 90 | + | require.Equal(t, ephemeral.id, views[0].ID) | |||
| 91 | + | require.Equal(t, "1", views[0].Name) | |||
| 92 | + | } | |||
| 93 | + | ||||
| 80 | func TestPickerViewsCarryNamedLifecycleIdentity(t *testing.T) { | 94 | func TestPickerViewsCarryNamedLifecycleIdentity(t *testing.T) { | ||
| 81 | d := newTestDaemon(t, nil, stubClock{}) | 95 | d := newTestDaemon(t, nil, stubClock{}) | ||
| 82 | ctx, cancel := context.WithCancel(d.serveCtx) | 96 | ctx, cancel := context.WithCancel(d.serveCtx) | ||
M internal/usecase/daemon/recent_sessions.go +6 -2
| @@ -16,8 +16,8 @@ type recentSession struct { | |||
| 16 | 16 | mruAt uint64 | |
| 17 | 17 | } | |
| 18 | 18 | ||
| 19 | - | // recentSessions returns the current session's capped MRU list. Callers may | |
| 20 | - | // retain the returned values for the lifetime of an interaction. | |
| 19 | + | // recentSessions returns the current session's capped named-session MRU list. | |
| 20 | + | // Callers may retain the returned values for the lifetime of an interaction. | |
| 21 | 21 | func (d *Daemon) recentSessions(current *session) []recentSession { | |
| 22 | 22 | if d == nil { | |
| 23 | 23 | return nil | |
| @@ -29,6 +29,10 @@ func (d *Daemon) recentSessions(current *session) []recentSession { | |||
| 29 | 29 | continue | |
| 30 | 30 | } | |
| 31 | 31 | sess.mu.Lock() | |
| 32 | + | if sess.ephemeral { | |
| 33 | + | sess.mu.Unlock() | |
| 34 | + | continue | |
| 35 | + | } | |
| 32 | 36 | entry := recentSession{ | |
| 33 | 37 | id: sess.id, | |
| 34 | 38 | name: sess.name, | |
| @@ -16,8 +16,8 @@ type recentSession struct { | |||||
| 16 | mruAt uint64 | 16 | mruAt uint64 | ||
| 17 | } | 17 | } | ||
| 18 | 18 | ||||
| 19 | - | // recentSessions returns the current session's capped MRU list. Callers may | 19 | + | // recentSessions returns the current session's capped named-session MRU list. |
| 20 | - | // retain the returned values for the lifetime of an interaction. | 20 | + | // Callers may retain the returned values for the lifetime of an interaction. |
| 21 | func (d *Daemon) recentSessions(current *session) []recentSession { | 21 | func (d *Daemon) recentSessions(current *session) []recentSession { | ||
| 22 | if d == nil { | 22 | if d == nil { | ||
| 23 | return nil | 23 | return nil | ||
| @@ -29,6 +29,10 @@ func (d *Daemon) recentSessions(current *session) []recentSession { | |||||
| 29 | continue | 29 | continue | ||
| 30 | } | 30 | } | ||
| 31 | sess.mu.Lock() | 31 | sess.mu.Lock() | ||
| 32 | + | if sess.ephemeral { | |||
| 33 | + | sess.mu.Unlock() | |||
| 34 | + | continue | |||
| 35 | + | } | |||
| 32 | entry := recentSession{ | 36 | entry := recentSession{ | ||
| 33 | id: sess.id, | 37 | id: sess.id, | ||
| 34 | name: sess.name, | 38 | name: sess.name, | ||
M internal/usecase/daemon/status_test.go +18 -0
| @@ -1020,6 +1020,24 @@ func TestBarStateForMRUFreshestFirstCapCurrentExcludedAndAttention(t *testing.T) | |||
| 1020 | 1020 | require.True(t, state.mru[1].attention, "s8 attention should be carried into MRU state") | |
| 1021 | 1021 | } | |
| 1022 | 1022 | ||
| 1023 | + | func TestBarStateForMRUExcludesEphemeralSessions(t *testing.T) { | |
| 1024 | + | p, releasePTY := newBlockingPTY(t) | |
| 1025 | + | d, current, _, _ := newManualSessionWithPTYs(t, p) | |
| 1026 | + | defer releasePTY() | |
| 1027 | + | ||
| 1028 | + | persistent := &session{id: "named", name: "named", tabs: []*tab{{}}} | |
| 1029 | + | persistent.mruAt.Store(1) | |
| 1030 | + | ephemeral := &session{id: "ephemeral", name: "1", ephemeral: true, tabs: []*tab{{}}} | |
| 1031 | + | ephemeral.mruAt.Store(2) | |
| 1032 | + | d.sessions[persistent.id] = persistent | |
| 1033 | + | d.sessions[ephemeral.id] = ephemeral | |
| 1034 | + | ||
| 1035 | + | state := d.barStateFor(current, "") | |
| 1036 | + | ||
| 1037 | + | require.Len(t, state.mru, 1) | |
| 1038 | + | require.Equal(t, "named", state.mru[0].name) | |
| 1039 | + | } | |
| 1040 | + | ||
| 1023 | 1041 | func TestBarStateForMRUZeroTimesUseDeterministicNameOrder(t *testing.T) { | |
| 1024 | 1042 | p, releasePTY := newBlockingPTY(t) | |
| 1025 | 1043 | d, sess, _, _ := newManualSessionWithPTYs(t, p) | |
| @@ -1020,6 +1020,24 @@ func TestBarStateForMRUFreshestFirstCapCurrentExcludedAndAttention(t *testing.T) | |||||
| 1020 | require.True(t, state.mru[1].attention, "s8 attention should be carried into MRU state") | 1020 | require.True(t, state.mru[1].attention, "s8 attention should be carried into MRU state") | ||
| 1021 | } | 1021 | } | ||
| 1022 | 1022 | ||||
| 1023 | + | func TestBarStateForMRUExcludesEphemeralSessions(t *testing.T) { | |||
| 1024 | + | p, releasePTY := newBlockingPTY(t) | |||
| 1025 | + | d, current, _, _ := newManualSessionWithPTYs(t, p) | |||
| 1026 | + | defer releasePTY() | |||
| 1027 | + | ||||
| 1028 | + | persistent := &session{id: "named", name: "named", tabs: []*tab{{}}} | |||
| 1029 | + | persistent.mruAt.Store(1) | |||
| 1030 | + | ephemeral := &session{id: "ephemeral", name: "1", ephemeral: true, tabs: []*tab{{}}} | |||
| 1031 | + | ephemeral.mruAt.Store(2) | |||
| 1032 | + | d.sessions[persistent.id] = persistent | |||
| 1033 | + | d.sessions[ephemeral.id] = ephemeral | |||
| 1034 | + | ||||
| 1035 | + | state := d.barStateFor(current, "") | |||
| 1036 | + | ||||
| 1037 | + | require.Len(t, state.mru, 1) | |||
| 1038 | + | require.Equal(t, "named", state.mru[0].name) | |||
| 1039 | + | } | |||
| 1040 | + | ||||
| 1023 | func TestBarStateForMRUZeroTimesUseDeterministicNameOrder(t *testing.T) { | 1041 | func TestBarStateForMRUZeroTimesUseDeterministicNameOrder(t *testing.T) { | ||
| 1024 | p, releasePTY := newBlockingPTY(t) | 1042 | p, releasePTY := newBlockingPTY(t) | ||
| 1025 | d, sess, _, _ := newManualSessionWithPTYs(t, p) | 1043 | d, sess, _, _ := newManualSessionWithPTYs(t, p) | ||