feat(tui-go): session list / resume view (E)
R opens a navigable overlay of recent sessions (GET /sessions: short id, status,
workflow/stage, relative age); enter resumes via POST /sessions/{id}/resume, which
replays onto the existing global /stream (no WS re-dial). Fetch/resume errors surface
in the overlay, never panic. stdlib-only (ws.Client.HTTPBase derives the base URL).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -51,12 +51,37 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.applyServerPhased(msg.m)
|
||||
return m, readServer(m.client)
|
||||
|
||||
case sessionsLoadedMsg:
|
||||
m.applySessionsLoaded(msg)
|
||||
return m, nil
|
||||
|
||||
case sessionResumeMsg:
|
||||
if msg.err != "" {
|
||||
m.sessionListErr = "resume: " + msg.err
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case tea.KeyMsg:
|
||||
return m.handleKey(msg)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// applySessionsLoaded folds a GET /sessions result into the browser, clamping the
|
||||
// cursor and surfacing any fetch error in place of a crash.
|
||||
func (m *Model) applySessionsLoaded(msg sessionsLoadedMsg) {
|
||||
m.sessionListLoading = false
|
||||
if msg.err != "" {
|
||||
m.sessionListErr = msg.err
|
||||
return
|
||||
}
|
||||
m.sessionListErr = ""
|
||||
m.sessionList = msg.sessions
|
||||
if m.sessionListIndex >= len(m.sessionList) {
|
||||
m.sessionListIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) applyConn(s ws.Status) {
|
||||
switch {
|
||||
case s.Connected:
|
||||
@@ -178,6 +203,10 @@ func (m Model) handleNormalKey(k tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
m.openArtifacts()
|
||||
case "I":
|
||||
m.openIdeas()
|
||||
case "R":
|
||||
// Resume browser: recent sessions over HTTP, reachable even after a server
|
||||
// restart + TUI reopen (the live stream only carries running sessions).
|
||||
return m, m.openSessions()
|
||||
case "S":
|
||||
m.openStats()
|
||||
case "g":
|
||||
@@ -359,6 +388,11 @@ func (m Model) handleOverlayKey(k tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
if m.overlay == OverlayConfig {
|
||||
return m.handleConfigKey(k)
|
||||
}
|
||||
// Sessions overlay owns all its keys: enter resumes (emits a cmd), so it can't
|
||||
// share the generic no-cmd esc path below.
|
||||
if m.overlay == OverlaySessions {
|
||||
return m.handleSessionsKey(k)
|
||||
}
|
||||
if k.Type == tea.KeyEsc {
|
||||
m.overlay = OverlayNone
|
||||
return m, nil
|
||||
@@ -566,6 +600,7 @@ func paletteCommands() []paletteCmd {
|
||||
{"events", "event inspector", "browse the event stream"},
|
||||
{"artifacts", "view artifacts", "browse this session's artifacts"},
|
||||
{"stats", "session stats", "metrics for the selected session"},
|
||||
{"sessions", "resume session", "browse / resume recent sessions"},
|
||||
{"config", "edit config", "view / change correx settings"},
|
||||
{"mode", "toggle mode", "switch chat / steering"},
|
||||
{"cancel", "cancel session", "stop the selected session"},
|
||||
@@ -605,6 +640,8 @@ func (m Model) execPalette(id string) (tea.Model, tea.Cmd) {
|
||||
m.openArtifacts()
|
||||
case "stats":
|
||||
m.openStats()
|
||||
case "sessions":
|
||||
return m, m.openSessions()
|
||||
case "config":
|
||||
m.openConfig()
|
||||
case "mode":
|
||||
|
||||
Reference in New Issue
Block a user