detached ec147af

fix(navigation): clear daemon state on local handoff

commit
ec147af352de9baeba1dbb6aeda31899131d0165
author
brice · · 2026-08-11 12:55 UTC
committer
brice · · 2026-08-11 12:56 UTC
parent
09015bc
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +48 -0
M internal/usecase/client/routes.go +8 -0
@@ -557,6 +557,10 @@ func (l *routeLedger) samePeerHandoff(active AttachRequest, target ports.AttachT
557557 // original discovery target would make its Hello request a second,
558558 // incorrect remote handoff instead of a same-peer session switch.
559559 request.RemoteTarget = nil
560+ if request.EnvironmentPolicy != environmentPolicy && environmentPolicy == ports.EnvironmentPolicyClientOwned {
561+ request.NavigationCapabilities = 0
562+ request.StartupOverlay = ports.StartupOverlayNone
563+ }
560564 request.EnvironmentPolicy = environmentPolicy
561565 request.ExactTarget = target.ExactTarget
562566 if target.ExactTarget == nil {
@@ -571,6 +575,10 @@ func (l *routeLedger) samePeerHandoff(active AttachRequest, target ports.AttachT
571575 request.ExactTarget = target.ExactTarget
572576 request.PreferredTabID = ""
573577 request.RemoteTarget = nil
578+ if request.EnvironmentPolicy != environmentPolicy && environmentPolicy == ports.EnvironmentPolicyClientOwned {
579+ request.NavigationCapabilities = 0
580+ request.StartupOverlay = ports.StartupOverlayNone
581+ }
574582 request.EnvironmentPolicy = environmentPolicy
575583 return request
576584 }
@@ -557,6 +557,10 @@ func (l *routeLedger) samePeerHandoff(active AttachRequest, target ports.AttachT
557 // original discovery target would make its Hello request a second,557 // original discovery target would make its Hello request a second,
558 // incorrect remote handoff instead of a same-peer session switch.558 // incorrect remote handoff instead of a same-peer session switch.
559 request.RemoteTarget = nil559 request.RemoteTarget = nil
560+ if request.EnvironmentPolicy != environmentPolicy && environmentPolicy == ports.EnvironmentPolicyClientOwned {
561+ request.NavigationCapabilities = 0
562+ request.StartupOverlay = ports.StartupOverlayNone
563+ }
560 request.EnvironmentPolicy = environmentPolicy564 request.EnvironmentPolicy = environmentPolicy
561 request.ExactTarget = target.ExactTarget565 request.ExactTarget = target.ExactTarget
562 if target.ExactTarget == nil {566 if target.ExactTarget == nil {
@@ -571,6 +575,10 @@ func (l *routeLedger) samePeerHandoff(active AttachRequest, target ports.AttachT
571 request.ExactTarget = target.ExactTarget575 request.ExactTarget = target.ExactTarget
572 request.PreferredTabID = ""576 request.PreferredTabID = ""
573 request.RemoteTarget = nil577 request.RemoteTarget = nil
578+ if request.EnvironmentPolicy != environmentPolicy && environmentPolicy == ports.EnvironmentPolicyClientOwned {
579+ request.NavigationCapabilities = 0
580+ request.StartupOverlay = ports.StartupOverlayNone
581+ }
574 request.EnvironmentPolicy = environmentPolicy582 request.EnvironmentPolicy = environmentPolicy
575 return request583 return request
576 }584 }
M internal/usecase/client/routes_test.go +40 -0
@@ -148,6 +148,46 @@ func TestRouteLedgerSamePeerHandoffRestoresRouteTab(t *testing.T) {
148148 }
149149 }
150150
151+func TestRouteLedgerSamePeerHandoffClearsDaemonOwnedNavigationForLocalRoutes(t *testing.T) {
152+ active := routeTestCandidate(2, ports.RouteOriginLocal)
153+ active.originKey = "local"
154+ active.request.EnvironmentPolicy = ports.EnvironmentPolicyDaemonOwned
155+ active.request.NavigationCapabilities = ports.NavigationCapabilityHomePicker
156+ active.request.StartupOverlay = ports.StartupOverlaySessionPicker
157+
158+ for _, tt := range []struct {
159+ name string
160+ matched bool
161+ }{
162+ {name: "matched route", matched: true},
163+ {name: "fallback route"},
164+ } {
165+ t.Run(tt.name, func(t *testing.T) {
166+ ledger := newRouteLedger()
167+ target := routeTestTarget(1)
168+ if tt.matched {
169+ stored := routeTestCandidate(1, ports.RouteOriginLocal)
170+ stored.originKey = "local"
171+ stored.request.EnvironmentPolicy = ports.EnvironmentPolicyDaemonOwned
172+ stored.request.NavigationCapabilities = ports.NavigationCapabilityHomePicker
173+ stored.request.StartupOverlay = ports.StartupOverlaySessionPicker
174+ _, err := ledger.commit(stored)
175+ require.NoError(t, err)
176+ }
177+
178+ request := ledger.samePeerHandoff(active.request, ports.AttachTarget{
179+ Session: target.SessionName, Intent: ports.IntentAttach, ExactTarget: &target,
180+ EnvironmentPolicy: ports.EnvironmentPolicyDaemonOwned,
181+ })
182+
183+ require.Equal(t, ports.EnvironmentPolicyClientOwned, request.EnvironmentPolicy)
184+ require.Zero(t, request.NavigationCapabilities)
185+ require.Equal(t, ports.StartupOverlayNone, request.StartupOverlay)
186+ require.NoError(t, validateAttachRequest(request))
187+ })
188+ }
189+}
190+
151191 func TestRouteLedgerSamePeerHandoffDropsOriginalRemoteTarget(t *testing.T) {
152192 ledger := newRouteLedger()
153193 work := routeTestCandidate(1, ports.RouteOriginRemote)
@@ -148,6 +148,46 @@ func TestRouteLedgerSamePeerHandoffRestoresRouteTab(t *testing.T) {
148 }148 }
149 }149 }
150 150
151+func TestRouteLedgerSamePeerHandoffClearsDaemonOwnedNavigationForLocalRoutes(t *testing.T) {
152+ active := routeTestCandidate(2, ports.RouteOriginLocal)
153+ active.originKey = "local"
154+ active.request.EnvironmentPolicy = ports.EnvironmentPolicyDaemonOwned
155+ active.request.NavigationCapabilities = ports.NavigationCapabilityHomePicker
156+ active.request.StartupOverlay = ports.StartupOverlaySessionPicker
157+
158+ for _, tt := range []struct {
159+ name string
160+ matched bool
161+ }{
162+ {name: "matched route", matched: true},
163+ {name: "fallback route"},
164+ } {
165+ t.Run(tt.name, func(t *testing.T) {
166+ ledger := newRouteLedger()
167+ target := routeTestTarget(1)
168+ if tt.matched {
169+ stored := routeTestCandidate(1, ports.RouteOriginLocal)
170+ stored.originKey = "local"
171+ stored.request.EnvironmentPolicy = ports.EnvironmentPolicyDaemonOwned
172+ stored.request.NavigationCapabilities = ports.NavigationCapabilityHomePicker
173+ stored.request.StartupOverlay = ports.StartupOverlaySessionPicker
174+ _, err := ledger.commit(stored)
175+ require.NoError(t, err)
176+ }
177+
178+ request := ledger.samePeerHandoff(active.request, ports.AttachTarget{
179+ Session: target.SessionName, Intent: ports.IntentAttach, ExactTarget: &target,
180+ EnvironmentPolicy: ports.EnvironmentPolicyDaemonOwned,
181+ })
182+
183+ require.Equal(t, ports.EnvironmentPolicyClientOwned, request.EnvironmentPolicy)
184+ require.Zero(t, request.NavigationCapabilities)
185+ require.Equal(t, ports.StartupOverlayNone, request.StartupOverlay)
186+ require.NoError(t, validateAttachRequest(request))
187+ })
188+ }
189+}
190+
151 func TestRouteLedgerSamePeerHandoffDropsOriginalRemoteTarget(t *testing.T) {191 func TestRouteLedgerSamePeerHandoffDropsOriginalRemoteTarget(t *testing.T) {
152 ledger := newRouteLedger()192 ledger := newRouteLedger()
153 work := routeTestCandidate(1, ports.RouteOriginRemote)193 work := routeTestCandidate(1, ports.RouteOriginRemote)

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