summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-25 09:45:53 +0200
committerPaul Buetow <paul@buetow.org>2026-02-25 09:45:53 +0200
commit0136c4528316d62810382d38bb007496cba97c24 (patch)
tree705e1e697d6f0e2d553f58905b3ad6b6427566e3
parent72ff234e97b16485553a79a876690a359058b110 (diff)
Refine overview layout and right-align sparkline history
-rw-r--r--internal/tui/dashboard/overview.go25
-rw-r--r--internal/tui/dashboard/sparkline.go24
-rw-r--r--internal/tui/dashboard/sparkline_test.go13
3 files changed, 44 insertions, 18 deletions
diff --git a/internal/tui/dashboard/overview.go b/internal/tui/dashboard/overview.go
index 7cbc7fe..f4ec5bc 100644
--- a/internal/tui/dashboard/overview.go
+++ b/internal/tui/dashboard/overview.go
@@ -15,6 +15,9 @@ func renderOverview(snap *statsengine.Snapshot, width, height int) string {
if snap == nil {
return common.PanelStyle.Render("Overview: waiting for stats...")
}
+ if width <= 0 {
+ width = 80
+ }
boxWidth := summaryBoxWidth(width)
box1 := renderSyscallBox(snap, boxWidth)
@@ -38,18 +41,18 @@ func renderOverview(snap *statsengine.Snapshot, width, height int) string {
latencyHist := "Latency buckets: " + summarizeHistogramBrief(snap.LatencyHistogram)
gapHist := "Gap buckets: " + summarizeHistogramBrief(snap.GapHistogram)
+ panel := common.PanelStyle.Width(width)
+ sparkPanel := panel.Render(strings.Join([]string{latencySpark, "", gapSpark, "", throughputSpark}, "\n"))
+ topPanel := panel.Render(strings.Join([]string{topSyscalls, topFiles, topProcesses}, "\n"))
+ histPanel := panel.Render(strings.Join([]string{latencyHist, gapHist}, "\n"))
+
return strings.Join(
[]string{
row,
common.HighlightStyle.Render(trends),
- common.PanelStyle.Render(latencySpark),
- common.PanelStyle.Render(gapSpark),
- common.PanelStyle.Render(throughputSpark),
- common.PanelStyle.Render(topSyscalls),
- common.PanelStyle.Render(topFiles),
- common.PanelStyle.Render(topProcesses),
- common.PanelStyle.Render(latencyHist),
- common.PanelStyle.Render(gapHist),
+ sparkPanel,
+ topPanel,
+ histPanel,
},
"\n",
)
@@ -67,7 +70,7 @@ func renderSyscallBox(snap *statsengine.Snapshot, width int) string {
snap.SyscallRatePerSec,
generatedAt,
)
- return common.PanelStyle.Width(width).Render(content)
+ return common.PanelStyle.Width(width).Height(5).Render(content)
}
func renderBytesBox(snap *statsengine.Snapshot, width int) string {
@@ -77,7 +80,7 @@ func renderBytesBox(snap *statsengine.Snapshot, width int) string {
formatBytes(snap.WriteBytesPerSec),
formatBytes(float64(snap.TotalBytes)),
)
- return common.PanelStyle.Width(width).Render(content)
+ return common.PanelStyle.Width(width).Height(5).Render(content)
}
func renderErrorBox(snap *statsengine.Snapshot, width int) string {
@@ -93,7 +96,7 @@ func renderErrorBox(snap *statsengine.Snapshot, width int) string {
snap.LatencyMeanNs,
snap.GapMeanNs,
)
- return common.PanelStyle.Width(width).Render(content)
+ return common.PanelStyle.Width(width).Height(5).Render(content)
}
func trendWithArrow(trend statsengine.Trend) string {
diff --git a/internal/tui/dashboard/sparkline.go b/internal/tui/dashboard/sparkline.go
index b94d84f..fad86e2 100644
--- a/internal/tui/dashboard/sparkline.go
+++ b/internal/tui/dashboard/sparkline.go
@@ -11,15 +11,23 @@ func renderSparkline(data []float64, width int) string {
}
samples := sampleForWidth(data, width)
+ leftPad := 0
+ if len(samples) < width {
+ leftPad = width - len(samples)
+ }
min, max := minMax(samples)
if min == max {
- top := repeatRune(' ', len(samples))
- bottom := repeatRune('ā–ˆ', len(samples))
+ top := repeatRune(' ', width)
+ bottom := repeatRune(' ', leftPad) + repeatRune('ā–ˆ', len(samples))
return top + "\n" + bottom
}
- top := make([]rune, len(samples))
- bottom := make([]rune, len(samples))
+ top := make([]rune, width)
+ bottom := make([]rune, width)
+ for i := 0; i < leftPad; i++ {
+ top[i] = ' '
+ bottom[i] = ' '
+ }
scale := 16.0
denom := max - min
for i, value := range samples {
@@ -39,9 +47,13 @@ func renderSparkline(data []float64, width int) string {
if bottomLevel > 8 {
bottomLevel = 8
}
+ if bottomLevel == 0 {
+ bottomLevel = 1
+ }
- top[i] = sparkRowChars[topLevel]
- bottom[i] = sparkRowChars[bottomLevel]
+ col := leftPad + i
+ top[col] = sparkRowChars[topLevel]
+ bottom[col] = sparkRowChars[bottomLevel]
}
return string(top) + "\n" + string(bottom)
}
diff --git a/internal/tui/dashboard/sparkline_test.go b/internal/tui/dashboard/sparkline_test.go
index 97dac03..e1fb316 100644
--- a/internal/tui/dashboard/sparkline_test.go
+++ b/internal/tui/dashboard/sparkline_test.go
@@ -16,7 +16,7 @@ func TestRenderSparklineEmptyOrInvalidWidth(t *testing.T) {
func TestRenderSparklineSingleValue(t *testing.T) {
got := renderSparkline([]float64{10}, 8)
- if got != " \nā–ˆ" {
+ if got != " \n ā–ˆ" {
t.Fatalf("expected two-line constant sparkline, got %q", got)
}
}
@@ -28,6 +28,17 @@ func TestRenderSparklineAllEqualValues(t *testing.T) {
}
}
+func TestRenderSparklineRightAlignsShortHistory(t *testing.T) {
+ got := renderSparkline([]float64{1, 2, 3}, 6)
+ lines := strings.Split(got, "\n")
+ if len(lines) != 2 {
+ t.Fatalf("expected 2 lines, got %q", got)
+ }
+ if !strings.HasPrefix(lines[1], " ") {
+ t.Fatalf("expected left padding for short history, got %q", lines[1])
+ }
+}
+
func TestRenderSparklineRespectsWidthTruncation(t *testing.T) {
got := renderSparkline([]float64{1, 2, 3, 4, 5, 6, 7, 8}, 4)
lines := strings.Split(got, "\n")