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:
2026-06-20 16:22:27 +00:00
parent 46a71ee84c
commit 1f7a5d96a7
7 changed files with 483 additions and 11 deletions
+22 -9
View File
@@ -28,23 +28,36 @@ type Status struct {
// Client is a reconnecting WebSocket client. Construct with New, then Run in a
// goroutine. Incoming and Conn are drained by the UI.
type Client struct {
url string
send chan []byte
in chan protocol.ServerMessage
conn chan Status
url string
httpBase string
send chan []byte
in chan protocol.ServerMessage
conn chan Status
}
// New builds a client targeting ws://host:port/stream.
func New(host string, port int) *Client {
u := url.URL{Scheme: "ws", Host: hostPort(host, port), Path: "/stream"}
hp := hostPort(host, port)
u := url.URL{Scheme: "ws", Host: hp, Path: "/stream"}
return &Client{
url: u.String(),
send: make(chan []byte, 64),
in: make(chan protocol.ServerMessage, 256),
conn: make(chan Status, 16),
url: u.String(),
httpBase: (&url.URL{Scheme: "http", Host: hp}).String(),
send: make(chan []byte, 64),
in: make(chan protocol.ServerMessage, 256),
conn: make(chan Status, 16),
}
}
// HTTPBase is the http://host:port origin the WS client connects to, for REST
// calls (e.g. GET /sessions) that share the server's host/port. Empty for a nil
// client (the test harness constructs Models with a nil client).
func (c *Client) HTTPBase() string {
if c == nil {
return ""
}
return c.httpBase
}
func hostPort(host string, port int) string {
h := host
if h == "" {