feat(app-menu,shortcuts): add native macOS menu bar with Open Recent - #177
Conversation
macOS shows a menu bar whether or not the app populates it, so GitDesktop now ships a real one; Windows and Linux keep the in-window repo dropdown as the idiom and build no menu. - `src-tauri/src/app_menu.rs`: constructs the App/File/Edit/View/Window/Help menu (Settings… in the app submenu, New/Open/Clone Repository… plus an Open Recent submenu in File), routes clicks into `app-menu-action` / `app-menu-open-recent` events, and exposes `set_recent_repos_menu` to rebuild the recents rows in place. The Edit predefined items are kept — macOS routes undo/cut/copy/paste in text inputs through them. Items carry no accelerators, since the app's hotkeys are rebindable and a static menu shortcut would both lie about and intercept a rebound chord. A no-op command twin keeps the wire shape identical off macOS. - `src/features/app-menu/useMacAppMenu.ts`: bridges the menu to the existing action dispatch, validating native payloads against the action registry, and pushes the last ten recents into Open Recent, suffixing repos that share a display name with their parent folder. - `src/App.tsx`: takes ownership of add-local-repository, clone-repository, and new-repository along with the clone/create dialogs so the menu works from Settings, Help, and Explore; the duplicate registrations in `RepoSwitcher` and `WelcomeScreen` are removed, since they only shadowed by mount order. - Docs: README highlight, site capability entry, help-guide paragraph, and a changelog fragment.
|
PR context from the orchestrated build — deliberate calls and the verification record, on the record before review. Single-claim items; later rounds can cite by number.
Posted by GitDesktop — automated agent comment, verify before acting on it. |
There was a problem hiding this comment.
Pull request overview
Adds a native macOS application menu (including File → Open Recent) and wires it into GitDesktop’s existing hotkey/action dispatch so menu items work across screens, while keeping Windows/Linux intentionally menu-less.
Changes:
- Implement a macOS-only native menu bar in Rust, including a dynamically updated Open Recent submenu and frontend event emission.
- Add a frontend bridge hook (
useMacAppMenu) to listen for native menu events, validate action payloads, open recents by path, and push recent repo entries to the native menu. - Hoist repository action ownership in
App.tsxand remove duplicate hotkey registrations from welcome/switcher surfaces; update README/help/site capability + add changelog fragment.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/features/welcome/WelcomeScreen.tsx | Removes repo-action hotkey registrations from the welcome screen. |
| src/features/repository/RepoSwitcher.tsx | Removes duplicate repo-action hotkey registrations from the repo switcher. |
| src/features/help/content.ts | Documents the macOS menu bar + Open Recent behavior in the in-app guide. |
| src/features/app-menu/useMacAppMenu.ts | New hook bridging native macOS menu events to action dispatch + recents sync. |
| src/App.tsx | Centralizes repo action handling and mounts clone/create dialogs at app root. |
| src-tauri/src/lib.rs | Wires macOS-only menu setup + registers the recents menu IPC command. |
| src-tauri/src/app_menu.rs | New Rust implementation of the macOS menu bar + Open Recent submenu rebuild command. |
| site/src/data/capabilities.ts | Adds marketing-site capability entry for the macOS menu bar feature. |
| README.md | Documents the macOS menu bar feature under app features. |
| changelog.d/added-macos-file-menu.md | Adds a changelog fragment announcing the new macOS File menu + Open Recent. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
🤖 GitDesktop AI review · SummaryAdds a macOS-only application menu (File + Open Recent + Settings) in Recorded decisions I'm not re-raising: no accelerators on our items, no New Window, one-level duplicate-name disambiguation, the enabled-submenu-with-disabled-row for empty recents, the skipped site Correctness
Edge cases
Readability / maintainability
Docs
Tests
Posted by GitDesktop — AI output, verify before acting on it. |
|
🤖 GitDesktop AI security audit · No genuine security issues in these changes. Verification notes behind that conclusion (not findings): the Posted by GitDesktop — AI output, verify before acting on it. |
|
Round-1 dispositions for the AI review (all findings verified against primary sources before disposition; fixes land in the next push together with the Copilot round):
One addition found during the fix round, disclosed: Posted by GitDesktop — automated agent comment, verify before acting on it. |
Deploying gitdesktop with
|
| Latest commit: |
9e83219
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://0e6b5e1c.gitdesktop.pages.dev |
| Branch Preview URL: | https://feat-macos-file-menu.gitdesktop.pages.dev |
- `app_menu.rs`: pull the id-to-destination mapping into a `classify_menu_id` function with a `MenuTarget` enum, compiled and unit-tested on every host so the wire strings can't drift; a bare `gd-menu-recent:` prefix now falls through instead of trying to open `""`; Open Recent rows are built before the live submenu is drained, so a mid-rebuild failure can't leave it empty; adds Show All beside Hide Others and drops the always-`Some` `Option` in `AppMenuState`. - `App.tsx`: gate `open-settings` on the git-installed probe. The menu bar stays clickable on the git-missing screen, where Settings… would otherwise switch to a view that never renders until Retry. - `RepoSwitcher` / `WelcomeScreen`: dispatch `clone-repository` and `new-repository` rather than mounting their own `CloneRepoDialog` / `CreateRepoDialog`, so the menu bar, palette, hotkeys, and buttons all drive the single instance App owns. - `CLAUDE.md`: the macOS Edit-menu rule now points at `build_menu` and names the seven predefined items it has to keep.
|
🤖 GitDesktop AI review · SummarySecond round on the macOS menu bar: all seven items from the previous review were addressed — the dialogs now have a single host at the App root, Recorded decision acknowledged: emitting menu ids instead of action strings stays declined — Documentation
Readability
Resolved since last review
Verdict: no blocking issues — remaining items are non-blocking; merge when ready Posted by GitDesktop — AI output, verify before acting on it. |
|
Round-2 dispositions — both items accepted, in the working tree now, landing in the next push:
One pre-existing latent trap surfaced by this round's format-parity check, reported for the record rather than fixed here: biome's formatter strips the U+FE0F variation selector from the 🗒️ emoji at content.ts:879 (a line this PR never touched) — the check path tolerates it, but any future Posted by GitDesktop — automated agent comment, verify before acting on it. |
The welcome screen and the repo switcher footer called `usePickAndOpenRepo` directly, so those buttons bypassed the registered `add-local-repository` action. Both now dispatch that action, putting every entry point — button, keyboard shortcut and command palette — on the same path that stays usable from any screen, including Settings, the user guide and Explore. The help guide's "Open your first repository" section is updated to describe the actions as available anywhere rather than only from the welcome screen, and a changelog fragment records the behavior change.
|
🤖 GitDesktop AI review · Round 3 verifies both round-2 items landed, and the fix hunks themselves are clean: Edge cases
Readability
Copilot's Resolved since last review
Verdict: no blocking issues — remaining items are non-blocking; merge when ready Posted by GitDesktop — AI output, verify before acting on it. |
|
Round-3 dispositions — both items accepted, in the working tree, landing in the next push:
The round-1 fragment and round-2 help copy were re-read against the new gate: both claim availability by screen, and a modal is a transient state, not a screen — no copy change needed. Posted by GitDesktop — automated agent comment, verify before acting on it. |
The native macOS menu bar sits outside the webview's modal overlay, so clicking Open repository…, Clone…, New… or Settings… while the clone or create dialog is up would stack a second dialog on top of it. App now gates all four hotkey actions on `!dialogOpen` alongside the existing git-installed check. Also refreshes the doc comments in `useOpenRepoByPath.ts` to name the current routes: every open-by-path path (recents list, File → Open Recent, folder picker) lands in `useOpenRepoByPath`, and App is the sole caller of `usePickAndOpenRepo` via the `add-local-repository` action.
|
🤖 GitDesktop AI review · Round 4 verifies both round-3 items landed and the fix hunks are sound: Correctness
The wider modality gap — a native File item still stacking over Readability
Resolved since last review
Verdict: no blocking issues — remaining items are non-blocking; merge when ready Posted by GitDesktop — AI output, verify before acting on it. |
|
Round-4 dispositions — both accepted, in the tree, next push:
Posted by GitDesktop — automated agent comment, verify before acting on it. |
The clone and create-repository dialogs now render above the view switch, so navigating away no longer unmounts them — routes like the clone dialog's "Open Settings → Accounts" left the modal stacked over the newly mounted screen. Reset both open flags whenever `view` changes so the dialog closes with the navigation that triggered it. Also trims the `useOpenRepoByPath` doc comment to list its callers without claiming exhaustiveness.
|
🤖 GitDesktop AI review · Round 5 verifies both round-4 items landed and the two fix hunks are sound: the close-on-view-change effect can't fire on either dialog's success path (both call Readability
The wider modality gap stays a recorded, tracked follow-up (not re-raised) — one concrete instance for that ticket: Resolved since last review
Leftover polish (non-blocking)
Verdict: no blocking issues — remaining items are non-blocking; merge when ready Posted by GitDesktop — AI output, verify before acting on it. |
|
Round-5 dispositions — closing the round loop here per the recorded stopping rule (round 5 contains no correctness finding in the round-4 hunks, so remaining polish defers to homes rather than buying a sixth round):
With that: five consecutive "merge when ready" verdicts, all executed checks green on Posted by GitDesktop — automated agent comment, verify before acting on it. |
Gives GitDesktop a proper macOS application menu so the repo entry points are reachable from the menu bar the way Mac users expect, instead of only through the in-window welcome screen and repo switcher. File now carries New / Open / Clone Repository… plus an Open Recent submenu of the last ten repos, and Settings… sits in the GitDesktop menu. Windows and Linux ship no app menu on purpose — the in-window repo dropdown remains the idiom there.
Native menu (Rust)
src-tauri/src/app_menu.rs, which builds the full macOS menu explicitly (mirroringMenu::default's composition) with the app, File, Edit, View, Window, and Help submenus. The Edit submenu keeps every predefined item, since macOS routes undo/redo/cut/copy/paste/select-all in text inputs through them.handle_menu_event, which matches onlygd-menu-ids (muda has one global event channel shared with tray items), callscrate::tray::show_main_windowso a hidden window can't swallow a dialog, and emitsapp-menu-action/app-menu-open-recent.ID_RECENT_PREFIX) so a click needs no lookup table and can't read a stale row after a concurrent rebuild.set_recent_repos_menu, which drains and re-appends rows on the liveSubmenuhandle held inAppMenuState, recovers from a poisoned lock, and falls back to a disabled "No Recent Repositories" row when the list is empty. A no-op twin is compiled for non-macOS so the command is registered on every platform.src-tauri/src/lib.rs, callingapp_menu::setup_app_menuunder#[cfg(target_os = "macos")]during setup.Frontend bridge
src/features/app-menu/useMacAppMenu.ts: listens for the two menu events, validates payloads against aMENU_ACTIONSallow-list beforedispatchAction, opens recents viauseOpenRepoByPath, and pushes the recents list (capped atMAX_RECENT_ITEMS) into the native submenu whenever it changes. Entries that share a display name are all suffixed with their parent folder so no twin reads as canonical. The listener effect guards against the StrictMode double-mount race wherelistenresolves after cleanup.add-local-repository,clone-repository, andnew-repositoryintosrc/App.tsx, along with theCloneRepoDialog/CreateRepoDialoghosts, so the actions work from Settings, Help, and Explore — screens that mount neither the welcome list nor the repo switcher. The registrations are gated ongitInstalled.isSuccessbecause the dialogs render below the early returns.useHotkeyActionregistrations fromsrc/features/repository/RepoSwitcher.tsxandsrc/features/welcome/WelcomeScreen.tsx, which would otherwise shadow by mount order.Documentation
README.mdand in the "getting started" section of the in-app guide,src/features/help/content.ts.site/src/data/capabilities.tsand a changelog fragment atchangelog.d/added-macos-file-menu.md.Note for reviewers: New Window is intentionally absent — the app is single-window — and the explicit menu construction must be re-checked against
tauri::menu::Menu::defaulton a Tauri major upgrade. Menu behavior still needs verification on a Mac.Relates to #171