Skip to content

Commit 13d9a67

Browse files
gitcoder89431claude
andcommitted
fix: prevent Help page infinite overscroll
Extract buildLines() so totalLines is precomputed at construction. Update clamps scrollOffset to max(0, totalLines-1) so hammering j stops at the last line rather than growing without bound. View applies the tighter per-height clamp for the correct visual cutoff. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2e71df3 commit 13d9a67

1 file changed

Lines changed: 51 additions & 44 deletions

File tree

internal/screens/help.go

Lines changed: 51 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ type Help struct {
1313
bindings [][]key.Binding
1414
theme theme.Theme
1515
scrollOffset int
16+
totalLines int
1617
}
1718

1819
func NewHelp(bindings [][]key.Binding, t theme.Theme) Help {
19-
return Help{bindings: bindings, theme: t}
20+
h := Help{bindings: bindings, theme: t}
21+
h.totalLines = len(h.buildLines())
22+
return h
2023
}
2124

2225
func (h Help) WithTheme(t theme.Theme) Help {
@@ -32,19 +35,44 @@ func (h Help) Update(msg tea.Msg) (Screen, tea.Cmd) {
3235
case "up", "k":
3336
h.scrollOffset = max(0, h.scrollOffset-1)
3437
case "down", "j":
35-
h.scrollOffset++
38+
h.scrollOffset = min(h.scrollOffset+1, max(0, h.totalLines-1))
3639
}
3740
}
3841
return h, nil
3942
}
4043

4144
func (h Help) View(width, height int) string {
45+
lines := h.buildLines()
46+
total := len(lines)
47+
48+
if total <= height {
49+
return lipgloss.NewStyle().Width(width).Height(height).Render(strings.Join(lines, "\n"))
50+
}
51+
52+
// tighter clamp now that we know height
53+
maxOffset := max(0, total-height+1)
54+
offset := min(h.scrollOffset, maxOffset)
55+
56+
visible := lines[offset : offset+height-1]
57+
58+
var indicator string
59+
if offset < maxOffset {
60+
indicator = h.theme.Muted.Render("↓ j/k to scroll")
61+
} else {
62+
indicator = h.theme.Muted.Render("↑ j/k to scroll")
63+
}
64+
65+
return lipgloss.NewStyle().Width(width).Height(height).Render(
66+
strings.Join(append(visible, indicator), "\n"),
67+
)
68+
}
69+
70+
// buildLines returns the full list of rendered lines for the Help screen.
71+
func (h Help) buildLines() []string {
4272
rule := func(label string) string {
43-
labelW := lipgloss.Width(label)
44-
fill := strings.Repeat("/", max(0, width-labelW-1))
45-
return h.theme.Title.Render(label) + h.theme.PaletteAccent.Render(" "+fill)
73+
return h.theme.Title.Render(label) + h.theme.PaletteAccent.Render(" ////")
4674
}
47-
binding := func(k, desc string) string {
75+
bind := func(k, desc string) string {
4876
return " " + h.theme.Accent.Render(k) + " " + h.theme.Muted.Render(desc)
4977
}
5078

@@ -56,63 +84,42 @@ func (h Help) View(width, height int) string {
5684
for _, group := range h.bindings {
5785
for _, kb := range group {
5886
help := kb.Help()
59-
add(binding(help.Key, help.Desc))
87+
add(bind(help.Key, help.Desc))
6088
}
6189
}
6290

6391
add("")
6492
add(rule("Dashboard"))
6593
add("")
66-
add(binding("j/k", "navigate files"))
67-
add(binding("enter", "open selected file/folder in terminal"))
68-
add(binding("r", "refresh + trigger rescan"))
94+
add(bind("j/k", "navigate files"))
95+
add(bind("enter", "open selected file/folder in terminal"))
96+
add(bind("r", "refresh + trigger rescan"))
6997

7098
add("")
7199
add(rule("Connect"))
72100
add("")
73-
add(binding("j/k", "navigate devices"))
74-
add(binding("p", "pair a new device by pasting its ID"))
75-
add(binding("a", "accept a pending device"))
76-
add(binding("i", "ignore a pending device"))
77-
add(binding("d", "remove an approved device"))
78-
add(binding("r", "rename a device"))
101+
add(bind("j/k", "navigate devices"))
102+
add(bind("p", "pair a new device by pasting its ID"))
103+
add(bind("a", "accept a pending device"))
104+
add(bind("i", "ignore a pending device"))
105+
add(bind("d", "remove an approved device"))
106+
add(bind("r", "rename a device"))
79107

80108
add("")
81109
add(rule("Settings"))
82110
add("")
83-
add(binding("c", "copy this device's ID to clipboard"))
111+
add(bind("c", "copy this device's ID to clipboard"))
84112

85113
add("")
86114
add(rule("Command Palette"))
87115
add("")
88-
add(binding("ctrl+k", "open palette"))
89-
add(binding("ctrl+t", "cycle to next theme"))
90-
add(binding("type", "filter commands"))
91-
add(binding("enter", "run selected command"))
92-
add(binding("esc", "close palette"))
93-
94-
total := len(lines)
95-
if total <= height {
96-
// no scrolling needed
97-
return lipgloss.NewStyle().Width(width).Height(height).Render(strings.Join(lines, "\n"))
98-
}
99-
100-
// clamp scroll so we never go past the last page
101-
maxOffset := total - height + 1 // +1 reserves space for the indicator
102-
h.scrollOffset = min(h.scrollOffset, maxOffset)
103-
104-
visible := lines[h.scrollOffset : h.scrollOffset+height-1]
105-
106-
// indicator line
107-
var indicator string
108-
if h.scrollOffset < maxOffset {
109-
indicator = h.theme.Muted.Render("↓ j/k to scroll")
110-
} else {
111-
indicator = h.theme.Muted.Render("↑ j/k to scroll")
112-
}
116+
add(bind("ctrl+k", "open palette"))
117+
add(bind("ctrl+t", "cycle to next theme"))
118+
add(bind("type", "filter commands"))
119+
add(bind("enter", "run selected command"))
120+
add(bind("esc", "close palette"))
113121

114-
content := strings.Join(append(visible, indicator), "\n")
115-
return lipgloss.NewStyle().Width(width).Height(height).Render(content)
122+
return lines
116123
}
117124

118125
func (h Help) Title() string { return "Help" }

0 commit comments

Comments
 (0)