From 454f24fc3a65a591b9c7defaea114a3396827310 Mon Sep 17 00:00:00 2001 From: kami Date: Mon, 22 Jun 2026 10:04:41 +0000 Subject: [PATCH] feat(tui-go): show last-activity date in the starting-view session list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The idle "sessions" panel listed id/status/name/stage but no time at all. Add a right-aligned last-activity timestamp (Session.LastEventAt) per row — "Jan 02 15:04" within the current year, "Jan 02 2006" for older — so old sessions are distinguishable at a glance. The name is truncated first so the box's width clamp never eats the date column. (The R resume overlay already shows a relative age; this is the live list on the starting screen.) Co-Authored-By: Claude Opus 4.8 --- apps/tui-go/internal/app/view.go | 39 ++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/apps/tui-go/internal/app/view.go b/apps/tui-go/internal/app/view.go index 3f3d37e5..b42abb72 100644 --- a/apps/tui-go/internal/app/view.go +++ b/apps/tui-go/internal/app/view.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "strings" + "time" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/x/ansi" @@ -431,16 +432,46 @@ func (m Model) sessionRows(w int) []string { 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)) - nameCell := lipgloss.NewStyle().Foreground(nameFg).Background(t.P.Bg).Render(" " + s.Name) - stage := "" + stageStr := "" if s.CurrentStage != "" { - stage = t.span(" ["+s.CurrentStage+"]", t.P.Dim) + stageStr = " [" + s.CurrentStage + "]" } - rows = append(rows, bar+idCell+statusCell+nameCell+stage) + 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). +func shortDateTime(ms int64) string { + if ms <= 0 { + return "" + } + ts := time.UnixMilli(ms) + if ts.Year() != time.Now().Year() { + return ts.Format("Jan 02 2006") + } + return ts.Format("Jan 02 15:04") +} + func (m Model) workflowRows(w int) []string { t := m.theme if len(m.workflows) == 0 {