Skip to content

Commit db1404f

Browse files
committed
fix(stats): bars leave a consistent margin before the number
The right-aligned compact counts lined up (every row is the same width), but when the top row's bar was full and its number widest, the coloured bar butted straight against the digits while other rows had a dotted gap. Cap the coloured bar 2 cells short so the dim track always leaves a margin and the leader runs to every number identically. Add TestPanelRowsAligned to pin it.
1 parent 6aeeee1 commit db1404f

2 files changed

Lines changed: 70 additions & 6 deletions

File tree

internal/tui/stats/panels.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import (
1010
"github.com/spoo-me/spoo-cli/internal/ui"
1111
)
1212

13+
// barGap is the minimum dim margin a coloured bar leaves before its number.
14+
const barGap = 2
15+
1316
// panelGrid lays the breakdown panels out in responsive columns. A
1417
// one-column gutter visually matches the stacked borders between rows
1518
// (terminal cells are ~2:1), and the division remainder widens the
@@ -77,10 +80,11 @@ func (m Model) panelView(idx, width, contentRows, topN int) string {
7780
for i, pt := range pts {
7881
label := kit.PadToWidth(kit.TruncateToWidth(m.rowLabel(p.key, pt.Label), labelW), labelW)
7982

80-
// dotted leader fills the count's right-align padding so the bar's
81-
// track runs continuously up to every number, no floating gap
83+
// cap the coloured bar 2 cells short so it never touches the number;
84+
// the dim track + leader fills that margin and the right-align
85+
// padding, so every row reads the same with no floating gap
8286
compact := kit.CompactNum(pt.Value)
83-
leader := ui.Dim.Render(strings.Repeat("·", countW-len(compact)))
87+
leader := ui.Dim.Render(strings.Repeat("·", barGap+countW-len(compact)))
8488
pct := " "
8589
if total > 0 {
8690
pct = fmt.Sprintf("%4.0f%%", pt.Value/total*100)
@@ -91,7 +95,7 @@ func (m Model) panelView(idx, width, contentRows, topN int) string {
9195
marker, labelStyle = ui.Title.Render("▸ "), ui.Title
9296
}
9397
lines = append(lines, marker+labelStyle.Render(label)+" "+
94-
ui.Bar(dashBarStyle, pt.Value, maxV, barMax, entityColor(pt.Label, panelHue))+
98+
ui.Bar(dashBarStyle, pt.Value, maxV, barMax-barGap, entityColor(pt.Label, panelHue))+
9599
leader+compact+ui.Dim.Render(pct))
96100
}
97101
return m.boxed(p.title, strings.Join(lines, "\n"), width, contentRows+3, focused, panelHue)
@@ -260,7 +264,7 @@ func (m Model) focusPanelBody(idx, width int) string {
260264
for i, pt := range pts {
261265
label := kit.PadToWidth(kit.TruncateToWidth(m.rowLabel(p.key, pt.Label), labelW), labelW)
262266
compact := kit.CompactNum(pt.Value)
263-
leader := ui.Dim.Render(strings.Repeat("·", countW-len(compact)))
267+
leader := ui.Dim.Render(strings.Repeat("·", barGap+countW-len(compact)))
264268
pct := " "
265269
if total > 0 {
266270
pct = fmt.Sprintf("%4.0f%%", pt.Value/total*100)
@@ -270,7 +274,7 @@ func (m Model) focusPanelBody(idx, width int) string {
270274
marker, labelStyle = ui.Title.Render("▸ "), ui.Title
271275
}
272276
lines = append(lines, marker+labelStyle.Render(label)+" "+
273-
ui.Bar(dashBarStyle, pt.Value, maxV, barMax, entityColor(pt.Label, panelHue))+
277+
ui.Bar(dashBarStyle, pt.Value, maxV, barMax-barGap, entityColor(pt.Label, panelHue))+
274278
leader+compact+ui.Dim.Render(pct))
275279
}
276280
return strings.Join(lines, "\n")

internal/tui/stats/panels_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package stats
2+
3+
import (
4+
"regexp"
5+
"strings"
6+
"testing"
7+
8+
tea "charm.land/bubbletea/v2"
9+
lipgloss "charm.land/lipgloss/v2"
10+
"github.com/zalando/go-keyring"
11+
12+
"github.com/spoo-me/spoo-cli/internal/api"
13+
"github.com/spoo-me/spoo-cli/internal/auth"
14+
)
15+
16+
var ansiRe = regexp.MustCompile("\x1b\\[[0-9;]*m")
17+
18+
// TestPanelRowsAligned renders a panel whose values span widths (132k …
19+
// 8.6k) and asserts every line is the same display width — the bar +
20+
// dotted leader + compact count + percentage must always line up, with
21+
// no row's bar touching its number.
22+
func TestPanelRowsAligned(t *testing.T) {
23+
keyring.MockInit()
24+
client := api.New("http://x", auth.NewStore(t.TempDir()))
25+
m := New(client, "", "all", "")
26+
resp := &api.StatsResponse{
27+
Scope: "all",
28+
Summary: api.StatsSummary{TotalClicks: 287558},
29+
Metrics: map[string][]map[string]any{
30+
"clicks_by_browser": {
31+
{"browser": "Chrome", "clicks": 131881.0},
32+
{"browser": "Safari", "clicks": 68807.0},
33+
{"browser": "Firefox", "clicks": 31537.0},
34+
{"browser": "Edge", "clicks": 25803.0},
35+
{"browser": "Brave", "clicks": 14335.0},
36+
{"browser": "Arc", "clicks": 8601.0},
37+
},
38+
},
39+
}
40+
next, _ := m.Update(statsLoadedMsg{res: resp})
41+
m = next.(Model)
42+
next, _ = m.Update(tea.WindowSizeMsg{Width: 200, Height: 50})
43+
m = next.(Model)
44+
45+
clean := ansiRe.ReplaceAllString(m.panelView(1, 50, 6, 6), "")
46+
lines := strings.Split(clean, "\n")
47+
want := lipgloss.Width(lines[0])
48+
for i, ln := range lines {
49+
if got := lipgloss.Width(ln); got != want {
50+
t.Errorf("panel line %d width = %d, want %d:\n%q", i, got, want, ln)
51+
}
52+
// a coloured bar block (▀) must never sit directly against a digit
53+
runes := []rune(ln)
54+
for j := 1; j < len(runes); j++ {
55+
if runes[j-1] == '▀' && runes[j] >= '0' && runes[j] <= '9' {
56+
t.Errorf("panel line %d: bar touches the number: %q", i, ln)
57+
}
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)