refactor(tui-go): move session dates to the R overlay; drop dead idle panels
The launcher removed the idle session list, so the per-row date moves to the R resume overlay — now the sole session list. sessionListRow shows an absolute local date (absDateISO → shortDateTime) alongside the relative age: "Jun 22 14:30 · 5m ago", with the year shown for older-than-this-year rows. Delete the now-unused idle-panel renderers (sessionRows / welcomeRows / workflowRows) and renderMainNarrow's dead idle branch (renderLauncher owns idle, narrow included). Add a "resume" preview kind to screenshot the R overlay. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,16 @@ func PreviewFrame(kind string, w, h int) string {
|
||||
m.bgUpdates = 3
|
||||
m.selectedID = "04a546aa"
|
||||
|
||||
case "resume":
|
||||
m.connected = true
|
||||
m.currentModel = "llama-cpp:default"
|
||||
m.overlay = OverlaySessions
|
||||
m.sessionList = []SessionSummary{
|
||||
{SessionID: "04a546aa8b2c", Status: "ACTIVE", WorkflowID: "healthcheck", StageCount: 3, LastActivityAt: "2026-06-22T14:30:05Z"},
|
||||
{SessionID: "0d7097bb1f3e", Status: "COMPLETED", WorkflowID: "healthcheck", StageCount: 5, LastActivityAt: "2026-06-21T09:15:00Z"},
|
||||
{SessionID: "1dae17cc77aa", Status: "FAILED", WorkflowID: "chat", LastActivityAt: "2025-12-30T18:02:00Z"},
|
||||
}
|
||||
|
||||
case "workflows":
|
||||
m.connected = true
|
||||
m.currentModel = "llama-cpp:default"
|
||||
|
||||
@@ -228,10 +228,25 @@ func (m Model) sessionListRow(i int) string {
|
||||
}
|
||||
stageCell := mbg(t, padRaw(stage, 22), t.P.Accent2)
|
||||
|
||||
age := relativeAge(s.LastActivityAt)
|
||||
ageCell := mbg(t, age, t.P.Faint)
|
||||
dateCell := mbg(t, padRaw(absDateISO(s.LastActivityAt), 12), t.P.Dim)
|
||||
ageCell := mbg(t, "· "+relativeAge(s.LastActivityAt), t.P.Faint)
|
||||
|
||||
return marker + idCell + " " + statusCell + " " + stageCell + " " + ageCell
|
||||
return marker + idCell + " " + statusCell + " " + stageCell + " " + dateCell + " " + ageCell
|
||||
}
|
||||
|
||||
// absDateISO renders an ISO-8601 instant (the server's LastActivityAt) as a compact local
|
||||
// date via shortDateTime, or "—" when absent/unparseable.
|
||||
func absDateISO(iso string) string {
|
||||
if iso == "" {
|
||||
return "—"
|
||||
}
|
||||
ts, err := time.Parse(time.RFC3339Nano, iso)
|
||||
if err != nil {
|
||||
if ts, err = time.Parse(time.RFC3339, iso); err != nil {
|
||||
return "—"
|
||||
}
|
||||
}
|
||||
return shortDateTime(ts.UnixMilli())
|
||||
}
|
||||
|
||||
// shortSessionID trims a long session id to a recognizable prefix for the list.
|
||||
|
||||
@@ -405,15 +405,8 @@ func (m Model) activeSessionCount() int {
|
||||
// — the strip is too valuable to drop, the constraint is width not height).
|
||||
func (m Model) renderMainNarrow(h int) string {
|
||||
w := m.width
|
||||
if m.displayState() == StateIdle {
|
||||
title := "sessions"
|
||||
body := m.sessionRows(w - 4)
|
||||
if m.wfVisible {
|
||||
title, body = "workflows", m.workflowRows(w-4)
|
||||
}
|
||||
return m.theme.box(title, body, w, h, true)
|
||||
}
|
||||
// in-session / approval: stack output over events.
|
||||
// Idle is handled by renderLauncher (which drops the rail when narrow); this only
|
||||
// runs for in-session / approval: stack output over events.
|
||||
outH := h * 55 / 100
|
||||
if outH < 3 {
|
||||
outH = 3
|
||||
@@ -495,54 +488,6 @@ func (m Model) renderInput() string {
|
||||
|
||||
// --- body row builders ---
|
||||
|
||||
func (m Model) sessionRows(w int) []string {
|
||||
t := m.theme
|
||||
list := m.filteredSessions()
|
||||
if len(list) == 0 {
|
||||
return []string{t.span("no sessions yet", t.P.Faint), "", t.span("type a name below to begin", t.P.Faint)}
|
||||
}
|
||||
rows := make([]string, 0, len(list))
|
||||
for _, s := range list {
|
||||
sel := s.ID == m.selectedID && !m.wfVisible
|
||||
short := s.ID
|
||||
if len(short) > 6 {
|
||||
short = short[:6]
|
||||
}
|
||||
nameFg := t.P.Fg
|
||||
bar := t.span(" ", t.P.Bg)
|
||||
if sel {
|
||||
bar = lipgloss.NewStyle().Foreground(t.P.Accent).Background(t.P.Bg).Render("▌ ")
|
||||
nameFg = t.P.FgStrong
|
||||
}
|
||||
idCell := t.span("["+short+"] ", t.P.Faint)
|
||||
statusCell := lipgloss.NewStyle().Foreground(statusColor(t, s.Status)).Background(t.P.Bg).Bold(true).
|
||||
Render(padRaw(statusLabel(s.Status), 9))
|
||||
stageStr := ""
|
||||
if s.CurrentStage != "" {
|
||||
stageStr = " [" + s.CurrentStage + "]"
|
||||
}
|
||||
dateCell := t.span(shortDateTime(s.LastEventAt), t.P.Faint)
|
||||
|
||||
// Right-align the last-activity date; truncate the name first so the date column
|
||||
// always survives the box's width clamp (padTo) even on a slim panel.
|
||||
fixed := lipgloss.Width(bar) + lipgloss.Width(idCell) + lipgloss.Width(statusCell) + 1 + len([]rune(stageStr))
|
||||
name := s.Name
|
||||
if budget := w - fixed - lipgloss.Width(dateCell) - 1; budget >= 1 && len([]rune(name)) > budget {
|
||||
name = truncate(name, budget)
|
||||
}
|
||||
nameCell := lipgloss.NewStyle().Foreground(nameFg).Background(t.P.Bg).Render(" " + name)
|
||||
stageCell := t.span(stageStr, t.P.Dim)
|
||||
|
||||
left := bar + idCell + statusCell + nameCell + stageCell
|
||||
gap := w - lipgloss.Width(left) - lipgloss.Width(dateCell)
|
||||
if gap < 1 {
|
||||
gap = 1
|
||||
}
|
||||
rows = append(rows, left+t.span(strings.Repeat(" ", gap), t.P.Bg)+dateCell)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// shortDateTime formats a millis-since-epoch instant as a compact local timestamp for the
|
||||
// session list: "Jan 02 15:04" within the current year, "Jan 02 2006" for older years.
|
||||
// Empty for a zero/absent time (renders as no date rather than a bogus epoch).
|
||||
@@ -557,56 +502,6 @@ func shortDateTime(ms int64) string {
|
||||
return ts.Format("Jan 02 15:04")
|
||||
}
|
||||
|
||||
func (m Model) workflowRows(w int) []string {
|
||||
t := m.theme
|
||||
if len(m.workflows) == 0 {
|
||||
return []string{t.span("no workflows advertised", t.P.Faint)}
|
||||
}
|
||||
rows := make([]string, 0, len(m.workflows))
|
||||
for i, wf := range m.workflows {
|
||||
marker := t.span(" ", t.P.Bg)
|
||||
fg := t.P.Fg
|
||||
if i == m.wfIndex {
|
||||
marker = lipgloss.NewStyle().Foreground(t.P.Accent).Background(t.P.Bg).Render("▸ ")
|
||||
fg = t.P.FgStrong
|
||||
}
|
||||
rows = append(rows, marker+lipgloss.NewStyle().Foreground(fg).Background(t.P.Bg).Render(wf.ID)+t.span(" "+wf.Description, t.P.Faint))
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func (m Model) welcomeRows(w int) []string {
|
||||
t := m.theme
|
||||
title := lipgloss.NewStyle().Foreground(t.P.FgStrong).Background(t.P.Bg).Bold(true).Render("Corre") +
|
||||
lipgloss.NewStyle().Foreground(t.P.Accent).Background(t.P.Bg).Bold(true).Render("x") +
|
||||
lipgloss.NewStyle().Foreground(t.P.FgStrong).Background(t.P.Bg).Bold(true).Render(" Agent Harness")
|
||||
|
||||
var status string
|
||||
if m.connected {
|
||||
status = lipgloss.NewStyle().Foreground(t.P.OK).Background(t.P.Bg).Render("Connected") +
|
||||
t.span(" · ", t.P.Faint) + t.span(plural(len(m.sessions), "session"), t.P.Dim)
|
||||
} else {
|
||||
status = lipgloss.NewStyle().Foreground(t.P.Bad).Background(t.P.Bg).Render("Disconnected")
|
||||
}
|
||||
|
||||
rows := []string{title, status, "",
|
||||
t.span("Select a session from the left", t.P.Dim),
|
||||
t.span("or type a name to start a new one.", t.P.Dim), ""}
|
||||
|
||||
if n := len(m.sessions); n > 0 {
|
||||
rows = append(rows, t.span("recent:", t.P.Faint))
|
||||
start := 0
|
||||
if n > 5 {
|
||||
start = n - 5
|
||||
}
|
||||
for _, s := range m.sessions[start:] {
|
||||
mark := statusGlyph(t, s.Status)
|
||||
rows = append(rows, t.span(" "+s.Name+" ", t.P.Fg)+t.span(shortDateTime(s.LastEventAt), t.P.Faint)+" "+mark)
|
||||
}
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func (m Model) routerRows(w, h int) []string {
|
||||
t := m.theme
|
||||
if len(m.routerMessages[m.selectedID]) == 0 {
|
||||
|
||||
Reference in New Issue
Block a user