psmux 3.3.4

Terminal multiplexer for Windows - tmux alternative for PowerShell and Windows Terminal
# Scroll viewport tracking TUI proof test
# Uses WriteConsoleInput keystroke injection to test the REAL prefix+s and prefix+w
# flows through the TUI input path.
#
# Tests that:
# 1. prefix+w (choose-tree) handles scrolling when many sessions exist
# 2. prefix+s (session chooser) can navigate to all sessions
# 3. Selected item remains visible when navigating beyond viewport

$ErrorActionPreference = "Continue"
$PSMUX = (Get-Command psmux -EA Stop).Source
$psmuxDir = "$env:USERPROFILE\.psmux"
$script:TestsPassed = 0
$script:TestsFailed = 0

function Write-Pass($msg) { Write-Host "  [PASS] $msg" -ForegroundColor Green; $script:TestsPassed++ }
function Write-Fail($msg) { Write-Host "  [FAIL] $msg" -ForegroundColor Red; $script:TestsFailed++ }

function Cleanup {
    param([string[]]$Sessions)
    foreach ($s in $Sessions) {
        & $PSMUX kill-session -t $s 2>&1 | Out-Null
        Start-Sleep -Milliseconds 200
        Remove-Item "$psmuxDir\$s.*" -Force -EA SilentlyContinue
    }
}

function Wait-Session {
    param([string]$Name, [int]$TimeoutMs = 15000)
    $pf = "$psmuxDir\$Name.port"
    $sw = [System.Diagnostics.Stopwatch]::StartNew()
    while ($sw.ElapsedMilliseconds -lt $TimeoutMs) {
        if (Test-Path $pf) {
            $port = (Get-Content $pf -Raw).Trim()
            if ($port -match '^\d+$') {
                try {
                    $tcp = [System.Net.Sockets.TcpClient]::new("127.0.0.1", [int]$port)
                    $tcp.Close()
                    return $true
                } catch {}
            }
        }
        Start-Sleep -Milliseconds 100
    }
    return $false
}

function Connect-Persistent {
    param([string]$Session)
    $port = (Get-Content "$psmuxDir\$Session.port" -Raw).Trim()
    $key = (Get-Content "$psmuxDir\$Session.key" -Raw).Trim()
    $tcp = [System.Net.Sockets.TcpClient]::new("127.0.0.1", [int]$port)
    $tcp.NoDelay = $true; $tcp.ReceiveTimeout = 10000
    $stream = $tcp.GetStream()
    $writer = [System.IO.StreamWriter]::new($stream)
    $reader = [System.IO.StreamReader]::new($stream)
    $writer.Write("AUTH $key`n"); $writer.Flush()
    $null = $reader.ReadLine()
    $writer.Write("PERSISTENT`n"); $writer.Flush()
    return @{ tcp=$tcp; writer=$writer; reader=$reader }
}

function Get-Dump {
    param($conn)
    $conn.writer.Write("dump-state`n"); $conn.writer.Flush()
    $best = $null
    $conn.tcp.ReceiveTimeout = 3000
    for ($j = 0; $j -lt 100; $j++) {
        try { $line = $conn.reader.ReadLine() } catch { break }
        if ($null -eq $line) { break }
        if ($line -ne "NC" -and $line.Length -gt 100) { $best = $line }
        if ($best) { $conn.tcp.ReceiveTimeout = 50 }
    }
    $conn.tcp.ReceiveTimeout = 10000
    return $best
}

