Skip to content

feat(ui): add menu bar visibility toggle#476

Open
eduwass wants to merge 4 commits into
modem-dev:mainfrom
eduwass:feat/menu-bar-toggle
Open

feat(ui): add menu bar visibility toggle#476
eduwass wants to merge 4 commits into
modem-dev:mainfrom
eduwass:feat/menu-bar-toggle

Conversation

@eduwass

@eduwass eduwass commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

This adds a dedicated way to hide Hunk's top menu bar for keyboard-driven review sessions. When the menu bar is hidden, the diff/sidebar layout moves up and reclaims that first terminal row for review content.

The default stays unchanged: Hunk still shows the menu bar unless a user opts out. That keeps the current UI discoverable for new users while giving experienced users a lower-chrome mode on short terminals.

Refs #459.

Demo

2026.06.22.23.08.16.mp4

How to use it

  • Set menu_bar = false in ~/.config/hunk/config.toml or .hunk/config.toml to start with the menu bar hidden
  • Press Shift-M during a session to toggle the menu bar on or off
  • Use View > Menu bar when the menu is visible

Behavior

Hiding the menu bar also hides the separator/chrome row beneath it, so the review content moves all the way to the top.

Hiding the menu bar does not disable menus. F10 still opens the keyboard menu, and when the bar is hidden the dropdown appears at the top row instead of leaving an empty chrome row behind.

Lowercase m continues to toggle hunk metadata. Shift-M is only for the menu bar.

Testing

  • bun run format
  • bun run format:check
  • bun run typecheck
  • bun test src/core/config.test.ts src/ui/lib/ui-lib.test.ts src/ui/AppHost.interactions.test.tsx src/ui/components/ui-components.test.tsx
  • bun run test:tty-smoke
  • Real source TTY launch with bun run src/main.tsx -- diff <before> <after>

@eduwass eduwass marked this pull request as ready for review June 22, 2026 21:12
@greptile-apps

greptile-apps Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a menu bar visibility toggle for keyboard-driven review sessions. Users can now hide the top chrome via menu_bar = false in config, Shift-M, or View > Menu bar, with the diff and sidebar layout reclaiming that first terminal row.

  • The config pipeline (CommonOptions, PersistedViewPreferences, AppBootstrap) is extended consistently with proper ?? fallbacks and a default of true so existing sessions are unaffected.
  • DiffPane and SidebarPane receive a new showTopChrome prop that removes their top border and padding when the bar is hidden; MenuDropdown gains a top prop so F10-triggered menus anchor to row 0 instead of row 1 when the bar is absent.
  • Two new integration tests verify the Shift-M toggle and the bootstrap-level opt-out, both confirming F10 menus remain functional while the bar is hidden.

Confidence Score: 5/5

The change is additive and defaults to the existing behavior (menu bar on), so no existing session is disrupted.

The full config pipeline is wired correctly with safe fallbacks, keyboard dispatch guards against double-firing, and two new integration tests cover the core toggle and bootstrap flows end-to-end. The only code worth a second look is the cross-parameter default in DiffPane, which is valid but fragile — it is a style issue, not a runtime concern.

DiffPane.tsx — the showTopChrome default references another destructured param and would silently misbehave if parameter order changed.

Important Files Changed

