Skip to content

feat: modularize TUI dashboard components into individual view files #363

Open
SuperCoolPencil wants to merge 4 commits intomainfrom
fix-activity-log
Open

feat: modularize TUI dashboard components into individual view files #363
SuperCoolPencil wants to merge 4 commits intomainfrom
fix-activity-log

Conversation

@SuperCoolPencil
Copy link
Copy Markdown
Member

@SuperCoolPencil SuperCoolPencil commented Apr 12, 2026

Greptile Summary

This PR modularizes the monolithic view.go TUI dashboard into individual files per pane (view_dashboard_header.go, view_dashboard_list.go, view_dashboard_graph.go, view_dashboard_log.go, view_dashboard_details.go, view_dashboard_chunkmap.go), and changes PaneTitleStyle from NeonCyan to LightGray.

The structural split is clean and improves navigability, but the refactoring introduced a subtle layout inconsistency: renderFocusedDetails is now called twice per frame with different content widths, causing the pre-measured detailHeight used for layout to potentially diverge from the actual rendered height.

Confidence Score: 4/5

Safe to merge after addressing the double-computation width mismatch in the detail pane; all other issues are minor visual regressions

One P1 defect: renderFocusedDetails is called twice per frame with a 2-unit content-width discrepancy between the height-measurement pass (rightWidth-4) and the rendering pass (rightWidth-2). This can produce a layout height that doesn't match the rendered content, misaligning the right column at certain terminal widths. Two P2 issues (GetGraphAreaDimensions inflation by +2, missing MinLogoWidth guard) are behavioural regressions worth fixing but won't break the primary path for most users.

internal/tui/view.go and internal/tui/view_dashboard_details.go (double computation / width mismatch), internal/tui/view_dashboard_graph.go (GetGraphAreaDimensions argument)

Important Files Changed

Filename Overview
internal/tui/view.go Core layout orchestration refactored to delegate to new render helpers; introduces double-computation of renderFocusedDetails with inconsistent widths and removes hideLogo guard
internal/tui/view_dashboard_details.go New file; recomputes renderFocusedDetails with contentWidth = width-2 while view.go already measured with rightWidth-4, causing a 2-unit width mismatch
internal/tui/view_dashboard_graph.go New file; hideGraphStats threshold and GetGraphAreaDimensions argument subtly differ from original, slightly changing graph layout at threshold widths
internal/tui/view_dashboard_header.go New file; logo/server info rendering extracted correctly, but MinLogoWidth guard removed — logo now appears at narrow widths where it was previously suppressed
internal/tui/view_dashboard_log.go New file; adds bottom-alignment padding for log entries; viewport sizing corrected from the original double-subtraction
internal/tui/view_dashboard_list.go New file; download list rendering extracted cleanly with correct internal padding and height calculations
internal/tui/view_dashboard_chunkmap.go New file; chunk map rendering extracted correctly; targetRows clamping is consistent with original
internal/tui/styles.go PaneTitleStyle foreground changed from NeonCyan to LightGray — intentional visual update
internal/tui/view_settings.go No logic changes; file moved/reorganised without modification

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    V["RootModel.View()"]
    V --> HD["renderHeaderBox()\nview_dashboard_header.go"]
    V --> LB["renderLogBox()\nview_dashboard_log.go"]
    V --> GB["renderGraphBox()\nview_dashboard_graph.go"]
    V --> DL["renderDownloadsBox()\nview_dashboard_list.go"]
    V --> DB["renderDetailsBox()\nview_dashboard_details.go"]
    V --> CB["renderChunkMapBox()\nview_dashboard_chunkmap.go"]
    V -->|"pre-measures detailHeight\nwith rightWidth-4"| RFD1["renderFocusedDetails()\nwidth = rightWidth-4"]
    DB -->|"re-renders with\nrightWidth-2"| RFD2["renderFocusedDetails()\nwidth = rightWidth-2"]
    RFD1 -.->|"height mismatch\npossible"| RFD2
    style RFD1 fill:#f9a,stroke:#f00
    style RFD2 fill:#f9a,stroke:#f00
    style DB fill:#fdd,stroke:#f00
Loading

