ai-agent/internal/tui/RESPONSIVE_WIDTH.md
admin 8dc496b626
Some checks failed
CI / test (push) Has been cancelled
Release / release (push) Failing after 4m36s
first commit
2026-03-08 15:40:34 +07:00

5.1 KiB

Responsive Width Implementation

Overview

This document describes the responsive width calculations implemented to prevent horizontal scrolling in the TUI chat interface.

Width Calculation Hierarchy

1. Viewport Width (Primary Constraint)

The viewport is the main container for chat content. All other widths derive from this.

Formula (from model.go:373-380):

viewportWidth := screenWidth - 1
if sidePanel.IsVisible() {
    viewportWidth = screenWidth - panelWidth - 2
}
if viewportWidth < 20 {
    viewportWidth = 20  // minimum width
}

Breakdown:

  • screenWidth - 1: Full width minus right edge padding (when panel hidden)
  • screenWidth - panelWidth - 2: Width minus panel and separator line (when panel visible)
  • Minimum 20 characters to ensure readability

2. Content Width (Text Wrapping)

Used for wrapping text in renderEntries(), renderUserMsg(), renderAssistantMsg(), etc.

Formula (from view.go:422-429):

contentW := screenWidth - 4
if sidePanel.IsVisible() {
    contentW = screenWidth - panelWidth - 5
}
if contentW < 20 {
    contentW = 20
}

Breakdown:

  • screenWidth - 4: Full width with 2-char padding on each side
  • screenWidth - panelWidth - 5: Accounts for panel, separator, and padding
  • Minimum 20 characters

3. Markdown Width (Glamour Rendering)

Used for rendering markdown content via Glamour.

Formula (from model.go:382-386):

markdownWidth := viewportWidth - 3
if markdownWidth < 20 {
    markdownWidth = 20
}

Breakdown:

  • Derived from viewport width minus 3 chars for padding/indentation
  • Minimum 20 characters

4. Input Width

Matches viewport width exactly for unified appearance.

Formula (from model.go:431):

input.SetWidth(viewportWidth)

Panel Width Calculation

Panel width is dynamic based on screen size (from model.go:365-371):

panelWidth := 30  // default
if screenWidth < 100 {
    panelWidth = 25
} else if screenWidth > 160 {
    panelWidth = 40
}

Layout Constraints

With Panel Visible

┌─────────────────────────────────────────────────┐
│ Panel (25-40) ││ Chat Viewport                 │
│               ││ (screen - panel - 2)           │
│               ││                                │
│               ││ Content wrapped to:            │
│               ││ (screen - panel - 5)           │
└─────────────────────────────────────────────────┘

Without Panel

┌─────────────────────────────────────────────────┐
│ Chat Viewport (screen - 1)                     │
│                                                 │
│ Content wrapped to: (screen - 4)               │
└─────────────────────────────────────────────────┘

Critical Invariants

The following invariants are enforced to prevent horizontal scrolling:

  1. viewportWidth ≤ screenWidth - 1 (or screenWidth - panelWidth - 1 when panel visible)
  2. contentWidth ≤ viewportWidth
  3. markdownWidth ≤ viewportWidth
  4. All widths ≥ 20 (minimum readability)

Test Coverage

Comprehensive tests in width_test.go verify:

  • TestViewportWidthCalculation: Validates width calculations for various screen sizes
  • TestResponsiveWidthToggle: Ensures widths adjust correctly when panel is toggled
  • TestMinimumWidthConstraints: Verifies minimum width enforcement on small screens
  • TestRenderedTextWidth: Tests actual text wrapping behavior
  • TestLayoutConsistency: Exhaustive testing across screen sizes 40-200 chars

Example Calculations

120-char screen with panel (30 chars)

Viewport:   120 - 30 - 2 = 88 chars
Content:    120 - 30 - 5 = 85 chars
Markdown:   88 - 3 = 85 chars
Input:      88 chars
Total:      30 (panel) + 1 (separator) + 88 (viewport) = 119 ✓

80-char screen without panel

Viewport:   80 - 1 = 79 chars
Content:    80 - 4 = 76 chars
Markdown:   79 - 3 = 76 chars
Input:      79 chars
Total:      79 chars ✓

40-char screen with panel (25 chars) - Edge Case

Viewport:   40 - 25 - 2 = 13 → 20 (minimum enforced)
Content:    40 - 25 - 5 = 10 → 20 (minimum enforced)
Markdown:   20 - 3 = 17 → 20 (minimum enforced)
Input:      20 chars
Total:      25 + 1 + 20 = 46 (exceeds screen, but minimum width takes priority)

Note: On very small screens (< 46 chars with panel), the minimum width constraints take precedence. Users should be advised to use larger terminal windows for optimal experience.

Responsive Behavior

When the side panel is toggled:

  1. Viewport width recalculates immediately
  2. Content is re-wrapped to new width via invalidateRenderedCache()
  3. Markdown renderer is recreated with new width
  4. Input field resizes to match viewport

This ensures seamless responsive behavior without horizontal scrolling.