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 changesite/AGENTS.md: site rules, and what every route does with nothing configuredmac/AGENTS.md: Swift rules: Core/ purity, required closures, signing, previewsscripts/doctor.sh: what state this project is in, and the next stepdocs/new-project.md: from a fresh clone to your own product
Where each fact lives
| File | Owns |
|---|---|
site/lib/site.ts | product name, domain, URL scheme, support email, tagline and descriptions, maker, navigation |
site/lib/legal.ts | seller's legal identity, jurisdiction, refund window, policy effective date |
site/lib/pricing.ts | prices, licence tiers and device counts, primary tier |
site/lib/deliverables/config.ts | what a paid order produces (licence key, GitHub repository access) |
site/lib/payments/selected.ts | which payment provider the product sells through, the seller named in the legal pages |
site/lib/launch.ts | whether the product is on sale |
site/lib/license.ts | licence terms, minimum macOS shown on the site, architectures |
site/lib/changelog.ts | releases, dates and notes, build number (by row position) |
site/content/blog | blog posts, guides and comparisons (MDX, one file each) |
site/lib/authors.ts | who writes on the site |
site/lib/features.ts | the features the site claims |
site/lib/faqs.tsx | FAQ answers |
site/app/globals.css | colours |
site/public/app-icon.svg | the icon; every favicon and the app icon set are drawn from it |
mac/project.yml | PRODUCT_NAME, bundle identifier, Team ID, deployment target, Sparkle dependency |
mac/App/Core/Brand.swift | every URL the app calls (frozen once shipped) |
mac/App/Resources/Info.plist | Sparkle public key, URL scheme registration, copyright |
Welds (duplicated facts, kept in step by a check)
| Weld | Fact | Checked by |
|---|---|---|
| brand | name, domain, scheme, maker, seller: owners agree, no literal anywhere else, no earlier name returns | scripts/check-brand.sh |
| brand-site | no product name in site source outside site.ts | cd site && pnpm test |
| palette | colour tokens: CSS ↔ TypeScript ↔ Swift, and contrast | mac/scripts/check-palette.sh |
| scheme | the URL scheme | mac/scripts/check-scheme.sh |
| wiring | every screen action is required and handled; every menu selector has a receiver | mac/scripts/check-wiring.sh |
| routes | every URL Brand.swift compiles in has a site route | mac: AppTests/BrandTests.swift, and scripts/check-map.sh |
| release | product name, macOS floor, primary tier ↔ LicenseSheet.defaultDevices, changelog row, Sparkle key | mac/scripts/release-preflight.sh |
| docs | every path a document names exists; every wiki link resolves | scripts/check-docs.sh |
| map | this file ↔ the code | scripts/check-map.sh |
Commands
| Command | What | When | Time |
|---|---|---|---|
scripts/doctor.sh | project state and the next step (--json, --online) | first thing in any session | seconds |
scripts/verify.sh --quick | every weld and both unit suites, no builds (--json) | after any change | ~1 min |
scripts/verify.sh | everything offline: welds, suites, both builds, launch smoke (--json) | before you finish | ~4 min |
scripts/check-map.sh | this map against the code; rewrites docs/MAP.md (--check only verifies) | after changing owners, routes, env vars or commands | seconds |
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 product | once, first | seconds |
scripts/configure.sh --payments creem|polar --deliverable licence-key|github-invite|both | choose the payment provider and what a purchase delivers (code constants) | at setup, or when switching (see /switch-payments first) | seconds |
swift scripts/make-icons.swift | favicons and the app icon set from site/public/app-icon.svg | after changing the icon | seconds |
cd site && pnpm dev | the site at http://localhost:3000, works with no env | developing the site | long-running |
cd site && pnpm dev | then open /dev/emails: every email, rendered as sent, with its plain-text part | after changing an email | long-running |
cd site && pnpm test | site unit tests | after site changes | seconds |
cd site && pnpm lint | eslint, zero warnings allowed | after site changes | seconds |
cd site && pnpm build | production build | before assert-metadata | ~1 min |
site/scripts/assert-metadata.sh http://localhost:3000 | fetches every page and asserts canonicals, descriptions, JSON-LD, robots, sitemap, llms.txt | after route or metadata changes, against pnpm build && pnpm start | seconds |
cd site && pnpm broadcast <on-sale|release> [--send] | emails the waitlist; a draft in Resend unless --send | launch day, and each release | seconds |
cd site && pnpm indexnow [paths…] | submits the live sitemap (or given paths) to IndexNow | after a deploy that published or changed pages | seconds |
cd site && pnpm creem:setup | creates the Creem products; --verify checks them | going live with Creem | seconds |
cd site && pnpm polar:setup | reads the Polar setup back and fails on any mismatch with the ladder (activation limits, benefits, prices) | going live with Polar | seconds |
mac/scripts/bootstrap.sh | generates mac/App.xcodeproj from project.yml | after cloning, and after adding any Swift file | seconds |
xcodebuild -project mac/App.xcodeproj -scheme App -destination 'platform=macOS' test | the 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 alive | after a build | ~20 s |
mac/scripts/verify-menu.sh "<path to .app>" | reads the main menu out of the running app | after a build | seconds |
mac/scripts/release-preflight.sh <version> <build> | every release precondition, before anything is built | before a release | seconds |
mac/scripts/build-dmg.sh <version> <build> [--unsigned] | signed (or rehearsal) DMG | release, or a rehearsal | minutes |
mac/scripts/release.sh <version> <build> | preflight → build → sign → smoke → notarize → staple → DMGs → signed appcast; stops before publishing | releasing by hand | 5–15 min |
mac/scripts/set-release-secrets.sh --check-apple --shred | validates and uploads the eight CI release secrets, never prints a value | once, before the first CI release | seconds |
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 agents | minutes per scenario |
Environment variables
Nothing here is required to build or run locally (AGENTS.md §4).
| Variable | Where | Enables | Without it | Stage |
|---|---|---|---|---|
NEXT_PUBLIC_SITE_URL | site | canonicals, og:url, JSON-LD ids, checkout return URLs | localhost locally; a production build fails (deliberate) | launch |
CREEM_API_KEY | site | Creem: licence routes, checkout, live pricing; creem_test_ prefix selects test mode | licence routes answer 502, never "invalid key" | sell |
CREEM_WEBHOOK_SECRET | site | the payment webhook | webhook answers 500 so Creem keeps retrying (deliberate) | sell |
CREEM_PRODUCT_ID_1 | site | live pricing for tier 1 | static prices from pricing.ts | sell |
CREEM_PRODUCT_ID_2 | site | live pricing for tier 2 | static prices | sell |
CREEM_PRODUCT_ID_3 | site | live pricing for tier 3 | static prices | sell |
POLAR_ACCESS_TOKEN | site | Polar: licence routes, recovery, thank-you page key, live pricing | licence routes answer 502, never "invalid key" | sell |
POLAR_ORGANIZATION_ID | site | Polar: required on every licence call | as without the token | sell |
POLAR_SERVER | site | Polar: "sandbox" selects sandbox-api.polar.sh | production | sell |
POLAR_WEBHOOK_SECRET | site | Polar: the payment webhook | webhook answers 500 so Polar keeps retrying (deliberate) | sell |
POLAR_PRODUCT_ID_1 | site | Polar: live pricing for tier 1 | static prices | sell |
POLAR_PRODUCT_ID_2 | site | Polar: live pricing for tier 2 | static prices | sell |
POLAR_PRODUCT_ID_3 | site | Polar: live pricing for tier 3 | static prices | sell |
NEXT_PUBLIC_CHECKOUT_URL_1 | site | tier 1 buy button (inlined at build) | button falls back to /#pricing | sell |
NEXT_PUBLIC_CHECKOUT_URL_2 | site | tier 2 buy button | falls back to /#pricing | sell |
NEXT_PUBLIC_CHECKOUT_URL_3 | site | tier 3 buy button | falls back to /#pricing | sell |
CHECKOUT_URL_1 | site | tier 1 link changeable without a redeploy; wins over NEXT_PUBLIC_ | the NEXT_PUBLIC_ value | sell |
CHECKOUT_URL_2 | site | as above, tier 2 | the NEXT_PUBLIC_ value | sell |
CHECKOUT_URL_3 | site | as above, tier 3 | the NEXT_PUBLIC_ value | sell |
DEV_PRICE_CENTS | site | live-mode build on a machine with no credentials (either provider) | nothing; refused in production | develop |
CREEM_API_URL | site | API host override; leave unset | host derived from the key | develop |
NEXT_PUBLIC_DATAFAST_ID | site | analytics script (with DOMAIN) | no script, crawler tracker off | launch |
NEXT_PUBLIC_DATAFAST_DOMAIN | site | analytics script (with ID) | no script | launch |
DATAFAST_API_KEY | site | revenue attribution from the webhook | logged, payment unaffected | launch |
RESEND_API_KEY | site | every email: receipt, recovery, refund, waitlist, repository access | emails skipped and logged | sell |
RESEND_FROM | site | custom from identity | SITE.name <SITE.supportEmail> | sell |
RESEND_SEGMENT_ID | site | waitlist kept as Resend contacts, confirmation email, broadcasts | addresses logged, no confirmation | launch |
GITHUB_RELEASE_REPO | site | downloads and latest-version from GitHub Releases | download routes answer 503, never 404 | release |
GITHUB_RELEASE_TOKEN | site | downloads from a private release repo | public repos only | release |
INDEXNOW_KEY | site | /indexnow-key.txt and pnpm indexnow (Bing, and so ChatGPT search, pick changes up within hours) | the route 404s; nothing pings | launch |
GITHUB_ACCESS_REPO | site | github-invite: the private repository a purchase grants access to | buyers get the claim link; the seller is emailed | sell |
GITHUB_ACCESS_TOKEN | site | github-invite: invite and remove collaborators (Administration: write, one repo) | as without the repo | sell |
CLAIM_SECRET | site | github-invite: signed /claim confirmation links (32+ chars) | /claim asks the buyer to email support | sell |
DOWNLOAD_URL | site | explicit binary location override | GitHub resolution | release |
RELEASE_ASSET_NAME | site+release | custom stable DMG name (spaces become dots) | .dmg | release |
NODE_ENV | platform | development-only pages (/dev/emails) | set by Next: development under pnpm dev, production otherwise | develop |
VERCEL_ENV | platform | production detection | treated as not production | launch |
VERCEL_PROJECT_PRODUCTION_URL | platform | site URL fallback on preview deployments | localhost | launch |
NOTARY_PROFILE | release | notarytool keychain profile name | -notary | release |
CODESIGN_IDENTITY | release | signing identity override | discovered from the keychain | release |
SPARKLE_BIN | release | Sparkle tools location override | searched in DerivedData | release |
SPARKLE_KEY_FILE | release | sign the appcast from a key file (CI) | keychain account | release |
SPARKLE_ACCOUNT | release | keychain account for the EdDSA key | the product slug | release |
GitHub secrets (release workflow)
| Secret | What |
|---|---|
MACOS_CERTIFICATE | Developer ID .p12, base64 |
MACOS_CERTIFICATE_PWD | the .p12 export password |
MACOS_DEVELOPER_ID | full identity name |
KEYCHAIN_PWD | throwaway runner keychain password |
NOTARIZATION_APPLE_ID | Apple ID email |
NOTARIZATION_PWD | app-specific password |
NOTARIZATION_TEAM_ID | 10-character Team ID |
SPARKLE_PRIVATE_KEY | EdDSA 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.
| Path | Served by |
|---|---|
/api/license/activate | site/app/api/license/activate/route.ts |
/api/license/validate | site/app/api/license/validate/route.ts |
/api/license/deactivate | site/app/api/license/deactivate/route.ts |
/buy | site/app/buy |
/api/pricing | site/app/api/pricing/route.ts |
/privacy | site/app/privacy |
/changelog | site/app/changelog |
/recover | site/app/recover |
/appcast.xml | site/public/appcast.xml |
Modules
| Module | Installed | Guide |
|---|---|---|
| database | no | docs/modules/database.md |
| menubar | no | docs/modules/menubar.md |
| theme (single appearance) | no | docs/modules/theme.md |
| marketing-shots | no | docs/modules/marketing-shots.md |
| blog: posts, guides, comparisons (built in) | yes | docs/blog.md |
| analytics | yes | docs/modules/analytics.md |
| own licence keys (provider without native keys) | no | docs/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.