<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: QUICOPY</title>
    <description>The latest articles on Forem by QUICOPY (@quicopy).</description>
    <link>https://forem.com/quicopy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3886173%2Fcecd83d7-aff8-4844-9e98-68ea115f1a29.png</url>
      <title>Forem: QUICOPY</title>
      <link>https://forem.com/quicopy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/quicopy"/>
    <language>en</language>
    <item>
      <title>The macOS Global Shortcut That Won't Fire in Zed: When a 23-Year-Old Carbon API Meets a Self-Drawn Terminal</title>
      <dc:creator>QUICOPY</dc:creator>
      <pubDate>Tue, 21 Apr 2026 03:38:43 +0000</pubDate>
      <link>https://forem.com/quicopy/the-macos-global-shortcut-that-wont-fire-in-zed-when-a-23-year-old-carbon-api-meets-a-self-drawn-24o9</link>
      <guid>https://forem.com/quicopy/the-macos-global-shortcut-that-wont-fire-in-zed-when-a-23-year-old-carbon-api-meets-a-self-drawn-24o9</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;A user reported that &lt;a href="https://apps.apple.com/app/quicopy/id6761418490" rel="noopener noreferrer"&gt;QUICOPY&lt;/a&gt;'s global shortcut &lt;code&gt;⌘1&lt;/code&gt; fires in iTerm2, Ghostty, Warp, and even in Zed's editor — but &lt;strong&gt;silently does nothing inside Zed's built-in terminal&lt;/strong&gt;. Changing the key combo doesn't help. Restarting doesn't help.&lt;/p&gt;

&lt;p&gt;The cause is not a key conflict. It's an &lt;strong&gt;event dispatch architecture mismatch&lt;/strong&gt;: Carbon's &lt;code&gt;RegisterEventHotKey&lt;/code&gt; (the API almost every indie macOS app uses for global hotkeys) only fires when the frontmost app &lt;em&gt;doesn't&lt;/em&gt; consume the event. Zed's GPUI-based terminal consumes every &lt;code&gt;keyDown:&lt;/code&gt; it sees. Carbon never gets a chance.&lt;/p&gt;

&lt;p&gt;I originally planned a three-tier fallback (&lt;code&gt;CGEventTap&lt;/code&gt; → &lt;code&gt;NSEvent&lt;/code&gt; → &lt;code&gt;Carbon&lt;/code&gt;) to cover every regime. What I actually shipped for the Mac App Store is &lt;strong&gt;two tiers&lt;/strong&gt; (&lt;code&gt;NSEvent&lt;/code&gt; + &lt;code&gt;Carbon&lt;/code&gt;) plus a separate paste-strategy rewrite I didn't see coming. This post is the design log for both.&lt;/p&gt;

&lt;p&gt;If you ship a macOS app with global shortcuts in 2026 and your users live in Zed, VS Code, or any Electron/Tauri/Flutter app that self-draws its text UI — you're going to hit this. Here's the full autopsy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Correction from my last post
&lt;/h2&gt;

&lt;p&gt;In my &lt;a href="https://www.quicopy.com/blog/macos-sandbox-keyboard-shortcuts.html" rel="noopener noreferrer"&gt;previous post&lt;/a&gt; I wrote that QUICOPY uses &lt;code&gt;CGEvent.tapCreate&lt;/code&gt; to capture global shortcuts. That was a lie — or more precisely, what I had originally prototyped but never shipped. The released build uses &lt;strong&gt;Carbon &lt;code&gt;RegisterEventHotKey&lt;/code&gt;&lt;/strong&gt;, because CGEventTap requires an Accessibility permission prompt I wanted to avoid, and Carbon works out of the box in a sandboxed app with zero entitlements.&lt;/p&gt;

&lt;p&gt;That decision is exactly what broke in Zed. This post is the correction.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bug Report
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"I pressed ⌘1 in Zed's terminal. Nothing happens. Menu bar icon doesn't flash. No text inserted."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Compatibility matrix after reproducing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Host app&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;⌘1&lt;/code&gt; fires?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iTerm2&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ghostty&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warp&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zed — &lt;strong&gt;editor&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zed — &lt;strong&gt;built-in terminal&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VS Code — editor&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VS Code — &lt;strong&gt;built-in terminal&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chrome / Safari&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The failure is not about Zed the application. It's specifically about the &lt;em&gt;terminal view inside Zed&lt;/em&gt;. That's a strange enough shape to rule out the usual suspects immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrong Hypothesis #1: "Zed stole the key"
&lt;/h2&gt;

&lt;p&gt;First instinct: Zed has its own &lt;code&gt;⌘1&lt;/code&gt; binding (switch to tab 1, or switch pane), and it's grabbing the key before the OS routes it to anything else.&lt;/p&gt;

&lt;p&gt;I unbound Zed's &lt;code&gt;⌘1&lt;/code&gt; and tried again. Still broken.&lt;br&gt;
I switched QUICOPY's shortcut to &lt;code&gt;⌘⌃F9&lt;/code&gt; — a combination no sane app would ever claim. Still broken.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you change the key and the bug stays, you're not looking at a key conflict. You're looking at &lt;strong&gt;something upstream of key identity&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Wrong Hypothesis #2: "My handler isn't registered"
&lt;/h2&gt;

&lt;p&gt;Second instinct: maybe Carbon failed to install the hotkey when Zed became frontmost. Some shell-out race condition.&lt;/p&gt;

&lt;p&gt;Logs from &lt;code&gt;HotkeyManager&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[HotkeyManager] Started with 7 hot keys registered
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All seven mappings register cleanly. But the Carbon callback — the function that Carbon is supposed to invoke when my hotkey matches — is &lt;strong&gt;never called&lt;/strong&gt; when I press &lt;code&gt;⌘1&lt;/code&gt; inside Zed's terminal.&lt;/p&gt;

&lt;p&gt;Which means the problem isn't in my code. It's in the pipe &lt;em&gt;before&lt;/em&gt; my code.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Culprit: How Carbon Actually Dispatches
&lt;/h2&gt;

