feat(menu): native File/View menus with Open Recent - #19
Merged
Merged
Conversation
Restructure the native menu to match standard macOS apps: - File: Open File… (⌘O), Open Folder… (⇧⌘O), Open Recent ▸, Print… (⌘P), Export as PDF… (⇧⌘S), Close Window - View: Toggle Dark Mode Print / Export PDF / theme toggle reuse the existing frontend handlers (extracted into named functions), so the titlebar buttons and menu items share one code path. Open Recent is dynamic: recents state lives in the frontend store (recentEntries, capped at 10, deduped by path, most-recent-first) and is pushed to a Rust-owned submenu via a new update_recent_menu command. Entries are recorded on top-level opens (picker, file association, CLI, restore), not on sidebar navigation. Item ids embed the path behind a fixed prefix so paths with ':' survive. The pure list-merge logic is extracted to utils.mergeRecent with tests. Deferred (documented in the PR): New Tab (needs a tabbing system), Save (no-op for a read-only viewer), Share (native sharing picker). Closes ekino#3
nprudhomme
added a commit
that referenced
this pull request
Aug 13, 2026
* chore(release): v0.11.0 Bump version to 0.11.0 and document the user-facing changes since 0.10.0: native File/View menus with Open Recent (#19), fast document open with in-memory cache, cloud-sync UX and a debug HUD (#18), and a search highlight fix on document switch / 1->0 clear (#17). * fix(ui): stop transparent modal backdrops from locking the window A modal backdrop could be left in `display: flex` while fully transparent, covering the whole window at z-index 2000. It swallowed every click and scroll while staying invisible, and because the close path had already detached its listeners, the user had no way to dismiss it — the app was unusable until restart. Reported on 0.10.0: no clicks on Open Folder, no file selection in the sidebar, no scrolling, while the native menu bar still worked. The backdrops were shown and hidden through deferred callbacks. WebKit suspends both animation frames and timers while a window is occluded, so neither the reveal nor the hide is guaranteed to run — leaving the layer displayed but never marked `visible`. Make interactivity follow opacity in CSS: an overlay without `.visible` is now click-through and hidden from the tab order and the accessibility tree. That is the load-bearing guarantee — it holds whatever the reason the layer got stuck, including a keyboard user reaching a destructive button inside an invisible dialog. Defence in depth on top of it: - reveal synchronously (forced reflow, then class) instead of from a frame callback, so a dialog opened on an occluded window is still visible and therefore dismissable; - track an open generation so a pending hide never acts on a backdrop a newer open has claimed, in both confirm-dialog and Preferences; - focus the trap immediately, since callers now reveal before trapping. Tests cover the two suspension modes that produce the field state: frames never delivered, and timers never delivered. Claude-Session: https://claude.ai/code/session_01NcJuB2iR4WEfuQVLpYqWi4 * docs(changelog): record multi-window support in 0.11.0 PR #22 landed without a changelog entry. Covers the new windows, the native Window menu, the arrangement commands, and the switch from broadcast to frontmost-window menu delivery. Claude-Session: https://claude.ai/code/session_01NcJuB2iR4WEfuQVLpYqWi4 * docs(changelog): set the 0.11.0 release date The heading still carried the date the release branch was prepared. Claude-Session: https://claude.ai/code/session_01NcJuB2iR4WEfuQVLpYqWi4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3
What
Restructures the native menu to match standard macOS apps and surfaces existing features (Print, Export PDF, theme) from the menu bar.
File
Open File…(⌘O)Open Folder…(⇧⌘O — moved off ⌘O)Open Recent ▸(dynamic)Print…(⌘P)Export as PDF…(⇧⌘S)Close WindowView
Toggle Dark ModeHow
printDocument,exportPdf,toggleTheme) so the titlebar buttons and the new menu items share one code path — no behavior change for the buttons.recentEntries, capped at 10, deduped by path, most-recent-first) and is rendered into a Rust-owned submenu via a newupdate_recent_menucommand (state stays on the TS side per the app's conventions; Rust owns the menu). Entries are recorded on top-level opens (picker, file association, CLI arg, folder restore), not on sidebar navigation. Menu-item ids embed the path behind a fixed prefix (recent-file:/recent-folder:) so paths containing:round-trip correctly.utils.mergeRecent+ unit tests.Scope decisions (from #3)
Implemented the items that fit a read-only viewer. Deliberately deferred, happy to split into follow-up issues:
NSSharingServicePicker, doable viaobjc2(already used for PDF export) but out of this PR's scope.Known follow-ups
Tests
npx tsc --noEmit✅cargo check✅npm test✅ 295 passed (5 new formergeRecent)Not manually run in a packaged build yet.