detached 932c0f3

test(navigation): harden identity and wire coverage

commit
932c0f31b72263453a701a01ebe69b6c5aeaf6a1
author
brice · · 2026-08-18 20:28 UTC
committer
brice · · 2026-08-19 04:15 UTC
parent
86aeb12
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
9 files · +78 -55
M internal/ports/routes_test.go +18 -2
@@ -197,8 +197,24 @@ func TestRecentRouteSnapshotRejectsOversizedFrameBeforeParsing(t *testing.T) {
197197 }
198198
199199 func TestRouteLabelBoundsMatchRemoteDisplayOrigin(t *testing.T) {
200- require.NoError(t, ValidateRouteLabel(strings.Repeat("a", 256), false))
201- require.Error(t, ValidateRouteLabel(strings.Repeat("a", 257), false))
200+ tests := []struct {
201+ name string
202+ length int
203+ wantErr bool
204+ }{
205+ {name: "maximum", length: 256},
206+ {name: "over maximum", length: 257, wantErr: true},
207+ }
208+ for _, tt := range tests {
209+ t.Run(tt.name, func(t *testing.T) {
210+ err := ValidateRouteLabel(strings.Repeat("a", tt.length), false)
211+ if tt.wantErr {
212+ require.Error(t, err)
213+ return
214+ }
215+ require.NoError(t, err)
216+ })
217+ }
202218 }
203219
204220 func TestRouteLabelsRejectTerminalUnsafeText(t *testing.T) {
@@ -197,8 +197,24 @@ func TestRecentRouteSnapshotRejectsOversizedFrameBeforeParsing(t *testing.T) {
197 }197 }
198 198
199 func TestRouteLabelBoundsMatchRemoteDisplayOrigin(t *testing.T) {199 func TestRouteLabelBoundsMatchRemoteDisplayOrigin(t *testing.T) {
200- require.NoError(t, ValidateRouteLabel(strings.Repeat("a", 256), false))200+ tests := []struct {
201- require.Error(t, ValidateRouteLabel(strings.Repeat("a", 257), false))201+ name string
202+ length int
203+ wantErr bool
204+ }{
205+ {name: "maximum", length: 256},
206+ {name: "over maximum", length: 257, wantErr: true},
207+ }
208+ for _, tt := range tests {
209+ t.Run(tt.name, func(t *testing.T) {
210+ err := ValidateRouteLabel(strings.Repeat("a", tt.length), false)
211+ if tt.wantErr {
212+ require.Error(t, err)
213+ return
214+ }
215+ require.NoError(t, err)
216+ })
217+ }
202 }218 }
203 219
204 func TestRouteLabelsRejectTerminalUnsafeText(t *testing.T) {220 func TestRouteLabelsRejectTerminalUnsafeText(t *testing.T) {
M internal/ports/wire_test.go +5 -5
@@ -173,15 +173,15 @@ func TestHelloEnvironmentCodec(t *testing.T) {
173173 base := MarshalHello(Hello{Version: ProtocolVersion, Size: domain.Size{Cols: 1, Rows: 1}})
174174 withCount := func(count byte) []byte {
175175 payload := append([]byte(nil), base...)
176- // The uint32 environment count occupies bytes len-13 through len-10.
177- payload[len(payload)-10] = count
176+ // The uint32 environment count occupies bytes len-11 through len-8.
177+ payload[len(payload)-8] = count
178178 return payload
179179 }
180180 withEntries := func(count byte, entries ...byte) []byte {
181181 payload := withCount(count)
182- // The nine-byte tail follows the environment-count field.
183- tail := append([]byte(nil), payload[len(payload)-9:]...)
184- payload = append(payload[:len(payload)-9], entries...)
182+ // The seven-byte tail follows the environment-count field.
183+ tail := append([]byte(nil), payload[len(payload)-7:]...)
184+ payload = append(payload[:len(payload)-7], entries...)
185185 return append(payload, tail...)
186186 }
187187 tests := []struct {
@@ -173,15 +173,15 @@ func TestHelloEnvironmentCodec(t *testing.T) {
173 base := MarshalHello(Hello{Version: ProtocolVersion, Size: domain.Size{Cols: 1, Rows: 1}})173 base := MarshalHello(Hello{Version: ProtocolVersion, Size: domain.Size{Cols: 1, Rows: 1}})
174 withCount := func(count byte) []byte {174 withCount := func(count byte) []byte {
175 payload := append([]byte(nil), base...)175 payload := append([]byte(nil), base...)
176- // The uint32 environment count occupies bytes len-13 through len-10.176+ // The uint32 environment count occupies bytes len-11 through len-8.
177- payload[len(payload)-10] = count177+ payload[len(payload)-8] = count
178 return payload178 return payload
179 }179 }
180 withEntries := func(count byte, entries ...byte) []byte {180 withEntries := func(count byte, entries ...byte) []byte {
181 payload := withCount(count)181 payload := withCount(count)
182- // The nine-byte tail follows the environment-count field.182+ // The seven-byte tail follows the environment-count field.
183- tail := append([]byte(nil), payload[len(payload)-9:]...)183+ tail := append([]byte(nil), payload[len(payload)-7:]...)
184- payload = append(payload[:len(payload)-9], entries...)184+ payload = append(payload[:len(payload)-7], entries...)
185 return append(payload, tail...)185 return append(payload, tail...)
186 }186 }
187 tests := []struct {187 tests := []struct {
M internal/usecase/daemon/palette.go +2 -2
@@ -84,7 +84,7 @@ func (d *Daemon) paletteResults(current *session, commands []command.Command, ro
8484 continue
8585 }
8686 target := ports.ExactSessionTarget{LifecycleID: snap.incarnation, SessionName: snap.name}
87- active = append(active, palette.NewActiveSessionResult(snap.name, time.Unix(0, snap.createdAt), target))
87+ active = append(active, palette.NewActiveSessionResult(target, time.Unix(0, snap.createdAt)))
8888 }
8989 sort.Slice(active, func(i, j int) bool {
9090 left, _ := active[i].SessionName()
@@ -95,7 +95,7 @@ func (d *Daemon) paletteResults(current *session, commands []command.Command, ro
9595 results = append(results, active...)
9696 for _, candidate := range stopped {
9797 target := ports.ExactSessionTarget{LifecycleID: candidate.incarnation, SessionName: candidate.name}
98- results = append(results, palette.NewStoppedSessionResult(candidate.name, time.Unix(0, candidate.createdAt), target))
98+ results = append(results, palette.NewStoppedSessionResult(target, time.Unix(0, candidate.createdAt)))
9999 localLifecycles[target.LifecycleID] = struct{}{}
100100 }
101101 formattedRoutes := formatRecentRouteSnapshot(routeSnapshot)
@@ -84,7 +84,7 @@ func (d *Daemon) paletteResults(current *session, commands []command.Command, ro
84 continue84 continue
85 }85 }
86 target := ports.ExactSessionTarget{LifecycleID: snap.incarnation, SessionName: snap.name}86 target := ports.ExactSessionTarget{LifecycleID: snap.incarnation, SessionName: snap.name}
87- active = append(active, palette.NewActiveSessionResult(snap.name, time.Unix(0, snap.createdAt), target))87+ active = append(active, palette.NewActiveSessionResult(target, time.Unix(0, snap.createdAt)))
88 }88 }
89 sort.Slice(active, func(i, j int) bool {89 sort.Slice(active, func(i, j int) bool {
90 left, _ := active[i].SessionName()90 left, _ := active[i].SessionName()
@@ -95,7 +95,7 @@ func (d *Daemon) paletteResults(current *session, commands []command.Command, ro
95 results = append(results, active...)95 results = append(results, active...)
96 for _, candidate := range stopped {96 for _, candidate := range stopped {
97 target := ports.ExactSessionTarget{LifecycleID: candidate.incarnation, SessionName: candidate.name}97 target := ports.ExactSessionTarget{LifecycleID: candidate.incarnation, SessionName: candidate.name}
98- results = append(results, palette.NewStoppedSessionResult(candidate.name, time.Unix(0, candidate.createdAt), target))98+ results = append(results, palette.NewStoppedSessionResult(target, time.Unix(0, candidate.createdAt)))
99 localLifecycles[target.LifecycleID] = struct{}{}99 localLifecycles[target.LifecycleID] = struct{}{}
100 }100 }
101 formattedRoutes := formatRecentRouteSnapshot(routeSnapshot)101 formattedRoutes := formatRecentRouteSnapshot(routeSnapshot)
M internal/usecase/daemon/status_test.go +28 -21
@@ -110,30 +110,37 @@ func TestStatusCompositionGolden(t *testing.T) {
110110 }
111111
112112 func TestStatusSegmentsResolvesActiveLifecyclePresentation(t *testing.T) {
113- p, releasePTY := newBlockingPTY(t)
114- _, sess, ac, _ := newManualSessionWithPTYs(t, p)
115- defer releasePTY()
116-
117- sess.name = "vive"
118- sess.incarnation = domain.SessionLifecycleID{1}
119- ref := ports.RouteRef{Key: 1, Generation: 1}
120- target := ports.ExactSessionTarget{LifecycleID: sess.incarnation, SessionName: sess.name}
121- snapshot := ports.RecentRouteSnapshot{
122- Generation: 1,
123- Active: ref,
124- ActiveEntry: ports.RecentRouteEntry{
125- Key: ref.Key, Generation: ref.Generation, Target: target,
126- Name: "vive", HostLabel: "user@arch", Kind: ports.RouteKindRemote,
127- },
113+ tests := []struct {
114+ name string
115+ routeLifecycle domain.SessionLifecycleID
116+ wantPresentation string
117+ }{
118+ {name: "matching lifecycle", routeLifecycle: domain.SessionLifecycleID{1}, wantPresentation: "vive@arch"},
119+ {name: "stale lifecycle", routeLifecycle: domain.SessionLifecycleID{99}, wantPresentation: "vive"},
128120 }
129- ac.setRouteSnapshot(snapshot)
121+ for _, tt := range tests {
122+ t.Run(tt.name, func(t *testing.T) {
123+ p, releasePTY := newBlockingPTY(t)
124+ _, sess, ac, _ := newManualSessionWithPTYs(t, p)
125+ defer releasePTY()
130126
131- require.Equal(t, "vive@arch", sess.statusSegmentsFor(ac, true).session)
132- require.Equal(t, "vive", sess.statusSegments(true).session, "route presentation belongs only to the selected attachment")
127+ sess.name = "vive"
128+ sess.incarnation = domain.SessionLifecycleID{1}
129+ ref := ports.RouteRef{Key: 1, Generation: 1}
130+ ac.setRouteSnapshot(ports.RecentRouteSnapshot{
131+ Generation: 1,
132+ Active: ref,
133+ ActiveEntry: ports.RecentRouteEntry{
134+ Key: ref.Key, Generation: ref.Generation,
135+ Target: ports.ExactSessionTarget{LifecycleID: tt.routeLifecycle, SessionName: sess.name},
136+ Name: "vive", HostLabel: "user@arch", Kind: ports.RouteKindRemote,
137+ },
138+ })
133139
134- snapshot.ActiveEntry.Target.LifecycleID = domain.SessionLifecycleID{99}
135- ac.setRouteSnapshot(snapshot)
136- require.Equal(t, "vive", sess.statusSegmentsFor(ac, true).session, "a stale UUID must not label the active session")
140+ require.Equal(t, tt.wantPresentation, sess.statusSegmentsFor(ac, true).session)
141+ require.Equal(t, "vive", sess.statusSegments(true).session, "route presentation belongs only to the selected attachment")
142+ })
143+ }
137144 }
138145
139146 func TestStatusSegmentsIncludesFocusedPaneTitle(t *testing.T) {
@@ -110,30 +110,37 @@ func TestStatusCompositionGolden(t *testing.T) {
110 }110 }
111 111
112 func TestStatusSegmentsResolvesActiveLifecyclePresentation(t *testing.T) {112 func TestStatusSegmentsResolvesActiveLifecyclePresentation(t *testing.T) {
113- p, releasePTY := newBlockingPTY(t)113+ tests := []struct {
114- _, sess, ac, _ := newManualSessionWithPTYs(t, p)114+ name string
115- defer releasePTY()115+ routeLifecycle domain.SessionLifecycleID
116-116+ wantPresentation string
117- sess.name = "vive"117+ }{
118- sess.incarnation = domain.SessionLifecycleID{1}118+ {name: "matching lifecycle", routeLifecycle: domain.SessionLifecycleID{1}, wantPresentation: "vive@arch"},
119- ref := ports.RouteRef{Key: 1, Generation: 1}119+ {name: "stale lifecycle", routeLifecycle: domain.SessionLifecycleID{99}, wantPresentation: "vive"},
120- target := ports.ExactSessionTarget{LifecycleID: sess.incarnation, SessionName: sess.name}
121- snapshot := ports.RecentRouteSnapshot{
122- Generation: 1,
123- Active: ref,
124- ActiveEntry: ports.RecentRouteEntry{
125- Key: ref.Key, Generation: ref.Generation, Target: target,
126- Name: "vive", HostLabel: "user@arch", Kind: ports.RouteKindRemote,
127- },
128 }120 }
129- ac.setRouteSnapshot(snapshot)121+ for _, tt := range tests {
122+ t.Run(tt.name, func(t *testing.T) {
123+ p, releasePTY := newBlockingPTY(t)
124+ _, sess, ac, _ := newManualSessionWithPTYs(t, p)
125+ defer releasePTY()
130 126
131- require.Equal(t, "vive@arch", sess.statusSegmentsFor(ac, true).session)127+ sess.name = "vive"
132- require.Equal(t, "vive", sess.statusSegments(true).session, "route presentation belongs only to the selected attachment")128+ sess.incarnation = domain.SessionLifecycleID{1}
129+ ref := ports.RouteRef{Key: 1, Generation: 1}
130+ ac.setRouteSnapshot(ports.RecentRouteSnapshot{
131+ Generation: 1,
132+ Active: ref,
133+ ActiveEntry: ports.RecentRouteEntry{
134+ Key: ref.Key, Generation: ref.Generation,
135+ Target: ports.ExactSessionTarget{LifecycleID: tt.routeLifecycle, SessionName: sess.name},
136+ Name: "vive", HostLabel: "user@arch", Kind: ports.RouteKindRemote,
137+ },
138+ })
133 139
134- snapshot.ActiveEntry.Target.LifecycleID = domain.SessionLifecycleID{99}140+ require.Equal(t, tt.wantPresentation, sess.statusSegmentsFor(ac, true).session)
135- ac.setRouteSnapshot(snapshot)141+ require.Equal(t, "vive", sess.statusSegments(true).session, "route presentation belongs only to the selected attachment")
136- require.Equal(t, "vive", sess.statusSegmentsFor(ac, true).session, "a stale UUID must not label the active session")142+ })
143+ }
137 }144 }
138 145
139 func TestStatusSegmentsIncludesFocusedPaneTitle(t *testing.T) {146 func TestStatusSegmentsIncludesFocusedPaneTitle(t *testing.T) {
M internal/usecase/palette/action_test.go +1 -1
@@ -54,7 +54,7 @@ func TestArgumentCommandKeepsExactRowVisible(t *testing.T) {
5454
5555 func TestArgumentResultReturnsTheMatchingRowAndCommand(t *testing.T) {
5656 jrs := command.Command{Code: "JRS", Arguments: command.ArgumentsRequired}
57- results := []Result{NewStoppedSessionResult("JRS", time.Time{}, testExactTarget("JRS", 1)), NewCommandResult(jrs)}
57+ results := []Result{NewStoppedSessionResult(testExactTarget("JRS", 1), time.Time{}), NewCommandResult(jrs)}
5858
5959 result, got, ok := argumentResult(results, "jrs 1")
6060
@@ -54,7 +54,7 @@ func TestArgumentCommandKeepsExactRowVisible(t *testing.T) {
54 54
55 func TestArgumentResultReturnsTheMatchingRowAndCommand(t *testing.T) {55 func TestArgumentResultReturnsTheMatchingRowAndCommand(t *testing.T) {
56 jrs := command.Command{Code: "JRS", Arguments: command.ArgumentsRequired}56 jrs := command.Command{Code: "JRS", Arguments: command.ArgumentsRequired}
57- results := []Result{NewStoppedSessionResult("JRS", time.Time{}, testExactTarget("JRS", 1)), NewCommandResult(jrs)}57+ results := []Result{NewStoppedSessionResult(testExactTarget("JRS", 1), time.Time{}), NewCommandResult(jrs)}
58 58
59 result, got, ok := argumentResult(results, "jrs 1")59 result, got, ok := argumentResult(results, "jrs 1")
60 60
M internal/usecase/palette/fuzzy_test.go +9 -9
@@ -92,9 +92,9 @@ func TestFuzzyOrdersMixedResults(t *testing.T) {
9292 {
9393 name: "command shortcode precedes sessions and description matches",
9494 results: []Result{
95- NewStoppedSessionResult("work", created, testExactTarget("work", 1)),
95+ NewStoppedSessionResult(testExactTarget("work", 1), created),
9696 NewCommandResult(cmd("WORK", "", "Create a workspace")),
97- NewActiveSessionResult("work", created, testExactTarget("work", 2)),
97+ NewActiveSessionResult(testExactTarget("work", 2), created),
9898 NewCommandResult(cmd("WQORRK", "", "")),
9999 NewCommandResult(cmd("ZZZ", "", "work tools")),
100100 },
@@ -106,8 +106,8 @@ func TestFuzzyOrdersMixedResults(t *testing.T) {
106106 {
107107 name: "equivalent sessions sort by normalized text",
108108 results: []Result{
109- NewStoppedSessionResult("aBravo", time.Time{}, testExactTarget("aBravo", 1)),
110- NewStoppedSessionResult("aAlpha", time.Time{}, testExactTarget("aAlpha", 2)),
109+ NewStoppedSessionResult(testExactTarget("aBravo", 1), time.Time{}),
110+ NewStoppedSessionResult(testExactTarget("aAlpha", 2), time.Time{}),
111111 },
112112 query: "a",
113113 wantText: []string{"Resume session aAlpha", "Resume session aBravo"},
@@ -117,11 +117,11 @@ func TestFuzzyOrdersMixedResults(t *testing.T) {
117117 {
118118 name: "display prefix positions sort before later active names",
119119 results: []Result{
120- NewStoppedSessionResult("aBravo", time.Time{}, testExactTarget("aBravo", 1)),
121- NewStoppedSessionResult("aAlpha", time.Time{}, testExactTarget("aAlpha", 2)),
122- NewActiveSessionResult("aZulu", time.Time{}, testExactTarget("aZulu", 3)),
123- NewActiveSessionResult("aEcho", time.Time{}, testExactTarget("aEcho", 4)),
124- NewActiveSessionResult("aEcho", time.Time{}, testExactTarget("aEcho", 5)),
120+ NewStoppedSessionResult(testExactTarget("aBravo", 1), time.Time{}),
121+ NewStoppedSessionResult(testExactTarget("aAlpha", 2), time.Time{}),
122+ NewActiveSessionResult(testExactTarget("aZulu", 3), time.Time{}),
123+ NewActiveSessionResult(testExactTarget("aEcho", 4), time.Time{}),
124+ NewActiveSessionResult(testExactTarget("aEcho", 5), time.Time{}),
125125 NewCommandResult(cmd("AX", "", "")),
126126 },
127127 query: "a",
@@ -92,9 +92,9 @@ func TestFuzzyOrdersMixedResults(t *testing.T) {
92 {92 {
93 name: "command shortcode precedes sessions and description matches",93 name: "command shortcode precedes sessions and description matches",
94 results: []Result{94 results: []Result{
95- NewStoppedSessionResult("work", created, testExactTarget("work", 1)),95+ NewStoppedSessionResult(testExactTarget("work", 1), created),
96 NewCommandResult(cmd("WORK", "", "Create a workspace")),96 NewCommandResult(cmd("WORK", "", "Create a workspace")),
97- NewActiveSessionResult("work", created, testExactTarget("work", 2)),97+ NewActiveSessionResult(testExactTarget("work", 2), created),
98 NewCommandResult(cmd("WQORRK", "", "")),98 NewCommandResult(cmd("WQORRK", "", "")),
99 NewCommandResult(cmd("ZZZ", "", "work tools")),99 NewCommandResult(cmd("ZZZ", "", "work tools")),
100 },100 },
@@ -106,8 +106,8 @@ func TestFuzzyOrdersMixedResults(t *testing.T) {
106 {106 {
107 name: "equivalent sessions sort by normalized text",107 name: "equivalent sessions sort by normalized text",
108 results: []Result{108 results: []Result{
109- NewStoppedSessionResult("aBravo", time.Time{}, testExactTarget("aBravo", 1)),109+ NewStoppedSessionResult(testExactTarget("aBravo", 1), time.Time{}),
110- NewStoppedSessionResult("aAlpha", time.Time{}, testExactTarget("aAlpha", 2)),110+ NewStoppedSessionResult(testExactTarget("aAlpha", 2), time.Time{}),
111 },111 },
112 query: "a",112 query: "a",
113 wantText: []string{"Resume session aAlpha", "Resume session aBravo"},113 wantText: []string{"Resume session aAlpha", "Resume session aBravo"},
@@ -117,11 +117,11 @@ func TestFuzzyOrdersMixedResults(t *testing.T) {
117 {117 {
118 name: "display prefix positions sort before later active names",118 name: "display prefix positions sort before later active names",
119 results: []Result{119 results: []Result{
120- NewStoppedSessionResult("aBravo", time.Time{}, testExactTarget("aBravo", 1)),120+ NewStoppedSessionResult(testExactTarget("aBravo", 1), time.Time{}),
121- NewStoppedSessionResult("aAlpha", time.Time{}, testExactTarget("aAlpha", 2)),121+ NewStoppedSessionResult(testExactTarget("aAlpha", 2), time.Time{}),
122- NewActiveSessionResult("aZulu", time.Time{}, testExactTarget("aZulu", 3)),122+ NewActiveSessionResult(testExactTarget("aZulu", 3), time.Time{}),
123- NewActiveSessionResult("aEcho", time.Time{}, testExactTarget("aEcho", 4)),123+ NewActiveSessionResult(testExactTarget("aEcho", 4), time.Time{}),
124- NewActiveSessionResult("aEcho", time.Time{}, testExactTarget("aEcho", 5)),124+ NewActiveSessionResult(testExactTarget("aEcho", 5), time.Time{}),
125 NewCommandResult(cmd("AX", "", "")),125 NewCommandResult(cmd("AX", "", "")),
126 },126 },
127 query: "a",127 query: "a",
M internal/usecase/palette/model_test.go +5 -5
@@ -463,11 +463,11 @@ func TestModelUsesDefensiveTypedResultsAndKeepsSessionsCommandInert(t *testing.T
463463 workTarget := testExactTarget("work", 1)
464464 results := []Result{
465465 NewCommandResult(cmd("JRS", "Jump", "Jump to recent session")),
466- NewActiveSessionResult("work", created, workTarget),
467- NewStoppedSessionResult("work", created, testExactTarget("work", 2)),
466+ NewActiveSessionResult(workTarget, created),
467+ NewStoppedSessionResult(testExactTarget("work", 2), created),
468468 }
469469 m := New(results)
470- results[1] = NewActiveSessionResult("changed", created, testExactTarget("changed", 3))
470+ results[1] = NewActiveSessionResult(testExactTarget("changed", 3), created)
471471
472472 for _, r := range "switch" {
473473 m.Insert(r)
@@ -494,7 +494,7 @@ func TestModelUsesDefensiveTypedResultsAndKeepsSessionsCommandInert(t *testing.T
494494 }
495495
496496 func TestRenderStoppedSessionHighlightsNameAfterResumePrefix(t *testing.T) {
497- m := New([]Result{NewStoppedSessionResult("work", time.Unix(0, 1), testExactTarget("work", 1))})
497+ m := New([]Result{NewStoppedSessionResult(testExactTarget("work", 1), time.Unix(0, 1))})
498498 m.Insert('w')
499499 m.Insert('k')
500500
@@ -507,7 +507,7 @@ func TestRenderStoppedSessionHighlightsNameAfterResumePrefix(t *testing.T) {
507507 }
508508
509509 func TestRenderFeedbackUsesSelectedSessionRowWithoutAddingResult(t *testing.T) {
510- m := New([]Result{NewActiveSessionResult("work", time.Unix(0, 1), testExactTarget("work", 1))})
510+ m := New([]Result{NewActiveSessionResult(testExactTarget("work", 1), time.Unix(0, 1))})
511511 frame := m.Render(domain.Size{Cols: 64, Rows: 3}, RenderOptions{Styles: DefaultRenderStyles(), Feedback: "requested session is unavailable"})
512512 require.Len(t, m.Matches(), 1)
513513 require.Contains(t, frameRow(frame, 1), "requested session is unavailable")
@@ -463,11 +463,11 @@ func TestModelUsesDefensiveTypedResultsAndKeepsSessionsCommandInert(t *testing.T
463 workTarget := testExactTarget("work", 1)463 workTarget := testExactTarget("work", 1)
464 results := []Result{464 results := []Result{
465 NewCommandResult(cmd("JRS", "Jump", "Jump to recent session")),465 NewCommandResult(cmd("JRS", "Jump", "Jump to recent session")),
466- NewActiveSessionResult("work", created, workTarget),466+ NewActiveSessionResult(workTarget, created),
467- NewStoppedSessionResult("work", created, testExactTarget("work", 2)),467+ NewStoppedSessionResult(testExactTarget("work", 2), created),
468 }468 }
469 m := New(results)469 m := New(results)
470- results[1] = NewActiveSessionResult("changed", created, testExactTarget("changed", 3))470+ results[1] = NewActiveSessionResult(testExactTarget("changed", 3), created)
471 471
472 for _, r := range "switch" {472 for _, r := range "switch" {
473 m.Insert(r)473 m.Insert(r)
@@ -494,7 +494,7 @@ func TestModelUsesDefensiveTypedResultsAndKeepsSessionsCommandInert(t *testing.T
494 }494 }
495 495
496 func TestRenderStoppedSessionHighlightsNameAfterResumePrefix(t *testing.T) {496 func TestRenderStoppedSessionHighlightsNameAfterResumePrefix(t *testing.T) {
497- m := New([]Result{NewStoppedSessionResult("work", time.Unix(0, 1), testExactTarget("work", 1))})497+ m := New([]Result{NewStoppedSessionResult(testExactTarget("work", 1), time.Unix(0, 1))})
498 m.Insert('w')498 m.Insert('w')
499 m.Insert('k')499 m.Insert('k')
500 500
@@ -507,7 +507,7 @@ func TestRenderStoppedSessionHighlightsNameAfterResumePrefix(t *testing.T) {
507 }507 }
508 508
509 func TestRenderFeedbackUsesSelectedSessionRowWithoutAddingResult(t *testing.T) {509 func TestRenderFeedbackUsesSelectedSessionRowWithoutAddingResult(t *testing.T) {
510- m := New([]Result{NewActiveSessionResult("work", time.Unix(0, 1), testExactTarget("work", 1))})510+ m := New([]Result{NewActiveSessionResult(testExactTarget("work", 1), time.Unix(0, 1))})
511 frame := m.Render(domain.Size{Cols: 64, Rows: 3}, RenderOptions{Styles: DefaultRenderStyles(), Feedback: "requested session is unavailable"})511 frame := m.Render(domain.Size{Cols: 64, Rows: 3}, RenderOptions{Styles: DefaultRenderStyles(), Feedback: "requested session is unavailable"})
512 require.Len(t, m.Matches(), 1)512 require.Len(t, m.Matches(), 1)
513 require.Contains(t, frameRow(frame, 1), "requested session is unavailable")513 require.Contains(t, frameRow(frame, 1), "requested session is unavailable")
M internal/usecase/palette/result.go +4 -4
@@ -43,18 +43,18 @@ func NewCommandResult(cmd command.Command) Result {
4343 }
4444
4545 // NewActiveSessionResult creates an immutable active named-session target.
46-func NewActiveSessionResult(name string, createdAt time.Time, target ports.ExactSessionTarget) Result {
46+func NewActiveSessionResult(target ports.ExactSessionTarget, createdAt time.Time) Result {
4747 return Result{
4848 kind: ResultKindActiveSession,
49- session: sessionPayload{name: name, createdAt: createdAt, target: target},
49+ session: sessionPayload{name: target.SessionName, createdAt: createdAt, target: target},
5050 }
5151 }
5252
5353 // NewStoppedSessionResult creates an immutable stopped named-session target.
54-func NewStoppedSessionResult(name string, createdAt time.Time, target ports.ExactSessionTarget) Result {
54+func NewStoppedSessionResult(target ports.ExactSessionTarget, createdAt time.Time) Result {
5555 return Result{
5656 kind: ResultKindStoppedSession,
57- session: sessionPayload{name: name, createdAt: createdAt, target: target},
57+ session: sessionPayload{name: target.SessionName, createdAt: createdAt, target: target},
5858 }
5959 }
6060
@@ -43,18 +43,18 @@ func NewCommandResult(cmd command.Command) Result {
43 }43 }
44 44
45 // NewActiveSessionResult creates an immutable active named-session target.45 // NewActiveSessionResult creates an immutable active named-session target.
46-func NewActiveSessionResult(name string, createdAt time.Time, target ports.ExactSessionTarget) Result {46+func NewActiveSessionResult(target ports.ExactSessionTarget, createdAt time.Time) Result {
47 return Result{47 return Result{
48 kind: ResultKindActiveSession,48 kind: ResultKindActiveSession,
49- session: sessionPayload{name: name, createdAt: createdAt, target: target},49+ session: sessionPayload{name: target.SessionName, createdAt: createdAt, target: target},
50 }50 }
51 }51 }
52 52
53 // NewStoppedSessionResult creates an immutable stopped named-session target.53 // NewStoppedSessionResult creates an immutable stopped named-session target.
54-func NewStoppedSessionResult(name string, createdAt time.Time, target ports.ExactSessionTarget) Result {54+func NewStoppedSessionResult(target ports.ExactSessionTarget, createdAt time.Time) Result {
55 return Result{55 return Result{
56 kind: ResultKindStoppedSession,56 kind: ResultKindStoppedSession,
57- session: sessionPayload{name: name, createdAt: createdAt, target: target},57+ session: sessionPayload{name: target.SessionName, createdAt: createdAt, target: target},
58 }58 }
59 }59 }
60 60
M internal/usecase/palette/result_test.go +6 -6
@@ -19,8 +19,8 @@ func TestResultKindsAndSessionLifecycleTargets(t *testing.T) {
1919 commandResult := NewCommandResult(command.Command{Code: "NT", Desc: "Create tab"})
2020 activeTarget := testExactTarget("work", 1)
2121 stoppedTarget := testExactTarget("archive", 2)
22- active := NewActiveSessionResult("work", created, activeTarget)
23- stopped := NewStoppedSessionResult("archive", created, stoppedTarget)
22+ active := NewActiveSessionResult(activeTarget, created)
23+ stopped := NewStoppedSessionResult(stoppedTarget, created)
2424
2525 require.Equal(t, ResultKindCommand, commandResult.Kind())
2626 require.Equal(t, "NT", commandResult.DisplayText())
@@ -78,10 +78,10 @@ func TestSessionResultsSearchDisplayedActionText(t *testing.T) {
7878 query string
7979 positions []int
8080 }{
81- {name: "active prefix", result: NewActiveSessionResult("work", created, testExactTarget("work", 1)), query: "switch", positions: []int{0, 1, 2, 3, 4, 5}},
82- {name: "active name", result: NewActiveSessionResult("work", created, testExactTarget("work", 1)), query: "work", positions: []int{1, 8, 20, 21}},
83- {name: "stopped prefix", result: NewStoppedSessionResult("archive", created, testExactTarget("archive", 2)), query: "resume", positions: []int{0, 1, 2, 3, 4, 5}},
84- {name: "stopped name", result: NewStoppedSessionResult("archive", created, testExactTarget("archive", 2)), query: "archive", positions: []int{15, 16, 17, 18, 19, 20, 21}},
81+ {name: "active prefix", result: NewActiveSessionResult(testExactTarget("work", 1), created), query: "switch", positions: []int{0, 1, 2, 3, 4, 5}},
82+ {name: "active name", result: NewActiveSessionResult(testExactTarget("work", 1), created), query: "work", positions: []int{1, 8, 20, 21}},
83+ {name: "stopped prefix", result: NewStoppedSessionResult(testExactTarget("archive", 2), created), query: "resume", positions: []int{0, 1, 2, 3, 4, 5}},
84+ {name: "stopped name", result: NewStoppedSessionResult(testExactTarget("archive", 2), created), query: "archive", positions: []int{15, 16, 17, 18, 19, 20, 21}},
8585 }
8686
8787 for _, tt := range tests {
@@ -19,8 +19,8 @@ func TestResultKindsAndSessionLifecycleTargets(t *testing.T) {
19 commandResult := NewCommandResult(command.Command{Code: "NT", Desc: "Create tab"})19 commandResult := NewCommandResult(command.Command{Code: "NT", Desc: "Create tab"})
20 activeTarget := testExactTarget("work", 1)20 activeTarget := testExactTarget("work", 1)
21 stoppedTarget := testExactTarget("archive", 2)21 stoppedTarget := testExactTarget("archive", 2)
22- active := NewActiveSessionResult("work", created, activeTarget)22+ active := NewActiveSessionResult(activeTarget, created)
23- stopped := NewStoppedSessionResult("archive", created, stoppedTarget)23+ stopped := NewStoppedSessionResult(stoppedTarget, created)
24 24
25 require.Equal(t, ResultKindCommand, commandResult.Kind())25 require.Equal(t, ResultKindCommand, commandResult.Kind())
26 require.Equal(t, "NT", commandResult.DisplayText())26 require.Equal(t, "NT", commandResult.DisplayText())
@@ -78,10 +78,10 @@ func TestSessionResultsSearchDisplayedActionText(t *testing.T) {
78 query string78 query string
79 positions []int79 positions []int
80 }{80 }{
81- {name: "active prefix", result: NewActiveSessionResult("work", created, testExactTarget("work", 1)), query: "switch", positions: []int{0, 1, 2, 3, 4, 5}},81+ {name: "active prefix", result: NewActiveSessionResult(testExactTarget("work", 1), created), query: "switch", positions: []int{0, 1, 2, 3, 4, 5}},
82- {name: "active name", result: NewActiveSessionResult("work", created, testExactTarget("work", 1)), query: "work", positions: []int{1, 8, 20, 21}},82+ {name: "active name", result: NewActiveSessionResult(testExactTarget("work", 1), created), query: "work", positions: []int{1, 8, 20, 21}},
83- {name: "stopped prefix", result: NewStoppedSessionResult("archive", created, testExactTarget("archive", 2)), query: "resume", positions: []int{0, 1, 2, 3, 4, 5}},83+ {name: "stopped prefix", result: NewStoppedSessionResult(testExactTarget("archive", 2), created), query: "resume", positions: []int{0, 1, 2, 3, 4, 5}},
84- {name: "stopped name", result: NewStoppedSessionResult("archive", created, testExactTarget("archive", 2)), query: "archive", positions: []int{15, 16, 17, 18, 19, 20, 21}},84+ {name: "stopped name", result: NewStoppedSessionResult(testExactTarget("archive", 2), created), query: "archive", positions: []int{15, 16, 17, 18, 19, 20, 21}},
85 }85 }
86 86
87 for _, tt := range tests {87 for _, tt := range tests {

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