From 3b1298813cdd9f125d2a82907877fde4b855155a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Jul 2026 06:08:31 +0000 Subject: [PATCH 01/10] Menu/search/UI polish: shortcuts dialog, Tools submenus, richer diff search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix Mermaid "Expand": close the snippet editor first and raise the viewer above all dialogs, so the diagram window actually appears - Restructure Tools into per-format submenus (Base64 / JSON / XML / SQL / Text Encryption) in both the native menu and the in-app menu bar - Add Help → Keyboard Shortcuts listing every shortcut for the host OS, backed by a shared utils/shortcuts.js (also feeds the floating hint bar) - Suppress the macOS-injected Edit → AutoFill item - Remove the redundant "Diff Bro" toolbar title - Diff search: add match-case and whole-word toggles, and guard regex search against ReDoS/over-long patterns (utils/searchRegex.js) - Align the "Saved diffs" header with the Left/Right file-slot labels and add bottom spacing beneath the Snippets section Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01V6QHAnibiPenmyN7q3Rh4m --- src/main/menu.js | 67 ++++++++++++++----- src/renderer/src/components/AppDialogs.vue | 2 + src/renderer/src/components/AppToolbar.vue | 2 - src/renderer/src/components/DiffViewer.vue | 20 +++++- .../components/KeyboardShortcutsDialog.vue | 39 +++++++++++ src/renderer/src/components/ShortcutBar.vue | 12 +--- .../src/components/styles/AppToolbar.css | 9 +-- .../src/components/styles/DiffViewer.css | 7 +- .../styles/KeyboardShortcutsDialog.css | 43 ++++++++++++ .../components/styles/MermaidViewerDialog.css | 4 +- .../src/components/styles/SavedDiffs.css | 5 +- .../src/components/styles/SnippetsPanel.css | 4 ++ src/renderer/src/composables/useDiffSearch.js | 27 +++++++- .../src/composables/useSnippetDraft.js | 8 ++- src/renderer/src/menus.js | 63 ++++++++++++----- src/renderer/src/stores/diffStore.js | 5 +- src/renderer/src/utils/searchRegex.js | 33 +++++++++ src/renderer/src/utils/shortcuts.js | 59 ++++++++++++++++ tests/renderer/utils/searchRegex.test.js | 32 +++++++++ tests/renderer/utils/shortcuts.test.js | 31 +++++++++ 20 files changed, 413 insertions(+), 59 deletions(-) create mode 100644 src/renderer/src/components/KeyboardShortcutsDialog.vue create mode 100644 src/renderer/src/components/styles/KeyboardShortcutsDialog.css create mode 100644 src/renderer/src/utils/searchRegex.js create mode 100644 src/renderer/src/utils/shortcuts.js create mode 100644 tests/renderer/utils/searchRegex.test.js create mode 100644 tests/renderer/utils/shortcuts.test.js diff --git a/src/main/menu.js b/src/main/menu.js index 38b64df..ac9908f 100644 --- a/src/main/menu.js +++ b/src/main/menu.js @@ -33,10 +33,15 @@ function resetZoom() { // "Edit". Both are dead ends here: dictation is a network service this app must // never touch, and the character palette cannot insert into a sandboxed // renderer, so it silently does nothing. Suppress them before the menu is built. +// AppKit also injects an "AutoFill" submenu (Passwords/Contacts) into text +// Edit menus on recent macOS. It is a dead end in a sandboxed, offline app — +// there is nothing to autofill and Passwords is a network-backed service this +// app must never touch — so suppress it alongside dictation and the palette. function disableInjectedMacMenuItems() { if (process.platform !== 'darwin') return systemPreferences.setUserDefault('NSDisabledDictationMenuItem', 'boolean', true) systemPreferences.setUserDefault('NSDisabledCharacterPaletteMenuItem', 'boolean', true) + systemPreferences.setUserDefault('NSDisabledAutoFillMenuItem', 'boolean', true) } export function installMenu() { @@ -145,33 +150,65 @@ export function installMenu() { }, { label: 'Tools', + // Grouped by format so each tool's operations live under their own + // heading (Tools → Base64 → …), leaving room to grow per format. submenu: [ { - label: 'Base64 Encode/Decode', - accelerator: 'CmdOrCtrl+Shift+B', - click: () => sendToFocused('tools-base64') + label: 'Base64', + submenu: [ + { + label: 'Encode / Decode', + accelerator: 'CmdOrCtrl+Shift+B', + click: () => sendToFocused('tools-base64') + } + ] }, { - label: 'JSON Format/Validate', - accelerator: 'CmdOrCtrl+Shift+J', - click: () => sendToFocused('tools-json') + label: 'JSON', + submenu: [ + { + label: 'Format / Validate', + accelerator: 'CmdOrCtrl+Shift+J', + click: () => sendToFocused('tools-json') + } + ] }, { - label: 'XML Format/Validate', - accelerator: 'CmdOrCtrl+Shift+M', - click: () => sendToFocused('tools-xml') + label: 'XML', + submenu: [ + { + label: 'Format / Validate', + accelerator: 'CmdOrCtrl+Shift+M', + click: () => sendToFocused('tools-xml') + } + ] }, { - label: 'SQL Format/Validate', - accelerator: 'CmdOrCtrl+Shift+Q', - click: () => sendToFocused('tools-sql') + label: 'SQL', + submenu: [ + { + label: 'Format / Validate', + accelerator: 'CmdOrCtrl+Shift+Q', + click: () => sendToFocused('tools-sql') + } + ] }, { - label: 'Encrypt/Decrypt Text', - accelerator: 'CmdOrCtrl+Shift+X', - click: () => sendToFocused('tools-crypt') + label: 'Text Encryption', + submenu: [ + { + label: 'Encrypt / Decrypt', + accelerator: 'CmdOrCtrl+Shift+X', + click: () => sendToFocused('tools-crypt') + } + ] } ] + }, + { + role: 'help', + label: 'Help', + submenu: [{ label: 'Keyboard Shortcuts', click: () => sendToFocused('shortcuts') }] } ] Menu.setApplicationMenu(Menu.buildFromTemplate(template)) diff --git a/src/renderer/src/components/AppDialogs.vue b/src/renderer/src/components/AppDialogs.vue index f0ac055..3fa634d 100644 --- a/src/renderer/src/components/AppDialogs.vue +++ b/src/renderer/src/components/AppDialogs.vue @@ -20,6 +20,7 @@ import TrustedKeysDialog from './TrustedKeysDialog.vue' import ShareKeyDialog from './ShareKeyDialog.vue' import ConfigBackupDialog from './ConfigBackupDialog.vue' import SettingsDialog from './SettingsDialog.vue' +import KeyboardShortcutsDialog from './KeyboardShortcutsDialog.vue' import MermaidViewerDialog from './MermaidViewerDialog.vue' const store = useDiffStore() @@ -35,6 +36,7 @@ const vault = useVaultStore() + diff --git a/src/renderer/src/components/AppToolbar.vue b/src/renderer/src/components/AppToolbar.vue index 3e97d7e..55286ea 100644 --- a/src/renderer/src/components/AppToolbar.vue +++ b/src/renderer/src/components/AppToolbar.vue @@ -10,8 +10,6 @@ const store = useDiffStore()