&lt;p&gt;Carbon's event model is documented, but the documentation buries the critical detail. Here's the dispatch order for a &lt;code&gt;keyDown&lt;/code&gt; event on modern macOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────────────────────────────────┐
│ Hardware → WindowServer → NSEvent     │
└──────────────────┬────────────────────┘
                   ▼
         ┌──────────────────┐
         │ Frontmost App    │  NSApplication.sendEvent:
         │ (AppKit)         │  → NSWindow.sendEvent:
         └────────┬─────────┘  → NSResponder chain
                  │
          ┌───────┴────────┐
          │                │
    event consumed   event not consumed
    (returns YES)    (returns NO / unhandled)
          │                │
          ▼                ▼
      STOP            ┌────────────────────┐
                      │ Carbon Event Mgr   │
                      │ RegisterEventHotKey│ ← your callback fires here
                      └────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Carbon's hotkey dispatch runs &lt;strong&gt;after&lt;/strong&gt; the frontmost app has had a chance to handle the event, and &lt;strong&gt;only&lt;/strong&gt; if the app reports "unhandled." This design comes from Carbon's original purpose: application-level shortcut registration for the &lt;em&gt;frontmost&lt;/em&gt; app, extended over time to become a pseudo-global mechanism by registering at the process-group level.&lt;/p&gt;

&lt;p&gt;For a traditional AppKit app, the responder chain dutifully returns "unhandled" for any key that doesn't match a menu item or an active text field. Carbon fires. All is well.&lt;/p&gt;

&lt;p&gt;For an app whose text area is an &lt;code&gt;NSTextView&lt;/code&gt; — iTerm2, Terminal.app, TextEdit — same story. &lt;code&gt;NSTextView&lt;/code&gt; gracefully forwards keys it doesn't recognize.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But Zed's editor and terminal are not &lt;code&gt;NSTextView&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Zed's Terminal Specifically: GPUI + Unconditional Consumption
&lt;/h2&gt;

&lt;p&gt;Zed is built on &lt;a href="https://www.gpui.rs/" rel="noopener noreferrer"&gt;GPUI&lt;/a&gt;, a Rust UI framework that renders its entire interface with Metal shaders. There are no &lt;code&gt;NSTextView&lt;/code&gt; instances inside Zed's main content area — just a single &lt;code&gt;NSView&lt;/code&gt; that captures raw input and dispatches it to GPUI's own keyboard handling.&lt;/p&gt;

&lt;p&gt;Zed's terminal view uses a class called &lt;code&gt;TerminalInputHandler&lt;/code&gt; (you can see traces of this in the open-source repo and in related bug reports: &lt;a href="https://github.com/zed-industries/zed/issues/15517" rel="noopener noreferrer"&gt;Zed #15517&lt;/a&gt;, &lt;a href="https://github.com/zed-industries/zed/issues/16187" rel="noopener noreferrer"&gt;Zed #16187&lt;/a&gt;). For every &lt;code&gt;keyDown:&lt;/code&gt; event, &lt;code&gt;TerminalInputHandler&lt;/code&gt; does one of two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If the key matches a registered Zed binding → run the Zed action, &lt;strong&gt;return handled&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If nothing matches → forward the keystroke to the shell process (PTY write), &lt;strong&gt;return handled&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note the important word in both branches: &lt;strong&gt;handled&lt;/strong&gt;. Zed's terminal never returns "unhandled" — because from its perspective, there's no such thing as an unhandled keystroke. Either it's a command or it goes to the shell.&lt;/p&gt;

&lt;p&gt;This is perfectly correct behavior for a terminal. A terminal's whole job is to eat keystrokes. But it means Carbon upstream sees "app consumed the event" and refuses to fire any registered hotkey.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Changing the shortcut combo changes nothing. The event was consumed before Carbon was even consulted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not a Zed bug. VS Code's integrated terminal, which uses xterm.js inside an Electron &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;, has the same behavior. Any modern self-drawn UI framework — Flutter macOS, Tauri with custom keyboard handling, JetBrains IDEs in certain modes — can reproduce this. Carbon's assumption that "the frontmost app will pass unknown keys through" has quietly rotted as native text views have been replaced by self-drawn ones.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Planned: A Three-Tier Auto Fallback
&lt;/h2&gt;

