Gongje started life as a GitHub download: a menu bar app that listens while you hold a hotkey, transcribes your Cantonese on-device, and drops the text wherever your cursor is. People asked for a Mac App Store version — automatic updates, no Gatekeeper warnings, one click to install. Getting there meant answering a question every Mac developer eventually faces: what does your app still do inside the App Sandbox?
One Target, Two Builds
We did not fork the project. Gongje has a single app target with four build configurations — Debug, Release, Debug-AppStore and AppStore — and two schemes. The App Store configurations set an APPSTORE compilation flag, point at their own Info.plist and entitlements file, and enable the hardened runtime. Everything else is shared code.
A compile-time flag rather than a runtime check was deliberate. Code behind #if !APPSTORE is not in the App Store binary at all, so there is nothing for review to find, and no setting a user could flip into a broken state.
The Feature the Sandbox Took Away
The direct-download build can type for you. After transcription it writes the text to the clipboard, then synthesizes a Cmd-V keystroke with CGEvent and restores whatever was on the clipboard before. There is even an optional Ctrl-V for apps running under CrossOver. Posting keyboard events into other apps requires the Accessibility permission, and that is not something a sandboxed App Store app can ask for.
So the App Store build compiles out:
- the synthetic paste and its Carbon import,
- the
AXIsProcessTrustedpermission checks, - the hands-free input, clipboard-preservation and CrossOver settings,
- the Accessibility section of the permissions screen.
What remains is honest and still useful: the transcription lands on the clipboard, VoiceOver announces it, and you paste. The two Info.plist files differ in exactly one key — the App Store version drops NSAccessibilityUsageDescription, because it never asks.
Making Auto-Paste Opt-In Everywhere
Preparing the App Store build changed the GitHub build too. Earlier versions presented Accessibility access as a requirement during setup. We reworked that so automatic pasting is off by default and clearly optional, and added VoiceOver announcements so the clipboard-only flow is usable without looking at the screen. An app that converts speech to text should not demand control of your keyboard just to be useful.
Both Builds Are Sandboxed
Somewhat to our own surprise, the entitlements files for the two builds ended up identical: app-sandbox, device.audio-input for the microphone, and network.client for downloading models. Once auto-paste was isolated behind the compile flag, nothing else in Gongje needed to escape the sandbox — so the GitHub version runs sandboxed as well.
Large Models in a Small Container
Gongje downloads its speech models on first use rather than shipping them in the app bundle: OpenAI's Whisper small, medium and large-v3, plus two Cantonese fine-tunes we converted to CoreML. The default depends on the Mac — under 16 GB of memory gets the small Cantonese model, otherwise the large-v3 turbo one. The optional text-correction LLM (Qwen3 in 1.7B, 4B or 8B, or a Cantonese Qwen2 7B) is downloaded the same way.
Under the sandbox, all of this lives in the app's own container, so it is cleaned up with the app and never touches the rest of the disk. The network.client entitlement is used for those downloads only; audio and text never leave the Mac.
What We'd Tell Another Team
- Decide what the sandbox build is for. Ours is “transcribe to clipboard”; the GitHub build adds “and type it for me”. A clear line made every
#ifeasy to place. - Prefer compile-time separation for capabilities the store will not allow, so the binary under review simply does not contain them.
- Let the store build improve the other one. Making Accessibility optional was the right default for everyone, not just App Store users.
Gongje is free on the Mac App Store, and the source — including both build configurations — is on GitHub.