feat: populate macOS menu bar with native menus (File, Edit, View, Window) (#77)
* feat: populate macOS menu bar with File, Edit, View, Window menus
Add complete native macOS menu structure with all app actions:
- Laputa menu: About, Settings (Cmd+,), Hide/Quit
- File: New Note (Cmd+N), Quick Open (Cmd+P), Save (Cmd+S), Close Tab (Cmd+W)
- Edit: standard Undo/Redo/Cut/Copy/Paste/Select All
- View: Editor Only (Cmd+1), Editor+Notes (Cmd+2), All Panels (Cmd+3),
Toggle Inspector, Command Palette (Cmd+K)
- Window: Minimize, Maximize, Close Window
All accelerators registered via Tauri menu API. Menu events dispatched
to frontend via useMenuEvents hook. Save/Close Tab items dynamically
disabled when no note tab is active.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wrap Enter selection assertion in waitFor for effect re-registration
* fix: resolve rebase conflict markers in menu.rs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:05:24 +01:00
|
|
|
import { useEffect, useRef } from 'react'
|
|
|
|
|
import { isTauri } from '../mock-tauri'
|
2026-04-11 10:39:08 +02:00
|
|
|
import {
|
|
|
|
|
APP_COMMAND_EVENT_NAME,
|
|
|
|
|
dispatchAppCommand,
|
|
|
|
|
isAppCommandId,
|
|
|
|
|
type AppCommandHandlers,
|
|
|
|
|
} from './appCommandDispatcher'
|
|
|
|
|
|
2026-04-11 10:59:38 +02:00
|
|
|
declare global {
|
|
|
|
|
interface Window {
|
|
|
|
|
__laputaTest?: {
|
|
|
|
|
dispatchBrowserMenuCommand?: (id: string) => void
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 10:39:08 +02:00
|
|
|
export interface MenuEventHandlers extends AppCommandHandlers {
|
feat: populate macOS menu bar with native menus (File, Edit, View, Window) (#77)
* feat: populate macOS menu bar with File, Edit, View, Window menus
Add complete native macOS menu structure with all app actions:
- Laputa menu: About, Settings (Cmd+,), Hide/Quit
- File: New Note (Cmd+N), Quick Open (Cmd+P), Save (Cmd+S), Close Tab (Cmd+W)
- Edit: standard Undo/Redo/Cut/Copy/Paste/Select All
- View: Editor Only (Cmd+1), Editor+Notes (Cmd+2), All Panels (Cmd+3),
Toggle Inspector, Command Palette (Cmd+K)
- Window: Minimize, Maximize, Close Window
All accelerators registered via Tauri menu API. Menu events dispatched
to frontend via useMenuEvents hook. Save/Close Tab items dynamically
disabled when no note tab is active.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wrap Enter selection assertion in waitFor for effect re-registration
* fix: resolve rebase conflict markers in menu.rs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:05:24 +01:00
|
|
|
activeTabPath: string | null
|
feat: reorganize menu bar with Go, Note, and Vault menus
Add missing command palette commands to menu bar and reorganize into
logical groups: File, Edit, View, Go, Note, Vault, Window. New menus
expose navigation filters, note actions, vault management, themes, and
git operations. Context-sensitive items (archive, trash, diff, raw
editor, commit, conflicts) are greyed out when not applicable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 14:13:54 +01:00
|
|
|
modifiedCount?: number
|
|
|
|
|
conflictCount?: number
|
2026-04-07 20:09:04 +02:00
|
|
|
hasRestorableDeletedNote?: boolean
|
feat: populate macOS menu bar with native menus (File, Edit, View, Window) (#77)
* feat: populate macOS menu bar with File, Edit, View, Window menus
Add complete native macOS menu structure with all app actions:
- Laputa menu: About, Settings (Cmd+,), Hide/Quit
- File: New Note (Cmd+N), Quick Open (Cmd+P), Save (Cmd+S), Close Tab (Cmd+W)
- Edit: standard Undo/Redo/Cut/Copy/Paste/Select All
- View: Editor Only (Cmd+1), Editor+Notes (Cmd+2), All Panels (Cmd+3),
Toggle Inspector, Command Palette (Cmd+K)
- Window: Minimize, Maximize, Close Window
All accelerators registered via Tauri menu API. Menu events dispatched
to frontend via useMenuEvents hook. Save/Close Tab items dynamically
disabled when no note tab is active.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wrap Enter selection assertion in waitFor for effect re-registration
* fix: resolve rebase conflict markers in menu.rs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:05:24 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-11 10:39:08 +02:00
|
|
|
interface MenuStatePayload {
|
|
|
|
|
hasActiveNote: boolean
|
|
|
|
|
hasModifiedFiles?: boolean
|
|
|
|
|
hasConflicts?: boolean
|
|
|
|
|
hasRestorableDeletedNote?: boolean
|
feat: populate macOS menu bar with native menus (File, Edit, View, Window) (#77)
* feat: populate macOS menu bar with File, Edit, View, Window menus
Add complete native macOS menu structure with all app actions:
- Laputa menu: About, Settings (Cmd+,), Hide/Quit
- File: New Note (Cmd+N), Quick Open (Cmd+P), Save (Cmd+S), Close Tab (Cmd+W)
- Edit: standard Undo/Redo/Cut/Copy/Paste/Select All
- View: Editor Only (Cmd+1), Editor+Notes (Cmd+2), All Panels (Cmd+3),
Toggle Inspector, Command Palette (Cmd+K)
- Window: Minimize, Maximize, Close Window
All accelerators registered via Tauri menu API. Menu events dispatched
to frontend via useMenuEvents hook. Save/Close Tab items dynamically
disabled when no note tab is active.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wrap Enter selection assertion in waitFor for effect re-registration
* fix: resolve rebase conflict markers in menu.rs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:05:24 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-11 10:39:08 +02:00
|
|
|
function readCustomEventDetail(event: Event): string | null {
|
|
|
|
|
if (!(event instanceof CustomEvent) || typeof event.detail !== 'string') {
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
return event.detail
|
feat: keyboard-first navigation — menu bar shortcuts, note list nav, inspector Tab/Enter (#158)
* feat: keyboard-first navigation — menu bar shortcuts, note list nav, inspector Tab/Enter
- Add missing menu bar items: Archive Note (⌘E), Trash Note (⌘⌫),
Find in Vault (⌘⇧F), Go Back (⌘[), Go Forward (⌘])
- Wire new menu events through useMenuEvents → useAppCommands
- Note list: ↑↓ moves highlight, Enter opens selected note (useNoteListKeyboard hook)
- Inspector properties: Tab between rows, Enter to start editing
- Settings panel: auto-focus first input on open
- Extract StatusDot, StateBadge, noteItemStyle from NoteItem (reduces complexity)
- Extract dispatchActiveTabEvent/dispatchOptionalEvent from useMenuEvents
- 21 new tests (useNoteListKeyboard + useMenuEvents for new events)
- All 1275 frontend tests pass, 319 Rust tests pass
- CodeScene quality gates: passed (NoteItem improved from 22→18 complexity)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: keyboard-first-nav wireframes (note list highlight, menu shortcuts, inspector tab nav)
* test: add search.rs coverage for fallback branches (qmd_uri_to_vault_path, extract_clean_snippet)
* chore: exclude search.rs and lib.rs from coverage (qmd integration + Tauri setup code)
---------
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:32:15 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-11 10:39:08 +02:00
|
|
|
function createWindowCommandListener(
|
|
|
|
|
dispatch: (id: string) => void,
|
|
|
|
|
): (event: Event) => void {
|
|
|
|
|
return (event: Event) => {
|
|
|
|
|
const detail = readCustomEventDetail(event)
|
|
|
|
|
if (detail) {
|
|
|
|
|
dispatch(detail)
|
|
|
|
|
}
|
|
|
|
|
}
|
feat: reorganize menu bar with Go, Note, and Vault menus
Add missing command palette commands to menu bar and reorganize into
logical groups: File, Edit, View, Go, Note, Vault, Window. New menus
expose navigation filters, note actions, vault management, themes, and
git operations. Context-sensitive items (archive, trash, diff, raw
editor, commit, conflicts) are greyed out when not applicable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 14:13:54 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-11 10:39:08 +02:00
|
|
|
function syncNativeMenuState(state: MenuStatePayload): void {
|
|
|
|
|
if (!isTauri()) return
|
feat: reorganize menu bar with Go, Note, and Vault menus
Add missing command palette commands to menu bar and reorganize into
logical groups: File, Edit, View, Go, Note, Vault, Window. New menus
expose navigation filters, note actions, vault management, themes, and
git operations. Context-sensitive items (archive, trash, diff, raw
editor, commit, conflicts) are greyed out when not applicable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 14:13:54 +01:00
|
|
|
|
2026-04-11 10:39:08 +02:00
|
|
|
import('@tauri-apps/api/core')
|
|
|
|
|
.then(({ invoke }) => invoke('update_menu_state', { state }))
|
|
|
|
|
.catch(() => {})
|
2026-02-28 12:41:57 +01:00
|
|
|
}
|
|
|
|
|
|
feat: populate macOS menu bar with native menus (File, Edit, View, Window) (#77)
* feat: populate macOS menu bar with File, Edit, View, Window menus
Add complete native macOS menu structure with all app actions:
- Laputa menu: About, Settings (Cmd+,), Hide/Quit
- File: New Note (Cmd+N), Quick Open (Cmd+P), Save (Cmd+S), Close Tab (Cmd+W)
- Edit: standard Undo/Redo/Cut/Copy/Paste/Select All
- View: Editor Only (Cmd+1), Editor+Notes (Cmd+2), All Panels (Cmd+3),
Toggle Inspector, Command Palette (Cmd+K)
- Window: Minimize, Maximize, Close Window
All accelerators registered via Tauri menu API. Menu events dispatched
to frontend via useMenuEvents hook. Save/Close Tab items dynamically
disabled when no note tab is active.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wrap Enter selection assertion in waitFor for effect re-registration
* fix: resolve rebase conflict markers in menu.rs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:05:24 +01:00
|
|
|
/** Dispatch a Tauri menu event ID to the matching handler. Exported for testing. */
|
|
|
|
|
export function dispatchMenuEvent(id: string, h: MenuEventHandlers): void {
|
2026-04-11 10:39:08 +02:00
|
|
|
if (!isAppCommandId(id)) return
|
|
|
|
|
dispatchAppCommand(id, h)
|
feat: populate macOS menu bar with native menus (File, Edit, View, Window) (#77)
* feat: populate macOS menu bar with File, Edit, View, Window menus
Add complete native macOS menu structure with all app actions:
- Laputa menu: About, Settings (Cmd+,), Hide/Quit
- File: New Note (Cmd+N), Quick Open (Cmd+P), Save (Cmd+S), Close Tab (Cmd+W)
- Edit: standard Undo/Redo/Cut/Copy/Paste/Select All
- View: Editor Only (Cmd+1), Editor+Notes (Cmd+2), All Panels (Cmd+3),
Toggle Inspector, Command Palette (Cmd+K)
- Window: Minimize, Maximize, Close Window
All accelerators registered via Tauri menu API. Menu events dispatched
to frontend via useMenuEvents hook. Save/Close Tab items dynamically
disabled when no note tab is active.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wrap Enter selection assertion in waitFor for effect re-registration
* fix: resolve rebase conflict markers in menu.rs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:05:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Listen for native macOS menu events and dispatch them to the appropriate handlers. */
|
|
|
|
|
export function useMenuEvents(handlers: MenuEventHandlers) {
|
|
|
|
|
const ref = useRef(handlers)
|
|
|
|
|
ref.current = handlers
|
2026-04-11 10:39:08 +02:00
|
|
|
const hasActiveNote = handlers.activeTabPath !== null
|
|
|
|
|
const hasModifiedFiles = handlers.modifiedCount != null ? handlers.modifiedCount > 0 : undefined
|
|
|
|
|
const hasConflicts = handlers.conflictCount != null ? handlers.conflictCount > 0 : undefined
|
|
|
|
|
const hasRestorableDeletedNote = handlers.hasRestorableDeletedNote
|
feat: populate macOS menu bar with native menus (File, Edit, View, Window) (#77)
* feat: populate macOS menu bar with File, Edit, View, Window menus
Add complete native macOS menu structure with all app actions:
- Laputa menu: About, Settings (Cmd+,), Hide/Quit
- File: New Note (Cmd+N), Quick Open (Cmd+P), Save (Cmd+S), Close Tab (Cmd+W)
- Edit: standard Undo/Redo/Cut/Copy/Paste/Select All
- View: Editor Only (Cmd+1), Editor+Notes (Cmd+2), All Panels (Cmd+3),
Toggle Inspector, Command Palette (Cmd+K)
- Window: Minimize, Maximize, Close Window
All accelerators registered via Tauri menu API. Menu events dispatched
to frontend via useMenuEvents hook. Save/Close Tab items dynamically
disabled when no note tab is active.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wrap Enter selection assertion in waitFor for effect re-registration
* fix: resolve rebase conflict markers in menu.rs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:05:24 +01:00
|
|
|
|
|
|
|
|
// Subscribe once to Tauri menu events
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!isTauri()) return
|
|
|
|
|
|
|
|
|
|
let cleanup: (() => void) | undefined
|
|
|
|
|
import('@tauri-apps/api/event').then(({ listen }) => {
|
|
|
|
|
const unlisten = listen<string>('menu-event', (event) => {
|
|
|
|
|
dispatchMenuEvent(event.payload, ref.current)
|
|
|
|
|
})
|
|
|
|
|
cleanup = () => { unlisten.then(fn => fn()) }
|
|
|
|
|
}).catch(() => { /* not in Tauri */ })
|
|
|
|
|
|
|
|
|
|
return () => cleanup?.()
|
|
|
|
|
}, [])
|
|
|
|
|
|
2026-04-11 10:39:08 +02:00
|
|
|
useEffect(() => {
|
|
|
|
|
const handleCommandEvent = createWindowCommandListener((detail) => {
|
|
|
|
|
if (isAppCommandId(detail)) {
|
|
|
|
|
dispatchAppCommand(detail, ref.current)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
window.addEventListener(APP_COMMAND_EVENT_NAME, handleCommandEvent)
|
|
|
|
|
return () => window.removeEventListener(APP_COMMAND_EVENT_NAME, handleCommandEvent)
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-04-11 10:59:38 +02:00
|
|
|
const bridge = (id: string) => {
|
|
|
|
|
dispatchMenuEvent(id, ref.current)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.__laputaTest = {
|
|
|
|
|
...window.__laputaTest,
|
|
|
|
|
dispatchBrowserMenuCommand: bridge,
|
|
|
|
|
}
|
2026-04-11 10:39:08 +02:00
|
|
|
|
2026-04-11 10:59:38 +02:00
|
|
|
return () => {
|
|
|
|
|
if (window.__laputaTest?.dispatchBrowserMenuCommand === bridge) {
|
|
|
|
|
delete window.__laputaTest.dispatchBrowserMenuCommand
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-11 10:39:08 +02:00
|
|
|
}, [])
|
|
|
|
|
|
refactor: remove tab bar — single note open at a time
Replace the multi-tab model with single-note navigation. One note is
open at a time; clicking any note (sidebar, wikilink, relationship)
replaces the current note in the editor. Back/Forward history still
works via Cmd+[/].
Removed: TabBar component, useClosedTabHistory, tab reorder/close/
reopen logic, Cmd+W/Cmd+Shift+T shortcuts, Close Tab and Reopen
Closed Tab menu items, tabLayout utility, and all related tests.
Simplified: useTabManagement (single-note), useAppNavigation (no tab
lookup), useKeyboardNavigation (note nav only), useNoteCreation (no
close-tab cleanup), useDeleteActions (deselect instead of close tab),
Editor (no TabBar render), Rust menu (removed 2 menu items).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:24:49 +01:00
|
|
|
// Sync menu item enabled state when active note or git state changes
|
feat: populate macOS menu bar with native menus (File, Edit, View, Window) (#77)
* feat: populate macOS menu bar with File, Edit, View, Window menus
Add complete native macOS menu structure with all app actions:
- Laputa menu: About, Settings (Cmd+,), Hide/Quit
- File: New Note (Cmd+N), Quick Open (Cmd+P), Save (Cmd+S), Close Tab (Cmd+W)
- Edit: standard Undo/Redo/Cut/Copy/Paste/Select All
- View: Editor Only (Cmd+1), Editor+Notes (Cmd+2), All Panels (Cmd+3),
Toggle Inspector, Command Palette (Cmd+K)
- Window: Minimize, Maximize, Close Window
All accelerators registered via Tauri menu API. Menu events dispatched
to frontend via useMenuEvents hook. Save/Close Tab items dynamically
disabled when no note tab is active.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wrap Enter selection assertion in waitFor for effect re-registration
* fix: resolve rebase conflict markers in menu.rs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:05:24 +01:00
|
|
|
useEffect(() => {
|
2026-04-11 10:39:08 +02:00
|
|
|
syncNativeMenuState({
|
|
|
|
|
hasActiveNote,
|
|
|
|
|
hasModifiedFiles,
|
|
|
|
|
hasConflicts,
|
|
|
|
|
hasRestorableDeletedNote,
|
|
|
|
|
})
|
|
|
|
|
}, [hasActiveNote, hasModifiedFiles, hasConflicts, hasRestorableDeletedNote])
|
feat: populate macOS menu bar with native menus (File, Edit, View, Window) (#77)
* feat: populate macOS menu bar with File, Edit, View, Window menus
Add complete native macOS menu structure with all app actions:
- Laputa menu: About, Settings (Cmd+,), Hide/Quit
- File: New Note (Cmd+N), Quick Open (Cmd+P), Save (Cmd+S), Close Tab (Cmd+W)
- Edit: standard Undo/Redo/Cut/Copy/Paste/Select All
- View: Editor Only (Cmd+1), Editor+Notes (Cmd+2), All Panels (Cmd+3),
Toggle Inspector, Command Palette (Cmd+K)
- Window: Minimize, Maximize, Close Window
All accelerators registered via Tauri menu API. Menu events dispatched
to frontend via useMenuEvents hook. Save/Close Tab items dynamically
disabled when no note tab is active.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wrap Enter selection assertion in waitFor for effect re-registration
* fix: resolve rebase conflict markers in menu.rs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:05:24 +01:00
|
|
|
}
|