From e18d72725fff2cf23f94a22538c6ca27da2a7353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=AE=89=E5=93=B2?= Date: Tue, 5 May 2026 00:03:55 +0800 Subject: [PATCH] Fix diagnostics scrolling and list data visibility --- internal/tui/tui.go | 87 ++++++++++++++++++++++++++++++++-------- internal/tui/tui_test.go | 44 ++++++++++++++++++++ 2 files changed, 115 insertions(+), 16 deletions(-) diff --git a/internal/tui/tui.go b/internal/tui/tui.go index c38d432..7b0af94 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -160,9 +160,10 @@ type Model struct { tableReady bool // Detail view - viewport viewport.Model - detailReady bool - loopResult engine.LoopResult // 循环检测结果 + viewport viewport.Model + detailReady bool + diagnosticsReady bool + loopResult engine.LoopResult // 循环检测结果 // Diff view diffResult engine.SessionDiff @@ -239,6 +240,7 @@ func (m *Model) startReload() tea.Cmd { m.sessions = nil m.view = viewOverview m.detailReady = false + m.diagnosticsReady = false m.loadQueue = nil m.loadProgress = 0 m.loadTotal = 0 @@ -470,6 +472,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.viewport.Height = m.detailViewportHeight() m.refreshDetailViewport() } + if m.diagnosticsReady { + m.viewport.Width = m.detailViewportWidth() + m.viewport.Height = m.detailViewportHeight() + m.refreshDiagnosticsViewport() + } case tea.KeyMsg: // During loading, only allow quit @@ -596,7 +603,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.view = viewDetail m.openDetail() case viewDetail: - m.view = viewDiagnostics + m.openDiagnostics() case viewDiagnostics: m.openDiff() case viewDiff: @@ -628,12 +635,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.openDetail() } case "3": - m.view = viewDiagnostics + m.openDiagnostics() case "4": m.openDiff() case "w": if (m.view == viewList || m.view == viewDetail) && len(m.filteredIndices) > 0 { - m.view = viewDiagnostics + m.openDiagnostics() } case "/": @@ -750,6 +757,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } case viewDetail: m.viewport, cmd = m.viewport.Update(msg) + case viewDiagnostics: + m.viewport, cmd = m.viewport.Update(msg) case viewDiff: // No sub-component in diff view } @@ -768,10 +777,12 @@ func (m *Model) toggleLanguage() { i18n.SetLang(m.lang) m.refreshColumns() m.refreshDetailViewport() + m.refreshDiagnosticsViewport() } func (m *Model) openDetail() { m.detailReady = false + m.diagnosticsReady = false if !m.tableReady || len(m.sessions) == 0 { return } @@ -786,7 +797,15 @@ func (m *Model) openDetail() { } } +func (m *Model) openDiagnostics() { + m.view = viewDiagnostics + m.detailReady = false + m.refreshDiagnosticsViewport() +} + func (m *Model) openDiff() { + m.detailReady = false + m.diagnosticsReady = false m.prepareDiffForCursor() m.view = viewDiff } @@ -834,6 +853,22 @@ func (m *Model) refreshDetailViewport() { m.detailReady = false } +func (m *Model) refreshDiagnosticsViewport() { + if m.view != viewDiagnostics { + return + } + idx := m.findSessionIndex() + if idx < 0 || idx >= len(m.sessions) { + m.diagnosticsReady = false + return + } + s := m.sessions[idx] + m.prepareDetailState(s) + m.viewport = viewport.New(m.detailViewportWidth(), m.detailViewportHeight()) + m.viewport.SetContent(m.renderWasteContent(s, m.detailViewportWidth())) + m.diagnosticsReady = true +} + func (m Model) detailViewportHeight() int { if m.height <= 0 { return 12 @@ -1205,7 +1240,7 @@ func (m Model) View() string { content = m.frameContent(dimStyle.Render(m.selectionHint())) } case viewDiagnostics: - content = m.renderWaste() + content = m.renderDiagnosticsView() case viewDiff: content = m.renderDiff() } @@ -1245,6 +1280,7 @@ func (m Model) renderListView() string { contentW := m.frameBodyWidth() var sections []string extraLines := 0 + showListPanels := m.height == 0 || m.height >= 30 if filterBar := m.renderListStatusBar(contentW); filterBar != "" { sections = append(sections, filterBar) extraLines += renderedLineCount(filterBar) @@ -1253,14 +1289,18 @@ func (m Model) renderListView() string { sections = append(sections, empty) extraLines += renderedLineCount(empty) } - if summary := m.renderDriverSummary(contentW); summary != "" { - sections = append(sections, summary) - extraLines += renderedLineCount(summary) + if showListPanels { + if summary := m.renderDriverSummary(contentW); summary != "" { + sections = append(sections, summary) + extraLines += renderedLineCount(summary) + } } - if selected := m.renderSelectedSessionSummary(maxInt(1, contentW-6)); selected != "" { - panel := subtlePanel(i18n.T("list_selected"), selected, contentW) - sections = append(sections, panel) - extraLines += renderedLineCount(panel) + if showListPanels { + if selected := m.renderSelectedSessionSummary(maxInt(1, contentW-6)); selected != "" { + panel := subtlePanel(i18n.T("list_selected"), selected, contentW) + sections = append(sections, panel) + extraLines += renderedLineCount(panel) + } } tableView := m.table tableView.SetWidth(contentW) @@ -2240,6 +2280,18 @@ func (m Model) renderWaste() string { return m.frameContent(dimStyle.Render(m.selectionHint())) } s := m.sessions[idx] + return m.frameContent(m.renderWasteContent(s, panelW)) +} + +func (m Model) renderDiagnosticsView() string { + if !m.diagnosticsReady { + return m.renderWaste() + } + scrollInfo := dimStyle.Render(fmt.Sprintf(" %s: %.0f%% ", i18n.T("scroll_label"), m.viewport.ScrollPercent()*100)) + return m.frameContent(lipgloss.JoinVertical(lipgloss.Left, scrollInfo, m.viewport.View())) +} + +func (m Model) renderWasteContent(s engine.Session, panelW int) string { cardW := panelW twoColumn := panelW >= 92 if twoColumn { @@ -2302,10 +2354,10 @@ func (m Model) renderWaste() string { } } sections = append(sections, lipgloss.JoinVertical(lipgloss.Left, rows...)) - return m.frameContent(lipgloss.JoinVertical(lipgloss.Left, sections...)) + return lipgloss.JoinVertical(lipgloss.Left, sections...) } sections = append(sections, lipgloss.JoinVertical(lipgloss.Left, cards...)) - return m.frameContent(lipgloss.JoinVertical(lipgloss.Left, sections...)) + return lipgloss.JoinVertical(lipgloss.Left, sections...) } func (m Model) renderSlowRunSummary(s engine.Session) string { @@ -3042,6 +3094,9 @@ func (m *Model) adjustColumnWidths(width int) { if width >= 170 { m.setColumnsAndRefreshRows(m.wideListColumns(22, 13, 18, 5, 5, 5, 5, 8, 7, 8, 5, 9, 16)) return + } else if width >= 150 { + m.setColumnsAndRefreshRows(m.wideListColumns(16, 9, 8, 5, 5, 5, 4, 9, 7, 8, 4, 9, 16)) + return } else if width > 130 { m.setColumnsAndRefreshRows(m.wideListColumns(14, 13, 10, 5, 5, 5, 4, 7, 6, 5, 4, 9, 7)) return diff --git a/internal/tui/tui_test.go b/internal/tui/tui_test.go index a745935..7137d75 100644 --- a/internal/tui/tui_test.go +++ b/internal/tui/tui_test.go @@ -578,6 +578,7 @@ func TestCommandBackspaceRemovesWholeRune(t *testing.T) { func TestTabIntoDiffPreparesComparison(t *testing.T) { m := resizeForTest(t, sampleModelForTest(), 100, 30) m.view = viewDiagnostics + m.openDiagnostics() m.rebuildFilteredView() next, _ := m.Update(tea.KeyMsg{Type: tea.KeyTab}) @@ -591,6 +592,23 @@ func TestTabIntoDiffPreparesComparison(t *testing.T) { } } +func TestDiagnosticsViewUsesScrollableViewport(t *testing.T) { + m := resizeForTest(t, sampleModelForTest(), 80, 24) + m.view = viewList + + next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("3")}) + m = next.(Model) + if m.view != viewDiagnostics || !m.diagnosticsReady { + t.Fatalf("expected diagnostics viewport to be ready, view=%d ready=%v", m.view, m.diagnosticsReady) + } + before := m.viewport.YOffset + next, _ = m.Update(tea.KeyMsg{Type: tea.KeyPgDown}) + m = next.(Model) + if m.viewport.YOffset <= before { + t.Fatalf("expected diagnostics viewport to scroll, before=%d after=%d", before, m.viewport.YOffset) + } +} + func TestCompactListKeepsTokensAndHealthReadable(t *testing.T) { m := resizeForTest(t, sampleModelForTest(), 80, 30) m.view = viewList @@ -613,6 +631,21 @@ func TestCompactListKeepsTokensAndHealthReadable(t *testing.T) { } } +func TestCompactHeightListKeepsTableRowsVisible(t *testing.T) { + m := resizeForTest(t, sampleModelForTest(), 80, 24) + m.view = viewList + + rendered := m.View() + for _, want := range []string{"session_alpha", "$0.4200", "214.0K", "92%"} { + if !strings.Contains(rendered, want) { + t.Fatalf("compact-height list should keep table data visible, missing %q:\n%s", want, rendered) + } + } + if strings.Contains(rendered, "TOP DRIVERS") { + t.Fatalf("compact-height list should prioritize table rows over summary panels:\n%s", rendered) + } +} + func TestStandardWidthListKeepsHeadersAndHelpReadable(t *testing.T) { m := resizeForTest(t, sampleModelForTest(), 80, 24) m.view = viewList @@ -666,6 +699,17 @@ func TestWideListKeepsFullOperationalColumns(t *testing.T) { if row[12] != "No major anomaly" { t.Fatalf("expected issue column, got row=%v", row) } + rendered := m.View() + for _, want := range []string{"DURATION", "$0.4200", "214.0K", "No major anomaly"} { + if !strings.Contains(rendered, want) { + t.Fatalf("wide list should render diagnostic data without hiding %q:\n%s", want, rendered) + } + } + for _, clipped := range []string{"DURA…", "$0.42…", "No maj…"} { + if strings.Contains(rendered, clipped) { + t.Fatalf("wide list should not clip core diagnostic data %q:\n%s", clipped, rendered) + } + } } func TestUltraWideListAddsDiagnosticColumns(t *testing.T) {