&lt;p&gt;Three APIs can observe keyboard events system-wide. The tradeoffs are what make this interesting:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Intercept point&lt;/th&gt;
&lt;th&gt;Fires even when app consumes&lt;/th&gt;
&lt;th&gt;Sandbox-friendly&lt;/th&gt;
&lt;th&gt;Permission&lt;/th&gt;
&lt;th&gt;Can swallow event&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;RegisterEventHotKey&lt;/code&gt; (Carbon)&lt;/td&gt;
&lt;td&gt;After frontmost app&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;CGEvent.tapCreate&lt;/code&gt; (&lt;code&gt;.cgSessionEventTap&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Before frontmost app&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;❌ in MAS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Accessibility&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NSEvent.addGlobalMonitorForEvents&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Outside frontmost app&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Accessibility&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two columns that matter: &lt;strong&gt;Fires even when app consumes&lt;/strong&gt; and &lt;strong&gt;Can swallow event&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Carbon loses the first. That's our bug.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CGEventTap&lt;/code&gt; wins both — but Apple does not allow it in Mac App Store sandboxed apps. (Officially, &lt;code&gt;com.apple.security.app-sandbox = true&lt;/code&gt; + event tap creation returns nil. Unofficially, confirmed across a half-decade of developer forum posts.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NSEvent&lt;/code&gt; global monitor wins the first column and runs fine under sandbox, but &lt;strong&gt;cannot swallow events&lt;/strong&gt;. So if you bind &lt;code&gt;⌘1&lt;/code&gt; and the user is in Chrome, they'll both switch to the first tab &lt;em&gt;and&lt;/em&gt; fire your shortcut. That's a bad experience for a text expansion tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No single backend wins. My original plan was to run all three behind a single protocol and pick at startup based on runtime conditions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="kt"&gt;HotkeyMonitoring&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AnyObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isRunning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isRecordingSuspended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;onHotkeyTriggered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;KeyMapping&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;mappings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;KeyMapping&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;   &lt;span class="c1"&gt;// return false → caller falls through&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;updateMappings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;mappings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;KeyMapping&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three implementations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;HotkeyManager&lt;/code&gt; — the existing Carbon code, untouched&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EventTapHotkeyManager&lt;/code&gt; — new, for non-sandbox + AX-authorized&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NSEventHotkeyManager&lt;/code&gt; — new, for sandbox-safe fallback&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Selection logic at launch and whenever Accessibility permission flips:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;chooseBackend&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;any&lt;/span&gt; &lt;span class="kt"&gt;HotkeyMonitoring&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;sandboxed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SandboxInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isSandboxed&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;axGranted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;permissionManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isAccessibilityGranted&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;sandboxed&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;axGranted&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;EventTapHotkeyManager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;        &lt;span class="c1"&gt;// best: Zed-compatible + swallows&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;axGranted&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;NSEventHotkeyManager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;          &lt;span class="c1"&gt;// sandbox fallback: Zed-compatible&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;HotkeyManager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                     &lt;span class="c1"&gt;// zero-permission baseline&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And subscribe to the permission flip so the user upgrades automatically once they grant Accessibility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;permissionManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;$isAccessibilityGranted&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeDuplicates&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dropFirst&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;weak&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hotSwapBackend&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cancellables&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To detect the sandbox at runtime (rather than &lt;code&gt;#if SANDBOXED&lt;/code&gt;), I planned to read the app's own entitlements via &lt;code&gt;Security.framework&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;SandboxInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;isSandboxed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SecTaskCreateFromSelf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SecTaskCopyValueForEntitlement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"com.apple.security.app-sandbox"&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;CFString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="kc"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also sketched out the CGEventTap edge cases I'd have to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.tapDisabledByTimeout&lt;/code&gt; / &lt;code&gt;.tapDisabledByUserInput&lt;/code&gt; → re-enable, circuit-break after 3 consecutive failures, fall back to NSEvent&lt;/li&gt;
&lt;li&gt;Fresh AX grant where &lt;code&gt;tapCreate&lt;/code&gt; returns &lt;code&gt;nil&lt;/code&gt; → treat as soft failure, fall back, show a one-time toast asking for restart&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@MainActor&lt;/code&gt; isolation for the C callback — planned to be nasty&lt;/li&gt;
&lt;li&gt;A generation token to reject stale callbacks after a backend swap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was the plan. It's a satisfying bit of architecture on paper. &lt;strong&gt;It's not what I shipped.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Actually Shipped: Two Tiers and a Paste Rewrite
&lt;/h2&gt;

&lt;p&gt;QUICOPY ships &lt;strong&gt;only on the Mac App Store&lt;/strong&gt;. The sandbox makes &lt;code&gt;CGEventTap&lt;/code&gt; permanently unavailable for this target. Rather than maintain a tier of code that's statically unreachable in my only distribution channel, I collapsed the architecture to what actually runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="kt"&gt;HotkeyMonitoring&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AnyObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isRunning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isRecordingSuspended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;onHotkeyTriggered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;KeyMapping&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;mappings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;KeyMapping&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;updateMappings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;mappings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;KeyMapping&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two implementations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;NSEventHotkeyManager&lt;/code&gt; — when Accessibility is granted&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HotkeyManager&lt;/code&gt; — Carbon, when it isn't&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Selection logic collapses to a single &lt;code&gt;if&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;chooseAndStartMonitor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;dispatchPrecondition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;suspended&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activeMonitor&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isRecordingSuspended&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="n"&gt;activeMonitor&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;authed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;permissionManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isAccessibilityGranted&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;any&lt;/span&gt; &lt;span class="kt"&gt;HotkeyMonitoring&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authed&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;NSEventHotkeyManager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;HotkeyManager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;installCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isRecordingSuspended&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;suspended&lt;/span&gt;
    &lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;mappings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mappingStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mappings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;activeMonitor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;monitor&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;start() -&amp;gt; Bool&lt;/code&gt;. No factory array. No &lt;code&gt;assertionFailure&lt;/code&gt; hard-floor. No generation tokens. The code reads exactly as the design is: two states, one switch.&lt;/p&gt;

&lt;p&gt;I still subscribe to the permission flip so the user upgrades automatically once they grant Accessibility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;permissionManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;$isAccessibilityGranted&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeDuplicates&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dropFirst&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;weak&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chooseAndStartMonitor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cancellables&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;End state: grant AX → &lt;code&gt;NSEventHotkeyManager&lt;/code&gt; starts → Zed's terminal receives &lt;code&gt;⌘1&lt;/code&gt; events via the global monitor → QUICOPY fires its mapping. Revoke AX → &lt;code&gt;chooseAndStartMonitor&lt;/code&gt; re-runs → Carbon comes back as the fallback → Zed's terminal stops responding (expected).&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;trade-off&lt;/strong&gt; you're making by shipping only NSEvent + Carbon on sandboxed macOS: when the user is in Chrome and hits &lt;code&gt;⌘1&lt;/code&gt;, Chrome still switches to its first tab &lt;em&gt;and&lt;/em&gt; QUICOPY inserts its text. No backend can swallow the event in the sandbox. In practice users learn to avoid &lt;code&gt;⌘1..9&lt;/code&gt; for bindings that collide with browser tab shortcuts, and the in-app permission screen suggests &lt;code&gt;⌘⌃1..9&lt;/code&gt; or &lt;code&gt;⌘⌥1..9&lt;/code&gt; combos instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Surprise: Carbon Is Not the Only Thing That Broke in Zed
&lt;/h2&gt;

&lt;p&gt;Getting the event to fire was half the fight. Getting text to actually appear in Zed's terminal was the other half — and this part isn't in any original design doc, because it only surfaced during the regression pass.&lt;/p&gt;

&lt;p&gt;QUICOPY's text-output strategy on the MAS side has always been: write to the pasteboard → trigger &lt;code&gt;Edit → Paste&lt;/code&gt; via AppleScript &lt;code&gt;click menu item "Paste" of menu 1 of menu bar item "Edit"&lt;/code&gt;. Menu click goes through AppKit's first-responder chain, doesn't depend on physical modifier keys, and is immune to IME state. It works beautifully in every &lt;code&gt;NSTextView&lt;/code&gt;-based app.&lt;/p&gt;

&lt;p&gt;In Zed's terminal, something very strange happens instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;⌘1&lt;/code&gt; fires (good — NSEvent monitor is working)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;onHotkeyTriggered&lt;/code&gt; callback runs (good)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TextOutputManager.output&lt;/code&gt; writes "hello world" to the pasteboard and runs the AppleScript&lt;/li&gt;
&lt;li&gt;The AppleScript &lt;strong&gt;returns without error&lt;/strong&gt; — my code logs &lt;code&gt;paste path=menu_en&lt;/code&gt;, marks it as autoPasted, and celebrates&lt;/li&gt;
&lt;li&gt;Nothing appears in the terminal&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No error. No failure. The AppleScript says it clicked the Paste menu item, the menu item exists in Zed's &lt;code&gt;Edit&lt;/code&gt; menu, and the click is reported as successful. &lt;strong&gt;But the paste doesn't actually happen.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The autoPasted Illusion
&lt;/h3&gt;

&lt;p&gt;This is a deeply subtle bug. The old &lt;code&gt;TextOutputManager&lt;/code&gt; judged "paste succeeded" purely by whether the AppleScript threw an exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeAndReturnError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clipboardOnly&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autoPasted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But AppleScript's &lt;code&gt;click menu item&lt;/code&gt; on a disabled-looking-but-technically-present menu item in a non-AppKit host returns &lt;strong&gt;no error&lt;/strong&gt; and performs &lt;strong&gt;no visible action&lt;/strong&gt;. Zed's &lt;code&gt;Edit → Paste&lt;/code&gt; menu item is routed to whichever buffer GPUI considers focused — and when that focused buffer is the built-in terminal, the menu-click route apparently doesn't reach the terminal's input handler.&lt;/p&gt;

&lt;p&gt;So the fix: stop trusting "AppleScript didn't throw" as a proxy for "paste happened." Ask AppleScript to check the menu item's &lt;code&gt;enabled&lt;/code&gt; state explicitly, and fall back to synthesizing the keystroke directly when the menu route looks unreliable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight applescript"&gt;&lt;code&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;frontProcess&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;editMenu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;menu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;menu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Edit"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;menu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;menu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Paste"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;editMenu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;menu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Paste"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;editMenu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nv"&gt;click&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;menu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Paste"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;editMenu&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nb"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"menu_en"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;editMenuZh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;menu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;menu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"编辑"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;menu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;menu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"粘贴"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;editMenuZh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;menu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"粘贴"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;editMenuZh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nv"&gt;click&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;menu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"粘贴"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;editMenuZh&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nb"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"menu_zh"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;keystroke&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;down&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;-- fallback&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"keystroke"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two useful wins from this structure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The three-way return value is a live diagnostic signal.&lt;/strong&gt; In production logs I now see &lt;code&gt;paste path=menu_en&lt;/code&gt; for iTerm2, &lt;code&gt;paste path=menu_zh&lt;/code&gt; for localized apps, and &lt;code&gt;paste path=keystroke&lt;/code&gt; for Zed, VS Code, and anything else self-drawn. When users report "paste doesn't work," the first question I ask is what path they're on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;keystroke is a real fallback, not a silent substitute.&lt;/strong&gt; Menu click still wins by default for every normal AppKit app (IME-safe, modifier-key-safe), and keystroke kicks in only when the menu route is genuinely missing or disabled.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Self-Paste Trap (a.k.a. error 1002)
&lt;/h3&gt;

&lt;p&gt;While I was testing the paste rewrite, I hit another class of bug: what happens when the user presses a bound shortcut while QUICOPY's own popover is in the foreground?&lt;/p&gt;

&lt;p&gt;Old behavior (Carbon): Carbon fires the callback, the callback writes to the pasteboard, AppleScript tries to keystroke &lt;code&gt;⌘V&lt;/code&gt; into the frontmost process — which &lt;em&gt;is QUICOPY itself&lt;/em&gt;. macOS Accessibility policy then says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;"QUICOPY" is not allowed to send keystrokes.&lt;/code&gt; (Error 1002)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No app is allowed to synthesize keystrokes into itself through System Events. The paste silently fails. The user sees a stray "Copied! Press ⌘V to paste" toast they never asked for. It looks broken.&lt;/p&gt;

&lt;p&gt;This bug has been in the Carbon code &lt;strong&gt;since day one&lt;/strong&gt;. It's just that nobody ever deliberately sits in QUICOPY's own popover and hits a bound shortcut. I found it because the regression matrix includes that exact case.&lt;/p&gt;

&lt;p&gt;I fixed it in two places, because Carbon and NSEvent reach the self-paste code path through different doors:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NSEvent local monitor&lt;/strong&gt; intercepts key events that are dispatched inside QUICOPY. Instead of firing the mapping and letting it fail downstream, I just swallow the event at the monitor layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleLocalKeyDown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;NSEvent&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isRecordingSuspended&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;matchMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Popover is frontmost; don't trigger, don't beep, don't paste to self&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;AppDelegate.handleTriggered&lt;/code&gt;&lt;/strong&gt; is the universal junction the Carbon callback also goes through. I added a frontmost check there as a second layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleTriggered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;KeyMapping&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;frontBundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NSWorkspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frontmostApplication&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bundleIdentifier&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;ownBundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Bundle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bundleIdentifier&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;popoverShown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;popover&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isShown&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;frontBundle&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ownBundle&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;popoverShown&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;   &lt;span class="c1"&gt;// self-paste would trigger error 1002, abort&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ... proceed to paste ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;popoverShown&lt;/code&gt; check is load-bearing. &lt;code&gt;NSWorkspace.frontmostApplication&lt;/code&gt; has an interesting property: when a menu-bar popover is displayed, &lt;code&gt;frontmostApplication&lt;/code&gt; still returns &lt;strong&gt;whatever app was frontmost before the popover opened&lt;/strong&gt;, not QUICOPY itself. I discovered this the hard way when the first version of my defense only checked &lt;code&gt;frontBundle == ownBundle&lt;/code&gt; and still let the popover case through. Reading &lt;code&gt;popover.isShown&lt;/code&gt; is the reliable way to detect "QUICOPY's UI is currently on screen."&lt;/p&gt;

&lt;p&gt;Neither of those checks is elegant. But together they cover both Carbon (which doesn't distinguish foreground/background) and NSEvent (which does), and neither backend now produces the stray &lt;code&gt;error 1002 → clipboardOnly → toast&lt;/code&gt; cascade.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Shortcut Recorder Problem (And Why It Almost Bit Me)
&lt;/h2&gt;

&lt;p&gt;QUICOPY has a "record a shortcut" UI, implemented with &lt;code&gt;NSEvent.addLocalMonitorForEvents(matching: .keyDown)&lt;/code&gt; to capture whatever combo the user presses. Perfectly fine in isolation.&lt;/p&gt;

&lt;p&gt;But the new &lt;code&gt;NSEventHotkeyManager&lt;/code&gt; also uses &lt;code&gt;addLocalMonitorForEvents&lt;/code&gt; to handle self-triggered shortcuts when the popover is frontmost. Two local monitors, same app, racing to consume the same &lt;code&gt;keyDown&lt;/code&gt;. One of them wins, which one depends on registration order and call site — and Apple's docs don't guarantee ordering.&lt;/p&gt;

&lt;p&gt;The fix is a shared &lt;code&gt;isRecordingSuspended: Bool&lt;/code&gt; flag on the monitor. When the user opens the mapping edit screen and taps "record shortcut," &lt;code&gt;MappingEditView.onChange(of: isRecordingShortcut)&lt;/code&gt; flips the flag on via a closure provider. The hotkey monitor's local callback short-circuits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In NSEventHotkeyManager's local monitor:&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isRecordingSuspended&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// pass through untouched&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;matchMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;                              &lt;span class="c1"&gt;// popover self-paste — swallow, don't trigger&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;                                &lt;span class="c1"&gt;// unrelated key — forward&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The order in which the two local monitors fire no longer matters. If the shortcut recorder fires first, it captures the key and returns &lt;code&gt;nil&lt;/code&gt;; my monitor never sees it. If mine fires first, the &lt;code&gt;isRecordingSuspended&lt;/code&gt; check passes the event straight through and the recorder captures it. Either way, the user's recorded shortcut ends up in the recorder, not triggering a mapping.&lt;/p&gt;

&lt;p&gt;The closure-provider pattern is worth flagging: I initially passed &lt;code&gt;HotkeyMonitoring&lt;/code&gt; instances directly into &lt;code&gt;MappingEditView&lt;/code&gt;. That worked until the backend swapped mid-edit (AX permission flipped while the edit screen was open), and the edit screen kept writing &lt;code&gt;isRecordingSuspended&lt;/code&gt; to the old, now-stopped monitor. Passing a closure that resolves &lt;code&gt;appDelegate.activeMonitor&lt;/code&gt; on every call instead of a captured instance reference made the whole chain swap-safe.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Narrowed the Design for a Mac App Store-only Release
&lt;/h2&gt;

&lt;p&gt;Three tiers on paper was intellectually satisfying. Two tiers in production is what QUICOPY actually runs. Here's why the simpler thing is the right thing for my distribution:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Constraint 1: MAS sandbox rejects CGEventTap.&lt;/strong&gt;&lt;br&gt;
Apple's Developer Forums have confirmed this across several macOS releases. There's no entitlement to request, no review workaround, no "just ask for Input Monitoring." &lt;code&gt;CGEvent.tapCreate&lt;/code&gt; under &lt;code&gt;com.apple.security.app-sandbox = true&lt;/code&gt; returns &lt;code&gt;nil&lt;/code&gt;. If I shipped the three-tier code, the CGEventTap branch would be unreachable in every App Store build I'd ever produce.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Constraint 2: I'm not shipping a Developer ID notarized build.&lt;/strong&gt;&lt;br&gt;
Originally I'd kept the Developer ID target as an option for users who wanted the "best" experience — event-swallowing included. Maintaining two distribution channels for a solo-developer indie app, with different permission flows, different crash-report pipelines, different update mechanisms, and a marketing story that has to explain which version to download, turned out to be more work than the feature was worth. I scoped down to MAS-only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consequence: The entire CGEventTap branch is orphan code.&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;EventTapHotkeyManager&lt;/code&gt;, &lt;code&gt;SandboxInfo&lt;/code&gt; runtime detection, the circuit-breaker, the generation token — all of it exists to serve a tier that will never run. Carrying it adds maintenance cost (tests, comments that reference non-existent types, code reviewers asking "why is there a fallback that can't trigger?") without buying anything.&lt;/p&gt;

&lt;p&gt;Shipping the two-tier version in full means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HotkeyMonitoring.start&lt;/code&gt; returns &lt;code&gt;Void&lt;/code&gt;, not &lt;code&gt;Bool&lt;/code&gt;. It can't fail-and-fall-through because there's nothing to fall through to.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chooseAndStartMonitor&lt;/code&gt; is a single &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;else&lt;/code&gt;, not a factory array with a for loop.&lt;/li&gt;
&lt;li&gt;Readers of the code see exactly two backends and one branch. The structure matches the runtime reality.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If I ever do re-introduce a Developer ID channel, I'll add the CGEventTap tier back then — along with the test matrix, the release pipeline, and the marketing copy that all go with it. Reintroducing a protocol member in Swift is a 10-minute mechanical change; scoping the whole effort correctly is the harder work. Keeping placeholder scaffolding around today doesn't reduce that cost materially.&lt;/p&gt;

&lt;p&gt;The word I'd avoid here is "killed." Nothing was killed. The scope was narrowed to the distribution I actually ship, and the code now reflects that.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Boring Problem of Entitlements
&lt;/h2&gt;

&lt;p&gt;People get scared of new entitlements because they're scared of App Review.&lt;/p&gt;

&lt;p&gt;For this migration, the entitlements file does not change at all:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;com.apple.security.app-sandbox&lt;/code&gt; — already set for MAS target&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;com.apple.security.automation.apple-events&lt;/code&gt; — already set for pasting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;NSAccessibilityUsageDescription&lt;/code&gt; is &lt;strong&gt;not required&lt;/strong&gt; for QUICOPY today. &lt;code&gt;AXIsProcessTrustedWithOptions(nil)&lt;/code&gt; performs a silent check that doesn't trigger the system permission prompt, and my UI routes the user to System Settings directly when they opt into the Zed-compatible mode. If your app calls any of the prompting variants of that API, you'll want to supply the usage string — but QUICOPY never does.&lt;/p&gt;

&lt;p&gt;Hardened Runtime is already on (&lt;code&gt;ENABLE_HARDENED_RUNTIME = YES&lt;/code&gt;) and requires no additional &lt;code&gt;com.apple.security.cs.*&lt;/code&gt; exceptions for &lt;code&gt;NSEvent&lt;/code&gt; monitors. I'd been paranoid about that for weeks before actually testing it. No exception needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Final Flow Looks Like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User launches QUICOPY
       │
       ▼
PermissionManager probes AX via AXIsProcessTrustedWithOptions(nil)
       │
       ▼
AppDelegate.chooseAndStartMonitor()
       │
       ├─ AX granted     → NSEventHotkeyManager starts
       └─ AX not granted → HotkeyManager starts (Carbon legacy)
       │
       ▼
User grants AX later → applicationDidBecomeActive → probeAccessibility() → $isAccessibilityGranted fires → chooseAndStartMonitor() re-runs → swap to NSEvent
       │
       ▼
User presses ⌘1 inside Zed's terminal
       │
       ▼ (with NSEvent backend)
Event captured via addGlobalMonitorForEvents BEFORE Zed's TerminalInputHandler sees it
       │
       ▼
KeyMapping matched → AppDelegate.handleTriggered
       │
       ▼
TextOutputManager writes text to pasteboard, then AppleScript:
  • Checks Edit→Paste enabled state
  • enabled → click menu item "Paste"
  • disabled / absent → fallback to keystroke ⌘V
       │
       ▼
Text appears at the cursor inside Zed's terminal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bottleneck in the chain is the clipboard → AppleScript round-trip. Hotkey capture itself is negligible on any backend.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Tell You If You're Building Something Similar
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't start with Carbon.&lt;/strong&gt; It's tempting because it's the zero-permission path. But in 2026, "zero permission" means "silently broken in a growing list of self-drawn apps." Budget an AX permission request into your onboarding from day one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test in Zed, VS Code, and at least one Electron/Tauri app.&lt;/strong&gt; &lt;code&gt;NSTextView&lt;/code&gt;-based apps will always work. Self-drawn UIs are the canary.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pick your distribution channel before you pick your hotkey API.&lt;/strong&gt; MAS-only means you can't use CGEventTap, full stop. Developer ID gives you that option but costs you a separate release pipeline. Decide first, architect second.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Subscribe to AX permission changes.&lt;/strong&gt; &lt;code&gt;applicationDidBecomeActive&lt;/code&gt; + &lt;code&gt;AXIsProcessTrustedWithOptions(nil)&lt;/code&gt; is the closest macOS gives you to a "permission flipped" signal. There's no proper notification API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't trust "AppleScript didn't throw" as "paste succeeded."&lt;/strong&gt; In self-drawn hosts, &lt;code&gt;click menu item&lt;/code&gt; can claim success on a menu that doesn't actually route to the focused view. Check &lt;code&gt;enabled&lt;/code&gt; explicitly and keep a &lt;code&gt;keystroke&lt;/code&gt; fallback.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Two local monitors on the same &lt;code&gt;keyDown&lt;/code&gt; is a race.&lt;/strong&gt; Use a shared suspended flag, not priority ordering. The recorder UI and the hotkey monitor cannot both assume they go first.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check &lt;code&gt;popover.isShown&lt;/code&gt; when defending against self-paste.&lt;/strong&gt; &lt;code&gt;NSWorkspace.frontmostApplication&lt;/code&gt; won't tell you that a menu-bar popover is currently visible; it still reports the app underneath.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Meta: Two Posts, Two Sides of the Same Wall
&lt;/h2&gt;

&lt;p&gt;If you read &lt;a href="https://www.quicopy.com/blog/macos-sandbox-keyboard-shortcuts.html" rel="noopener noreferrer"&gt;the previous post&lt;/a&gt;, you have the full story now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Output side&lt;/strong&gt;: &lt;code&gt;CGEvent.post()&lt;/code&gt; is blocked by sandbox. Workaround: clipboard + AppleScript &lt;code&gt;System Events&lt;/code&gt;, with menu-enabled checks for self-drawn hosts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input side&lt;/strong&gt;: Carbon &lt;code&gt;RegisterEventHotKey&lt;/code&gt; is blocked by self-drawn frontmost apps. Workaround: upgrade to &lt;code&gt;NSEvent&lt;/code&gt; global monitor whenever Accessibility is granted; keep Carbon as the zero-permission fallback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both problems stem from the same underlying fact: macOS's global-shortcut APIs were designed when the frontmost app was always an AppKit app with an &lt;code&gt;NSTextView&lt;/code&gt; in it, and always happy to let the OS see events it didn't care about. That world is gone. Zed, VS Code, Warp's Rust UI, Flutter desktop apps, every Electron app with a canvas text surface — they all break the old assumptions, each in their own way.&lt;/p&gt;

&lt;p&gt;What you ship for this class of problem is not a single API choice. It's an &lt;em&gt;adaptive&lt;/em&gt; architecture that picks the right tool for the permission and distribution channel you're actually in.&lt;/p&gt;




&lt;h2&gt;
  
  
  About QUICOPY
&lt;/h2&gt;

&lt;p&gt;I build &lt;a href="https://www.quicopy.com" rel="noopener noreferrer"&gt;QUICOPY&lt;/a&gt; — a macOS menu bar app that turns global shortcuts into instant text output, with 7 built-in AI prompt templates. It's on the &lt;a href="https://apps.apple.com/app/quicopy/id6761418490" rel="noopener noreferrer"&gt;Mac App Store&lt;/a&gt; for $9.99 lifetime or $1.99/month with a 7-day free trial.&lt;/p&gt;

&lt;p&gt;If you want to see how all the pieces in this post ship in a real app, grab the binary.&lt;/p&gt;

&lt;p&gt;Questions, corrections, or better approaches? Especially interested in hearing from anyone building non-AppKit macOS apps who has opinions on whether Carbon should finally be retired, or whether GPUI / Flutter should be patched to forward unhandled keys upstream. I don't think either fix is coming. So here we are.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally posted on &lt;a href="https://www.quicopy.com" rel="noopener noreferrer"&gt;quicopy.com&lt;/a&gt;. Follow me for more macOS indie dev notes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>macos</category>
      <category>swift</category>
      <category>zed</category>
      <category>indie</category>
    </item>
    <item>
      <title>Shipping Global Keyboard Shortcuts on macOS Sandbox: The Part Apple Doesn't Document</title>
      <dc:creator>QUICOPY</dc:creator>
      <pubDate>Sat, 18 Apr 2026 14:49:56 +0000</pubDate>
      <link>https://forem.com/quicopy/shipping-global-keyboard-shortcuts-on-macos-sandbox-the-part-apple-doesnt-document-57no</link>
      <guid>https://forem.com/quicopy/shipping-global-keyboard-shortcuts-on-macos-sandbox-the-part-apple-doesnt-document-57no</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I shipped a macOS menu bar app (&lt;a href="https://apps.apple.com/app/quicopy/id6761418490" rel="noopener noreferrer"&gt;QUICOPY&lt;/a&gt;) to the Mac App Store. Its core feature is &lt;strong&gt;global keyboard shortcuts that output text in any app&lt;/strong&gt;. The obvious implementation — &lt;code&gt;CGEvent.post()&lt;/code&gt; — is silently blocked by App Sandbox. Apple does not document this clearly. Here's what actually works, and why.&lt;/p&gt;

&lt;p&gt;If you're building anything that simulates keyboard input on macOS and targeting the App Store, this will save you a week.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I wanted a menu bar app where pressing &lt;code&gt;⌘⇧1&lt;/code&gt; in any application types a piece of pre-set text at the cursor. TextExpander-style. Simple, right?&lt;/p&gt;

&lt;p&gt;Two subproblems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Capture&lt;/strong&gt; a global keyboard shortcut (even when my app is not focused)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output&lt;/strong&gt; text into whatever app is focused&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both sound like 10-line solutions. Neither is, if you want to be on the Mac App Store.&lt;/p&gt;




&lt;h2&gt;
  
  
  Capturing Global Shortcuts: CGEvent Tap
&lt;/h2&gt;

&lt;p&gt;This part is mostly fine. Use &lt;code&gt;CGEvent.tapCreate&lt;/code&gt; with &lt;code&gt;.cgSessionEventTap&lt;/code&gt; and listen for &lt;code&gt;.keyDown&lt;/code&gt; events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;eventMask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="kt"&gt;CGEventType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keyDown&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;tap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tapCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgSessionEventTap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;place&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headInsertEventTap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;defaultTap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;eventsOfInterest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGEventMask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eventMask&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nv"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="c1"&gt;// Inspect event.flags and keycode here&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;Unmanaged&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;passUnretained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nv"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sandbox gotcha:&lt;/strong&gt; &lt;code&gt;CGEvent.tapCreate&lt;/code&gt; requires the user to grant your app &lt;strong&gt;Input Monitoring&lt;/strong&gt; (or in some cases Accessibility) permission. This is fine — you get a permission prompt, user approves, done.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ &lt;strong&gt;This part works in a sandboxed Mac App Store app.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; monitor events at the session level as long as the user grants the permission. The permission is called "Input Monitoring" in macOS 10.15+ and it specifically covers event taps.&lt;/p&gt;

&lt;p&gt;No entitlement file magic needed for input monitoring — the sandbox allows the system permission prompt to handle it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Outputting Text: The Approach That Everyone Tries First
&lt;/h2&gt;

&lt;p&gt;Naturally, you reach for &lt;code&gt;CGEvent.post()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Synthesize ⌘V&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGEventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;stateID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;combinedSessionState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;cmdVDown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;keyboardEventSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;virtualKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;keyDown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cmdVDown&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maskCommand&lt;/span&gt;
&lt;span class="n"&gt;cmdVDown&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgSessionEventTap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works flawlessly in a non-sandboxed development build. You celebrate.&lt;/p&gt;

&lt;p&gt;Then you enable App Sandbox (required for App Store), run it, and... &lt;strong&gt;nothing happens&lt;/strong&gt;. No error. No log. No permission prompt. The &lt;code&gt;.post()&lt;/code&gt; call just silently does nothing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Undocumented Rule
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;CGEvent.post()&lt;/code&gt; is completely blocked inside App Sandbox.&lt;/strong&gt; There is no entitlement that re-enables it. Apple will not provide one.&lt;/p&gt;

&lt;p&gt;I spent two days reading every developer forum thread, every entitlement documentation page, every StackOverflow answer. The closest Apple documentation admits it is this line in the App Sandbox Design Guide, which you have to squint to find:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sending synthetic events to other processes is disallowed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it. One sentence. In a guide that most indie devs stop reading after the "Container Directory" section.&lt;/p&gt;

&lt;p&gt;The workarounds I saw suggested online, which &lt;strong&gt;do not work&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Adding &lt;code&gt;com.apple.security.temporary-exception.user-interaction&lt;/code&gt; — deprecated, no longer honored&lt;/li&gt;
&lt;li&gt;❌ Adding &lt;code&gt;com.apple.security.device.input&lt;/code&gt; — wrong entitlement, not related&lt;/li&gt;
&lt;li&gt;❌ Using &lt;code&gt;HIDPostAuxKey&lt;/code&gt; — same sandbox rule applies&lt;/li&gt;
&lt;li&gt;❌ Using &lt;code&gt;IOHIDPostEvent&lt;/code&gt; — same&lt;/li&gt;
&lt;li&gt;❌ Requesting Accessibility permission — has no effect on this&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What Actually Works: AppleScript System Events
&lt;/h2&gt;

&lt;p&gt;The workaround is to pretend to be an automation client and ask &lt;code&gt;System Events&lt;/code&gt; (an Apple-signed helper) to type for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write the text into the clipboard (&lt;code&gt;NSPasteboard&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Send an AppleScript "System Events → keystroke ⌘V" via &lt;code&gt;NSAppleScript&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Restore the previous clipboard contents
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;outputText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;pb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NSPasteboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;general&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;savedItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pasteboardItems&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compactMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* snapshot */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearContents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;forType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
    tell application "&lt;/span&gt;&lt;span class="kt"&gt;System&lt;/span&gt; &lt;span class="kt"&gt;Events&lt;/span&gt;&lt;span class="s"&gt;"
        keystroke "&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="s"&gt;" using command down
    end tell
    """&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSDictionary&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="kt"&gt;NSAppleScript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeAndReturnError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Restore clipboard after a short delay (let the paste complete)&lt;/span&gt;
    &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asyncAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearContents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;// re-write savedItems&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Required entitlement:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;com.apple.security.automation.apple-events&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Required Info.plist entry&lt;/strong&gt; (macOS 10.14+):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSAppleEventsUsageDescription&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;QUICOPY uses System Events to paste your shortcut text into the currently focused application.&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On first use, macOS shows the user a permission prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"QUICOPY wants permission to control System Events."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;User clicks Allow → done. Works for all subsequent invocations.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Latency Trade-off
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CGEvent.post()&lt;/code&gt; (if it worked) would be ~5 ms. The AppleScript round-trip is 40–80 ms on a modern M-series Mac, closer to 100 ms on Intel.&lt;/p&gt;

&lt;p&gt;For a text-expansion use case, this is invisible to the user — it feels instant. The whole flow (user releases shortcut → text appears) stays under 100 ms on Apple Silicon, which is the human perception threshold.&lt;/p&gt;

&lt;p&gt;If you need &lt;em&gt;true&lt;/em&gt; sub-10-ms latency (game input, accessibility apps), App Store distribution is probably not your path. Direct notarized distribution outside the store lifts the sandbox and lets you use &lt;code&gt;CGEvent.post()&lt;/code&gt; directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Clipboard Restoration Problem
&lt;/h2&gt;

&lt;p&gt;The paste trick mutates the user's clipboard. If you don't restore it, users will paste your shortcut text into their next real Cmd+V — very bad UX.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Naive fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NSPasteboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;general&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// ... do paste ...&lt;/span&gt;
&lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asyncAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;NSPasteboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;general&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;forType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; clipboard can hold multiple representations — images, rich text, file promises. A &lt;code&gt;.string(forType:)&lt;/code&gt; snapshot throws all of that away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pasteboardItems&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compactMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;NSPasteboardItem&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NSPasteboardItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;forType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ... paste ...&lt;/span&gt;

&lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asyncAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearContents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeObjects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Iterate every &lt;code&gt;NSPasteboardItem.types&lt;/code&gt; and copy the raw &lt;code&gt;Data&lt;/code&gt; for each. Preserves everything including image clipboard, file references, and rich text.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Timing Problem
&lt;/h2&gt;

&lt;p&gt;If you restore the clipboard too fast, you restore &lt;em&gt;before&lt;/em&gt; &lt;code&gt;System Events&lt;/code&gt; has read it → the paste gets nothing.&lt;/p&gt;

&lt;p&gt;If you restore too slowly, there's a visible window where the user's original clipboard is gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Empirical finding:&lt;/strong&gt; 100 ms delay is safe on Apple Silicon. 150 ms on Intel. &lt;code&gt;executeAndReturnError&lt;/code&gt; does not block until the paste is truly complete — it returns as soon as the AppleScript dispatch succeeds.&lt;/p&gt;

&lt;p&gt;Do &lt;strong&gt;not&lt;/strong&gt; rely on &lt;code&gt;DispatchQueue.sync&lt;/code&gt; thinking it'll wait — it won't, the paste is asynchronous on System Events' side.&lt;/p&gt;




&lt;h2&gt;
  
  
  App Store Review Pitfalls
&lt;/h2&gt;

&lt;p&gt;When I submitted to the App Store, the reviewer flagged &lt;strong&gt;Accessibility&lt;/strong&gt; in my initial build. I had included Accessibility API calls as a fallback for when Automation permission wasn't granted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apple's position&lt;/strong&gt;: If you can accomplish the task through a less-privileged mechanism (Automation), don't ask for the more-privileged one (Accessibility).&lt;/p&gt;

&lt;p&gt;I removed all Accessibility code and replaced it with a non-blocking permission-request flow that only asks for Automation. Resubmitted. Approved.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ &lt;strong&gt;Lesson:&lt;/strong&gt; Minimize entitlements. Each entitlement is a question the reviewer will ask "do you really need this?"&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What the Final Architecture Looks Like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User presses ⌘⇧1
         │
         ▼
CGEvent Tap (captures system-wide keydown)
         │
         ▼
Match against user's shortcut mapping
         │
         ▼
Load text snippet → NSPasteboard
         │
         ▼
NSAppleScript: "keystroke v using command down"
         │
         ▼
macOS System Events types Cmd+V into focused app
         │
         ▼
Restore previous clipboard (100ms delay)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total latency: ~80–100 ms on M-series.&lt;br&gt;
Total code: ~200 lines Swift.&lt;br&gt;
App binary size: 2.8 MB (no Electron).&lt;br&gt;
App Store: approved.&lt;/p&gt;




&lt;h2&gt;
  
  
  If You're Building Something Similar
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read the App Sandbox Design Guide twice.&lt;/strong&gt; The fine print matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't assume CGEvent.post works&lt;/strong&gt;, even if your dev build works. Test with sandbox enabled early.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snapshot clipboard as pasteboard items&lt;/strong&gt;, not strings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a permission request UI&lt;/strong&gt; for &lt;code&gt;com.apple.security.automation.apple-events&lt;/code&gt;. Without it, the AppleScript fails silently the first time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimize entitlements.&lt;/strong&gt; Every one is a review friction point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consider whether you really need App Store&lt;/strong&gt;. Direct notarized distribution gives you more freedom but less reach.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  About QUICOPY
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://www.quicopy.com" rel="noopener noreferrer"&gt;QUICOPY&lt;/a&gt; — a menu bar app that does exactly what this post describes, plus 7 built-in AI prompt templates mapped to &lt;code&gt;⌘⇧1&lt;/code&gt; through &lt;code&gt;⌘⇧7&lt;/code&gt;. It's on the &lt;a href="https://apps.apple.com/app/quicopy/id6761418490" rel="noopener noreferrer"&gt;Mac App Store&lt;/a&gt; for $9.99 lifetime or $1.99/month.&lt;/p&gt;

&lt;p&gt;If you want to see the end result of all this sandbox wrestling, there it is.&lt;/p&gt;

&lt;p&gt;Questions or corrections? Happy to discuss in the comments — especially interested in hearing from anyone who's found a way to make &lt;code&gt;CGEvent.post()&lt;/code&gt; work under sandbox (I don't think it's possible, but would love to be wrong).&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally posted on &lt;a href="https://www.quicopy.com" rel="noopener noreferrer"&gt;quicopy.com&lt;/a&gt;. Follow me for more macOS indie dev notes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>swift</category>
      <category>appstore</category>
      <category>sandbox</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
