Build Mac AppDocsMenu

Get started

Starting a new product from this template

In the order that keeps everything working at every step. It ends in a gate rather than a "you're done": the last command verifies the rename mechanically and hands you a list of anything missed, with file and line.

Nothing here needs an account, a key or a deploy until step 9.

Throughout: no brand literals. The product's name, domain and URL scheme are spelled in their owner files and nowhere else (root AGENTS.md §2). scripts/check-brand.sh and site/lib/brand.test.ts enforce it across every language in the repository — they are why step 10 works at all.


1. Rename

scripts/new-app.sh --name "Your App" --domain your-app.com --team ABCDE12345 \
    --maker "Your Name" --entity "Your Name or Company" \
    --payments creem --deliverable licence-key

With an agent, /new-app asks for these and runs it. After any step here, scripts/doctor.sh shows where the project is up to and the next step; each section below turns one of its lines green.

It rewrites the owner files — site/lib/site.ts, site/lib/legal.ts, mac/App/Core/Brand.swift, mac/project.yml, mac/App/Resources/Info.plist — records the old names in scripts/brand-ancestors.txt so they can never come back, deletes the DMG layout (it records the volume name), and runs the brand check. Options it derives when you leave them out: --scheme (the name, lower-case, no spaces), --bundle-prefix (the domain reversed), --email (hello@<domain>).

  • --payments: creem (the default) or polar. Only the selected adapter is called; the other stays as the worked example.

  • --deliverable: licence-key for a Mac app, github-invite to sell access to a private repository (docs/deliverables.md), or both. Both choices can be changed later with scripts/configure.sh (/switch-payments explains what to do first when customers already have keys).

  • --team is your Apple Team ID (security find-identity -v -p codesigning shows it in brackets). Without it the app signs ad hoc, which is fine for running from Xcode and fatal for a Release build (mac/AGENTS.md).

  • The scheme and the bundle identifier are frozen the day the first build ships. Every installed copy keeps its Keychain items under the bundle id and listens for the scheme it shipped with. Choose them now.

2. site/lib/site.ts — the words

The rename set the identity; the copy is yours. Every field has a comment saying what it is load-bearing for — read them rather than pattern-matching.

  • tagline — the one-line promise, under about 60 characters (it is set on the OG card). Name the symptom the buyer has a word for.
  • description — two sentences, a claim rather than a category.
  • searchDescription — ≤ 155 characters, no price. site/lib/seo.test.ts holds it to that.
  • socialDescription — ≤ 120 characters, must not restate the tagline.
  • x — the product's own account, or empty if the account that speaks for it is yours (claiming it twice merges you and the product into one entity — see site/lib/schema.ts).
  • MAKER — your site, handle and a real photo in site/public/maker/. The section exists to make the site attributable to a person.
  • KEYWORDS, NAV, CTA, FOOTER — check every href still points at a section or page you kept.
  • location, jurisdiction — where you are and whose law governs the terms. The three policy pages read these.
  • refundDays — quoted by the FAQ, the pricing card and the refunds page.
  • effective — today, as YYYY-MM-DD. Never new Date(); bump it in the same commit that changes a policy.
  • Read site/app/terms, privacy and refunds end to end. They are a starting point written from the template's code, not legal advice — and the privacy page must describe what your app sends.

4. What it is, and what it costs

  • site/lib/features.ts — the features, each one something the shipped build actually does (root AGENTS.md §7).
  • site/lib/faqs.tsx — every answer re-read against the Swift. The move- Macs answer quotes Settings labels word for word.
  • site/lib/pricing.ts — the ladder (LICENSE_TIERS), which rung is primary, and the amounts. One rung is a valid ladder. The primary rung must equal LicenseSheet.defaultDevices in mac/App/Core/LicenseSheet.swift.
  • site/lib/launch.ts — leave onSale false until docs/go-live.md says otherwise.
  • site/lib/license.ts — the macOS floor must name the same release as deploymentTarget in mac/project.yml.
  • site/lib/changelog.ts — keep one row, and set its date on release day.

5. The look

  • site/public/app-icon.svg — your mark, 1024×1024. Then, from the root:

    ```bash
    swift scripts/make-icons.swift
    ```
    
    which writes the favicons, the touch icon and the Mac app's icon set from
    that one drawing. `mac/AppTests/AppIconTests.swift` fails if you forget.
    
  • Colours: site/app/globals.css (the :root block and the light-dark() pairs), site/lib/palette.ts, and mac/App/App/Palette.swift — three files, one edit. site/lib/palette.test.ts names the file and token out of step, measures contrast, and mac/scripts/check-palette.sh welds the app. A dark-only product: docs/modules/theme.md.

  • Optionally swift mac/scripts/make-dmg-background.swift after a palette change; it reads the dark tokens.

6. The app

  • Replace mac/App/Screens/HomeView.swift with your product. Logic goes in mac/App/Core/ with tests (mac/AGENTS.md); screens take values and required closures.
  • A menubar product instead of a window: docs/modules/menubar.md.
  • mac/App/Resources/App.entitlements — add only what a feature needs, and say which feature, in the file.

7. The plan

  • docs/PRD.template.md → docs/PRD.md, filled in, then frozen.
  • wiki/ — replace the placeholders; keep R1 Licence Gate as the model for your own requirements.

8. Deleting what this product will never need

Most of what ships is scaffolding for something a product may never do, and deleting it is expected. After each deletion, scripts/verify.sh --quick — if it passes, the deletion was complete.

  • No analytics — docs/modules/analytics.md lists every file in the chain.
  • No tiers — delete two rows of LICENSE_TIERS; the switch disappears.
  • No waitlist — keep it; with onSale true it is not rendered, and the day you want to pause sales it is one boolean away.

What must survive every deletion: site/lib/site.ts, seo.ts, schema.ts, palette.ts and their tests, and every route named in mac/App/Core/Brand.swift — shipped binaries call those forever.

9. Hosting

  • cd site && cp .env.example .env.local, fill in nothing, and confirm pnpm dev works. That is the degradation contract, worth seeing once with your own name on the page.
  • Create the Vercel project with Root Directory site and build command pnpm run vercel-build (the site-URL gate runs before next build).
  • Set NEXT_PUBLIC_SITE_URL on Production: https:// and the apex host, no www. The one variable a production build refuses to go without.
  • Push to GitHub. .github/workflows/ci.yml runs on every push.

10. The gate

scripts/verify.sh

Every offline check: the brand across the repository (the list of anything the rename missed), the docs, the welds between app and site, both test suites, both builds, and a launch of the app on every architecture this Mac can run.

Then, against a production build of the site:

cd site && pnpm build && pnpm start
./scripts/assert-metadata.sh http://localhost:3000

It fetches every page and asserts the indexing contract — one canonical per page, naming itself; descriptions; JSON-LD that parses; robots, sitemap, llms.txt, feed. Run it again against the deployed site.

What comes next

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