Filename Overview
src/ui/App.tsx Adds showMenuBar state, toggleMenuBar handler, conditional MenuBar rendering, and plumbs showTopChrome/top to DiffPane, SidebarPane, and MenuDropdown; logic is correct and dependencies are maintained consistently.
src/ui/components/panes/DiffPane.tsx Introduces showTopChrome prop that decouples top border/padding from pagerMode; uses a cross-parameter default showTopChrome = !pagerMode that is technically valid but subtle.
src/ui/components/panes/SidebarPane.tsx Adds showTopChrome prop to mirror DiffPane's treatment; correctly defaults to true, splits paddingY into top/bottom.
src/ui/hooks/useAppKeyboardShortcuts.ts isUppercaseMKey helper follows exact pattern of isUppercaseGKey and is checked before the lowercase m branch, so Shift-M correctly toggles the menu bar without triggering hunk metadata toggle.
src/ui/components/chrome/MenuDropdown.tsx Adds optional top prop (default 1) so the dropdown anchors at row 0 when the menu bar is hidden; backward-compatible.
src/core/config.ts Adds menu_bar TOML key through the full config pipeline (read, merge, resolve) with ?? fallbacks to the default of true; no migration concern.
src/core/types.ts Adds menuBar?: boolean to CommonOptions, showMenuBar: boolean (required) to PersistedViewPreferences, and optional initialShowMenuBar to AppBootstrap.
src/ui/lib/appMenus.ts Adds a Menu bar checkable item with hint M to the View menu, placed between Sidebar and the Themes separator.
src/ui/AppHost.interactions.test.tsx Two new integration tests cover: Shift-M hides the bar and F10 still opens menus; bootstrap with initialShowMenuBar: false starts hidden with F10 remaining functional.
src/core/loaders.ts Threads initialShowMenuBar through loadAppBootstrap with a ?? true fallback, consistent with the other initial-* fields.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User action] --> B{Source}
    B -->|Shift-M key| C[isUppercaseMKey]
    B -->|View > Menu bar| D[toggleMenuBar in appMenus]
    B -->|config file menu_bar=false| E[initialShowMenuBar in bootstrap]
    C --> F[toggleMenuBar]
    D --> F
    E --> G[showMenuBar state initialized false]
    F --> H[setShowMenuBar toggle]
    H --> I{showMenuBar}
    G --> I
    I -->|true| J[MenuBar rendered at row 0]
    I -->|false| K[MenuBar hidden]
    J --> L[diffPaneScreenTop = 1]
    K --> M[diffPaneScreenTop = 0]
    J --> N[DiffPane showTopChrome=true]
    K --> O[DiffPane showTopChrome=false]
    J --> P[SidebarPane showTopChrome=true]
    K --> Q[SidebarPane showTopChrome=false]
    J --> R[MenuDropdown top=1]
    K --> S[MenuDropdown top=0]
    N --> T[border top + paddingTop=1]
    O --> U[no border + paddingTop=0]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[User action] --> B{Source}
    B -->|Shift-M key| C[isUppercaseMKey]
    B -->|View > Menu bar| D[toggleMenuBar in appMenus]
    B -->|config file menu_bar=false| E[initialShowMenuBar in bootstrap]
    C --> F[toggleMenuBar]
    D --> F
    E --> G[showMenuBar state initialized false]
    F --> H[setShowMenuBar toggle]
    H --> I{showMenuBar}
    G --> I
    I -->|true| J[MenuBar rendered at row 0]
    I -->|false| K[MenuBar hidden]
    J --> L[diffPaneScreenTop = 1]
    K --> M[diffPaneScreenTop = 0]
    J --> N[DiffPane showTopChrome=true]
    K --> O[DiffPane showTopChrome=false]
    J --> P[SidebarPane showTopChrome=true]
    K --> Q[SidebarPane showTopChrome=false]
    J --> R[MenuDropdown top=1]
    K --> S[MenuDropdown top=0]
    N --> T[border top + paddingTop=1]
    O --> U[no border + paddingTop=0]
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
src/ui/components/panes/DiffPane.tsx:194
The default `showTopChrome = !pagerMode` relies on the sequential evaluation of destructuring parameters — `pagerMode` must stay above `showTopChrome` in this list for the expression to work correctly. If the parameters are ever reordered, `pagerMode` will be `undefined` at the point of evaluation, silently flipping the default to `true`. An explicit `false` default keeps the intent self-contained and doesn't depend on declaration order.

```suggestion
  showTopChrome = false,
```

Reviews (1): Last reviewed commit: "fix(ui): hide menu chrome separator with..." | Re-trigger Greptile

Comment thread src/ui/components/panes/DiffPane.tsx Outdated
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.

1 participant