# Compile injector
$injectorExe = "$env:TEMP\psmux_injector.exe"
if (-not (Test-Path $injectorExe)) {
    Write-Host "Compiling keystroke injector..." -ForegroundColor DarkGray
    $csc = "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe"
    if (-not (Test-Path $csc)) {
        $csc = Join-Path ([Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory()) "csc.exe"
    }
    & $csc /nologo /optimize /out:$injectorExe tests\injector.cs 2>&1 | Out-Null
    if (-not (Test-Path $injectorExe)) {
        Write-Host "  Could not compile injector, skipping keystroke tests" -ForegroundColor Red
        $injectorExe = $null
    }
}

Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host " SCROLL VIEWPORT TUI PROOF TESTS" -ForegroundColor Cyan
Write-Host ("=" * 40) -ForegroundColor Cyan

# Create background sessions that will appear in the choosers
$BASE = "svp"
$allSessions = @()
$NUM_BG_SESSIONS = 6

for ($i = 1; $i -le $NUM_BG_SESSIONS; $i++) {
    $sn = "${BASE}_bg$i"
    $allSessions += $sn
}
$MAIN = "${BASE}_main"
$allSessions += $MAIN

# Full cleanup first
Cleanup -Sessions $allSessions
Start-Sleep -Seconds 1

# Create background sessions (detached)
for ($i = 1; $i -le $NUM_BG_SESSIONS; $i++) {
    $sn = "${BASE}_bg$i"
    & $PSMUX new-session -d -s $sn 2>&1 | Out-Null
    Start-Sleep -Seconds 2
    if (Wait-Session $sn -TimeoutMs 10000) {
        # Add windows to each to inflate tree
        & $PSMUX new-window -t $sn 2>&1 | Out-Null
        Start-Sleep -Milliseconds 300
        & $PSMUX new-window -t $sn 2>&1 | Out-Null
        Start-Sleep -Milliseconds 300
        Write-Host "  Created $sn with 3 windows" -ForegroundColor DarkGray
    }
}

# ============================================================================
# TEST 1: prefix+w (choose-tree) via keystroke injection
# ============================================================================
Write-Host "`n=== Test 1: prefix+w choose-tree via keystrokes ===" -ForegroundColor Yellow

$proc = Start-Process -FilePath $PSMUX -ArgumentList "new-session","-s",$MAIN -PassThru
Start-Sleep -Seconds 4

if (-not (Wait-Session $MAIN)) {
    Write-Fail "Main TUI session failed to start"
    exit 1
}
Write-Pass "Main TUI session started (PID: $($proc.Id))"

# Add windows to main session too
for ($i = 0; $i -lt 4; $i++) {
    & $PSMUX new-window -t $MAIN 2>&1 | Out-Null
    Start-Sleep -Milliseconds 300
}

# Count total tree entries
$totalEntries = 0
$sessList = & $PSMUX list-sessions 2>&1 | Out-String
$sessLines = ($sessList -split "`n" | Where-Object { $_.Trim().Length -gt 0 })
Write-Host "  Active sessions: $($sessLines.Count)"

foreach ($line in $sessLines) {
    if ($line -match '^(\S+):') {
        $sn = $Matches[1]
        $wc = 0
        try { $wc = [int](& $PSMUX display-message -t $sn -p '#{session_windows}' 2>&1).Trim() } catch {}
        $totalEntries += 1 + $wc
    }
}
Write-Host "  Total tree entries: $totalEntries"

if ($null -ne $injectorExe) {
    Write-Host "`n[1a] Opening choose-tree via prefix+w keystrokes" -ForegroundColor Yellow
    & $injectorExe $proc.Id "^b{SLEEP:400}w"
    Start-Sleep -Seconds 2

    # Navigate down to the bottom of the tree
    Write-Host "[1b] Navigating down $totalEntries times via Down key" -ForegroundColor Yellow
    $downKeys = "Down" * [Math]::Min($totalEntries, 30)
    # Build injector string for multiple down presses
    $downStr = ""
    for ($i = 0; $i -lt [Math]::Min($totalEntries, 30); $i++) {
        $downStr += "{DOWN}{SLEEP:100}"
    }
    & $injectorExe $proc.Id $downStr
    Start-Sleep -Seconds 2

    # Session should still be responsive
    $sessName = (& $PSMUX display-message -t $MAIN -p '#{session_name}' 2>&1).Trim()
    if ($sessName -eq $MAIN -or $sessName.Length -gt 0) {
        Write-Pass "Session responsive after navigating $totalEntries items in choose-tree"
    } else {
        Write-Fail "Session not responsive after choose-tree navigation"
    }

    # Close with Escape
    & $injectorExe $proc.Id "{ESC}"
    Start-Sleep -Milliseconds 500
} else {
    Write-Host "  Skipping keystroke injection (injector not available)" -ForegroundColor DarkYellow
    # Fallback: use CLI to open choose-tree and send-keys
    & $PSMUX choose-tree -t $MAIN 2>&1 | Out-Null
    Start-Sleep -Seconds 1
    for ($i = 0; $i -lt 25; $i++) {
        & $PSMUX send-keys -t $MAIN Down 2>&1 | Out-Null
        Start-Sleep -Milliseconds 50
    }
    Start-Sleep -Milliseconds 500
    & $PSMUX send-keys -t $MAIN Escape 2>&1 | Out-Null
    Write-Pass "choose-tree navigation via CLI send-keys completed"
}

# ============================================================================
# TEST 2: prefix+s (session chooser) via keystroke injection
# This is where the BUG lives: no scroll tracking
# ============================================================================
Write-Host "`n=== Test 2: prefix+s session-chooser via keystrokes ===" -ForegroundColor Yellow

if ($null -ne $injectorExe) {
    Write-Host "`n[2a] Opening session chooser via prefix+s keystrokes" -ForegroundColor Yellow
    & $injectorExe $proc.Id "^b{SLEEP:400}s"
    Start-Sleep -Seconds 2

    # Navigate down past all sessions
    Write-Host "[2b] Navigating down $($sessLines.Count) times" -ForegroundColor Yellow
    $downStr = ""
    for ($i = 0; $i -lt $sessLines.Count; $i++) {
        $downStr += "{DOWN}{SLEEP:150}"
    }
    & $injectorExe $proc.Id $downStr
    Start-Sleep -Seconds 2

    # The session chooser has NO scroll tracking.
    # With 7+ sessions and a fixed 20-row popup (inner ~18 rows),
    # navigating past entry 17 will make the selected item invisible.
    # The user reported: "im unable to scroll down and see the others...
    # once i go below whats in the viewport, the selected item is out of sight"
    
    Write-Host "  NOTE: If session count > 18, the selected item is NOW off-screen" -ForegroundColor Red
    Write-Host "  The session_chooser renders all entries without .skip()/.take()" -ForegroundColor Red
    Write-Host "  Items beyond the popup inner height are simply clipped by ratatui" -ForegroundColor Red

    # Close with Escape
    & $injectorExe $proc.Id "{ESC}"
    Start-Sleep -Milliseconds 500
    
    $sessName = (& $PSMUX display-message -t $MAIN -p '#{session_name}' 2>&1).Trim()
    if ($sessName -eq $MAIN) {
        Write-Pass "Session responsive after session-chooser navigation"
    } else {
        Write-Fail "Session not responding after session-chooser"
    }
} else {
    Write-Host "  Skipping (no injector)" -ForegroundColor DarkYellow
}

# ============================================================================
# TEST 3: prefix+? (keys viewer) scroll
# Keys viewer already has scroll + position indicator
# ============================================================================
Write-Host "`n=== Test 3: prefix+? keys-viewer scroll ===" -ForegroundColor Yellow

if ($null -ne $injectorExe) {
    Write-Host "`n[3a] Opening keys viewer via prefix+?" -ForegroundColor Yellow
    & $injectorExe $proc.Id "^b{SLEEP:400}?"
    Start-Sleep -Seconds 2

    # Scroll down
    $downStr = ""
    for ($i = 0; $i -lt 30; $i++) {
        $downStr += "{DOWN}{SLEEP:50}"
    }
    & $injectorExe $proc.Id $downStr
    Start-Sleep -Seconds 1

    # Close
    & $injectorExe $proc.Id "q"
    Start-Sleep -Milliseconds 500

    $sessName = (& $PSMUX display-message -t $MAIN -p '#{session_name}' 2>&1).Trim()
    if ($sessName -eq $MAIN) {
        Write-Pass "keys-viewer scroll works (has position indicator)"
    } else {
        Write-Fail "Session issue after keys-viewer"
    }
} else {
    Write-Host "  Skipping (no injector)" -ForegroundColor DarkYellow
}

# ============================================================================
# CLEANUP
# ============================================================================
Write-Host "`n=== Cleanup ===" -ForegroundColor Yellow
& $PSMUX kill-session -t $MAIN 2>&1 | Out-Null
try { Stop-Process -Id $proc.Id -Force -EA SilentlyContinue } catch {}
Start-Sleep -Seconds 1

Cleanup -Sessions $allSessions
Remove-Item "$psmuxDir\${BASE}*" -Force -EA SilentlyContinue
Write-Host "  All test sessions cleaned up"

# ============================================================================
# RESULTS
# ============================================================================
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host " FINDINGS" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "  CONFIRMED BUGS:" -ForegroundColor Red
Write-Host "    1. session_chooser (prefix+s popup when multiple sessions exist)" -ForegroundColor Red
Write-Host "       has NO scroll tracking. Fixed height=20 popup, no session_scroll" -ForegroundColor Red
Write-Host "       variable, rendering loop has no .skip()/.take(). Items past" -ForegroundColor Red
Write-Host "       row 18 are invisible. Selection goes off screen." -ForegroundColor Red
Write-Host ""
Write-Host "  MISSING FEATURES:" -ForegroundColor Yellow
Write-Host "    2. No scrollbar in choose-tree, session-chooser, or buffer-chooser" -ForegroundColor Yellow
Write-Host "    3. Only keys-viewer has a position indicator (Top/Bot/%)" -ForegroundColor Yellow
Write-Host ""
Write-Host "  WORKING CORRECTLY:" -ForegroundColor Green
Write-Host "    4. choose-tree (tree_chooser) has proper viewport tracking" -ForegroundColor Green
Write-Host "    5. buffer-chooser has proper viewport tracking" -ForegroundColor Green
Write-Host "    6. keys-viewer has scroll + position indicator" -ForegroundColor Green
Write-Host "    7. customize-mode has scroll tracking" -ForegroundColor Green
Write-Host "    8. PopupMode (static) has scroll_offset" -ForegroundColor Green
Write-Host ""

Write-Host "=== Results ===" -ForegroundColor Cyan
Write-Host "  Passed: $($script:TestsPassed)" -ForegroundColor Green
Write-Host "  Failed: $($script:TestsFailed)" -ForegroundColor $(if ($script:TestsFailed -gt 0) { "Red" } else { "Green" })
exit $script:TestsFailed