Repository navigation
How the Menus Work
Nibtree builds its own native menu bar: Nibtree, File, Edit, View, Code, Git, Window and Help, with the right layout per platform and a reduced one for the mergetool window. The user side is in Menus. How keys reach the page or the menu is in Menu Keys and Routing, and the Git menu's items in How the Git menu works.
Mac users look for commands, and their keys, in the menu bar. Tauri's default menu had only the basics, and its Cmd+W closed the whole window. Users also expect File, Edit, Code and Git menus with the keys they know.
The menu is built from the frontend with @tauri-apps/api/menu, because the items, their actions and their state all live in the Svelte stores.
flowchart LR
Spec["menuSpec(platform, mode)<br/>pure data"] --> Install["appMenu.install(mode)"]
State["menuState(inputs)<br/>pure"] --> Install
Install --> Stateful["MenuItem / CheckMenuItem<br/>for items with a state"]
Install --> Subs["Submenu.new per menu<br/>tracked when items come and go"]
Stateful --> Subs
Subs --> Bar["Menu.new, setAsAppMenu"]
Bar --> Mac["macOS: Window and Help roles"]
menuSpec.ts describes every menu as plain data: action items (an id from menuIds.ts, a label, an accelerator), native items (Cut, Copy, Quit...), separators, submenus and the Open Recent slot. menuSpec(platform, mode) returns the layout for macOS, Windows or Linux, for the main window ("app") or git's mergetool window ("mergeTool").
AppMenu.install in appMenu.svelte.ts runs once per window from App.svelte, after the launch mode is known. It creates an object for every item that has a state, then each top menu. On macOS the Window and Help menus get their system roles, so macOS lists the open windows and adds its search field. A failure shows the toast "Could not build the menu bar". Every item id is action#BUILD_ID, with a random suffix per page load (see Bugs we fixed).
sequenceDiagram
participant Stores as Svelte stores
participant Effect as appMenu follow()
participant Pure as menuState.ts
participant Native as Tauri menu
Stores->>Effect: a reactive read changes
Effect->>Pure: menuState(currentMenuInputs(mode))
Effect->>Effect: wait 60 ms for more changes
Effect->>Pure: changedState(shown, next)
Pure-->>Effect: only what differs
Effect->>Native: setEnabled, setChecked, setText
Effect->>Native: insert or remove items that come and go
currentMenuInputs reads the stores and menuState turns them into an ItemState per action: enabled, checked, text and visible. After 60 ms without changes (SYNC_DELAY_MS), changedState keeps only what differs from the menu, so a burst of store changes costs a few IPC calls.
Items with visible (the Git menu's operation items, Git Console) and entries with visibleWith live in tracked menus, which keep a handle per entry. applyVisibility removes an entry and inserts it again in place later, one change at a time on a queue.
The system ticks a check item on click, so onAction syncs it again at once to show the app's real state.
Open Recent has its own effect: when the recentEntries text changes, it closes the old entries and inserts new MenuItem objects above Clear Recent.
runMenuAction in menuActions.ts sends the Find and Code items to the focused editor (runEditorCommand). Everything else has one handler in HANDLERS, wrapped in a guard:
-
app(...): does nothing while an in-app dialog or a Git dialog is open. -
workspace(...): also needs an open workspace, and does nothing behind Search Everywhere and the merge tool, like the window shortcuts (shortcutsBlocked). -
git(...): also needs an active repository that is not busy. -
shortcut(name): runs the samerunWorkspaceShortcutas the key handler.
Undo, Redo, Delete and Select All have no guard, so they work in dialog fields; the Help links (Nibtree Help, Release Notes, Report a Bug, Request a Feature, Star on GitHub) have none either. A handler that throws shows "The menu command failed". The MCP tools list_menu_commands and run_menu_command reuse the same spec, state and handlers (src/lib/mcp/menuCommands.ts), see How MCP and the CLI work.
| File | What it does |
|---|---|
src/lib/menu/menuIds.ts |
MENU_ACTIONS, EDITOR_ACTIONS, platform and mode types |
src/lib/menu/menuSpec.ts |
The menus as data, per platform and window |
src/lib/menu/menuState.ts |
menuState, gitMenuState, changedState, countLabel
|
src/lib/menu/appMenu.svelte.ts |
Builds the Tauri menu, follows the state, Open Recent |
src/lib/menu/menuActions.ts |
runMenuAction and the guarded handlers |
src/lib/menu/editorFocus.svelte.ts |
Whether an editor has the keyboard |
src/lib/views/recentEntries.ts |
The recent list for Open Recent and the header |
The menu is data. menuSpec is pure, so tests check ids, keys and the layout per platform without Tauri.
Send only changes, after a pause. Store changes come in bursts (focus moves, status refreshes, edits). The pause and the diff keep IPC traffic small.
Hide items that do not apply. Continue Merge or Skip Commit appear only during an operation; grey items there would be noise.
Same functions as the buttons. Handlers call what the views already use (gitActions.ts, fileCommands), so a menu item and its button never drift apart.
Settings... and About did nothing when clicked.
- The issue: Nibtree > Settings..., About Nibtree and the Open Recent entries did nothing on click, while View > Scripts worked.
-
Why it happened: Tauri routes a click to the handler registered for the item's id, and freeing any menu item removes the handler of its id. An item passed as options inside
Submenu.newgets a handle that Tauri frees right away, so its handler was gone. Only items created withMenuItem.newand kept by the page (the ones with a state, like View > Scripts) still fired. -
The fix and why we chose it: every clickable item is now created with
MenuItem.newand kept:itemOptionsstores them inkept, andfillRecentkeeps the Open Recent entries inrecentItemsuntil the list changes. Ids also get a per-page-load suffix (BUILD_ID), because after a reload the old menu is still being freed, and with the same ids that would remove the new items' handlers. Holding the handles is cheap and is how Tauri expects menus to be kept. Found with temporaryconsole.warnlogs.
-
src/lib/menu/menuSpec.test.ts: the macOS layout, File and Help items elsewhere, the reduced mergetool menu, every action once and each key on one item. -
src/lib/menu/menuState.test.ts: Save, Revert and Close Tab, Code and Find items, Markdown modes, ticks, Git Console hiding, the Git menu andchangedState. -
src/lib/views/recentEntries.test.tsandsrc/lib/mcp/menuCommands.test.ts.
Clicks on native items need a manual check in the real app. See Testing.
- A new menu item needs an id in
menuIds.ts, an entry inmenuSpec.ts, a handler inmenuActions.tsand, when it can be grey, a rule inmenuState.tswith a test. - Update Menus, Keyboard Shortcuts and the
menus-shortcuts-window.pngscreenshot. See Docs and Screenshots.
These pages are generated from docs/wiki. To correct something, open a pull request against the source file; edits made here are overwritten.
Using Nibtree
- Getting Started
- Welcome Screen
- Workspaces
- Files Panel
- File Operations
- File Icons
- Rounded Panels
- Editor and Tabs
- Pin Reorder and Wrap Tabs
- Single Tab Title
- Split Editor
- New File and Unsaved Changes
- Unload Hidden Tabs
- Editing Code
- Code Appearance
- Markdown Editor
- Image and PDF Preview
- Search Everywhere
- Recent Files
- Navigation Bar
- Find and Replace
- Changes and Commits
- Sign In Prompts
- Commit Box Layout
- Repository Actions
- Commit Options
- Ignoring Files
- Diffs
- Inline Diffs
- Staging View
- Revert a Change
- Resolving Conflicts
- Merge Tool
- Blame
- History and Log
- Merge Commits
- Branches and Tags
- Branches Popup
- Remotes
- Stashes
- Shelf
- Git Menu
- Git Dialogs
- Interactive Rebase
- Worktrees
- AI Agents
- AI Tasks
- AI Usage
- Keep Computer Awake
- Submodules
- Git LFS
- GitHub
- Git Console
- Terminal
- Terminal Features
- Scripts
- Navigation
- Menus
- Settings
- Settings Search
- Color Themes
- Settings Terminal and Automation
- Settings Files
- Updates
- Status Bar and Help
- Notifications
- Memory Use
- Clear Cache
- MCP and CLI
- Git Mergetool
- Keyboard Shortcuts
- Keyboard Shortcuts Editor
- Troubleshooting
- Troubleshooting Git
Developers
- Developer Guide
- Architecture
- Project Layout
- Backend
- How Folder Watching Works
- Folder Watching Bugs We Fixed
- How Workspace Files Work
- How the Preview Scheme Works
- Backend Services
- How Terminal Features Work
- Terminal Bugs We Fixed
- Frontend
- How the Rich Markdown Editor Works
- Rich Markdown Bugs We Fixed
- Git Menu Links
- Menu Keys and Routing
- Frontend Modules
- Blame Bugs We Fixed
- Project Color And Title Bar
- One Diff View
- Diff Bugs We Fixed
- Merge Tool Layouts
- Merge Tool Bugs We Fixed
- Color Theme Bugs We Fixed
- How the Connected Diff Works
- Rounded Panels Bugs We Fixed
- Update Bugs We Fixed
- Status Bar Bugs We Fixed
- MCP and CLI Bugs We Fixed
- Repository Actions Bugs We Fixed
- How the Changes Tab Works
- CodeMirror Patterns
- How the Cursor Works
- How Editor Features Work
- How the Path Bar Works
- Commands and Events
- Settings Reference
- Commands Workspace and Changes
- Commands Branches and Remotes
- Commands History and Extras
- Commands App and Tools
- Testing
- Test Suites
- Debugging
- Debugging with MCP
- Measuring Setting Memory
- How Terminal Memory Is Measured
- How Markdown Diagrams Work
- Releases and CI
- Publishing the Wiki
- Versioning and Changelog
- Browser Version Packages
- Browser Release Checks
- Docker Image
- Platforms and Signing
- Windows Support
- Windows Bugs We Fixed
- Windows MCP and CLI
- Docs and Screenshots
- UI Library
- UI Library MCP
- UI Library Bugs
- UI Library Migration
- Contributing
How features work
- Welcome screen
- Workspaces and folders
- Files panel
- File operations
- File icons
- Editor and tabs
- Pin, reorder and wrap tabs
- Single tab title
- Split editor
- New File and unsaved changes
- Unload hidden tabs
- Editing code
- Code appearance
- Markdown editor
- Image and PDF preview
- Search Everywhere
- Recent Files
- Navigation Bar
- Find and replace
- Changes and commits
- Sign-in prompts
- Commit box layout
- Repository actions
- Commit options
- Ignoring files
- Diffs
- Inline diffs
- Staging view
- Revert a change
- Resolving conflicts
- Merge tool
- Blame
- History and log
- Merge commits
- Commit tabs
- Branches and tags
- Branches popup
- Remotes
- Stashes
- Shelf
- Git menu
- Git dialogs
- Interactive rebase
- Worktrees
- AI agents
- AI usage
- Keep computer awake
- AI tasks
- Submodules
- Git LFS
- GitHub account
- Git Console
- Back and Forward
- Integrated terminal
- Scripts tool window
- Menu bar
- Settings
- Search in Settings
- Color themes
- Rounded panels
- Updates and release channels
- Status bar and help links
- Notifications
- Memory readout and memory log
- Clear Cache
- MCP server and command line tool
- git mergetool mode