fix(palette): close remote handoff overlay
- commit
- a353d5543508d8cdf4f2d06bbbaf88624c1c51c7
- author
- brice · · 2026-08-25 10:58 UTC
- parent
- fb6668c
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +12 -2
M internal/usecase/daemon/palette.go +6 -1
| @@ -438,7 +438,12 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte, effects ... | |||
| 438 | 438 | RemoteKey: &remoteKey, RemoteTarget: &remoteTarget, RemoteHost: remoteKey.Host, | |
| 439 | 439 | UnavailableReason: remoteUnavailableReason, TabIndex: -1, | |
| 440 | 440 | } | |
| 441 | - | if err := d.switchToTargetForAttachment(effect.connectionToken(), target, sessionHandoffGuard{}, "palette-remote-session"); err != nil && !errors.Is(err, errAttachmentTransition) { | |
| 441 | + | err := d.switchToTargetForAttachment(effect.connectionToken(), target, sessionHandoffGuard{}, "palette-remote-session") | |
| 442 | + | if err == nil { | |
| 443 | + | d.closeExecutedPalette(ac, generation, rawQuery) | |
| 444 | + | return | |
| 445 | + | } | |
| 446 | + | if !errors.Is(err, errAttachmentTransition) { | |
| 442 | 447 | ac.paletteFailure(generation, rawQuery, "requested remote session is unavailable") | |
| 443 | 448 | d.invalidateRender(entry, ac, true, "palette.go") | |
| 444 | 449 | } | |
| @@ -438,7 +438,12 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte, effects ... | |||||
| 438 | RemoteKey: &remoteKey, RemoteTarget: &remoteTarget, RemoteHost: remoteKey.Host, | 438 | RemoteKey: &remoteKey, RemoteTarget: &remoteTarget, RemoteHost: remoteKey.Host, | ||
| 439 | UnavailableReason: remoteUnavailableReason, TabIndex: -1, | 439 | UnavailableReason: remoteUnavailableReason, TabIndex: -1, | ||
| 440 | } | 440 | } | ||
| 441 | - | if err := d.switchToTargetForAttachment(effect.connectionToken(), target, sessionHandoffGuard{}, "palette-remote-session"); err != nil && !errors.Is(err, errAttachmentTransition) { | 441 | + | err := d.switchToTargetForAttachment(effect.connectionToken(), target, sessionHandoffGuard{}, "palette-remote-session") |
| 442 | + | if err == nil { | |||
| 443 | + | d.closeExecutedPalette(ac, generation, rawQuery) | |||
| 444 | + | return | |||
| 445 | + | } | |||
| 446 | + | if !errors.Is(err, errAttachmentTransition) { | |||
| 442 | ac.paletteFailure(generation, rawQuery, "requested remote session is unavailable") | 447 | ac.paletteFailure(generation, rawQuery, "requested remote session is unavailable") | ||
| 443 | d.invalidateRender(entry, ac, true, "palette.go") | 448 | d.invalidateRender(entry, ac, true, "palette.go") | ||
| 444 | } | 449 | } | ||
M internal/usecase/daemon/palette_test.go +5 -0
| @@ -500,6 +500,7 @@ func TestPaletteRemoteCatalogSelectionSendsExactAttachTarget(t *testing.T) { | |||
| 500 | 500 | p, release := newBlockingPTY(t) | |
| 501 | 501 | defer release() | |
| 502 | 502 | d, current, ac, sends := newManualSessionWithPTYs(t, p) | |
| 503 | + | ac.resumeCapable = true | |
| 503 | 504 | now := time.Unix(1_000, 0) | |
| 504 | 505 | d.clock = fixedRemoteRefreshClock{now: now} | |
| 505 | 506 | remoteLifecycle := domain.SessionLifecycleID{31} | |
| @@ -530,6 +531,10 @@ func TestPaletteRemoteCatalogSelectionSendsExactAttachTarget(t *testing.T) { | |||
| 530 | 531 | Endpoint: "user@arch", Session: "work", Intent: ports.IntentAttach, | |
| 531 | 532 | RemoteTarget: &remoteTarget, EnvironmentPolicy: ports.EnvironmentPolicyDaemonOwned, | |
| 532 | 533 | }, target) | |
| 534 | + | require.False(t, ac.overlays.paletteActive()) | |
| 535 | + | d.remoteCatalog.mu.Lock() | |
| 536 | + | require.Zero(t, d.remoteCatalog.consumers[ac]&remoteDiscoveryPalette) | |
| 537 | + | d.remoteCatalog.mu.Unlock() | |
| 533 | 538 | } | |
| 534 | 539 | ||
| 535 | 540 | func TestPaletteCachedRemoteSelectionFailsClosed(t *testing.T) { | |
| @@ -500,6 +500,7 @@ func TestPaletteRemoteCatalogSelectionSendsExactAttachTarget(t *testing.T) { | |||||
| 500 | p, release := newBlockingPTY(t) | 500 | p, release := newBlockingPTY(t) | ||
| 501 | defer release() | 501 | defer release() | ||
| 502 | d, current, ac, sends := newManualSessionWithPTYs(t, p) | 502 | d, current, ac, sends := newManualSessionWithPTYs(t, p) | ||
| 503 | + | ac.resumeCapable = true | |||
| 503 | now := time.Unix(1_000, 0) | 504 | now := time.Unix(1_000, 0) | ||
| 504 | d.clock = fixedRemoteRefreshClock{now: now} | 505 | d.clock = fixedRemoteRefreshClock{now: now} | ||
| 505 | remoteLifecycle := domain.SessionLifecycleID{31} | 506 | remoteLifecycle := domain.SessionLifecycleID{31} | ||
| @@ -530,6 +531,10 @@ func TestPaletteRemoteCatalogSelectionSendsExactAttachTarget(t *testing.T) { | |||||
| 530 | Endpoint: "user@arch", Session: "work", Intent: ports.IntentAttach, | 531 | Endpoint: "user@arch", Session: "work", Intent: ports.IntentAttach, | ||
| 531 | RemoteTarget: &remoteTarget, EnvironmentPolicy: ports.EnvironmentPolicyDaemonOwned, | 532 | RemoteTarget: &remoteTarget, EnvironmentPolicy: ports.EnvironmentPolicyDaemonOwned, | ||
| 532 | }, target) | 533 | }, target) | ||
| 534 | + | require.False(t, ac.overlays.paletteActive()) | |||
| 535 | + | d.remoteCatalog.mu.Lock() | |||
| 536 | + | require.Zero(t, d.remoteCatalog.consumers[ac]&remoteDiscoveryPalette) | |||
| 537 | + | d.remoteCatalog.mu.Unlock() | |||
| 533 | } | 538 | } | ||
| 534 | 539 | ||||
| 535 | func TestPaletteCachedRemoteSelectionFailsClosed(t *testing.T) { | 540 | func TestPaletteCachedRemoteSelectionFailsClosed(t *testing.T) { | ||
M internal/usecase/palette/result.go +1 -1
| @@ -20,7 +20,7 @@ const ( | |||
| 20 | 20 | ) | |
| 21 | 21 | ||
| 22 | 22 | // Result is an immutable palette target. Its kind is the sole discriminator | |
| 23 | - | // for its private command or session payload. | |
| 23 | + | // for its private command, local-session, remote-session, or route payload. | |
| 24 | 24 | type Result struct { | |
| 25 | 25 | kind ResultKind | |
| 26 | 26 | command command.Command | |
| @@ -20,7 +20,7 @@ const ( | |||||
| 20 | ) | 20 | ) | ||
| 21 | 21 | ||||
| 22 | // Result is an immutable palette target. Its kind is the sole discriminator | 22 | // Result is an immutable palette target. Its kind is the sole discriminator | ||
| 23 | - | // for its private command or session payload. | 23 | + | // for its private command, local-session, remote-session, or route payload. |
| 24 | type Result struct { | 24 | type Result struct { | ||
| 25 | kind ResultKind | 25 | kind ResultKind | ||
| 26 | command command.Command | 26 | command command.Command | ||