Thank you for your interest in contributing to Pine! This guide will help you set up the development environment and understand the project workflow.
-
macOS 26 (Tahoe) or later
-
Xcode 26+ (includes Swift 5.9+ with
@Observablemacro) -
SwiftLint — install via Homebrew:
brew install swiftlint
git clone https://github.com/batonogov/pine.git
cd pineRun once after cloning to enable the pre-commit hook and merge driver:
git config core.hooksPath .githooks
git config merge.ours.driver trueThe pre-commit hook auto-unstages cosmetic-only changes to Localizable.xcstrings (Xcode build artifacts). The ours merge driver avoids conflicts in xcstrings files.
Open Pine.xcodeproj in Xcode. SPM dependencies (SwiftTerm, Sparkle, swift-markdown) resolve automatically.
Press Cmd+R in Xcode, or build from the command line:
xcodebuild -project Pine.xcodeproj -scheme Pine buildIf
xcodebuildcan't find the SDK, runsudo xcode-select -s /Applications/Xcode.app/Contents/Developerfirst.
New .swift files placed in Pine/, PineTests/, or PineUITests/ are automatically picked up by Xcode (the project uses PBXFileSystemSynchronizedRootGroup). No manual project.pbxproj edits needed.
Pine's minimum deployment target remains macOS 26.0. The current macOS 27 beta is an additional compatibility target, so compatibility fixes must preserve macOS 26 support rather than raising the deployment target.
For OS-, SDK-, or rendering-specific bug reports and pull requests, capture the exact environment:
sw_vers
xcodebuild -version
xcrun --sdk macosx --show-sdk-version
xcrun --sdk macosx --show-sdk-build-versionInclude the complete output, the Mac model/chip, and the display configuration. When both runtimes are available, state the result separately for macOS 26 and the current macOS 27 beta; use not tested where a runtime was unavailable.
For terminal rendering bugs, perform a manual comparison after each code change:
- Launch normally to exercise the default path (Metal when available), and note any fallback-to-CoreGraphics message in Console.
- Quit Pine completely, then relaunch with the
--disable-metalargument orPINE_DISABLE_METAL=1environment variable to force CoreGraphics. - Report the effective renderer that reproduces the problem and whether behavior differs between macOS 26 and macOS 27 beta.
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
xcodebuild test -project Pine.xcodeproj -scheme Pine \
-destination 'platform=macOS' -only-testing:PineTestsRun a single test class:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
xcodebuild test -project Pine.xcodeproj -scheme Pine \
-destination 'platform=macOS' -only-testing:PineTests/GoToLineTestsDEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
xcodebuild test -project Pine.xcodeproj -scheme Pine \
-destination 'platform=macOS' -only-testing:PineUITestsRun SwiftLint before every commit and fix all warnings and errors:
swiftlintIf SwiftLint crashes with a sourcekitdInProc error, prefix the command:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer swiftlintPine follows MVVM with SwiftUI views backed by AppKit via NSViewRepresentable.
ProjectManager(@Observable) is the central state object managing file tree, editor tabs, terminal tabs, and git statusCodeEditorViewwraps NSScrollView + customGutterTextView(NSTextView subclass) for the code editorTerminalContentViewwraps SwiftTerm'sLocalProcessTerminalViewfor the integrated terminalSyntaxHighlighterloads JSON grammar files fromPine/Grammars/for syntax highlighting- Menu commands are defined in
PineApp.swiftand dispatched viaNotificationCenter
For more details, see the Architecture section in AGENTS.md.
Create a feature branch from main:
git checkout -b feat/my-featureAll commits must follow Conventional Commits:
| Prefix | Use case |
|---|---|
feat: |
New feature |
fix: |
Bug fix |
docs: |
Documentation only |
refactor: |
Code restructuring without behavior change |
perf: |
Performance improvement |
test: |
Adding or updating tests |
chore: |
Build, CI, tooling changes |
Use feat!: or a BREAKING CHANGE: footer for breaking changes.
Examples:
feat: add Python syntax grammar
fix: prevent crash when opening empty file
docs: update CONTRIBUTING.md with test instructions
Every new feature or bug fix must include tests:
- Unit tests (
PineTests/) using Swift Testing framework — cover public API, edge cases, error paths, and boundary conditions - UI tests (
PineUITests/) using XCTest/XCUITest where applicable
Do not submit a PR without corresponding tests. Aim for comprehensive coverage, not just the happy path.
- Use
@Observablemacro, notObservableObject/@Published - Use semantic system colors, not hardcoded color values
- Strings go in
Strings.swift, menu icons inMenuIcons.swift - Never reserialize
Localizable.xcstringswith JSON serializers — make targeted text insertions to preserve Xcode formatting
Add a JSON file to Pine/Grammars/ following the format of existing grammars. It will be picked up automatically by SyntaxHighlighter at startup.
- Make sure all tests pass and
swiftlintreports no issues - Push your branch and open a PR against
main - Fill in the PR description: what changed, why, and how to test it
- CI will run lint, build, unit tests, and UI tests automatically
- All checks must pass before merge
The CI runs: Lint -> Build -> Unit Tests (with coverage) -> UI Tests (6 parallel shards). Code coverage threshold is 70% (logic-only, SwiftUI view files excluded).
Check the issue tracker for open issues. Issues labeled good first issue are a great starting point for new contributors.
Open a discussion or comment on the relevant issue. We are happy to help!