detached 1469f94

fix(daemon): synchronize pane start logging

commit
1469f94a0971b36db2d463b17da938ae8d1150b9
author
brice · · 2026-07-10 05:51 UTC
parent
59ce3a4
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +59 -1
M internal/usecase/daemon/floating_test.go +55 -0
@@ -523,6 +523,61 @@ func TestFloatingLaunchFailureCapturesSessionNameBeforeConcurrentRename(t *testi
523523 require.True(t, strings.Contains(logs.String(), "session=captured"), logs.String())
524524 }
525525
526+func TestFloatingInstallLogsSessionNameSafelyDuringRename(t *testing.T) {
527+ floatingPTY, _ := newBlockingPTY(t)
528+ factory := portsmocks.NewMockPTYFactory(t)
529+ opened := make(chan struct{})
530+ allowOpen := make(chan struct{})
531+ factory.EXPECT().Open(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).RunAndReturn(
532+ func(string, []string, []string, string, domain.Size) (ports.PTY, error) {
533+ close(opened)
534+ <-allowOpen
535+ return floatingPTY, nil
536+ }).Once()
537+ var logs bytes.Buffer
538+ handled := make(chan struct{})
539+ d := newTestDaemon(t, factory, stubClock{})
540+ d.log = slog.New(floatingLogHandler{Handler: slog.NewTextHandler(&logs, nil), handled: handled})
541+ tb := newFloatingTestTab(t)
542+ sess := &session{name: "captured", tabs: []*tab{tb}, ctx: t.Context()}
543+
544+ d.startFloating(sess, tb, false)
545+ <-opened
546+ renamed := make(chan struct{})
547+ stopRename := make(chan struct{})
548+ renameDone := make(chan struct{})
549+ go func() {
550+ defer close(renameDone)
551+ sess.mu.Lock()
552+ sess.name = "renamed"
553+ sess.mu.Unlock()
554+ close(renamed)
555+ for {
556+ select {
557+ case <-stopRename:
558+ return
559+ default:
560+ }
561+ sess.mu.Lock()
562+ sess.name = "renamed"
563+ sess.mu.Unlock()
564+ }
565+ }()
566+ <-renamed
567+ close(allowOpen)
568+ select {
569+ case <-handled:
570+ case <-time.After(time.Second):
571+ t.Fatal("installed floating pane was not logged")
572+ }
573+ close(stopRename)
574+ <-renameDone
575+ require.Contains(t, logs.String(), "session=renamed", logs.String())
576+
577+ d.teardownFloating(tb)
578+ d.sessWg.Wait()
579+}
580+
526581 func TestFloatingToggleUsesVisibleTarget(t *testing.T) {
527582 d := newTestDaemon(t, nil, stubClock{})
528583 tb := newFloatingTestTab(t)
@@ -523,6 +523,61 @@ func TestFloatingLaunchFailureCapturesSessionNameBeforeConcurrentRename(t *testi
523 require.True(t, strings.Contains(logs.String(), "session=captured"), logs.String())523 require.True(t, strings.Contains(logs.String(), "session=captured"), logs.String())
524 }524 }
525 525
526+func TestFloatingInstallLogsSessionNameSafelyDuringRename(t *testing.T) {
527+ floatingPTY, _ := newBlockingPTY(t)
528+ factory := portsmocks.NewMockPTYFactory(t)
529+ opened := make(chan struct{})
530+ allowOpen := make(chan struct{})
531+ factory.EXPECT().Open(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).RunAndReturn(
532+ func(string, []string, []string, string, domain.Size) (ports.PTY, error) {
533+ close(opened)
534+ <-allowOpen
535+ return floatingPTY, nil
536+ }).Once()
537+ var logs bytes.Buffer
538+ handled := make(chan struct{})
539+ d := newTestDaemon(t, factory, stubClock{})
540+ d.log = slog.New(floatingLogHandler{Handler: slog.NewTextHandler(&logs, nil), handled: handled})
541+ tb := newFloatingTestTab(t)
542+ sess := &session{name: "captured", tabs: []*tab{tb}, ctx: t.Context()}
543+
544+ d.startFloating(sess, tb, false)
545+ <-opened
546+ renamed := make(chan struct{})
547+ stopRename := make(chan struct{})
548+ renameDone := make(chan struct{})
549+ go func() {
550+ defer close(renameDone)
551+ sess.mu.Lock()
552+ sess.name = "renamed"
553+ sess.mu.Unlock()
554+ close(renamed)
555+ for {
556+ select {
557+ case <-stopRename:
558+ return
559+ default:
560+ }
561+ sess.mu.Lock()
562+ sess.name = "renamed"
563+ sess.mu.Unlock()
564+ }
565+ }()
566+ <-renamed
567+ close(allowOpen)
568+ select {
569+ case <-handled:
570+ case <-time.After(time.Second):
571+ t.Fatal("installed floating pane was not logged")
572+ }
573+ close(stopRename)
574+ <-renameDone
575+ require.Contains(t, logs.String(), "session=renamed", logs.String())
576+
577+ d.teardownFloating(tb)
578+ d.sessWg.Wait()
579+}
580+
526 func TestFloatingToggleUsesVisibleTarget(t *testing.T) {581 func TestFloatingToggleUsesVisibleTarget(t *testing.T) {
527 d := newTestDaemon(t, nil, stubClock{})582 d := newTestDaemon(t, nil, stubClock{})
528 tb := newFloatingTestTab(t)583 tb := newFloatingTestTab(t)
M internal/usecase/daemon/session.go +4 -1
@@ -409,7 +409,10 @@ func (d *Daemon) startTabGoroutines(sess *session, tb *tab) {
409409
410410 func (d *Daemon) startPaneGoroutines(sess *session, tb *tab, p *pane) {
411411 if p != nil {
412- d.log.Info("pane created", "session", sess.name, "pane", p.id)
412+ sess.mu.Lock()
413+ name := sess.name
414+ sess.mu.Unlock()
415+ d.log.Info("pane created", "session", name, "pane", p.id)
413416 }
414417 d.sessWg.Add(2)
415418 go d.ptyReader(sess, tb, p)
@@ -409,7 +409,10 @@ func (d *Daemon) startTabGoroutines(sess *session, tb *tab) {
409 409
410 func (d *Daemon) startPaneGoroutines(sess *session, tb *tab, p *pane) {410 func (d *Daemon) startPaneGoroutines(sess *session, tb *tab, p *pane) {
411 if p != nil {411 if p != nil {
412- d.log.Info("pane created", "session", sess.name, "pane", p.id)412+ sess.mu.Lock()
413+ name := sess.name
414+ sess.mu.Unlock()
415+ d.log.Info("pane created", "session", name, "pane", p.id)
413 }416 }
414 d.sessWg.Add(2)417 d.sessWg.Add(2)
415 go d.ptyReader(sess, tb, p)418 go d.ptyReader(sess, tb, p)

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