Fix keyboard help dialog row overlap#122
Conversation
3349fa9 to
7df7582
Compare
Greptile SummaryThis PR fixes a long-standing layout bug in the keyboard help dialog where shortcut rows would overlap each other due to an incorrect height calculation ( Key changes:
Confidence Score: 5/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["HelpDialog renders"] --> B["Compute contentRowCount\n(sections × title + items)"]
B --> C["requiredModalHeight =\ncontentRowCount + modalFrameChromeRowCount(6)"]
C --> D["modalHeight = min(requiredModalHeight,\nmax(8, terminalHeight − 2))"]
D --> E{"shouldScroll?\nmodalHeight < requiredModalHeight"}
E -- "No (terminal large enough)" --> F["Render content directly\ninside ModalFrame"]
E -- "Yes (terminal too short)" --> G["Wrap content in\nscrollbox focused=false"]
F --> H["ModalFrame with height=modalHeight"]
G --> H
Reviews (1): Last reviewed commit: "fix keyboard help overlay overlap" | Re-trigger Greptile |
7df7582 to
f7ffd7e
Compare
| // ModalFrame contributes the border rows, title row, padding, and one blank spacer row. | ||
| const modalFrameChromeRowCount = 6; | ||
| const requiredModalHeight = contentRowCount + modalFrameChromeRowCount; |
There was a problem hiding this comment.
modalFrameChromeRowCount may be off by one
The comment says the 6 accounts for "border rows, title row, padding, and one blank spacer row," but tracing through ModalFrame's JSX I can only identify 5 rows of overhead:
| ModalFrame structure | Rows |
|---|---|
Top border (border: true) |
1 |
paddingTop: 1 on the header box |
1 |
| Title text row | 1 |
paddingBottom: 1 on the content box |
1 |
| Bottom border | 1 |
| Total | 5 |
There is no explicit blank spacer element in ModalFrame. If the real chrome overhead is 5, requiredModalHeight is overestimated by 1 — the modal will be 1 row taller than necessary in normal conditions, and shouldScroll will trigger one terminal row earlier than it needs to. For example, with contentRowCount = 18 and terminalHeight = 25:
- Current (chrome=6):
requiredModalHeight=24,modalHeight=23,shouldScroll=true← unnecessary scrollbox - If chrome=5:
requiredModalHeight=23,modalHeight=23,shouldScroll=false← all content fits
It would be worth double-checking the opentui border and padding accounting, and if the actual overhead is indeed 5, updating the constant and comment:
| // ModalFrame contributes the border rows, title row, padding, and one blank spacer row. | |
| const modalFrameChromeRowCount = 6; | |
| const requiredModalHeight = contentRowCount + modalFrameChromeRowCount; | |
| // ModalFrame contributes: top border, paddingTop, title row, paddingBottom, bottom border = 5 rows. | |
| const modalFrameChromeRowCount = 5; |
There was a problem hiding this comment.
I checked this against the actual rendered frame instead of just the JSX structure. With one fewer chrome row, the dialog still overlaps content (for example Review/ m), so 6 is intentional here even though the visible structure looks like 5 rows at a glance.
| test("HelpDialog renders every keyboard shortcut row without overlap", async () => { | ||
| const theme = resolveTheme("midnight", null); | ||
| const frame = await captureFrame( | ||
| <HelpDialog | ||
| canRefresh={true} | ||
| terminalHeight={20} | ||
| terminalHeight={26} | ||
| terminalWidth={76} | ||
| theme={theme} | ||
| onClose={() => {}} | ||
| />, | ||
| 76, | ||
| 20, | ||
| 26, | ||
| ); | ||
|
|
||
| expect(frame).toContain("Keyboard help"); | ||
| expect(frame).toContain("[Esc]"); | ||
| expect(frame).toContain("Navigation"); | ||
| expect(frame).toContain("View"); | ||
| expect(frame).toContain("Review"); | ||
| expect(frame).toContain("F10"); | ||
| expect(frame).toContain("reload / quit"); | ||
| const expectedRows = [ | ||
| "Keyboard help", | ||
| "[Esc]", | ||
| "Navigation", | ||
| "↑ / ↓ move line-by-line", | ||
| "Space / f page down (alt: f)", | ||
| "b page up", | ||
| "Shift+Space page up (alt)", | ||
| "d / u half page down / up", | ||
| "[ / ] previous / next hunk", | ||
| "Home / End jump to top / bottom", |
There was a problem hiding this comment.
No test coverage for the scroll fallback path
The updated test validates that content renders without overlap when the terminal is large enough (terminalHeight=26), which maps to shouldScroll=false. The new shouldScroll=true code path (the <scrollbox> branch) is not exercised by any test — a terminal height of, say, 22 would trigger it.
Consider adding a companion test that renders at a smaller terminal height (e.g., terminalHeight=20) and asserts that the first and last sections are still present/readable, confirming that the scroll fallback at least mounts without crashing and doesn't silently clip all content.
There was a problem hiding this comment.
Fair point. I kept this PR focused on the overlap regression path and asserted the full visible help content that was previously getting corrupted. I’d rather cover the small-terminal scroll fallback in a separate follow-up test than broaden this fix further.
f7ffd7e to
76a96fe
Compare


Summary
Testing
bun test test/ui-components.test.tsx -t 'HelpDialog renders every keyboard shortcut row without overlap'bun run typecheck