Skip to content

host(macos): install default Edit menu so Cmd+C/V work in WKWebView - #5

Open
dominicletz wants to merge 2 commits into
masterfrom
fix/macos-default-edit-menu
Open

host(macos): install default Edit menu so Cmd+C/V work in WKWebView#5
dominicletz wants to merge 2 commits into
masterfrom
fix/macos-default-edit-menu

Conversation

@dominicletz

@dominicletz dominicletz commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

The macOS host never installed an Edit submenu on the AppKit main menu, so NSApp.performKeyEquivalent could not route Cmd+C/V/X/A into the responder chain. WKWebView implements copy: / cut: / paste: / selectAll: for the focused text-input view, but AppKit only reaches those selectors when an Edit menu item is registered with a matching keyEquivalent and keyEquivalentModifierMask.

Reported on diode-drive-wt-native (Diode Collab): the restore-code and invite-code text fields do not respond to Cmd+C / Cmd+V. The keyboard event is dropped because the AppKit responder chain has no Edit menu to handle the equivalent.

This change:

  • Adds a private buildEditMenu() helper in HostController.swift that returns an NSMenuItem titled Edit with Undo, Redo, Cut, Copy, Paste, Delete, and Select All. Targets are nil so AppKit dispatches via the responder chain; the WKWebView's internal text-input views implement the actions.
  • Installs the menu in start() via installMainMenuPreservingApple(extraItems:), so it is present before the first menu.create RPC and survives the first menu.set_apple.
  • Rebuilds the main menu on every window.set_menubar so the Edit menu is also present next to user-supplied menubars (Apple → Edit → user menus). When menu_id is omitted, set_menubar is a no-op as before.
  • Adds a test.menu.list RPC (gated by --edw-test-rpc) that snapshots the host's main menu and returns {title, items:[{label, key, modifiers, action}]} for each top-level menu. The macOS host implements it; other hosts return -32601 until they do.
  • Adds one new E2E test in test/e2e/e2e_test.exs that branches on the platform field the setup block already exposes. On macOS it asserts that the Edit menu exists and that the four reported regressions (Cut, Copy, Paste, Select All) carry the expected key equivalent and cut: / copy: / paste: / selectAll: actions. On any other platform it asserts the documented contract: test.menu.list returns -32601 "Unknown test method".
  • Updates docs/protocol.md to document the new test.menu.list method (and to mark it macOS-only), docs/status/macos.md to mark the feature done (E2E-covered), and docs/porting.md to require an Edit menu on Linux/Windows.

No Elixir-side change. No protocol-version bump (additive only, gated by the existing --edw-test-rpc flag).

Why this is the right fix

AppKit walks the main menu on every key event to find a matching keyEquivalent. Without a registered Edit menu, Cmd+C / Cmd+V simply have nothing to match. The webview's text-input view already implements the standard responder actions, so installing the menu is the missing piece. This is the same approach Electron and WKWebView Hosted Apps take; it is not a WKWebView limitation.

Test plan

  • ./scripts/build_macos.sh succeeds (universal arm64 + x86_64 binary built)
  • mix test test/e2e/e2e_test.exs --only e2e --no-start → 8 tests, 0 failures on macOS (includes the new Edit menu test, macOS branch)
  • mix test --exclude e2e --no-start → 17 unit tests, 0 failures
  • mix format --check-formatted clean
  • CI macos-e2e job green
  • CI linux-e2e job green (the new test runs the non-macOS branch and asserts the documented -32601 contract; the Linux Edit menu itself is a follow-up port required by docs/porting.md)
  • Manual: build the macOS host, open a window, focus an <input>, type Cmd+C / Cmd+V — clipboard operations succeed
  • Manual: confirm Edit menu is visible in the macOS menubar with the standard labels and accelerators

Notes

  • Targets on each menu item are nil. This routes actions through the responder chain. The first responder — typically the WKWebView's text-input view — implements cut(_:), copy(_:), paste(_:), selectAll(_:), and delete(_:) via the standard Cocoa text responder behaviour.
  • undo: and redo: go through UndoManager on the first responder. WKWebView's editable views carry an UndoManager, so Undo/Redo work inside text fields. They will be a no-op elsewhere, matching every other macOS app.
  • The Delete key equivalent is NSBackspaceCharacter (0x08) and has no modifier mask — matches Finder, TextEdit, and the system Edit menu.
  • test.menu.list is intentionally minimal: it serialises only what the E2E suite needs to assert (label, key equivalent, modifier bitmask, action selector) and nothing more.
  • The Linux GTK host is not touched by this PR. The default Edit menu is required by docs/porting.md and will be added by a follow-up port; until then test.menu.list returns -32601 on Linux and the new E2E test asserts exactly that.

The macOS host never installed an Edit submenu on the AppKit main menu, so
NSApp.performKeyEquivalent could not route Cmd+C/V/X/A into the responder
chain. WKWebView implements copy:/cut:/paste:/selectAll:, but AppKit only
reaches those selectors through a registered Edit menu item.

Install a default Edit menu (Undo, Redo, Cut, Copy, Paste, Delete, Select
All) in start() and rebuild it on every window.set_menubar so user
menubars do not displace it. Targets are nil so the responder chain
selects the first responder; no notifications or RPC are emitted.

Add test.menu.list (gated by --edw-test-rpc) so the E2E suite can assert
the Edit menu exists with the expected accelerators and actions. The new
E2E test in test/e2e/e2e_test.exs covers the four reported regressions
(Cut, Copy, Paste, Select All) and runs against the universal binary.

Update docs/protocol.md, docs/status/macos.md, and docs/porting.md to
document the new test method, the default Edit menu, and the
requirement for other platform hosts to install an equivalent.

Builds with Apple Swift 6.3 against origin/master; full E2E suite (8
tests) and unit suite (17 tests) pass.
The Edit menu RPC and assertion are macOS-only: the GTK-based Linux host
does not implement test.menu.list and returns -32601, so the new E2E
test failed on the Linux CI run. Branch the assertion on the platform
the setup block already exposes: on macOS, run the same checks as
before; on any other platform, assert the documented contract (the RPC
returns -32601 "Unknown test method"). The Linux host's Edit menu
itself is still required by docs/porting.md and will be added by a
follow-up port.

Also clarify in docs/protocol.md that test.menu.list is macOS-only
until other hosts implement the equivalent.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant