detached 74cc71c

fix(session): clear discarded parked toggle state

commit
74cc71cc67adb8bacd7af0d602ac7bf9d65ef65f
author
brice · · 2026-07-11 11:51 UTC
parent
391596f
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +41 -0
M internal/usecase/daemon/resume.go +1 -0
@@ -96,6 +96,7 @@ func (d *Daemon) expireParked(token uint64, parked *parkedAttachment) {
9696 // has verified d.parked[token] still points at parked when that matters.
9797 func (d *Daemon) removeParkedLocked(token uint64, parked *parkedAttachment) {
9898 delete(d.parked, token)
99+ parked.ac.clearPreviousSession()
99100 parked.ac.resumeToken = 0
100101 parked.ac.parked = false
101102 if parked.timer != nil {
@@ -96,6 +96,7 @@ func (d *Daemon) expireParked(token uint64, parked *parkedAttachment) {
96 // has verified d.parked[token] still points at parked when that matters.96 // has verified d.parked[token] still points at parked when that matters.
97 func (d *Daemon) removeParkedLocked(token uint64, parked *parkedAttachment) {97 func (d *Daemon) removeParkedLocked(token uint64, parked *parkedAttachment) {
98 delete(d.parked, token)98 delete(d.parked, token)
99+ parked.ac.clearPreviousSession()
99 parked.ac.resumeToken = 0100 parked.ac.resumeToken = 0
100 parked.ac.parked = false101 parked.ac.parked = false
101 if parked.timer != nil {102 if parked.timer != nil {
M internal/usecase/daemon/resume_test.go +40 -0
@@ -322,6 +322,46 @@ func TestParkExpiryAndShutdownCleanup(t *testing.T) {
322322 require.Zero(t, parked)
323323 }
324324
325+func TestDiscardingParkedAttachmentClearsPreviousSession(t *testing.T) {
326+ for _, tc := range []struct {
327+ name string
328+ discard func(*Daemon, uint64, *parkedAttachment)
329+ }{
330+ {
331+ name: "expiry",
332+ discard: func(d *Daemon, token uint64, parked *parkedAttachment) {
333+ d.removeParkedLocked(token, parked)
334+ },
335+ },
336+ {
337+ name: "session purge",
338+ discard: func(d *Daemon, _ uint64, parked *parkedAttachment) {
339+ d.purgeParkedForSessionLocked(parked.sess)
340+ },
341+ },
342+ } {
343+ t.Run(tc.name, func(t *testing.T) {
344+ pty, release := newBlockingPTY(t)
345+ defer release()
346+ d := newTestDaemon(t, newFactory(t, pty), stubClock{})
347+ tr, _, _ := newConn(t, mustHello(ports.IntentAttach, "unused", domain.Size{}))
348+ sess, ac, err := d.route(helloResumeCapable(ports.IntentNew, "work", 0), tr)
349+ require.NoError(t, err)
350+
351+ ac.previousSession.Set(&session{id: "previous"})
352+ d.clientGone(sess, ac, ac.transport(), false)
353+
354+ d.mu.Lock()
355+ parked := d.parked[ac.resumeToken]
356+ require.NotNil(t, parked)
357+ tc.discard(d, ac.resumeToken, parked)
358+ d.mu.Unlock()
359+
360+ require.Nil(t, ac.previousSession.Get())
361+ })
362+ }
363+}
364+
325365 func TestEphemeralParkExpiryKeepsSession(t *testing.T) {
326366 clk := &signalClock{timers: make(chan *signalTimer, 8)}
327367 pty, release := newBlockingPTY(t)
@@ -322,6 +322,46 @@ func TestParkExpiryAndShutdownCleanup(t *testing.T) {
322 require.Zero(t, parked)322 require.Zero(t, parked)
323 }323 }
324 324
325+func TestDiscardingParkedAttachmentClearsPreviousSession(t *testing.T) {
326+ for _, tc := range []struct {
327+ name string
328+ discard func(*Daemon, uint64, *parkedAttachment)
329+ }{
330+ {
331+ name: "expiry",
332+ discard: func(d *Daemon, token uint64, parked *parkedAttachment) {
333+ d.removeParkedLocked(token, parked)
334+ },
335+ },
336+ {
337+ name: "session purge",
338+ discard: func(d *Daemon, _ uint64, parked *parkedAttachment) {
339+ d.purgeParkedForSessionLocked(parked.sess)
340+ },
341+ },
342+ } {
343+ t.Run(tc.name, func(t *testing.T) {
344+ pty, release := newBlockingPTY(t)
345+ defer release()
346+ d := newTestDaemon(t, newFactory(t, pty), stubClock{})
347+ tr, _, _ := newConn(t, mustHello(ports.IntentAttach, "unused", domain.Size{}))
348+ sess, ac, err := d.route(helloResumeCapable(ports.IntentNew, "work", 0), tr)
349+ require.NoError(t, err)
350+
351+ ac.previousSession.Set(&session{id: "previous"})
352+ d.clientGone(sess, ac, ac.transport(), false)
353+
354+ d.mu.Lock()
355+ parked := d.parked[ac.resumeToken]
356+ require.NotNil(t, parked)
357+ tc.discard(d, ac.resumeToken, parked)
358+ d.mu.Unlock()
359+
360+ require.Nil(t, ac.previousSession.Get())
361+ })
362+ }
363+}
364+
325 func TestEphemeralParkExpiryKeepsSession(t *testing.T) {365 func TestEphemeralParkExpiryKeepsSession(t *testing.T) {
326 clk := &signalClock{timers: make(chan *signalTimer, 8)}366 clk := &signalClock{timers: make(chan *signalTimer, 8)}
327 pty, release := newBlockingPTY(t)367 pty, release := newBlockingPTY(t)

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