feat(window): multi-window support with a native Window menu - #22
Merged
Merged
Conversation
Add File › New Window (⌘N) for an empty window and File › Open Folder in New Window… (⇧⌘N) to open a picked folder in its own window. Each window keeps its own document, outline, search and scroll position. The pending-open buffer becomes a map keyed by window label so a window only ever drains the folder meant for it; a new PendingOpen::Empty marker keeps a ⌘N window on the welcome screen instead of restoring lastFolder, which would just clone the window it was spawned from. Spawned windows are labelled viewer-N and allowlisted in the capability, without which they would come up with no plugin permissions at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
app_handle.emit broadcasts to every webview, which was harmless with a single window but means a second window turns one ⌘F into two focused search fields and one "Open Folder…" into two folder pickers. Route every menu command through emit_to_focused instead, and target a Finder "Open With" on a running app at the frontmost window rather than all of them. focused_window walks webview_windows() because Manager::get_focused_window sits behind tauri's unstable feature. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add a Window submenu holding one checkable entry per open window, checked on the frontmost one, that brings the picked window forward. The list is rebuilt from every event that can change it: window opened, closed, focused, or renamed. macOS can populate a Window menu itself via setWindowsMenu:, but AppKit only adopts windows created after that call — our main window already exists when the menu is built, and tao's windows were never picked up at all — so the entries are built here instead. Windows are also named after the document they show (or their folder), or the list would read as several identical "Markdown Viewer" entries. Renaming goes through a set_window_title command that updates the title and the menu together, so the two cannot drift apart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round out the Window menu with the macOS window-arrangement commands: Fill, Center, halves and quarters under Move & Resize, Return to Previous Size, and Bring All to Front. They act on the frontmost window, using the screen it currently sits on. Frames come from NSScreen.visibleFrame rather than Tauri's Monitor::work_area(), which reports the right size but an origin of (0,0) — it ignores the menu bar. Top-anchored arrangements hid that because macOS clamps them below the menu bar, but bottom halves, bottom quarters and centering all landed one menu-bar-height too high. No accelerators on purpose: macOS already binds ⌃⌥+arrows to its own window tiling, and shadowing those would be a coin flip over which wins. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
nprudhomme
added a commit
that referenced
this pull request
Aug 13, 2026
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
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.
Description
What
Adds multi-window support: any folder can be opened in its own window, with a native Window menu to list, switch between and arrange those windows.
Why
The app only ever had one window, so reading two sets of docs side by side — or a guide next to its reference — meant closing one folder and reopening the other. Several windows plus a standard Window menu is what a macOS document viewer is expected to offer.
Changes
Multiple windows (
99f0331)File › New Window(⌘N) opens an empty window;File › Open Folder in New Window…(⇧⌘N) opens a picked folder in its own window, leaving the current one untouchedopen_new_windowcommand: spawned windows are labelledviewer-Nand cascade off the frontmost onePendingOpen::Emptymarker keeps a ⌘N window on the welcome screen rather than restoringlastFolder, which would make it a clone of the window it came fromviewer-*added to the capability's window list — without it, spawned windows come up with no plugin permissionsresolveInitialView(src/utils.ts) decides what a window shows at startupMenu commands now target the frontmost window (
40756b1)app_handle.emitbroadcasts to every webview. Harmless with one window; with two, a single ⌘F focused both search fields and one "Open Folder…" raised two pickersemit_to_focused, and a Finder "Open With" on a running app lands in the frontmost window instead of all of themWindow menu (
de45952)set_as_windows_menu_for_nsapp()was tried first and populates nothing here — AppKit only adopts windows created after that call, and never picked up tao's — so the entries are built explicitlyset_window_titlecommand that updates the title and the menu together so they cannot driftWindow arrangement (
b5cd5ad)Move & Resize(halves, quarters, Return to Previous Size) and Bring All to FrontNSScreen.visibleFramerather than Tauri'sMonitor::work_area(), which reports the right size but an origin of(0, 0): top-anchored layouts looked fine because macOS clamps them below the menu bar, but bottom halves, bottom quarters and centering all landed one menu-bar-height too highobjc2-app-kitdependency (already in the tree transitively viaobjc2-web-kit)Docs — README feature bullet,
docs/guide/features.mdsection, keyboard-shortcuts table.Verification
npx tsc --noEmit, 311 tests (10 new, coveringresolveInitialViewandwindowTitle),cargo checky=1368on a work area ofy=78 h=2580, so its bottom edge is exactly the screen's, and left/right halves meet with no gapNotes for review:
lastFolderstays app-wide, so the next launch restores the folder of the last window that navigated. Preferences are read when a window opens, so changing a theme does not retro-apply to windows already open.Type of Change
Checklist
🤖 Generated with Claude Code