Comments Outside Diff (1)

  1. internal/tui/view.go, line 326-342 (link)

    P1 Double computation with mismatched widths breaks detail pane height measurement

    renderFocusedDetails is called here to measure detailHeight for layout, using detailWidth = rightWidth - PaneStyle.GetHorizontalFrameSize(). Because DefaultPaddingX = 1, PaneStyle.GetHorizontalFrameSize() = 4, so the measurement width is rightWidth - 4.

    renderDetailsBox then calls renderFocusedDetails again internally with contentWidth = width - 2 = rightWidth - 2 — 2 units wider. Inside renderFocusedDetails, the actual inner content width is w - 4, so:

    • Height-measurement call: inner contentWidth = rightWidth - 8
    • Rendering call: inner contentWidth = rightWidth - 6

    If any text (filename, path, ID, stats) wraps at one width but not the other, the measured detailHeight diverges from the actual rendered height, mis-sizing the right column's layout. The original code computed this once with a consistent width.

    The simplest fix is to pass the already-computed detailContent into renderDetailsBox instead of recomputing:

    // view.go — pass pre-computed content
    detailBox := m.renderDetailsBox(rightWidth, detailHeight, detailContent)
    // view_dashboard_details.go — accept pre-computed content
    func (m *RootModel) renderDetailsBox(width, height int, innerContent string) string {
        return renderBtopBox("", PaneTitleStyle.Render(" File Details "), innerContent, width, height, colors.Gray)
    }
    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: internal/tui/view.go
    Line: 326-342
    
    Comment:
    **Double computation with mismatched widths breaks detail pane height measurement**
    
    `renderFocusedDetails` is called here to measure `detailHeight` for layout, using `detailWidth = rightWidth - PaneStyle.GetHorizontalFrameSize()`. Because `DefaultPaddingX = 1`, `PaneStyle.GetHorizontalFrameSize() = 4`, so the measurement width is `rightWidth - 4`.
    
    `renderDetailsBox` then calls `renderFocusedDetails` again internally with `contentWidth = width - 2 = rightWidth - 2` — 2 units wider. Inside `renderFocusedDetails`, the actual inner content width is `w - 4`, so:
    - Height-measurement call: inner `contentWidth = rightWidth - 8`
    - Rendering call: inner `contentWidth = rightWidth - 6`
    
    If any text (filename, path, ID, stats) wraps at one width but not the other, the measured `detailHeight` diverges from the actual rendered height, mis-sizing the right column's layout. The original code computed this once with a consistent width.
    
    The simplest fix is to pass the already-computed `detailContent` into `renderDetailsBox` instead of recomputing:
    
    ```go
    // view.go — pass pre-computed content
    detailBox := m.renderDetailsBox(rightWidth, detailHeight, detailContent)
    ```
    
    ```go
    // view_dashboard_details.go — accept pre-computed content
    func (m *RootModel) renderDetailsBox(width, height int, innerContent string) string {
        return renderBtopBox("", PaneTitleStyle.Render(" File Details "), innerContent, width, height, colors.Gray)
    }
    ```
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: internal/tui/view.go
Line: 326-342

Comment:
**Double computation with mismatched widths breaks detail pane height measurement**

`renderFocusedDetails` is called here to measure `detailHeight` for layout, using `detailWidth = rightWidth - PaneStyle.GetHorizontalFrameSize()`. Because `DefaultPaddingX = 1`, `PaneStyle.GetHorizontalFrameSize() = 4`, so the measurement width is `rightWidth - 4`.

`renderDetailsBox` then calls `renderFocusedDetails` again internally with `contentWidth = width - 2 = rightWidth - 2` — 2 units wider. Inside `renderFocusedDetails`, the actual inner content width is `w - 4`, so:
- Height-measurement call: inner `contentWidth = rightWidth - 8`
- Rendering call: inner `contentWidth = rightWidth - 6`

If any text (filename, path, ID, stats) wraps at one width but not the other, the measured `detailHeight` diverges from the actual rendered height, mis-sizing the right column's layout. The original code computed this once with a consistent width.

The simplest fix is to pass the already-computed `detailContent` into `renderDetailsBox` instead of recomputing:

```go
// view.go — pass pre-computed content
detailBox := m.renderDetailsBox(rightWidth, detailHeight, detailContent)
```

```go
// view_dashboard_details.go — accept pre-computed content
func (m *RootModel) renderDetailsBox(width, height int, innerContent string) string {
    return renderBtopBox("", PaneTitleStyle.Render(" File Details "), innerContent, width, height, colors.Gray)
}
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: internal/tui/view_dashboard_header.go
Line: 11-73

Comment:
**`MinLogoWidth` guard dropped — logo renders at previously-suppressed widths**

The original code had `hideLogo := leftWidth < MinLogoWidth` (threshold: 60) and skipped the ASCII art at narrow left-column widths. `renderHeaderBox` removes this guard entirely and only falls back on `contentHeight < 7`. At `leftWidth` between 35–59, the logo gradient will now be rendered inside a narrower-than-expected area, potentially wrapping or overflowing.

Consider restoring the width check:

```go
if contentHeight < 7 || width < MinLogoWidth {
    innerContent = lipgloss.Place(contentWidth, contentHeight, lipgloss.Center, lipgloss.Center, serverPortContent)
} else {
    // render logo...
}
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: internal/tui/view_dashboard_graph.go
Line: 124

