8 min read
Shipping Fanta to the Mac App Store: the unglamorous build log
A dated account of Fanta's first Mac App Store submission path: RevenueCat products and account identity, a rejected package, a processed build, and the checks still between us and a public release.
On this page
Fanta build log · Part 4 of 4 · 25 September Store checkpoint
An app can open perfectly on our Mac, pass its tests, and still be nowhere near the Mac App Store. Fanta reached that gap this month. The editor, canvas, and local design files were already real. Turning them into a store app meant account identity, Apple products, a sandboxed binary, signing, review material, and a purchase path that could grant the right credits to the right person.
This is the release log as we can verify it. Our last recorded store checkpoint is 25 September 2026: Apple had finished processing version 1.0, build 5, after Transporter delivered it. Processing is not App Review approval, and it is not a public store release. We still needed to select the build for review, finish the listing and purchase review material, and run signed-in purchase QA. We are publishing those edges because they are the part of shipping we would have wanted someone else to show us.
The earlier posts cover why we built Fanta on Zed, why design files are its source, and what large documents taught us. This one starts where a working editor meets a store.
Run / Deploy / Read
Pick the path that matches your hardware and patience
A store build is another product surface
The Mac App Store version needs Apple's App Sandbox and a valid provisioning
profile, as well as the usual compiled application. Our release script builds
that version with ./script/bundle-mac -s, embeds the native RevenueCat
library, signs the app with a Mac App Distribution identity, and signs its
installer package separately. The package is what Transporter sends to Apple.
We check the signatures locally before it leaves the machine, but those checks
cannot promise that App Store Connect will accept it.
That distinction became concrete with build 2. Transporter rejected the
arm64-only package with ITMS-90869: it declared a macOS deployment target
below 12.0. We had a signed app, yet the deployment metadata across the binary
and its bundled library did not meet Apple's requirement for this build. We
set the App Store build target to macOS 12.0 and checked the app's Info.plist,
main binary, and RevenueCat library together. Build 3 cleared that validation
and became ready for internal testing.
We kept iterating after the upload worked. Build 4 routed the AI provider's account button into the in-app Apple purchase flow. Build 5 corrected the design Agent prompt so it names the tools available to it. On 25 September, Transporter delivered build 5 at 18:57 Madrid time and later reported that Apple had finished processing it. A successful upload did not end the build log; it gave us a build we could choose for review.
A real Mac build with a two-layer smoke design. This proves the canvas and inspector rendered in that test; it is not a final App Store showcase image.
The sandbox changed the editor around the purchase screen too. A regular developer build can run local helper commands; a Mac App Store app cannot assume that freedom. Later September changes guarded subprocess paths such as local agent commands and formatter execution. That means the external MCP workflow we tested in a non-store release build should not be advertised as identical in the Store build.
The lesson was small and expensive enough to remember: signature validation, Transporter validation, internal testing, App Review, and public availability are separate gates. We should name the gate we passed instead of calling the whole sequence “shipped.”
Purchases need a Fanta identity
Local editing does not need a subscription. Hosted AI generation does need
credits, and the Mac App Store version is built to use Apple in-app purchases through
RevenueCat for those credits. We configured two US products: Fanta Pro
Monthly, at $44.99 per month for 3,000 credits, and a consumable pack of
500 credits, at $8.99. The subscription belongs to RevenueCat's pro
entitlement. The credit pack does not: it is a consumable grant, recorded by
Fanta's own backend rather than treated as an enduring entitlement.
We had to decide what a purchase belongs to before building the button. A Mac can have more than one Fanta account, while the Apple ID on that Mac may stay the same. If restoring a purchase silently moved it to whichever Fanta account was open last, we could give one person's subscription or credits to another workspace. We use Fanta's backend user ID as the RevenueCat App User ID and configure restore behavior to keep the purchase with its original App User ID. The purchase screen also requires a personal Fanta workspace. If the account changes during a request, it stops and asks the person to reopen Settings.
The native bridge is deliberately narrow. RevenueCat's Apple SDK runs in a Swift package; the Rust app asks it for products, starts a purchase, restores or syncs purchases, and receives structured results. Before opening the Apple flow, the Rust side fetches the signed-in Fanta user and configures RevenueCat with that user's ID. Afterward it checks that the returned customer still has the same ID. The product prices shown in the UI come from the store's localized product data, rather than a price string compiled into the app.
There is another boundary after Apple's sheet closes. A successful client callback is not permission for the app to mint credits. Our backend is meant to process RevenueCat's purchase events, verify what product was bought, and grant credits once per transaction to the linked Fanta account. Restore and sync check purchase history; neither pretends to add credits on its own. The production webhook handler and its database tables were deployed by the 25 September checkpoint, but the production RevenueCat webhook had not been connected. We had not verified an end-to-end Apple purchase and credit grant. It would be misleading to show a product card as proof that billing works.
The review form is part of the build
The store listing was more than a name and an icon. We needed a real screenshot from the final interface, privacy answers that cover usage and diagnostics, support and policy links, and review information for each in-app purchase. The Mac screenshot in the draft predated the restored workspace panels; it needed to be recaptured from the final build. Both purchase products needed their own review screenshots. A reviewer also needs a working Fanta account and a path through the purchase flow without a mystery login challenge.
Production sign-in was a particular blocker at that checkpoint. The live sign-in path still reached a development identity environment, while the production identity change required mapping existing users to their Fanta accounts before switching keys. We could not treat that as a cosmetic domain change. A user ID is what ties together designs, credits, and purchases. Changing it carelessly would make the store build look finished while a returning person opened an empty account.
Our next QA pass has to use a signed sandbox build and exercise both products, renewal, cancellation, refund, restore, and account switching. It also has to verify that a credited balance can actually pay for hosted generation. We need to reopen and save a local design in the sandboxed app, because purchase work should not break the editor it is funding. Until those checks pass, the purchase flow remains an implementation under test, not a customer story.
What the Shipaton clock changes
We are preparing Fanta's Mac release during RevenueCat's Shipaton. Its official rules require the first public version of a qualifying Mac app to be released in the event window, and the submission closes 30 September at 11:45 p.m. PDT. A processed package or an internal test build does not replace a public store listing. The submission also needs a short public demo, store link, images, and a way for judges to try the paid features. We have work left on all of that.
For the #BuildInPublic award, the rules ask us to link publicly visible progress posts and explain how sharing helped the app. Judges look at the story, the response from others, and what we learned or changed because of it; audience size does not matter. That gives us a useful standard for these posts. A polished retrospective is easy to write after the risk is gone. A dated account of a rejected build, a processed one, and the untested gap between them makes it possible for someone to tell us where the plan is weak.
If you have shipped a Mac app with purchases, we would value specific advice: which account-switching or restore case did your first test miss? What made App Review's purchase path easy to follow? If you are evaluating Fanta as an editor, does the line between free local editing and paid hosted generation make sense before a purchase sheet appears? We can still change the answers in the product. Leave the failure mode, not just a vote of confidence.
We will link the next verified release checkpoint when there is one. Our last verified App Store Connect checkpoint is build 5 processed on 25 September; public release and payment verification are still ahead. #Shipaton
Comments
Tags in this post
Keep reading
A design file you can diff is harder than it sounds
Why readable FNX was not enough: Fanta's 25,311-line diff, overwritten source edits, and the move to project files as the authority.
6 min · Sep 30, 2026
What 40,000 nodes taught us about Fanta's canvas
We opened a 128 MB, 40,141-node Figma file in Fanta, then followed the copies, image decodes, saves, and agent responses that made large designs hard to use. Here are the measurements and the work still left.
7 min · Sep 30, 2026
Why we built Fanta on top of a code editor
How our 2024 browser-canvas experiments and 2026 native editor fork shaped Fanta's readable design files, canvas, and agent workflow.
7 min · Sep 29, 2026
All tags
- #ai
- #blender
- #build-in-public
- #cubemap
- #design-tools
- #diamond
- #environment-art
- #expo
- #fanta
- #file-formats
- #git
- #graphics
- #houdini
- #huggingface
- #image-generation
- #licensing
- #macos
- #mdx
- #meta
- #next.js
- #ocr
- #onnx
- #open-source
- #performance
- #procedural
- #python
- #r3f
- #ray-tracing
- #react-native
- #rendering
- #replicate
- #revenuecat
- #rust
- #sam2
- #segmentation
- #shaders
- #shipaton
- #technical-art
- #three.js
- #vercel
- #wasm
- #web-worker
- #webgl
- #webgpu