handleSwitchTab switches to a different session. Existing chat pages and editors are preserved (not recreated) so that in-flight streaming content and draft text are retained when switching back to a tab.
(sessionID string)
| 1521 | // Existing chat pages and editors are preserved (not recreated) so that in-flight streaming |
| 1522 | // content and draft text are retained when switching back to a tab. |
| 1523 | func (m *appModel) handleSwitchTab(sessionID string) (tea.Model, tea.Cmd) { |
| 1524 | // If a background dialog (e.g. pending elicitation) is open on the |
| 1525 | // outgoing tab, capture both its originating event and the live dialog |
| 1526 | // instance before the supervisor flips activeID. We only commit the |
| 1527 | // re-stash after SwitchTo succeeds — otherwise a failed switch would |
| 1528 | // leave the supervisor with a stale pending event and the dialog still |
| 1529 | // on screen. |
| 1530 | // |
| 1531 | // Stashing the dialog instance (rather than rebuilding it from the event |
| 1532 | // on return) preserves any in-progress input the user typed — e.g. text |
| 1533 | // already entered into a user_prompt elicitation. See issue #2770. |
| 1534 | var ( |
| 1535 | backgroundEvent tea.Msg |
| 1536 | backgroundDialog dialog.Dialog |
| 1537 | outgoingTabID string |
| 1538 | ) |
| 1539 | if m.dialogMgr.Open() && m.dialogMgr.TopIsBackground() { |
| 1540 | backgroundEvent = m.dialogMgr.TopBackgroundEvent() |
| 1541 | backgroundDialog = m.dialogMgr.TopDialog() |
| 1542 | outgoingTabID = m.supervisor.ActiveID() |
| 1543 | } |
| 1544 | |
| 1545 | runner := m.supervisor.SwitchTo(sessionID) |
| 1546 | if runner == nil { |
| 1547 | return m, notification.ErrorCmd("Session not found") |
| 1548 | } |
| 1549 | |
| 1550 | // Now that the switch is committed, finalize the dialog hand-off. |
| 1551 | var closeBackgroundDialogCmd tea.Cmd |
| 1552 | if backgroundEvent != nil && outgoingTabID != "" && outgoingTabID != sessionID { |
| 1553 | m.supervisor.SetPendingEvent(outgoingTabID, backgroundEvent) |
| 1554 | if backgroundDialog != nil { |
| 1555 | m.stashedDialogs[outgoingTabID] = stashedDialog{ |
| 1556 | dialog: backgroundDialog, |
| 1557 | event: backgroundEvent, |
| 1558 | } |
| 1559 | } |
| 1560 | closeBackgroundDialogCmd = core.CmdHandler(dialog.CloseDialogMsg{}) |
| 1561 | } |
| 1562 | |
| 1563 | // Blur current editor before switching |
| 1564 | m.editor.Blur() |
| 1565 | |
| 1566 | // If this tab has a pending session restore, load it through |
| 1567 | // replaceActiveSession — the same code path as the /sessions command. |
| 1568 | if oldSessionID, ok := m.pendingRestores[sessionID]; ok { |
| 1569 | delete(m.pendingRestores, sessionID) |
| 1570 | m.application = runner.App |
| 1571 | if store := runner.App.SessionStore(); store != nil { |
| 1572 | if sess, err := store.GetSession(m.ctx(), oldSessionID); err == nil { |
| 1573 | m.persistActiveTab(sess.ID) |
| 1574 | model, cmd := m.replaceActiveSession(m.ctx(), sess) |
| 1575 | |
| 1576 | if m.tuiStore != nil && sess.WorkingDir != "" { |
| 1577 | if err := m.tuiStore.UpdateTabWorkingDir(m.ctx(), oldSessionID, sess.WorkingDir); err != nil { |
| 1578 | slog.Warn("Failed to update persisted working dir", "error", err) |
| 1579 | } |
| 1580 | } |
no test coverage detected