Build Mac AppDocsMenu

Reference

Project map

The machine-readable map of this repository: where each fact lives, what keeps duplicated facts in step, every command, every environment variable and secret, every URL a shipped binary calls, and the checks only a human can do. scripts/check-map.sh fails when this file and the code disagree, and regenerates docs/MAP.md from it. Edit this file, never docs/MAP.md.

Start here

  • AGENTS.md: the rule book for the whole repository; read before any change
  • site/AGENTS.md: site rules, and what every route does with nothing configured
  • mac/AGENTS.md: Swift rules: Core/ purity, required closures, signing, previews
  • scripts/doctor.sh: what state this project is in, and the next step
  • docs/new-project.md: from a fresh clone to your own product

Where each fact lives

FileOwns
site/lib/site.tsproduct name, domain, URL scheme, support email, tagline and descriptions, maker, navigation
site/lib/legal.tsseller's legal identity, jurisdiction, refund window, policy effective date
site/lib/pricing.tsprices, licence tiers and device counts, primary tier
site/lib/deliverables/config.tswhat a paid order produces (licence key, GitHub repository access)
site/lib/payments/selected.tswhich payment provider the product sells through, the seller named in the legal pages
site/lib/launch.tswhether the product is on sale
site/lib/license.tslicence terms, minimum macOS shown on the site, architectures
site/lib/changelog.tsreleases, dates and notes, build number (by row position)
site/content/blogblog posts, guides and comparisons (MDX, one file each)
site/lib/authors.tswho writes on the site
site/lib/features.tsthe features the site claims
site/lib/faqs.tsxFAQ answers
site/app/globals.csscolours
site/public/app-icon.svgthe icon; every favicon and the app icon set are drawn from it
mac/project.ymlPRODUCT_NAME, bundle identifier, Team ID, deployment target, Sparkle dependency
mac/App/Core/Brand.swiftevery URL the app calls (frozen once shipped)
mac/App/Resources/Info.plistSparkle public key, URL scheme registration, copyright

Welds (duplicated facts, kept in step by a check)

WeldFactChecked by
brandname, domain, scheme, maker, seller: owners agree, no literal anywhere else, no earlier name returnsscripts/check-brand.sh
brand-siteno product name in site source outside site.tscd site && pnpm test
palettecolour tokens: CSS ↔ TypeScript ↔ Swift, and contrastmac/scripts/check-palette.sh
schemethe URL schememac/scripts/check-scheme.sh
wiringevery screen action is required and handled; every menu selector has a receivermac/scripts/check-wiring.sh
routesevery URL Brand.swift compiles in has a site routemac: AppTests/BrandTests.swift, and scripts/check-map.sh
releaseproduct name, macOS floor, primary tier ↔ LicenseSheet.defaultDevices, changelog row, Sparkle keymac/scripts/release-preflight.sh
docsevery path a document names exists; every wiki link resolvesscripts/check-docs.sh
mapthis file ↔ the codescripts/check-map.sh

Commands

CommandWhatWhenTime
scripts/doctor.shproject state and the next step (--json, --online)first thing in any sessionseconds
scripts/verify.sh --quickevery weld and both unit suites, no builds (--json)after any change~1 min
scripts/verify.sheverything offline: welds, suites, both builds, launch smoke (--json)before you finish~4 min
scripts/check-map.shthis map against the code; rewrites docs/MAP.md (--check only verifies)after changing owners, routes, env vars or commandsseconds
scripts/new-app.sh --name "Your App" --domain your-app.com --team ABCDE12345 [--payments creem|polar] [--deliverable licence-key|github-invite|both]rename the template to your productonce, firstseconds
scripts/configure.sh --payments creem|polar --deliverable licence-key|github-invite|bothchoose the payment provider and what a purchase delivers (code constants)at setup, or when switching (see /switch-payments first)seconds
swift scripts/make-icons.swiftfavicons and the app icon set from site/public/app-icon.svgafter changing the iconseconds
cd site && pnpm devthe site at http://localhost:3000, works with no envdeveloping the sitelong-running
cd site && pnpm devthen open /dev/emails: every email, rendered as sent, with its plain-text partafter changing an emaillong-running
cd site && pnpm testsite unit testsafter site changesseconds
cd site && pnpm linteslint, zero warnings allowedafter site changesseconds
cd site && pnpm buildproduction buildbefore assert-metadata~1 min
site/scripts/assert-metadata.sh http://localhost:3000fetches every page and asserts canonicals, descriptions, JSON-LD, robots, sitemap, llms.txtafter route or metadata changes, against pnpm build && pnpm startseconds
cd site && pnpm broadcast <on-sale|release> [--send]emails the waitlist; a draft in Resend unless --sendlaunch day, and each releaseseconds
cd site && pnpm indexnow [paths…]submits the live sitemap (or given paths) to IndexNowafter a deploy that published or changed pagesseconds
cd site && pnpm creem:setupcreates the Creem products; --verify checks themgoing live with Creemseconds
cd site && pnpm polar:setupreads the Polar setup back and fails on any mismatch with the ladder (activation limits, benefits, prices)going live with Polarseconds
mac/scripts/bootstrap.shgenerates mac/App.xcodeproj from project.ymlafter cloning, and after adding any Swift fileseconds
xcodebuild -project mac/App.xcodeproj -scheme App -destination 'platform=macOS' testthe app's unit tests (read the count)after Swift changes~1 min
mac/scripts/smoke-launch.sh "<path to .app>"launches every architecture slice and checks it stays aliveafter a build~20 s
mac/scripts/verify-menu.sh "<path to .app>"reads the main menu out of the running appafter a buildseconds
mac/scripts/release-preflight.sh <version> <build>every release precondition, before anything is builtbefore a releaseseconds
mac/scripts/build-dmg.sh <version> <build> [--unsigned]signed (or rehearsal) DMGrelease, or a rehearsalminutes
mac/scripts/release.sh <version> <build>preflight → build → sign → smoke → notarize → staple → DMGs → signed appcast; stops before publishingreleasing by hand5–15 min
mac/scripts/set-release-secrets.sh --check-apple --shredvalidates and uploads the eight CI release secrets, never prints a valueonce, before the first CI releaseseconds
evals/run.sh [scenario…]agent evals: headless Claude Code on a fresh copy must reach a verified result (EVAL_RUNS=3 for a claim)before a template release, and before changing a claim about agentsminutes per scenario

Environment variables

Nothing here is required to build or run locally (AGENTS.md §4).

VariableWhereEnablesWithout itStage
NEXT_PUBLIC_SITE_URLsitecanonicals, og:url, JSON-LD ids, checkout return URLslocalhost locally; a production build fails (deliberate)launch
CREEM_API_KEYsiteCreem: licence routes, checkout, live pricing; creem_test_ prefix selects test modelicence routes answer 502, never "invalid key"sell
CREEM_WEBHOOK_SECRETsitethe payment webhookwebhook answers 500 so Creem keeps retrying (deliberate)sell
CREEM_PRODUCT_ID_1sitelive pricing for tier 1static prices from pricing.tssell
CREEM_PRODUCT_ID_2sitelive pricing for tier 2static pricessell
CREEM_PRODUCT_ID_3sitelive pricing for tier 3static pricessell
POLAR_ACCESS_TOKENsitePolar: licence routes, recovery, thank-you page key, live pricinglicence routes answer 502, never "invalid key"sell
POLAR_ORGANIZATION_IDsitePolar: required on every licence callas without the tokensell
POLAR_SERVERsitePolar: "sandbox" selects sandbox-api.polar.shproductionsell
POLAR_WEBHOOK_SECRETsitePolar: the payment webhookwebhook answers 500 so Polar keeps retrying (deliberate)sell
POLAR_PRODUCT_ID_1sitePolar: live pricing for tier 1static pricessell
POLAR_PRODUCT_ID_2sitePolar: live pricing for tier 2static pricessell
POLAR_PRODUCT_ID_3sitePolar: live pricing for tier 3static pricessell
NEXT_PUBLIC_CHECKOUT_URL_1sitetier 1 buy button (inlined at build)button falls back to /#pricingsell
NEXT_PUBLIC_CHECKOUT_URL_2sitetier 2 buy buttonfalls back to /#pricingsell
NEXT_PUBLIC_CHECKOUT_URL_3sitetier 3 buy buttonfalls back to /#pricingsell
CHECKOUT_URL_1sitetier 1 link changeable without a redeploy; wins over NEXT_PUBLIC_the NEXT_PUBLIC_ valuesell
CHECKOUT_URL_2siteas above, tier 2the NEXT_PUBLIC_ valuesell
CHECKOUT_URL_3siteas above, tier 3the NEXT_PUBLIC_ valuesell
DEV_PRICE_CENTSsitelive-mode build on a machine with no credentials (either provider)nothing; refused in productiondevelop
CREEM_API_URLsiteAPI host override; leave unsethost derived from the keydevelop
NEXT_PUBLIC_DATAFAST_IDsiteanalytics script (with DOMAIN)no script, crawler tracker offlaunch
NEXT_PUBLIC_DATAFAST_DOMAINsiteanalytics script (with ID)no scriptlaunch
DATAFAST_API_KEYsiterevenue attribution from the webhooklogged, payment unaffectedlaunch
RESEND_API_KEYsiteevery email: receipt, recovery, refund, waitlist, repository accessemails skipped and loggedsell
RESEND_FROMsitecustom from identitySITE.name <SITE.supportEmail>sell
RESEND_SEGMENT_IDsitewaitlist kept as Resend contacts, confirmation email, broadcastsaddresses logged, no confirmationlaunch
GITHUB_RELEASE_REPOsitedownloads and latest-version from GitHub Releasesdownload routes answer 503, never 404release
GITHUB_RELEASE_TOKENsitedownloads from a private release repopublic repos onlyrelease
INDEXNOW_KEYsite/indexnow-key.txt and pnpm indexnow (Bing, and so ChatGPT search, pick changes up within hours)the route 404s; nothing pingslaunch
GITHUB_ACCESS_REPOsitegithub-invite: the private repository a purchase grants access tobuyers get the claim link; the seller is emailedsell
GITHUB_ACCESS_TOKENsitegithub-invite: invite and remove collaborators (Administration: write, one repo)as without the reposell
CLAIM_SECRETsitegithub-invite: signed /claim confirmation links (32+ chars)/claim asks the buyer to email supportsell
DOWNLOAD_URLsiteexplicit binary location overrideGitHub resolutionrelease
RELEASE_ASSET_NAMEsite+releasecustom stable DMG name (spaces become dots).dmgrelease
NODE_ENVplatformdevelopment-only pages (/dev/emails)set by Next: development under pnpm dev, production otherwisedevelop
VERCEL_ENVplatformproduction detectiontreated as not productionlaunch
VERCEL_PROJECT_PRODUCTION_URLplatformsite URL fallback on preview deploymentslocalhostlaunch
NOTARY_PROFILEreleasenotarytool keychain profile name-notaryrelease
CODESIGN_IDENTITYreleasesigning identity overridediscovered from the keychainrelease
SPARKLE_BINreleaseSparkle tools location overridesearched in DerivedDatarelease
SPARKLE_KEY_FILEreleasesign the appcast from a key file (CI)keychain account release
SPARKLE_ACCOUNTreleasekeychain account for the EdDSA keythe product slugrelease