Comment:
**`GetGraphAreaDimensions` receives `width + 2` instead of `width`**

The original code passed `rightWidth` directly:
```go
// original
graphAreaWidth, axisWidth := GetGraphAreaDimensions(rightWidth, true)
```

The new code passes `contentWidth + BoxStyle.GetHorizontalFrameSize()*2`:
```go
// new: contentWidth = width-2, GetHorizontalFrameSize()*2 = 4 → passes width+2
graphAreaWidth, axisWidth := GetGraphAreaDimensions(contentWidth+BoxStyle.GetHorizontalFrameSize()*2, true)
```

This inflates the argument by 2 units compared to the original, widening the computed graph area and potentially overflowing the box at tight widths. Passing `width` directly, as the original did, is more correct here.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: internal/tui/view.go
Line: 446-453

Comment:
**Trailing whitespace on several lines**

The refactoring stripped meaningful inline comments (e.g. `// Rest for log box`, `// Minimum for server box content`, `// Minimum for viewport`) but left behind trailing spaces on the same lines.

```suggestion
	logWidth := leftWidth - logoWidth - BoxStyle.GetHorizontalFrameSize() // Rest for log box

	if logoWidth < 4 {
		logoWidth = 4 // Minimum for server box content
	}
	if logWidth < 4 {
		logWidth = 4 // Minimum for viewport
	}
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "refactor: replace if-else block with swi..." | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

Comment on lines +11 to +73
func (m *RootModel) renderHeaderBox(width, height int) string {
contentWidth := width - 2
contentHeight := height - 2

if contentWidth < 0 {
contentWidth = 0
}
if contentHeight < 1 {
contentHeight = 1
}

logoText := `
_______ ___________ ____
/ ___/ / / / ___/ __ '/ _ \
(__ ) /_/ / / / /_/ / __/
/____/\__,_/_/ \__, /\___/
/____/ `

// Server info part
greenDot := lipgloss.NewStyle().Foreground(colors.StateDownloading).Render("●")
host := m.ServerHost
if host == "" {
host = "127.0.0.1"
}
serverAddr := fmt.Sprintf("%s:%d", host, m.ServerPort)

var statusLine string
if m.IsRemote {
statusLine = lipgloss.NewStyle().Foreground(colors.NeonCyan).Bold(true).Render(" Connected to " + serverAddr)
} else {
statusLine = lipgloss.NewStyle().Foreground(colors.NeonCyan).Bold(true).Render(" Serving at " + serverAddr)
}

serverPortContent := lipgloss.NewStyle().
Width(contentWidth).
Align(lipgloss.Center).
Render(greenDot + statusLine)

var innerContent string
// If the height is too short for both logo and server text, just return server text centered vertically
if contentHeight < 7 {
innerContent = lipgloss.Place(contentWidth, contentHeight, lipgloss.Center, lipgloss.Center, serverPortContent)
} else {
var logoContent string
if m.logoCache != "" {
logoContent = m.logoCache
} else {
gradientLogo := ApplyGradient(logoText, colors.NeonPink, colors.NeonPurple)
m.logoCache = lipgloss.NewStyle().Render(gradientLogo)
logoContent = m.logoCache
}

logoBoxHeight := contentHeight - 1 // 1 line for the server text at the bottom
if logoBoxHeight < 1 {
logoBoxHeight = 1
}

logoBox := lipgloss.Place(contentWidth, logoBoxHeight, lipgloss.Center, lipgloss.Center, logoContent)
innerContent = lipgloss.JoinVertical(lipgloss.Left, logoBox, serverPortContent)
}

return renderBtopBox("", PaneTitleStyle.Render(" Server "), innerContent, width, height, colors.Gray)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 MinLogoWidth guard dropped — logo renders at previously-suppressed widths

The original code had hideLogo := leftWidth < MinLogoWidth (threshold: 60) and skipped the ASCII art at narrow left-column widths. renderHeaderBox removes this guard entirely and only falls back on contentHeight < 7. At leftWidth between 35–59, the logo gradient will now be rendered inside a narrower-than-expected area, potentially wrapping or overflowing.

Consider restoring the width check:

if contentHeight < 7 || width < MinLogoWidth {
    innerContent = lipgloss.Place(contentWidth, contentHeight, lipgloss.Center, lipgloss.Center, serverPortContent)
} else {
    // render logo...
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/tui/view_dashboard_header.go
Line: 11-73

Comment:
**`MinLogoWidth` guard dropped — logo renders at previously-suppressed widths**

The original code had `hideLogo := leftWidth < MinLogoWidth` (threshold: 60) and skipped the ASCII art at narrow left-column widths. `renderHeaderBox` removes this guard entirely and only falls back on `contentHeight < 7`. At `leftWidth` between 35–59, the logo gradient will now be rendered inside a narrower-than-expected area, potentially wrapping or overflowing.

Consider restoring the width check:

```go
if contentHeight < 7 || width < MinLogoWidth {
    innerContent = lipgloss.Place(contentWidth, contentHeight, lipgloss.Center, lipgloss.Center, serverPortContent)
} else {
    // render logo...
}
```

How can I resolve this? If you propose a fix, please make it concise.


if hideGraphStats {
// No stats box — graph gets almost full width
graphAreaWidth, axisWidth := GetGraphAreaDimensions(contentWidth+BoxStyle.GetHorizontalFrameSize()*2, true)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 GetGraphAreaDimensions receives width + 2 instead of width

The original code passed rightWidth directly:

// original
graphAreaWidth, axisWidth := GetGraphAreaDimensions(rightWidth, true)

The new code passes contentWidth + BoxStyle.GetHorizontalFrameSize()*2:

// new: contentWidth = width-2, GetHorizontalFrameSize()*2 = 4 → passes width+2
graphAreaWidth, axisWidth := GetGraphAreaDimensions(contentWidth+BoxStyle.GetHorizontalFrameSize()*2, true)

This inflates the argument by 2 units compared to the original, widening the computed graph area and potentially overflowing the box at tight widths. Passing width directly, as the original did, is more correct here.

Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/tui/view_dashboard_graph.go
Line: 124

Comment:
**`GetGraphAreaDimensions` receives `width + 2` instead of `width`**

The original code passed `rightWidth` directly:
```go
// original
graphAreaWidth, axisWidth := GetGraphAreaDimensions(rightWidth, true)
```

The new code passes `contentWidth + BoxStyle.GetHorizontalFrameSize()*2`:
```go
// new: contentWidth = width-2, GetHorizontalFrameSize()*2 = 4 → passes width+2
graphAreaWidth, axisWidth := GetGraphAreaDimensions(contentWidth+BoxStyle.GetHorizontalFrameSize()*2, true)
```

This inflates the argument by 2 units compared to the original, widening the computed graph area and potentially overflowing the box at tight widths. Passing `width` directly, as the original did, is more correct here.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +446 to 453
logWidth := leftWidth - logoWidth - BoxStyle.GetHorizontalFrameSize()

if logoWidth < 4 {
logoWidth = 4 // Minimum for server box content
logoWidth = 4
}
if logWidth < 4 {
logWidth = 4 // Minimum for viewport
logWidth = 4
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Trailing whitespace on several lines

The refactoring stripped meaningful inline comments (e.g. // Rest for log box, // Minimum for server box content, // Minimum for viewport) but left behind trailing spaces on the same lines.

Suggested change
logWidth := leftWidth - logoWidth - BoxStyle.GetHorizontalFrameSize()
if logoWidth < 4 {
logoWidth = 4 // Minimum for server box content
logoWidth = 4
}
if logWidth < 4 {
logWidth = 4 // Minimum for viewport
logWidth = 4
}
logWidth := leftWidth - logoWidth - BoxStyle.GetHorizontalFrameSize() // Rest for log box
if logoWidth < 4 {
logoWidth = 4 // Minimum for server box content
}
if logWidth < 4 {
logWidth = 4 // Minimum for viewport
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/tui/view.go
Line: 446-453

Comment:
**Trailing whitespace on several lines**

The refactoring stripped meaningful inline comments (e.g. `// Rest for log box`, `// Minimum for server box content`, `// Minimum for viewport`) but left behind trailing spaces on the same lines.

```suggestion
	logWidth := leftWidth - logoWidth - BoxStyle.GetHorizontalFrameSize() // Rest for log box

	if logoWidth < 4 {
		logoWidth = 4 // Minimum for server box content
	}
	if logWidth < 4 {
		logWidth = 4 // Minimum for viewport
	}
```

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant