Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/changes/unreleased/1951-cold-team-tab-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
kind: fixed
title: Keep team navigation responsive while opening a worker for the first time
pr: 1951
surface: [chat, docs]
invalidates:
- "Opening an unseen team chat previously blocked input; independent chat opens now run in the background."
---

The manager and worker tabs remain usable while a first connection waits on the
engine. Canceling or selecting another chat prevents a late opening from taking
over the selected conversation. Already-open chats keep their immediate switch.
Connections limited to a single shared conversation retain their existing swap
behavior.
8 changes: 8 additions & 0 deletions internal/manual/chat/sessions-and-rewind.md
Original file line number Diff line number Diff line change
Expand Up @@ -977,3 +977,11 @@ with the error, so your message is not lost or sent to the old chat.

Returning to an already open chat keeps its last displayed text visible while
its current history refreshes. The refreshed history replaces that preview.

## Switching to a team chat that is not open yet

A team tab can name a manager or worker that this window has not opened yet.
In windows that can keep several chats open, its connection opens in the
background, with an opening notice on the current chat. You can select another tab or cancel with Escape while the engine is
busy. A canceled opening does not replace the selected chat when it finishes.
Chats already open in this window switch directly.
12 changes: 10 additions & 2 deletions internal/tui3/chattabs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1424,8 +1424,8 @@ func (a *app) tabPress(x, y int) (tea.Cmd, bool) {
// "that folder is gone" is two features to keep in step.
func (a *app) tabGo(tab chatTab) (cmd tea.Cmd) {
defer func() {
if t, ok := a.teamActive(); ok && !a.wall.on && !a.teamOverlayHolds(t, a.frontTabKey()) {
a.teamViewSet("")
if !a.conversationOpening {
a.tabSettleTeam()
}
}()
if tab.work {
Expand Down Expand Up @@ -1456,6 +1456,14 @@ func (a *app) tabGo(tab chatTab) (cmd tea.Cmd) {
return a.hopStart(hopRow{file: tab.file, where: tab.where, title: tab.word})
}

// tabSettleTeam validates the overlay against the conversation actually selected,
// after an immediate switch or after an asynchronous opening has settled.
func (a *app) tabSettleTeam() {
if t, ok := a.teamActive(); ok && !a.wall.on && !a.teamOverlayHolds(t, a.frontTabKey()) {
a.teamViewSet("")
}
}

// ── DISMISSING A TAB ────────────────────────────────────────────────────────

// tabDismiss removes a conversation's tab from this window. Its transcript and
Expand Down
3 changes: 3 additions & 0 deletions internal/tui3/conversation_open.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ func (a *app) conversationLater(ask func() (Conversation, error), say func(strin
}

func (a *app) cancelConversationOpening() {
if a.conversationPreview == nil {
defer a.tabSettleTeam()
}
a.restoreOpeningDraft(a.conversationPreview)
a.conversationOpening = false
a.conversationPreview = nil
Expand Down
25 changes: 25 additions & 0 deletions internal/tui3/hop.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tui3

import (
"errors"
"path/filepath"
"sort"
"strings"
Expand Down Expand Up @@ -1222,6 +1223,30 @@ func (a *app) hopStart(row hopRow) tea.Cmd {
a.hopSay(WorkspaceGoneWord + " · " + row.where)
return nil
}
if !a.shared && a.open != nil {
if cmd, ours := a.bringForward(row.file); ours {
return a.hopLand(cmd)
}
// A team's visible tab may not have a connection in this window yet.
// Opening it can wait on the engine; keep later clicks and typing live.
// Shared handles retain their existing swap contract because cancelling
// a swap cannot safely release the returned handle independently.
open := a.open
return a.conversationLater(func() (Conversation, error) {
return open(row.where, row.file)
}, a.hopSay, func(conv Conversation) tea.Cmd {
defer a.tabSettleTeam()
return a.hopLand(a.takeBeside(conv))
}, func(err error) tea.Cmd {
defer a.tabSettleTeam()
if errors.Is(err, session.ErrSessionLocked) {
a.hopSay(sessionBusyWord)
} else {
a.hopSay(err.Error())
}
return nil
})
}
cmd, refusal := a.openBeside(row.where, row.file)
if refusal != "" {
a.hopSay(refusal)
Expand Down
2 changes: 1 addition & 1 deletion internal/tui3/teamoverlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (a *app) teamOverlaySync() tea.Cmd {
}
a.teamViews.pending = target.key
cmd := a.tabGo(target)
if a.frontTabKey() != target.key {
if !a.conversationOpening && a.frontTabKey() != target.key {
a.teamViewSet("")
}
return cmd
Expand Down
Loading