detached 475bd98

refactor(client): inline trivial navigation accessors and test navigation state

commit
475bd987af3c5a2f3f8277ea5aad7e303283fe7a
author
brice · · 2026-10-08 06:03 UTC
committer
brice · · 2026-10-08 08:12 UTC
parent
b96d645
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
refs
refactor/supervisor-split
6 files · +86 -42
M internal/usecase/client/supervisor.go +2 -2
@@ -590,7 +590,7 @@ func (s *Supervisor) Run(ctx context.Context) (retErr error) {
590590 retire := func() {
591591 s.retirePickerKill()
592592 s.picker.preview.close(s.cfg.Picker)
593- s.nav.clearReady()
593+ s.nav.readySub = nil
594594 if sub != nil {
595595 sub.Close()
596596 sub = nil
@@ -659,7 +659,7 @@ func (s *Supervisor) Run(ctx context.Context) (retErr error) {
659659 }
660660
661661 service, sub = result.service, result.sub
662- s.nav.setReady(sub)
662+ s.nav.readySub = sub
663663 if s.cfg.Picker != nil {
664664 // Render the first committed publication immediately, before waiting
665665 // for the next one; the picker never shows a stale empty catalogue
@@ -590,7 +590,7 @@ func (s *Supervisor) Run(ctx context.Context) (retErr error) {
590 retire := func() {590 retire := func() {
591 s.retirePickerKill()591 s.retirePickerKill()
592 s.picker.preview.close(s.cfg.Picker)592 s.picker.preview.close(s.cfg.Picker)
593- s.nav.clearReady()593+ s.nav.readySub = nil
594 if sub != nil {594 if sub != nil {
595 sub.Close()595 sub.Close()
596 sub = nil596 sub = nil
@@ -659,7 +659,7 @@ func (s *Supervisor) Run(ctx context.Context) (retErr error) {
659 }659 }
660 660
661 service, sub = result.service, result.sub661 service, sub = result.service, result.sub
662- s.nav.setReady(sub)662+ s.nav.readySub = sub
663 if s.cfg.Picker != nil {663 if s.cfg.Picker != nil {
664 // Render the first committed publication immediately, before waiting664 // Render the first committed publication immediately, before waiting
665 // for the next one; the picker never shows a stale empty catalogue665 // for the next one; the picker never shows a stale empty catalogue
M internal/usecase/client/supervisor_navigation.go +6 -25
@@ -12,22 +12,21 @@ import (
1212 type navigationState struct {
1313 // readySub is the adopted connection's subscription while the ready phase
1414 // runs, so the picker overlay over a live attachment keeps folding broker
15- // publications. It is only touched from the run goroutine.
15+ // publications.
1616 readySub ports.BrokerSubscription
1717 // pendingSwap is the request the picker overlay or a daemon navigation
18- // committed to another target while an attachment was live. It is only
19- // touched from the run goroutine and consumed by settleAttachment.
18+ // committed to another target while an attachment was live. Consumed by
19+ // settleAttachment.
2020 pendingSwap *pickerAttachmentTarget
2121 // pendingInPlace is the choice the live attachment is switching to in
22- // place on its own daemon. A refusal turns it into pendingSwap. Only
23- // touched from the run goroutine; cleared when the attachment settles.
22+ // place on its own daemon. A refusal turns it into pendingSwap. Cleared
23+ // when the attachment settles.
2424 pendingInPlace *pendingInPlace
2525 // inPlaceSeq numbers in-place choices so a late outcome for an older one
2626 // is never applied to a newer one.
2727 inPlaceSeq uint64
2828 // routes is the client route ledger published to the serving daemon;
29- // routesSent is the attachment that received its latest snapshot. Both are
30- // only touched from the run goroutine.
29+ // routesSent is the attachment that received its latest snapshot.
3130 routes *routeLedger
3231 routesSent AttachmentToken
3332 }
@@ -40,18 +39,6 @@ func (n *navigationState) brokerChanged() <-chan struct{} {
4039 return n.readySub.Changed()
4140 }
4241
43-// setReady records the adopted connection's subscription.
44-func (n *navigationState) setReady(sub ports.BrokerSubscription) { n.readySub = sub }
45-
46-// clearReady forgets the adopted connection's subscription.
47-func (n *navigationState) clearReady() { n.readySub = nil }
48-
49-// setSwap commits the next attachment target.
50-func (n *navigationState) setSwap(target *pickerAttachmentTarget) { n.pendingSwap = target }
51-
52-// hasSwap reports whether a swap target is already committed.
53-func (n *navigationState) hasSwap() bool { return n.pendingSwap != nil }
54-
5542 // takeSwap returns and clears the committed swap target.
5643 func (n *navigationState) takeSwap() *pickerAttachmentTarget {
5744 swap := n.pendingSwap
@@ -102,12 +89,6 @@ func (n *navigationState) buildRoutes(snapshot ports.BrokerSnapshot, active rout
10289 return n.routes.build(snapshot, active)
10390 }
10491
105-// routesSentTo reports whether token already received a route snapshot.
106-func (n *navigationState) routesSentTo(token AttachmentToken) bool { return n.routesSent == token }
107-
108-// markRoutesSent records the attachment that received the latest snapshot.
109-func (n *navigationState) markRoutesSent(token AttachmentToken) { n.routesSent = token }
110-
11192 // resolveRoute resolves one route reference against the ledger.
11293 func (n *navigationState) resolveRoute(ref protocol.RouteRef) (routeLedgerTarget, bool) {
11394 if n.routes == nil || ref.IsZero() {
@@ -12,22 +12,21 @@ import (
12 type navigationState struct {12 type navigationState struct {
13 // readySub is the adopted connection's subscription while the ready phase13 // readySub is the adopted connection's subscription while the ready phase
14 // runs, so the picker overlay over a live attachment keeps folding broker14 // runs, so the picker overlay over a live attachment keeps folding broker
15- // publications. It is only touched from the run goroutine.15+ // publications.
16 readySub ports.BrokerSubscription16 readySub ports.BrokerSubscription
17 // pendingSwap is the request the picker overlay or a daemon navigation17 // pendingSwap is the request the picker overlay or a daemon navigation
18- // committed to another target while an attachment was live. It is only18+ // committed to another target while an attachment was live. Consumed by
19- // touched from the run goroutine and consumed by settleAttachment.19+ // settleAttachment.
20 pendingSwap *pickerAttachmentTarget20 pendingSwap *pickerAttachmentTarget
21 // pendingInPlace is the choice the live attachment is switching to in21 // pendingInPlace is the choice the live attachment is switching to in
22- // place on its own daemon. A refusal turns it into pendingSwap. Only22+ // place on its own daemon. A refusal turns it into pendingSwap. Cleared
23- // touched from the run goroutine; cleared when the attachment settles.23+ // when the attachment settles.
24 pendingInPlace *pendingInPlace24 pendingInPlace *pendingInPlace
25 // inPlaceSeq numbers in-place choices so a late outcome for an older one25 // inPlaceSeq numbers in-place choices so a late outcome for an older one
26 // is never applied to a newer one.26 // is never applied to a newer one.
27 inPlaceSeq uint6427 inPlaceSeq uint64
28 // routes is the client route ledger published to the serving daemon;28 // routes is the client route ledger published to the serving daemon;
29- // routesSent is the attachment that received its latest snapshot. Both are29+ // routesSent is the attachment that received its latest snapshot.
30- // only touched from the run goroutine.
31 routes *routeLedger30 routes *routeLedger
32 routesSent AttachmentToken31 routesSent AttachmentToken
33 }32 }
@@ -40,18 +39,6 @@ func (n *navigationState) brokerChanged() <-chan struct{} {
40 return n.readySub.Changed()39 return n.readySub.Changed()
41 }40 }
42 41
43-// setReady records the adopted connection's subscription.
44-func (n *navigationState) setReady(sub ports.BrokerSubscription) { n.readySub = sub }
45-
46-// clearReady forgets the adopted connection's subscription.
47-func (n *navigationState) clearReady() { n.readySub = nil }
48-
49-// setSwap commits the next attachment target.
50-func (n *navigationState) setSwap(target *pickerAttachmentTarget) { n.pendingSwap = target }
51-
52-// hasSwap reports whether a swap target is already committed.
53-func (n *navigationState) hasSwap() bool { return n.pendingSwap != nil }
54-
55 // takeSwap returns and clears the committed swap target.42 // takeSwap returns and clears the committed swap target.
56 func (n *navigationState) takeSwap() *pickerAttachmentTarget {43 func (n *navigationState) takeSwap() *pickerAttachmentTarget {
57 swap := n.pendingSwap44 swap := n.pendingSwap
@@ -102,12 +89,6 @@ func (n *navigationState) buildRoutes(snapshot ports.BrokerSnapshot, active rout
102 return n.routes.build(snapshot, active)89 return n.routes.build(snapshot, active)
103 }90 }
104 91
105-// routesSentTo reports whether token already received a route snapshot.
106-func (n *navigationState) routesSentTo(token AttachmentToken) bool { return n.routesSent == token }
107-
108-// markRoutesSent records the attachment that received the latest snapshot.
109-func (n *navigationState) markRoutesSent(token AttachmentToken) { n.routesSent = token }
110-
111 // resolveRoute resolves one route reference against the ledger.92 // resolveRoute resolves one route reference against the ledger.
112 func (n *navigationState) resolveRoute(ref protocol.RouteRef) (routeLedgerTarget, bool) {93 func (n *navigationState) resolveRoute(ref protocol.RouteRef) (routeLedgerTarget, bool) {
113 if n.routes == nil || ref.IsZero() {94 if n.routes == nil || ref.IsZero() {
A internal/usecase/client/supervisor_navigation_test.go +66 -0
@@ -0,0 +1,66 @@
1+package client
2+
3+import (
4+ "testing"
5+
6+ "github.com/bnema/vev/internal/ports"
7+ "github.com/bnema/vev/internal/protocol"
8+)
9+
10+func TestNavigationState(t *testing.T) {
11+ tests := []struct {
12+ name string
13+ run func(t *testing.T)
14+ }{
15+ {"takeInPlace stale seq keeps pending", func(t *testing.T) {
16+ var n navigationState
17+ n.startInPlace(2, pickerAttachmentTarget{})
18+ if got := n.takeInPlace(1); got != nil {
19+ t.Fatalf("stale takeInPlace = %v, want nil", got)
20+ }
21+ if n.inPlaceIdle() {
22+ t.Fatal("stale takeInPlace cleared pending")
23+ }
24+ }},
25+ {"takeInPlace matching seq clears", func(t *testing.T) {
26+ var n navigationState
27+ n.startInPlace(2, pickerAttachmentTarget{})
28+ if got := n.takeInPlace(2); got == nil || got.seq != 2 {
29+ t.Fatalf("takeInPlace = %v, want seq 2", got)
30+ }
31+ if !n.inPlaceIdle() {
32+ t.Fatal("matching takeInPlace kept pending")
33+ }
34+ }},
35+ {"resolveRoute zero value", func(t *testing.T) {
36+ var n navigationState
37+ if _, ok := n.resolveRoute(protocol.RouteRef{Key: 1, Generation: 1}); ok {
38+ t.Fatal("resolveRoute on zero value = true, want false")
39+ }
40+ }},
41+ {"buildRoutes creates ledger lazily", func(t *testing.T) {
42+ var n navigationState
43+ if n.routes != nil {
44+ t.Fatal("routes non-nil at zero value")
45+ }
46+ n.buildRoutes(ports.BrokerSnapshot{}, routeActive{})
47+ if n.routes == nil {
48+ t.Fatal("routes nil after buildRoutes")
49+ }
50+ }},
51+ {"takeSwap returns and clears", func(t *testing.T) {
52+ var n navigationState
53+ want := &pickerAttachmentTarget{}
54+ n.pendingSwap = want
55+ if got := n.takeSwap(); got != want {
56+ t.Fatalf("takeSwap = %p, want %p", got, want)
57+ }
58+ if n.pendingSwap != nil {
59+ t.Fatal("takeSwap kept swap")
60+ }
61+ }},
62+ }
63+ for _, tt := range tests {
64+ t.Run(tt.name, tt.run)
65+ }
66+}
@@ -0,0 +1,66 @@
1+package client
2+
3+import (
4+ "testing"
5+
6+ "github.com/bnema/vev/internal/ports"
7+ "github.com/bnema/vev/internal/protocol"
8+)
9+
10+func TestNavigationState(t *testing.T) {
11+ tests := []struct {
12+ name string
13+ run func(t *testing.T)
14+ }{
15+ {"takeInPlace stale seq keeps pending", func(t *testing.T) {
16+ var n navigationState
17+ n.startInPlace(2, pickerAttachmentTarget{})
18+ if got := n.takeInPlace(1); got != nil {
19+ t.Fatalf("stale takeInPlace = %v, want nil", got)
20+ }
21+ if n.inPlaceIdle() {
22+ t.Fatal("stale takeInPlace cleared pending")
23+ }
24+ }},
25+ {"takeInPlace matching seq clears", func(t *testing.T) {
26+ var n navigationState
27+ n.startInPlace(2, pickerAttachmentTarget{})
28+ if got := n.takeInPlace(2); got == nil || got.seq != 2 {
29+ t.Fatalf("takeInPlace = %v, want seq 2", got)
30+ }
31+ if !n.inPlaceIdle() {
32+ t.Fatal("matching takeInPlace kept pending")
33+ }
34+ }},
35+ {"resolveRoute zero value", func(t *testing.T) {
36+ var n navigationState
37+ if _, ok := n.resolveRoute(protocol.RouteRef{Key: 1, Generation: 1}); ok {
38+ t.Fatal("resolveRoute on zero value = true, want false")
39+ }
40+ }},
41+ {"buildRoutes creates ledger lazily", func(t *testing.T) {
42+ var n navigationState
43+ if n.routes != nil {
44+ t.Fatal("routes non-nil at zero value")
45+ }
46+ n.buildRoutes(ports.BrokerSnapshot{}, routeActive{})
47+ if n.routes == nil {
48+ t.Fatal("routes nil after buildRoutes")
49+ }
50+ }},
51+ {"takeSwap returns and clears", func(t *testing.T) {
52+ var n navigationState
53+ want := &pickerAttachmentTarget{}
54+ n.pendingSwap = want
55+ if got := n.takeSwap(); got != want {
56+ t.Fatalf("takeSwap = %p, want %p", got, want)
57+ }
58+ if n.pendingSwap != nil {
59+ t.Fatal("takeSwap kept swap")
60+ }
61+ }},
62+ }
63+ for _, tt := range tests {
64+ t.Run(tt.name, tt.run)
65+ }
66+}
M internal/usecase/client/supervisor_overlay.go +2 -2
@@ -179,7 +179,7 @@ func (o *attachmentPickerOverlay) takeOp() {
179179 o.release(false)
180180 return
181181 }
182- s.nav.setSwap(&target)
182+ s.nav.pendingSwap = &target
183183 o.swapping = true
184184 s.picker.preview.close(o.picker())
185185 s.attachments.requestDetach(o.run.token)
@@ -281,7 +281,7 @@ func (o *attachmentPickerOverlay) inPlaceSettled(result inPlaceResult) {
281281 if o.active {
282282 o.exit()
283283 }
284- s.nav.setSwap(&pending.target)
284+ s.nav.pendingSwap = &pending.target
285285 o.swapping = true
286286 s.attachments.requestDetach(o.run.token)
287287 }
@@ -179,7 +179,7 @@ func (o *attachmentPickerOverlay) takeOp() {
179 o.release(false)179 o.release(false)
180 return180 return
181 }181 }
182- s.nav.setSwap(&target)182+ s.nav.pendingSwap = &target
183 o.swapping = true183 o.swapping = true
184 s.picker.preview.close(o.picker())184 s.picker.preview.close(o.picker())
185 s.attachments.requestDetach(o.run.token)185 s.attachments.requestDetach(o.run.token)
@@ -281,7 +281,7 @@ func (o *attachmentPickerOverlay) inPlaceSettled(result inPlaceResult) {
281 if o.active {281 if o.active {
282 o.exit()282 o.exit()
283 }283 }
284- s.nav.setSwap(&pending.target)284+ s.nav.pendingSwap = &pending.target
285 o.swapping = true285 o.swapping = true
286 s.attachments.requestDetach(o.run.token)286 s.attachments.requestDetach(o.run.token)
287 }287 }
M internal/usecase/client/supervisor_picker_session.go +3 -2
@@ -3,8 +3,9 @@ package client
33 // pickerSession groups the picker state the supervisor carries across its run:
44 // the commit observed while connecting, the selected row's preview
55 // subscription, and the runner for the picker's kill operations. Like the
6-// fields it owns, it is only touched from the run goroutine. The zero value is
7-// ready once preview has its clock.
6+// fields it owns, it is only touched from the run goroutine, except the kill
7+// goroutine's kills.wg/kills.done handoff. Construct with preview.clock set;
8+// other fields are ready at zero.
89 type pickerSession struct {
910 // pendingPickerKey retains a commit observed while connecting. It is
1011 // revalidated against the adopted service just like a ready-phase commit.
@@ -3,8 +3,9 @@ package client
3 // pickerSession groups the picker state the supervisor carries across its run:3 // pickerSession groups the picker state the supervisor carries across its run:
4 // the commit observed while connecting, the selected row's preview4 // the commit observed while connecting, the selected row's preview
5 // subscription, and the runner for the picker's kill operations. Like the5 // subscription, and the runner for the picker's kill operations. Like the
6-// fields it owns, it is only touched from the run goroutine. The zero value is6+// fields it owns, it is only touched from the run goroutine, except the kill
7-// ready once preview has its clock.7+// goroutine's kills.wg/kills.done handoff. Construct with preview.clock set;
8+// other fields are ready at zero.
8 type pickerSession struct {9 type pickerSession struct {
9 // pendingPickerKey retains a commit observed while connecting. It is10 // pendingPickerKey retains a commit observed while connecting. It is
10 // revalidated against the adopted service just like a ready-phase commit.11 // revalidated against the adopted service just like a ready-phase commit.
M internal/usecase/client/supervisor_routes.go +7 -11
@@ -39,11 +39,11 @@ func (s *Supervisor) publishRoutes(service ports.BrokerNavigator, run *attachmen
3939 return
4040 }
4141 snapshot, changed := s.nav.buildRoutes(service.Snapshot(), routeActive{known: true, authority: requestAuthority(request), target: target})
42- if snapshot.Generation == 0 || (!changed && s.nav.routesSentTo(run.token)) {
42+ if snapshot.Generation == 0 || (!changed && s.nav.routesSent == run.token) {
4343 return
4444 }
4545 if s.attachments.publishRoutes(run.token, snapshot) {
46- s.nav.markRoutesSent(run.token)
46+ s.nav.routesSent = run.token
4747 }
4848 }
4949
@@ -85,7 +85,7 @@ func (s *Supervisor) settleDaemonNavigation(service ports.BrokerNavigator, overl
8585 if overlay.active {
8686 overlay.exit()
8787 }
88- s.nav.setSwap(&target)
88+ s.nav.pendingSwap = &target
8989 overlay.swapping = true
9090 s.attachments.requestDetach(overlay.run.token)
9191 }
@@ -108,14 +108,14 @@ func navigationCauseActionID(message protocol.ServerMessage) uint64 {
108108 // daemon detached the source right after sending it.
109109 func (s *Supervisor) takeSettledNavigation(service ports.BrokerNavigator, token AttachmentToken, request ports.BrokerOpenStreamRequest) {
110110 message, ok := s.attachments.takeNavigation()
111- if !ok || s.nav.hasSwap() {
111+ if !ok || s.nav.pendingSwap != nil {
112112 return
113113 }
114114 if target, ok := s.resolveDaemonNavigation(service, token, request, message); ok {
115115 if actionID := navigationCauseActionID(message); actionID != 0 && s.cfg.UI != nil {
116116 s.cfg.UI.follow(s.cfg.UI.generation, actionID)
117117 }
118- s.nav.setSwap(&target)
118+ s.nav.pendingSwap = &target
119119 }
120120 }
121121
@@ -128,7 +128,7 @@ func (s *Supervisor) resolveDaemonNavigation(service ports.BrokerNavigator, toke
128128 s.attachments.replyNavigation(token, protocol.RouteNavigationFailure{Key: typed.Key, Generation: typed.Generation, Code: code})
129129 return pickerAttachmentTarget{}, false
130130 }
131- routed, ok := s.resolveRoute(protocol.RouteRef{Key: typed.Key, Generation: typed.Generation})
131+ routed, ok := s.nav.resolveRoute(protocol.RouteRef{Key: typed.Key, Generation: typed.Generation})
132132 if !ok || routed.host {
133133 return fail(protocol.RouteFailureStaleSelection)
134134 }
@@ -145,7 +145,7 @@ func (s *Supervisor) resolveDaemonNavigation(service ports.BrokerNavigator, toke
145145 s.attachments.replyNavigation(token, protocol.SessionCreationFailure{RequestID: typed.RequestID, Code: code})
146146 return pickerAttachmentTarget{}, false
147147 }
148- routed, ok := s.resolveRoute(protocol.RouteRef{Key: typed.Key, Generation: typed.Generation})
148+ routed, ok := s.nav.resolveRoute(protocol.RouteRef{Key: typed.Key, Generation: typed.Generation})
149149 if !ok {
150150 return fail(protocol.RouteFailureStaleSelection)
151151 }
@@ -191,10 +191,6 @@ func (s *Supervisor) resolveServingHandoff(service ports.BrokerNavigator, reques
191191 return pickerAttachmentTarget{request: next, tab: attachmentTab{preferred: handoff.PreferredTabID}}, true
192192 }
193193
194-func (s *Supervisor) resolveRoute(ref protocol.RouteRef) (routeLedgerTarget, bool) {
195- return s.nav.resolveRoute(ref)
196-}
197-
198194 // resolveRouteSelection resolves one route through the same catalogue rules
199195 // as a picker commit, against the latest broker publication.
200196 func (s *Supervisor) resolveRouteSelection(service ports.BrokerNavigator, selection pickerSelectionRef) (pickerAttachmentTarget, error) {
@@ -39,11 +39,11 @@ func (s *Supervisor) publishRoutes(service ports.BrokerNavigator, run *attachmen
39 return39 return
40 }40 }
41 snapshot, changed := s.nav.buildRoutes(service.Snapshot(), routeActive{known: true, authority: requestAuthority(request), target: target})41 snapshot, changed := s.nav.buildRoutes(service.Snapshot(), routeActive{known: true, authority: requestAuthority(request), target: target})
42- if snapshot.Generation == 0 || (!changed && s.nav.routesSentTo(run.token)) {42+ if snapshot.Generation == 0 || (!changed && s.nav.routesSent == run.token) {
43 return43 return
44 }44 }
45 if s.attachments.publishRoutes(run.token, snapshot) {45 if s.attachments.publishRoutes(run.token, snapshot) {
46- s.nav.markRoutesSent(run.token)46+ s.nav.routesSent = run.token
47 }47 }
48 }48 }
49 49
@@ -85,7 +85,7 @@ func (s *Supervisor) settleDaemonNavigation(service ports.BrokerNavigator, overl
85 if overlay.active {85 if overlay.active {
86 overlay.exit()86 overlay.exit()
87 }87 }
88- s.nav.setSwap(&target)88+ s.nav.pendingSwap = &target
89 overlay.swapping = true89 overlay.swapping = true
90 s.attachments.requestDetach(overlay.run.token)90 s.attachments.requestDetach(overlay.run.token)
91 }91 }
@@ -108,14 +108,14 @@ func navigationCauseActionID(message protocol.ServerMessage) uint64 {
108 // daemon detached the source right after sending it.108 // daemon detached the source right after sending it.
109 func (s *Supervisor) takeSettledNavigation(service ports.BrokerNavigator, token AttachmentToken, request ports.BrokerOpenStreamRequest) {109 func (s *Supervisor) takeSettledNavigation(service ports.BrokerNavigator, token AttachmentToken, request ports.BrokerOpenStreamRequest) {
110 message, ok := s.attachments.takeNavigation()110 message, ok := s.attachments.takeNavigation()
111- if !ok || s.nav.hasSwap() {111+ if !ok || s.nav.pendingSwap != nil {
112 return112 return
113 }113 }
114 if target, ok := s.resolveDaemonNavigation(service, token, request, message); ok {114 if target, ok := s.resolveDaemonNavigation(service, token, request, message); ok {
115 if actionID := navigationCauseActionID(message); actionID != 0 && s.cfg.UI != nil {115 if actionID := navigationCauseActionID(message); actionID != 0 && s.cfg.UI != nil {
116 s.cfg.UI.follow(s.cfg.UI.generation, actionID)116 s.cfg.UI.follow(s.cfg.UI.generation, actionID)
117 }117 }
118- s.nav.setSwap(&target)118+ s.nav.pendingSwap = &target
119 }119 }
120 }120 }
121 121
@@ -128,7 +128,7 @@ func (s *Supervisor) resolveDaemonNavigation(service ports.BrokerNavigator, toke
128 s.attachments.replyNavigation(token, protocol.RouteNavigationFailure{Key: typed.Key, Generation: typed.Generation, Code: code})128 s.attachments.replyNavigation(token, protocol.RouteNavigationFailure{Key: typed.Key, Generation: typed.Generation, Code: code})
129 return pickerAttachmentTarget{}, false129 return pickerAttachmentTarget{}, false
130 }130 }
131- routed, ok := s.resolveRoute(protocol.RouteRef{Key: typed.Key, Generation: typed.Generation})131+ routed, ok := s.nav.resolveRoute(protocol.RouteRef{Key: typed.Key, Generation: typed.Generation})
132 if !ok || routed.host {132 if !ok || routed.host {
133 return fail(protocol.RouteFailureStaleSelection)133 return fail(protocol.RouteFailureStaleSelection)
134 }134 }
@@ -145,7 +145,7 @@ func (s *Supervisor) resolveDaemonNavigation(service ports.BrokerNavigator, toke
145 s.attachments.replyNavigation(token, protocol.SessionCreationFailure{RequestID: typed.RequestID, Code: code})145 s.attachments.replyNavigation(token, protocol.SessionCreationFailure{RequestID: typed.RequestID, Code: code})
146 return pickerAttachmentTarget{}, false146 return pickerAttachmentTarget{}, false
147 }147 }
148- routed, ok := s.resolveRoute(protocol.RouteRef{Key: typed.Key, Generation: typed.Generation})148+ routed, ok := s.nav.resolveRoute(protocol.RouteRef{Key: typed.Key, Generation: typed.Generation})
149 if !ok {149 if !ok {
150 return fail(protocol.RouteFailureStaleSelection)150 return fail(protocol.RouteFailureStaleSelection)
151 }151 }
@@ -191,10 +191,6 @@ func (s *Supervisor) resolveServingHandoff(service ports.BrokerNavigator, reques
191 return pickerAttachmentTarget{request: next, tab: attachmentTab{preferred: handoff.PreferredTabID}}, true191 return pickerAttachmentTarget{request: next, tab: attachmentTab{preferred: handoff.PreferredTabID}}, true
192 }192 }
193 193
194-func (s *Supervisor) resolveRoute(ref protocol.RouteRef) (routeLedgerTarget, bool) {
195- return s.nav.resolveRoute(ref)
196-}
197-
198 // resolveRouteSelection resolves one route through the same catalogue rules194 // resolveRouteSelection resolves one route through the same catalogue rules
199 // as a picker commit, against the latest broker publication.195 // as a picker commit, against the latest broker publication.
200 func (s *Supervisor) resolveRouteSelection(service ports.BrokerNavigator, selection pickerSelectionRef) (pickerAttachmentTarget, error) {196 func (s *Supervisor) resolveRouteSelection(service ports.BrokerNavigator, selection pickerSelectionRef) (pickerAttachmentTarget, error) {

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