feat(tui-go): transcript free-scroll + help overlay + event filter
Three TUI-polish items. - OUTPUT free-scroll: PgUp/PgDn (and ^u/^d half-page) scroll the transcript back through history; esc snaps to tail-follow. outputScroll is clamped against outputViewport(), which mirrors View/renderMain geometry, so the edges land exactly (no dead presses unwinding an overshoot). routerRows split into buildTranscriptRows (full render) + windowing so the handler measures the same content. Tests cover the clamp + the fits-in-panel no-op. - Help overlay: `?` (or the "help" palette command) opens a keybinding cheat-sheet grouped by context (session / overlays / approval band) — the non-palette keys especially. Any key dismisses it. - Event-inspector filter: `/` filters the event list by a case-insensitive substring of type/detail (currentEvents applies it); `c` clears, esc stops editing then closes. Fresh per open. EVENTS side panel stays unfiltered. go build / vet / test green; rendered help + filtered inspector via cmd/preview (help, events-filter kinds). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -493,10 +493,42 @@ func (m Model) welcomeRows(w int) []string {
|
||||
|
||||
func (m Model) routerRows(w, h int) []string {
|
||||
t := m.theme
|
||||
msgs := m.routerMessages[m.selectedID]
|
||||
if len(msgs) == 0 {
|
||||
if len(m.routerMessages[m.selectedID]) == 0 {
|
||||
return []string{t.span("awaiting router response…", t.P.Faint)}
|
||||
}
|
||||
rows, msgStart := m.buildTranscriptRows(w)
|
||||
// With a selection, scroll so the selected message is visible (top-aligned, clamped to the
|
||||
// end); otherwise honour the free-scroll offset (0 = tail-follow the newest output).
|
||||
if m.transcriptSel >= 0 && m.transcriptSel < len(msgStart) && len(rows) > h {
|
||||
start := msgStart[m.transcriptSel]
|
||||
if start > len(rows)-h {
|
||||
start = len(rows) - h
|
||||
}
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
return rows[start : start+h]
|
||||
}
|
||||
if len(rows) > h {
|
||||
off := m.outputScroll
|
||||
if max := len(rows) - h; off > max {
|
||||
off = max
|
||||
}
|
||||
if off < 0 {
|
||||
off = 0
|
||||
}
|
||||
end := len(rows) - off
|
||||
return rows[end-h : end]
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// buildTranscriptRows renders the selected session's transcript to display rows (and records each
|
||||
// message's first row index for scroll-to-selection). Split out so the scroll handler can measure
|
||||
// the full height against the same content the renderer windows.
|
||||
func (m Model) buildTranscriptRows(w int) ([]string, []int) {
|
||||
t := m.theme
|
||||
msgs := m.routerMessages[m.selectedID]
|
||||
var rows []string
|
||||
msgStart := make([]int, len(msgs)) // first row index of each message, for scroll-to-selection
|
||||
for mi, e := range msgs {
|
||||
@@ -552,22 +584,7 @@ func (m Model) routerRows(w, h int) []string {
|
||||
rows = append(rows, " "+icon+" "+t.span(e.Content, t.P.Dim))
|
||||
}
|
||||
}
|
||||
// With a selection, scroll so the selected message is visible (top-aligned, clamped
|
||||
// to the end); otherwise tail-follow the newest output.
|
||||
if m.transcriptSel >= 0 && m.transcriptSel < len(msgStart) && len(rows) > h {
|
||||
start := msgStart[m.transcriptSel]
|
||||
if start > len(rows)-h {
|
||||
start = len(rows) - h
|
||||
}
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
return rows[start : start+h]
|
||||
}
|
||||
if len(rows) > h {
|
||||
rows = rows[len(rows)-h:]
|
||||
}
|
||||
return rows
|
||||
return rows, msgStart
|
||||
}
|
||||
|
||||
func (m Model) eventRows(w, h int) []string {
|
||||
|
||||
Reference in New Issue
Block a user