Menu bar reference
tmnl ships a real native macOS menu bar via muda.
Predefined items (About, Hide, Show All, Quit, Cut, Copy, Select All, Minimize, …)
delegate to AppKit so the OS handles them with its own actions. Custom items
carry a MenuId that the event loop routes through drain_menu_events to an
App method — almost every one of which fires the matching registry command via
command::dispatch_by_id, so menu and chord-bound paths share the same code.
This page is the discoverable surface. The chord registry is authoritative for keystroke-to-action mapping (some chords intentionally have no menu accelerator — see the notes below); the menu shape mirrors the surface a keyboard-curious user would expect from a Mac app in 2026.
The menu source is src/menu.rs. Build edits there and the bar updates on
the next launch.
The application menu — standard macOS shape, plus per-window config verbs.
| Item | Chord | Notes |
| --- | --- | --- |
| About tmnl | — | Predefined (AppKit). |
| Settings… | ⌘, | Opens the in-grid Settings modal. |
| Reset Window Settings | — | Clears the focused window’s cfg_override so it falls back to the global cfg. No-op when no override exists. |
| Apply Window Settings to All Windows | — | Copies the focused window’s cfg_override into the global cfg and clears every other window’s override. Persists. |
| Check for Updates… | — | Opens the GitHub releases page (URL sourced from update_check::REPO so it stays in lockstep with the background poller). |
| Services | — | Predefined. |
| Hide tmnl / Hide Others / Show All | ⌘H / ⌥⌘H / — | Predefined. |
| Quit tmnl | ⌘Q | Predefined. |
The two per-window verbs are the menu-level surface for the multi-window config-override layer. See Multi-window for what an override actually is and which knobs honour it.
Everything that spawns or rearranges shell sessions. Most of the heavy verbs live in Shell > Splits.
| Item | Chord | Notes |
| --- | --- | --- |
| New Window | ⌘N | In-process; inherits last_window_* defaults. |
| New Tab | ⌘T | Same-flavour as launching tab (shell-mode → shell; --mnml → mnml). |
| Close Tab | ⌘W | Forwarded as ⌃W inside Native tabs. |
| Splits ▸ | — | Submenu — see below. |
| Quick Terminal | ⌃ | Toggle the system-wide quick-terminal overlay. | | Recent Workspaces ▸ | — | Dynamic submenu — one entry per~/.config/tmnl/recents.tomlrow; clicks fireApp::open_recent_entry. Empty rail shows (no recents). | | Integrations ▸ | — | Dynamic submenu — one entry per cfg.launcher_iconsrow (= one per rail chip). Labels prefer the icon'stooltip. Trailer: Manage Integrations…` opens the discovery overlay (same path as View > Add Integration…). See Integrations. |
| Save Scrollback… | — | Writes the focused pane’s full scrollback to a file picker. |
| Save Selection… | — | Writes the current selection to a file picker. |
Shell > Splits ▸
Section titled “Shell > Splits ▸”Heavy-user submenu — every pane verb the splits audit added, grouped in one discoverable tree. No accelerators on the resize / equalize rows so they don’t compete with mnml’s own pane-resize chords inside Native panes.
| Item | Chord |
| --- | --- |
| Split Right | ⌘D |
| Split Down | ⌘⇧D |
| ─ | |
| Split Right with Browser | ⌘⌥B |
| Split Right with Clipboard URL | ⌘⌥V |
| Split with Integration ▸ | — |
| ─ | |
| Resize Pane Wider | — |
| Resize Pane Narrower | — |
| Resize Pane Taller | — |
| Resize Pane Shorter | — |
| Equalize Splits | — |
| Maximize Pane | ⌘⇧↩ |
| ─ | |
| Close Pane | ⌘⇧W |
Split with Integration ▸ is dynamic — same lifecycle as the top-level
Integrations submenu, but the IDs are split_integration_<idx> and clicks
fire App::split_active_pane_with_command instead of new_launcher_tab.
Same launcher row → same command + args, just splits the active pane
instead of spawning a fresh tab.
Maximize Pane toggles Tab.zoomed_pane. The focused pane fills the
tab; the split tree underneath is preserved. Hit ⌘⇧↩ again to restore.
Single-pane tabs no-op. See Tabs, splits, and panes.
Resize / Equalize drives Layout::resize_focused (Δ = ±5%, clamped to
[0.05, 0.95] so neither child collapses) and Layout::equalize_all
(every ratio → 0.5).
Standard Mac editing chords plus Find and the AI submenu.
| Item | Chord | Notes |
| --- | --- | --- |
| Undo / Redo | ⌘Z / ⌘⇧Z | Predefined. |
| Cut / Copy / Select All | ⌘X / ⌘C / ⌘A | Predefined (AppKit intercepts in any focused text field). |
| Paste | ⌘V | Custom — the predefined paste: selector lands at the winit window which can’t handle it. Routes through App::paste_into_focused so Shell tabs paste correctly. |
| Find… | ⌘F | Opens the in-pane find bar. |
| Find Next / Find Previous | — | Works while a find session is active. ⌘G is intentionally not registered — the fwd.cmd_g forwarder sends ⌘G to Native panes as ⌃G for mnml’s goto-line. |
| Clear Scrollback | ⌘⇧K | Terminal.app uses ⌘K, but ⌘K is bound to ai.generate. |
| Previous Prompt | ⌘↑ | Jumps scrollback to the previous OSC 133 B mark. |
| Next Prompt | ⌘↓ | Jumps to the next mark. |
| Quick Look | ⌃⌘D | macOS Quick Look on the focused word / URL. |
| AI ▸ Complete Command Line | ⌘I | Local FIM continuation. See AI completion. |
| AI ▸ Generate Command from Description | ⌘K | Local FIM natural-language → command. |
Both AI items gate on no_modal_open so they don’t fire while a settings /
help / palette overlay is up.
Everything that changes what you see without spawning a new shell.
| Item | Chord | Notes |
| --- | --- | --- |
| Command Palette… | ⌘⇧P | Greedy modal — see Tabs, splits, and panes. |
| Recents… | ⌘R | Recents picker overlay. |
| Search Tabs… | — | Tab-name filter chip. ⌘⇧T is reserved for tab.reopen_closed (browser convention). |
| Add Integration… | ⌘⇧I | Opens the discovery overlay. See Integrations. |
| Toggle Sidebar | — | Flips cfg.tab_layout between Horizontal and Vertical. |
| Reset Sidebar Width | — | Clears win.sidebar_w_override so the auto-fit width takes over again. Recovery path for “I dragged the sidebar too narrow and have no way back”. |
| Toggle Tab Bar | — | Live-relabels to Hide Tab Bar / Show Tab Bar. Inverts cfg.force_hide_tab_bar. Persists. |
| Open Browser Split | ⌘⌥B | Same as Splits > Split Right with Browser. |
| Open Clipboard URL in Browser | ⌘⌥V | Same as Splits > Split Right with Clipboard URL. |
| Playwright Dashboard ▸ | — | Submenu — see below. |
| Increase / Decrease / Reset Font Size | ⌘= / ⌘- / ⌘0 | |
| Toggle Full Screen | ⌃⌘F | |
| Next Theme | ⌘⌥] | theme.cycle_next. |
| Previous Theme | ⌘⌥[ | theme.cycle_prev. |
View > Playwright Dashboard ▸
Section titled “View > Playwright Dashboard ▸”The Playwright trace-viewer cluster — used heavily during test runs.
| Item | Chord |
| --- | --- |
| Attach Dashboard | ⌘⌥D |
| Attach + Auto-pick Session | ⌘⌥⇧D |
| Toggle Dashboard Sidebar | ⌘⌥H |
See Browser pane > The Playwright dashboard workflow for what each does.
Window
Section titled “Window”macOS-conventional home for window and tab-focus navigation. Tab cycle, pane-focus moves, window cycle. The bottom section is a dynamic window list.
| Item | Chord | Notes |
| --- | --- | --- |
| Minimize | ⌘M | Predefined. |
| Next Tab | ⌘⇧] | tab.cycle_forward. |
| Previous Tab | ⌘⇧[ | tab.cycle_back. |
| Reopen Closed Tab | ⌘⇧T | tab.reopen_closed. |
| Rename Tab | F2 | tab.rename. Opens the inline rename modal on the focused tab — same path as right-clicking the chip. |
| Move Tab Left / Right | — | Reorders the focused tab chip. No chord — the menu is the discoverable surface. |
| Move Tab to New Window | — | Pops the focused tab into a fresh window. No-op when the focused window has only one tab (the move would leave it empty). |
| Jump to Tab ▸ Tab 1 … Tab 9 | — | No accelerators. ⌘1..⌘9 stays chord-only so the Native-mode ⌘N → ⌥N forward to mnml’s tab.goto_N isn’t preempted. |
| Next Window | ⌘ |focus_background_window— standard macOS chord. | | Previous Window |⌘⇧ | Reverse cycle. |
| Focus Pane Left / Right / Up / Down | ⌘⌥← / ⌘⌥→ / ⌘⌥↑ / ⌘⌥↓ | focus_dir. |
| ─ | | |
| ✓ <focused-window-label> | — | Dynamic — focused window, bullet-marked. Clicking dismisses (standard macOS pattern). |
| <background-window-label> | — | Dynamic — one per background window. Click swaps focus. IDs are window_focus_<idx>. |
The window-list section is rebuilt each tick from
(focused_label, background_labels); a fingerprint guard skips the rebuild
when nothing changed. Closing or spawning windows live-updates the list.
Chord-only verbs (no menu entry)
Section titled “Chord-only verbs (no menu entry)”A few commands are bound to chords but not exposed via the menu — usually because they’re keyboard-purist conveniences whose audience already knows the chord, or because they’re rare enough that a menu slot would be clutter.
| Chord | Command | Notes |
| --- | --- | --- |
| ⌘⇧1 … ⌘⇧9 | launcher.open_<N> | Open the Nth configured [[ui.launcher_icon]] entry as a new tab. Out-of-range indices silently no-op. |
| ⌘Home | scroll.top | Jump to the top of the focused Shell pane’s scrollback. |
| ⌘End | scroll.bottom | Snap back to the live cursor row (cancels scrollback). |
| ⇧PageUp / ⇧PageDown | scroll.page_up / scroll.page_down | Page through scrollback row-by-row. |
| ⌘[ / ⌘] (Browser) | browser.back / browser.forward | History navigation. Gated on Browser-pane focus. |
| ⌘L (Browser) | browser.focus_url_bar | Seeds the chrome URL bar with the current URL, cursor at end — same as clicking the URL chip. |
| ⌘R (Browser) | browser.reload (via view.recents) | In Browser focus, ⌘R reloads the page; elsewhere it opens recents. |
Modal-superseding chords
Section titled “Modal-superseding chords”⌘⇧P (palette) and ⌘R (recents) supersede transient input overlays —
if the welcome / tab_search / find / discovery / palette_overlay /
context_menu is up, pressing the chord auto-dismisses that overlay and
opens the requested one. The destructive modals (settings, help,
rename, confirm-close, confirm-paste) still block — they hold unsaved
edits or guard real actions.
Re-pressing either chord while its own overlay is open closes it —
true toggle semantics, mirrors tab_search and help / discovery.
| Item | Chord | Notes |
| --- | --- | --- |
| tmnl Help | — | Opens the in-app help overlay. |
| Show Shortcuts | ⌘⇧/ | Lists every registered chord, grouped by section. Was chord-only before; now discoverable. |
How items get wired
Section titled “How items get wired”A custom menu item is three things in the source:
- An ID. Either a stored
MenuIdfield onAppMenu(when other code needs to compare against it) or a plain string ID parsed back from the mudaMenuEvent. - A
MenuItemwith the ID and accelerator. Built inAppMenu::new, appended into its submenu. - A dispatcher arm in
App::drain_menu_events. Either calls the App method directly or firescommand::dispatch_by_id("some.registry.id", &mut self).
The dispatcher pattern keeps menu and chord paths in lockstep — adding a new menu item for an existing chord-bound command is two lines (menu item
- dispatcher arm), no new App method needed.
Dynamic submenus
Section titled “Dynamic submenus”Three submenus are rebuilt per tick from live state:
| Submenu | Source | Item ID format |
| --- | --- | --- |
| Window > (bottom section) | App’s open-window list | window_focus_<idx>, window_focus_main |
| Shell > Recent Workspaces | recents::load() | recent_workspace_<idx> |
| Shell > Integrations | cfg.launcher_icons | integration_<idx>, integration_manage |
| Shell > Splits > Split with Integration | cfg.launcher_icons | split_integration_<idx> |
Each one carries a String fingerprint on AppMenu and a RefCell<Vec<MenuItem>>
of the currently-appended dynamic items. The per-tick sync function rebuilds
the fingerprint, no-ops when it matches the previous one, otherwise drops
the old items and appends the new ones. Per-tick rebuild cost is cheap.
What’s deliberately chord-less
Section titled “What’s deliberately chord-less”Some menu items intentionally ship without an accelerator so a registered
chord stays free for forwarding semantics. The pattern matters for the
Native-mode story — ⌘P, ⌘1..⌘9, ⌘G, ⌘W all get translated to
⌃-equivalents when the focused pane is Native (so mnml’s view.palette,
tab.goto_N, goto.line, etc. fire). Registering those chords on a menu
item would preempt the winit event before forwarding could run.
The chord-less menu rows for these — Find Next, Move Tab Left, the
Jump to Tab N items — are the discoverable surface; their chords stay
reserved.
Where to go next
Section titled “Where to go next”- Tabs, splits, and panes — the underlying model every Shell menu item operates on.
- Multi-window — what the tmnl > Reset / Apply Window Settings items actually do, plus the Window > Next / Previous Window chords in context.
- Integrations — the launcher rail, the discovery overlay, and how the dynamic Shell > Integrations submenu mirrors them.
- Browser pane — what View > Open Browser Split, Open Clipboard URL, and the Playwright Dashboard submenu open.
- AI completion — what Edit > AI > Complete / Generate dispatch into.
- The source:
src/menu.rs.