From c7e1b207df85bcb2268bea05980cb09ec6e23a67 Mon Sep 17 00:00:00 2001 From: Joshua Vial Date: Mon, 12 Jan 2026 15:50:04 +1300 Subject: [PATCH] Improve tmux statusline: sort rigs by activity and add visual grouping (#337) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improve tmux statusline: sort rigs by activity and add visual grouping - Sort rigs by running state, then polecat count, then operational state - Add visual grouping with | separators between state groups - Show process state with icons (🟒 both running, 🟑 one running, πŸ…ΏοΈ parked, πŸ›‘ docked, ⚫ idle) - Display polecat counts for active rigs - Improve icon spacing: 2 spaces after Park emoji, 1 space for others * Fix golangci-lint warnings - Check error return from os.Setenv - Check error return from lock.Unlock - Mark intentionally unused parameters with _ --------- Co-authored-by: joshuavial --- internal/cmd/statusline.go | 117 +++++++++++++++++++++++++++++++------ 1 file changed, 98 insertions(+), 19 deletions(-) diff --git a/internal/cmd/statusline.go b/internal/cmd/statusline.go index 31f84bd4..b61a1127 100644 --- a/internal/cmd/statusline.go +++ b/internal/cmd/statusline.go @@ -182,10 +182,12 @@ func runMayorStatusLine(t *tmux.Tmux) error { } } - // Track per-rig status for LED indicators + // Track per-rig status for LED indicators and sorting type rigStatus struct { - hasWitness bool - hasRefinery bool + hasWitness bool + hasRefinery bool + polecatCount int + opState string // "OPERATIONAL", "PARKED", or "DOCKED" } rigStatuses := make(map[string]*rigStatus) @@ -212,10 +214,21 @@ func runMayorStatusLine(t *tmux.Tmux) error { rigStatuses[agent.Rig].hasRefinery = true case AgentPolecat: polecatCount++ + rigStatuses[agent.Rig].polecatCount++ } } } + // Get operational state for each rig + for rigName, status := range rigStatuses { + opState, _ := getRigOperationalState(townRoot, rigName) + if opState == "PARKED" || opState == "DOCKED" { + status.opState = opState + } else { + status.opState = "OPERATIONAL" + } + } + // Build status var parts []string parts = append(parts, fmt.Sprintf("%d 😺", polecatCount)) @@ -223,30 +236,96 @@ func runMayorStatusLine(t *tmux.Tmux) error { // Build rig status display with LED indicators // 🟒 = both witness and refinery running (fully active) // 🟑 = one of witness/refinery running (partially active) - // ⚫ = neither running (inactive) - var rigParts []string - var rigNames []string - for rigName := range rigStatuses { - rigNames = append(rigNames, rigName) - } - sort.Strings(rigNames) + // πŸ…ΏοΈ = parked (nothing running, intentionally paused) + // πŸ›‘ = docked (nothing running, global shutdown) + // ⚫ = operational but nothing running (unexpected state) - for _, rigName := range rigNames { - status := rigStatuses[rigName] + // Create sortable rig list + type rigInfo struct { + name string + status *rigStatus + } + var rigs []rigInfo + for rigName, status := range rigStatuses { + rigs = append(rigs, rigInfo{name: rigName, status: status}) + } + + // Sort by: 1) running state, 2) polecat count (desc), 3) operational state, 4) alphabetical + sort.Slice(rigs, func(i, j int) bool { + isRunningI := rigs[i].status.hasWitness || rigs[i].status.hasRefinery + isRunningJ := rigs[j].status.hasWitness || rigs[j].status.hasRefinery + + // Primary sort: running rigs before non-running rigs + if isRunningI != isRunningJ { + return isRunningI + } + + // Secondary sort: polecat count (descending) + if rigs[i].status.polecatCount != rigs[j].status.polecatCount { + return rigs[i].status.polecatCount > rigs[j].status.polecatCount + } + + // Tertiary sort: operational state (for non-running rigs: OPERATIONAL < PARKED < DOCKED) + stateOrder := map[string]int{"OPERATIONAL": 0, "PARKED": 1, "DOCKED": 2} + stateI := stateOrder[rigs[i].status.opState] + stateJ := stateOrder[rigs[j].status.opState] + if stateI != stateJ { + return stateI < stateJ + } + + // Quaternary sort: alphabetical + return rigs[i].name < rigs[j].name + }) + + // Build display with group separators + var rigParts []string + var lastGroup string + for _, rig := range rigs { + isRunning := rig.status.hasWitness || rig.status.hasRefinery + var currentGroup string + if isRunning { + currentGroup = "running" + } else { + currentGroup = "idle-" + rig.status.opState + } + + // Add separator when group changes (running -> non-running, or different opStates within non-running) + if lastGroup != "" && lastGroup != currentGroup { + rigParts = append(rigParts, "|") + } + lastGroup = currentGroup + + status := rig.status var led string - // Check if rig is parked or docked - opState, _ := getRigOperationalState(townRoot, rigName) - if opState == "PARKED" || opState == "DOCKED" { - led = "⏸️" // Parked/docked - intentionally offline - } else if status.hasWitness && status.hasRefinery { + // Check if processes are running first (regardless of operational state) + if status.hasWitness && status.hasRefinery { led = "🟒" // Both running - fully active } else if status.hasWitness || status.hasRefinery { led = "🟑" // One running - partially active } else { - led = "⚫" // Neither running - inactive + // Nothing running - show operational state + switch status.opState { + case "PARKED": + led = "πŸ…ΏοΈ" // Parked - intentionally paused + case "DOCKED": + led = "πŸ›‘" // Docked - global shutdown + default: + led = "⚫" // Operational but nothing running + } } - rigParts = append(rigParts, led+rigName) + + // Show polecat count if > 0 + // All icons get 1 space, Park gets 2 + space := " " + if led == "πŸ…ΏοΈ" { + space = " " + } + display := led + space + rig.name + if status.polecatCount > 0 { + display += fmt.Sprintf("(%d)", status.polecatCount) + } + rigParts = append(rigParts, display) } if len(rigParts) > 0 {