GitHub secrets (release workflow)

SecretWhat
MACOS_CERTIFICATEDeveloper ID .p12, base64
MACOS_CERTIFICATE_PWDthe .p12 export password
MACOS_DEVELOPER_IDfull identity name
KEYCHAIN_PWDthrowaway runner keychain password
NOTARIZATION_APPLE_IDApple ID email
NOTARIZATION_PWDapp-specific password
NOTARIZATION_TEAM_ID10-character Team ID
SPARKLE_PRIVATE_KEYEdDSA private key for the appcast; irreplaceable

Frozen URLs

Compiled into every shipped build; renaming one strands every installed copy (AGENTS.md §5). Source: mac/App/Core/Brand.swift.

PathServed by
/api/license/activatesite/app/api/license/activate/route.ts
/api/license/validatesite/app/api/license/validate/route.ts
/api/license/deactivatesite/app/api/license/deactivate/route.ts
/buysite/app/buy
/api/pricingsite/app/api/pricing/route.ts
/privacysite/app/privacy
/changelogsite/app/changelog
/recoversite/app/recover
/appcast.xmlsite/public/appcast.xml

Modules

ModuleInstalledGuide
databasenodocs/modules/database.md
menubarnodocs/modules/menubar.md
theme (single appearance)nodocs/modules/theme.md
marketing-shotsnodocs/modules/marketing-shots.md
blog: posts, guides, comparisons (built in)yesdocs/blog.md
analyticsyesdocs/modules/analytics.md
own licence keys (provider without native keys)nodocs/modules/own-licences.md

Checks only a human can do

  • paste a licence key with ⌘V into the running Release app: synthesising the keystroke needs an Accessibility grant no script can obtain (mac/AGENTS.md)
  • site/scripts/assert-metadata.sh against a running production build (cd site && pnpm build && pnpm start): the canonical bug is invisible to type checks and unit tests (AGENTS.md)
  • open the DMG on a second Mac and see Gatekeeper accept it: the only proof that notarization reaches a stranger's machine (docs/releasing.md)

This page is docs/MAP.md in the repository, copied 2026-09-25.