Skip to content

Fix keyboard help dialog row overlap#122

Merged
benvinegar merged 1 commit intomodem-dev:mainfrom
ferologics:fix/help-dialog-overlap
Mar 26, 2026
Merged

Fix keyboard help dialog row overlap#122
benvinegar merged 1 commit intomodem-dev:mainfrom
ferologics:fix/help-dialog-overlap

Conversation

@ferologics
Copy link
Copy Markdown
Contributor

@ferologics ferologics commented Mar 25, 2026

Summary

  • fix the keyboard help dialog height calculation so shortcut rows no longer overlap
  • keep the help content scrollable when the terminal is too short to fit the full dialog
  • add blank spacer rows between the Navigation, View, and Review sections for readability
  • add a focused regression test that asserts every visible help row renders cleanly and that the old mangled strings do not reappear

Testing

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

@ferologics
Copy link
Copy Markdown
Contributor Author

ferologics commented Mar 25, 2026

before after
Ghostty 2026-03-25 11 21 28 PM image

@ferologics ferologics force-pushed the fix/help-dialog-overlap branch from 3349fa9 to 7df7582 Compare March 25, 2026 22:23
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 25, 2026

Greptile Summary

This 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 (+ 3 for chrome overhead was far too small). The fix correctly computes contentRowCount from the actual section/item data, adds a named modalFrameChromeRowCount constant, and gracefully degrades to a <scrollbox> fallback when the terminal is too short to fit the full dialog. A targeted regression test is added that both proves the fix and guards against future reintroductions of the overlap.

Key changes:

  • contentRowCount is now derived from the sections data structure rather than inlined into the height formula
  • modalFrameChromeRowCount = 6 replaces the old hardcoded + 3, though the actual chrome visible in ModalFrame's JSX counts to 5 rows — worth verifying
  • A conditional <scrollbox focused={false}> wrapper is applied when shouldScroll is true, consistent with the PierreDiffView pattern elsewhere in the codebase
  • Each section title and each item row now have an explicit height: 1 on their wrapping boxes, which is what prevents the overlap at the layout level
  • The regression test uses overlap-detection strings ("linese/Awrapt/smetadata") to assert that adjacent rows have not been concatenated by the renderer

Confidence Score: 5/5

  • Safe to merge; the primary bug is fixed, the approach matches existing codebase patterns, and a regression test guards the fix.
  • The root cause (chrome rows undercounted as 3 instead of ~5-6) is clearly identified and fixed. The height: 1 annotations on each row box address the layout-level overlap. The scroll fallback uses the same focused={false} scrollbox pattern already established in PierreDiffView. The only open question — whether modalFrameChromeRowCount should be 5 rather than 6 — has at most a 1-row cosmetic impact on dialog sizing and does not cause content overlap or data loss.
  • No files require special attention; the potential off-by-one in modalFrameChromeRowCount in HelpDialog.tsx is worth a quick sanity-check but is non-blocking.

Important Files Changed

Filename Overview
src/ui/components/chrome/HelpDialog.tsx Height calculation is properly fixed: contentRowCount is now computed separately and a chrome overhead constant is added. The scroll fallback is correctly gated by shouldScroll. One minor concern: modalFrameChromeRowCount = 6 may overcount by 1 vs the 5 rows actually visible in ModalFrame's JSX.
test/ui-components.test.tsx Test is renamed and expanded with a larger terminal height to prevent regression; overlap-detection assertions (not.toContain) are a clean approach. The shouldScroll=true code path has no test coverage.

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
Loading

Reviews (1): Last reviewed commit: "fix keyboard help overlay overlap" | Re-trigger Greptile

@ferologics ferologics force-pushed the fix/help-dialog-overlap branch from 7df7582 to f7ffd7e Compare March 25, 2026 22:25
Comment on lines +60 to +62
// ModalFrame contributes the border rows, title row, padding, and one blank spacer row.
const modalFrameChromeRowCount = 6;
const requiredModalHeight = contentRowCount + modalFrameChromeRowCount;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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:

Suggested change
// 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;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +839 to +863
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",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@ferologics ferologics force-pushed the fix/help-dialog-overlap branch from f7ffd7e to 76a96fe Compare March 25, 2026 22:30
@benvinegar benvinegar merged commit c8c7d8e into modem-dev:main Mar 26, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants