feat(tui-go): in-session right panel cycles events / changes / off (d)
Press d (or the "panel" palette command) in-session to cycle the right panel:
- events — the live event stream (default, unchanged)
- changes — a token-usage line (tokens + turns, summed from the transcript's
TurnMetrics) over a git-status-style list of files the session has
written, each with summed +/− from its diff. ^x still opens the full
diff.
- off — hide the panel; the output transcript takes the full width.
changesRows derives everything from data already in the model (router-turn
metrics + the tool entries' unified diffs), so no new protocol. Footer + ? help
updated; "changes" preview kind added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -261,7 +261,7 @@ func (m Model) renderFooter() string {
|
||||
} else {
|
||||
// Trimmed to fit ~100 cols: tools/stats/model are all reachable via `p cmds`,
|
||||
// so the footer keeps the high-traffic keys + the new transcript nav/copy.
|
||||
hints = []string{hint("i", "message"), hint("^↑↓", "msgs"), hint("y", "copy"), hint("e", "events"), hint("t", "tools"), hint("l", "back"), hint("p", "cmds"), hint("q", "quit")}
|
||||
hints = []string{hint("i", "message"), hint("^↑↓", "msgs"), hint("y", "copy"), hint("e", "events"), hint("d", "panel"), hint("l", "back"), hint("p", "cmds"), hint("q", "quit")}
|
||||
}
|
||||
if m.copiedFlash != 0 && m.frame-m.copiedFlash < copiedFlashFrames {
|
||||
hints = append(hints, lipgloss.NewStyle().Foreground(t.P.OK).Background(bg).Bold(true).Render("✓ copied"))
|
||||
@@ -295,12 +295,68 @@ func (m Model) renderMain(h int) string {
|
||||
leftW := m.width * 63 / 100
|
||||
rightW := m.width - leftW
|
||||
|
||||
// In-session / approval: output transcript over the event stream.
|
||||
// In-session / approval. The right panel cycles (d): events, changes (token usage +
|
||||
// changed files), or off (output takes the full width).
|
||||
if m.rightPanel == 2 {
|
||||
return m.theme.box("output", m.routerRows(m.width-4, h-2), m.width, h, true)
|
||||
}
|
||||
left := m.theme.box("output", m.routerRows(leftW-4, h-2), leftW, h, true)
|
||||
right := m.theme.box("events", m.eventRows(rightW-4, h), rightW, h, false)
|
||||
var right string
|
||||
if m.rightPanel == 1 {
|
||||
right = m.theme.box("changes", m.changesRows(rightW-4, h), rightW, h, false)
|
||||
} else {
|
||||
right = m.theme.box("events", m.eventRows(rightW-4, h), rightW, h, false)
|
||||
}
|
||||
return lipgloss.JoinHorizontal(lipgloss.Top, left, right)
|
||||
}
|
||||
|
||||
// changesRows is the in-session "changes" panel: a token-usage line plus a git-status-style
|
||||
// list of files the session has written (summed +/- from each write's diff). ^x opens the
|
||||
// full diff. Drives the right panel when rightPanel == 1.
|
||||
func (m Model) changesRows(w, h int) []string {
|
||||
t := m.theme
|
||||
turns, toks := 0, 0
|
||||
for _, e := range m.routerMessages[m.selectedID] {
|
||||
if e.Role == "router" && e.Metrics != nil {
|
||||
turns++
|
||||
toks += e.Metrics.TotalTokens
|
||||
}
|
||||
}
|
||||
rows := []string{
|
||||
t.span("tokens ", t.P.Faint) + t.span(itoa(toks), t.P.FgStrong) +
|
||||
t.span(" · turns ", t.P.Faint) + t.span(itoa(turns), t.P.Dim),
|
||||
"",
|
||||
}
|
||||
|
||||
type chg struct{ add, del int }
|
||||
var order []string
|
||||
seen := map[string]*chg{}
|
||||
for _, e := range m.routerMessages[m.selectedID] {
|
||||
if e.Role != "tool" || !isUnifiedDiff(e.Content) {
|
||||
continue
|
||||
}
|
||||
p, a, d := diffSummary(e.Content)
|
||||
if seen[p] == nil {
|
||||
seen[p] = &chg{}
|
||||
order = append(order, p)
|
||||
}
|
||||
seen[p].add += a
|
||||
seen[p].del += d
|
||||
}
|
||||
if len(order) == 0 {
|
||||
return append(rows, t.span("no file changes yet", t.P.Faint))
|
||||
}
|
||||
rows = append(rows, t.span("changes (^x for full diff)", t.P.Faint))
|
||||
for _, p := range order {
|
||||
c := seen[p]
|
||||
icon := lipgloss.NewStyle().Foreground(t.P.Accent2).Background(t.P.Bg).Render("✎ ")
|
||||
delta := lipgloss.NewStyle().Foreground(t.P.OK).Background(t.P.Bg).Render("+"+itoa(c.add)) +
|
||||
t.span(" ", t.P.Bg) + lipgloss.NewStyle().Foreground(t.P.Bad).Background(t.P.Bg).Render("−"+itoa(c.del))
|
||||
rows = append(rows, icon+t.span(truncate(p, w-16), t.P.Fg)+" "+delta)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// renderLauncher is the idle screen: a compact, opencode-style input centered in the main
|
||||
// area, with a hideable right rail of status + quick keys. The session list isn't shown —
|
||||
// it's a keypress away (R). The input's lower-right shows <workflow> · <model>, Tab-cycled.
|
||||
|
||||
Reference in New Issue
Block a user