Skip to content

fix(desktop): stop Cmd+F crash and add a find-in-note bar (macOS)#577

Open
JakubAnderwald wants to merge 2 commits into
mainfrom
worktree-fix+desktop-cmdf-crash-find-bar
Open

fix(desktop): stop Cmd+F crash and add a find-in-note bar (macOS)#577
JakubAnderwald wants to merge 2 commits into
mainfrom
worktree-fix+desktop-cmdf-crash-find-bar

Conversation

@JakubAnderwald

@JakubAnderwald JakubAnderwald commented Jul 6, 2026

Copy link
Copy Markdown
Owner

What & why

Pressing Cmd+F in the macOS app crashed it instantly. Root cause (from the native crash report): DraftoMenuManager.setupMenus replaces AppKit's default main menu but its custom Edit menu has no Find item, so Cmd+F is no longer a menu key-equivalent — it leaks to the focused tentap WKWebView, and react-native-macos aborts building an NSArray with a nil element (NSInvalidArgumentExceptionSIGABRT).

The fix

  1. Stop the crash — bind Cmd+F / Cmd+G / Cmd+Shift+G as menu key-equivalents in the native Edit ▸ Find submenu via the existing onMenuAction bridge (target = the menu manager, so the item is always enabled). AppKit absorbs them in performKeyEquivalent: before keyDown: ever reaches the WebView.
  2. Deliver find-in-note — Cmd+F opens a find bar that drives the tentap editor by injecting a find engine (window.__draftoFind) which highlights matches with the CSS Custom Highlight API (overlay only — no DOM mutation, so find can't corrupt content or trigger autosave). Match counts come back through a custom onMessage passthrough.

Notes

  • Scope: parity:desktop-only — the crash and its fix are macOS-native-menu specific. Find-in-note on mobile (same tentap engine) and web (BlockNote) are follow-ups.
  • A local /code-review (xhigh) caught a critical bug before merge: the onMessage passthrough needs exclusivelyUseCustomOnMessage={false} — tentap's default is true, which would have suppressed tentap's own bridge and broken the editor entirely. Also fixed: a locale toLowerCase() offset overflow (IndexSizeError) → offset-safe regex match, a whitespace-query UI mismatch, and a Cmd+F-during-load race.
  • Separate follow-up (not in this PR): the desktop native build is currently broken on a clean install by an incomplete react-native-macos 0.81.6 → 0.81.8 bump (stale Podfile.lock + a react-native-svg Fabric incompatibility). main only builds because its node_modules is frozen at 0.81.6. This PR's code compiles on that frozen toolchain; the de-fossilization will land as its own PR.

Testing

  • pnpm --filter @drafto/desktop test317 pass (incl. new unit tests for parseFindResult / the engine JS builders / FindBar, plus a jsdom runtime suite that executes the injected engine to cover case-insensitive counting, next/prev wrap, regex-literal matching, and the locale-offset fix).
  • tsc --noEmit, eslint src/, prettier --check — all green.
  • Native compile verified: Drafto-macOS builds and DraftoMenuManager.mm compiles with the new Find actions in the binary.
  • Remaining manual check (needs a running build): press Cmd+F in an open note → find bar appears, no crash.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added desktop “Find in note” support with match counts, next/previous navigation, and keyboard shortcuts.
    • Search now highlights matches in the editor and shows “No results” when applicable.
    • Native menu shortcuts for Find, Find Next, and Find Previous are now available on macOS.
  • Bug Fixes

    • Search input and navigation behavior now handle empty queries, literal special characters, and wrapping between matches more reliably.
  • Tests

    • Added coverage for find bar interactions, search engine behavior, and message parsing.
  • Documentation

    • Added an ADR and updated the ADR index.

Cmd+F crashed the macOS app. DraftoMenuManager replaces AppKit's default
main menu but dropped the Find item, so Cmd+F leaked past the menu to the
focused tentap WKWebView and react-native-macos aborted building an NSArray
with a nil element (NSInvalidArgumentException -> SIGABRT).

Bind Cmd+F / Cmd+G / Cmd+Shift+G as menu key-equivalents via the onMenuAction
bridge so AppKit absorbs them in performKeyEquivalent before keyDown ever
reaches the WebView, and wire them to a find-in-note bar. The bar drives the
tentap editor by injecting a find engine (window.__draftoFind) that highlights
matches with the CSS Custom Highlight API (no DOM mutation, offset-safe regex
match) and posts match counts back through a custom onMessage passthrough
(exclusivelyUseCustomOnMessage=false so tentap's own bridge keeps working).

Adds find-engine + find-bar with unit and jsdom-runtime tests, and ADR-0028.
Scope: parity:desktop-only (macOS-native-menu-specific crash).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
drafto Ready Ready Preview, Comment Jul 6, 2026 9:19pm

@JakubAnderwald JakubAnderwald added the parity:desktop-only Skip cross-platform parity check (desktop-only feature). label Jul 6, 2026
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@JakubAnderwald, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5d8907ad-037f-4d7a-b8ad-85bb1b7524f6

📥 Commits

Reviewing files that changed from the base of the PR and between d9d8920 and f9b5515.

📒 Files selected for processing (1)
  • apps/desktop/src/components/notes/note-editor-panel.tsx
📝 Walkthrough

Walkthrough

This PR adds a desktop-only "Find in note" feature: an injected WebView engine highlighting matches via the CSS Custom Highlight API, a FindBar UI, native macOS Cmd+F/Find Next/Previous menu bindings, wiring through NoteEditorPanel and MainScreen, plus tests and an ADR.

Changes

Desktop Find-in-Note Feature

Layer / File(s) Summary
Find engine and message protocol
apps/desktop/src/components/editor/find-engine.ts, apps/desktop/__tests__/components/editor/find-engine*.test.ts
Defines FIND_RESULT_TYPE/FindResult, an injected engine (FIND_ENGINE_SETUP) that searches, highlights, and navigates matches via the CSS Custom Highlight API, command builders (findSearchJS, findStepJS, findClearJS), parseFindResult, and unit/runtime tests.
FindBar UI component
apps/desktop/src/components/editor/find-bar.tsx, apps/desktop/__tests__/components/editor/find-bar.test.tsx
Adds FindBar component with input, match-count display, next/prev/close controls, keyboard handling, themed styling, and its test suite.
NoteEditor message passthrough
apps/desktop/src/components/editor/note-editor.tsx
Adds optional onMessage prop and sets exclusivelyUseCustomOnMessage={false} so WebView messages reach both tentap and the caller.
Native macOS Find menu bindings
apps/desktop/macos/Drafto-macOS/DraftoMenuManager.mm
Adds a Find submenu (Find…, Find Next, Find Previous) bound to onMenuAction, inserted into the Edit menu.
NoteEditorPanel find wiring
apps/desktop/src/components/notes/note-editor-panel.tsx
Adds FindSignal interface, find UI state, resetFind, injection/handler callbacks, and renders FindBar/onMessage wiring.
MenuAction types and MainScreen dispatch
apps/desktop/src/lib/native/menu-manager.ts, apps/desktop/src/screens/main.tsx
Extends MenuAction union with find actions; adds findSignal state and handlers wired via useMenuActions and passed to NoteEditorPanel.
ADR documentation
docs/adr/0028-desktop-find-in-note.md, docs/adr/README.md
Adds ADR 0028 describing the crash cause and find-in-note design, and updates the ADR index.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant MainScreen
  participant NoteEditorPanel
  participant WebView as Tentap WebView
  participant Engine as window.__draftoFind

  User->>MainScreen: Cmd+F (findInNote)
  MainScreen->>NoteEditorPanel: findSignal { command: "open", nonce }
  NoteEditorPanel->>NoteEditorPanel: show FindBar, set findVisible
  User->>NoteEditorPanel: type query
  NoteEditorPanel->>WebView: inject findSearchJS(query)
  WebView->>Engine: search(query, pattern)
  Engine->>Engine: highlight matches
  Engine->>WebView: postMessage({type, total, current})
  WebView->>NoteEditorPanel: onMessage(event)
  NoteEditorPanel->>NoteEditorPanel: parseFindResult, update match counts
  User->>MainScreen: Cmd+G (findNext)
  MainScreen->>NoteEditorPanel: findSignal { command: "next", nonce }
  NoteEditorPanel->>WebView: inject findStepJS("next")
  WebView->>Engine: next()
  Engine->>WebView: postMessage(updated counts)
  WebView->>NoteEditorPanel: onMessage(event)
Loading

Possibly related PRs

  • JakubAnderwald/drafto#201: Both PRs modify NoteEditorPanel, with this PR extending it with findSignal/find-state plumbing and injecting the find engine and FindBar.
  • JakubAnderwald/drafto#215: This PR's find-in-note implementation extends the native menu/action plumbing from #215 by adding Find-related items to DraftoMenuManager.buildEditMenu and the MenuAction/useMenuActions flow.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main macOS Cmd+F crash fix and the new find-in-note bar.
Description check ✅ Passed The description covers what, why, scope, and testing; it only omits the checklist/template headings.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-fix+desktop-cmdf-crash-find-bar

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/desktop/src/components/editor/find-engine.ts`:
- Around line 84-111: The full-document tree walk in search() is being triggered
on every keystroke, so keep this function unchanged and fix the call site
instead. Update handleFindChange in note-editor-panel.tsx to debounce or
otherwise throttle calls into search() so repeated input does not re-run the
ProseMirror text-node scan on every character. Use the existing search() and
handleFindChange symbols to locate the integration point and apply the debounce
where the query state is forwarded.

In `@apps/desktop/src/components/editor/note-editor.tsx`:
- Around line 5-23: The NoteEditorProps setup should explicitly disable the
default custom message exclusivity so TenTap’s bridge keeps working. In
note-editor.tsx where NoteEditorProps declares the optional onMessage callback
and the RichText integration is configured, set exclusivelyUseCustomOnMessage to
false whenever a custom onMessage is supplied so editor readiness, onChange, and
getJSON messages still flow through TenTap’s internal handler.

In `@apps/desktop/src/components/notes/note-editor-panel.tsx`:
- Around line 433-439: The find-bar change handler in note-editor-panel’s
handleFindChange is triggering a full findSearchJS/injectFind search on every
keystroke, which can cause typing lag. Update this path to debounce the query
handling so setFindQuery can still update immediately while
injectFind(findSearchJS(query)) runs only after a short delay; make sure the
debounce is wired through handleFindChange and cleaned up properly in the
note-editor-panel component.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 779cbb0d-4531-46f0-a4b8-29ec4199116c

📥 Commits

Reviewing files that changed from the base of the PR and between 99af1b6 and d9d8920.

📒 Files selected for processing (12)
  • apps/desktop/__tests__/components/editor/find-bar.test.tsx
  • apps/desktop/__tests__/components/editor/find-engine.runtime.test.ts
  • apps/desktop/__tests__/components/editor/find-engine.test.ts
  • apps/desktop/macos/Drafto-macOS/DraftoMenuManager.mm
  • apps/desktop/src/components/editor/find-bar.tsx
  • apps/desktop/src/components/editor/find-engine.ts
  • apps/desktop/src/components/editor/note-editor.tsx
  • apps/desktop/src/components/notes/note-editor-panel.tsx
  • apps/desktop/src/lib/native/menu-manager.ts
  • apps/desktop/src/screens/main.tsx
  • docs/adr/0028-desktop-find-in-note.md
  • docs/adr/README.md

Comment thread apps/desktop/src/components/editor/find-engine.ts
Comment thread apps/desktop/src/components/editor/note-editor.tsx
Comment thread apps/desktop/src/components/notes/note-editor-panel.tsx
Addresses CodeRabbit review: the injected engine re-scans every ProseMirror
text node per search, so debounce injectFind(findSearchJS()) by 150ms in
handleFindChange (input stays responsive). Cancel the pending timer on reset
(close / note switch) so a late search can't re-highlight, and on unmount.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

parity:desktop-only Skip cross-platform parity check (desktop-only feature).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant