Thanks for taking the time to improve CheatSheet.
This project is meant to stay small, native, and easy to build. The best contributions stay focused and come with tests that make them easy to review.
- Install Xcode 26. An Xcode 27 beta is fine for development, but must
never be used for a submission build. See
Docs/os-support-policy.md. The app requires macOS 26 / iOS 26, so a Mac running macOS 26 or later is needed to run the macOS app and its tests. - Install XcodeGen.
- Generate the project.
brew install xcodegen
xcodegen generate- Open
CheatSheet.xcodeprojor build from the command line.
Scripts/verify-macos.shBefore archiving for the App Store, confirm the active toolchain ships the SDK this branch targets. A beta SDK is rejected at upload.
Scripts/verify-build-sdk.shScripts/verify-macos.shThe script generates its project and DerivedData under /private/tmp. This avoids
NSFileCoordinator hangs when a checkout lives in a File Provider-managed
Documents folder.
If you are only changing shared parsing or model code, add or update focused tests in CheatSheetTests.
For iOS and iPadOS unit tests:
xcodegen generate --spec project.yml
xcodebuild test -project CheatSheet.xcodeproj -scheme CheatSheetiOS -destination "id=$(Scripts/resolve-ios-simulator.sh)" -derivedDataPath /tmp/CheatSheet-iOS-Test-DD CODE_SIGNING_ALLOWED=NOTests run on iOS 26.5 or newer. Scripts/resolve-ios-simulator.sh fails rather
than selecting an older runtime; install a newer simulator via Xcode > Settings >
Components, or override the floor with CHEATSHEET_MIN_IOS_RUNTIME.
CheatSheet builds and tests on Xcode Cloud, not GitHub Actions. Every push to
main, and every pull request against it, triggers a build.
Xcode Cloud regenerates the project from project.yml on clone
(ci_scripts/ci_post_clone.sh), so nothing needs to change in that script when
adding new source files.
The widgets need a shared app group. Forks should use their own Apple Developer
Team ID and app groups. Update project.yml, the macOS and iOS entitlement files
under CheatSheetApp/ and CheatSheetWidgets/, and
Shared/Sources/CheatSheetNote.swift, then run xcodegen generate.
This project uses a single long-lived branch, main, which must stay submittable.
main: the only long-lived branch. Everything merges here by pull request.feature/<short-name>andfix/<short-name>: short-lived branches cut frommain. Open a pull request back intomainwhen done.feature/ios-<major>-readiness: a long-lived OS adoption branch cut frommainfor a different SDK contract from the shipping line. Keep it thin and mergemaininto it often. SeeDocs/os-support-policy.md.gh-pages: hosts the GitHub Pages site (landing page and privacy mirror). It is not a development branch.
Guidelines:
- Never commit directly to
main; use a pull request. - Keep branch names lowercase and hyphenated, e.g.
feature/widget-color-picker,fix/checklist-crash. - Delete a branch after it merges.
- Tag every release on
mainwith its version (e.g.v1.2.0). - Keep
mainsubmittable at all times. Beta-SDK work belongs on an OS readiness branch.
- Prefer SwiftUI and system frameworks.
- Keep features small and local to the relevant target.
- Avoid third-party dependencies unless there is a clear reason.
- Use Swift Testing for behavior that can be tested without launching the app.
- Keep UI accessible with labels, Dynamic Type-friendly fonts, and minimum 44 point hit targets.
- Keep generated files out of commits.
Be kind, specific, and practical. Assume good intent and keep feedback focused on the work. Many contributors here are still learning macOS app development, so help keep the space welcoming.
Before opening a pull request:
- Regenerate the project if
project.ymlchanged. - Run the relevant tests.
- Update documentation when user-facing behavior changes.
- Keep the pull request focused on one change.
Please include:
- What changed.
- Why it changed.
- How you tested it.
Bug reports should include:
- Platform and OS version (macOS, iOS, or iPadOS).
- Xcode version if the issue is build related.
- Steps to reproduce.
- What you expected to happen.
- What happened instead.
Feature requests should explain the use case and why it belongs in a small note and widget app.