How the witr TUI Visually Integrates Process Details with the Process Ancestry Tree View

The witr TUI renders process details alongside a live ancestry tree by splitting the interface into horizontal panes using lipgloss.JoinHorizontal, dynamically updating the tree viewport header based on the selected PID, and maintaining the tree context when transitioning to detailed process views.

The pranshuparmar/witr repository implements a terminal user interface (TUI) that combines real-time process monitoring with hierarchical relationship visualization. Understanding how the witr TUI visually integrates process details with the process ancestry tree view reveals a sophisticated layout system built on the Bubble Tea framework and Lipgloss styling library.

Dual-Pane Architecture in the Processes Tab

When the Processes tab is active, the interface presents two complementary visualization areas. The Process List Pane displays a tabular view of all discovered processes using m.table, while the Ancestry Tree Pane renders the parent-child relationship hierarchy for the currently highlighted process within m.treeViewport.

This split-view approach allows operators to scan through process lists while simultaneously inspecting the lineage of any selected entry. The architecture relies on distinct model fields defined in internal/tui/model.go, where the treeViewport field manages the scrollable tree content independently from the main process table.

Horizontal Layout Implementation with Lipgloss

The visual joining of these panes occurs in the viewList function within internal/tui/view.go. Around line 37, the code uses lipgloss.JoinHorizontal to arrange the table and tree viewport side-by-side:

mainContent := lipgloss.JoinHorizontal(lipgloss.Top,
    lipgloss.NewStyle().Width(processListPaneWidth).Render(m.table.View()),
    treeContainerStyle.Render(
        lipgloss.JoinVertical(lipgloss.Left,
            treeHeaderStyle.Render(treeHeader),
            paddedStyle.Render(m.treeViewport.View()),
        ),
    ),
)

The layout applies specific styling constraints to each pane. The process list receives a fixed width allocation, while the tree container stacks elements vertically—placing the dynamic header above the scrollable viewport content. This horizontal composition ensures that both data sources remain visible within the terminal dimensions without overlapping.

Dynamic Tree Header and Scroll Indicators

The tree pane header adapts to user interaction, displaying either "Details" or "PID " depending on the current selection state (lines 81-85 in internal/tui/view.go). This immediate visual feedback confirms which process's ancestry is currently rendered in the adjacent viewport.

Navigation state is communicated through arrow symbols (↕, ↑, ↓) that indicate scroll position within the tree. These indicators update based on AtTop and AtBottom checks on the viewport (lines 87-93), alerting users when additional ancestry nodes exist beyond the current visible area.

Populating the Ancestry Tree from Process Data

The ancestry content generation logic resides in internal/tui/data.go. When a user selects a different process, the application formats the parent-child hierarchy and injects it into the viewport around line 553:

m.treeViewport.SetContent("")               // clear old content
if m.treeViewport.Width > 0 {
    content = wrap.String(content, m.treeViewport.Width)
}
m.treeViewport.SetContent(content)          // set new ancestry tree

The code first clears existing content to prevent visual artifacts, then applies wrap.String to ensure the tree renders correctly within the available terminal width before setting the final formatted string. This dynamic content refresh keeps the ancestry visualization synchronized with the user's current selection in the process table.

State Transitions to the Detail View

Pressing Enter on a process row triggers a state transition to stateDetail, which activates the detailed process view while preserving the tree context. The main View function in internal/tui/view.go handles this transition (around lines 25-33):

case stateDetail:
    if m.selectedDetail == nil && m.selectedContainer == nil {
        return m.viewDetailLoading(outerStyle)
    }
    if m.selectedContainer != nil {
        return m.viewContainerDetail(outerStyle)
    }
    return m.viewProcessDetail(outerStyle)

The viewProcessDetail method renders comprehensive process information—including environment variables and command-line arguments—while maintaining the ancestry tree pane on the left side of the interface. This preserves the visual relationship between the detailed process data and its hierarchical position within the system.

Summary

  • Side-by-side layout: The viewList function uses lipgloss.JoinHorizontal to combine the process table and tree viewport in internal/tui/view.go.
  • Dynamic content: The treeViewport receives formatted ancestry strings via SetContent in internal/tui/data.go whenever the selection changes.
  • Context preservation: Switching to stateDetail displays the viewProcessDetail pane while keeping the ancestry tree visible for reference.
  • Scroll management: Arrow indicators update based on viewport position checks to signal additional content availability.
  • Header synchronization: The tree pane header reflects the currently selected PID, providing immediate orientation within the process hierarchy.

Frequently Asked Questions

How does witr handle the layout of multiple panes?

The witr TUI uses the Lipgloss library's JoinHorizontal function to arrange the process list table and ancestry tree viewport side-by-side. This approach, implemented in internal/tui/view.go, allows both components to share screen real estate while maintaining independent scroll states and styling contexts.

What triggers the ancestry tree to update in the witr TUI?

The ancestry tree updates when the user highlights a different process in the main table. This selection change triggers a content refresh in internal/tui/data.go, where the application generates a new parent-child hierarchy string and calls m.treeViewport.SetContent to render the updated lineage information.

Can users scroll through the ancestry tree independently?

Yes, the treeViewport operates as an independent scrollable region within the TUI. The component tracks its own scroll position using AtTop and AtBottom methods, displaying arrow indicators (↕, ↑, ↓) to signal when additional ancestry nodes exist above or below the current view. Scroll handling logic is managed in internal/tui/update.go.

Which files control the visual integration in witr?

Four primary files orchestrate the visual integration: internal/tui/view.go handles layout rendering and state transitions; internal/tui/data.go prepares ancestry tree content; internal/tui/model.go defines the viewport and table structures; and internal/tui/update.go manages viewport dimension updates and scrolling behavior.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →