Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: CI

on:
push:
branches: [main]
pull_request:

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Test and build
runs-on: macos-26
steps:
- uses: actions/checkout@v4

- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable

- name: Unit tests (SwiftPM)
run: swift test

- name: Build app (Xcode, Release)
run: |
xcodebuild -project PRMonitor.xcodeproj -scheme PRMonitor -configuration Release \
-derivedDataPath build CODE_SIGNING_ALLOWED=NO build
66 changes: 66 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Release

# Push a tag like v1.0.0 to build, sign, notarize, and publish a GitHub release.
on:
push:
tags: ["v*"]

permissions:
contents: write

jobs:
release:
runs-on: macos-26
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Build number is the commit count.

- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable

- name: Test
run: swift test

- name: Import Developer ID certificate
env:
P12_BASE64: ${{ secrets.DEVELOPER_ID_P12_BASE64 }}
P12_PASSWORD: ${{ secrets.DEVELOPER_ID_P12_PASSWORD }}
run: |
KEYCHAIN="$RUNNER_TEMP/release.keychain-db"
KEYCHAIN_PASSWORD="$(openssl rand -base64 24)"
echo "$P12_BASE64" | base64 --decode > "$RUNNER_TEMP/cert.p12"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security set-keychain-settings -lut 21600 "$KEYCHAIN"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security import "$RUNNER_TEMP/cert.p12" -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN"
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"')
rm "$RUNNER_TEMP/cert.p12"

- name: Write App Store Connect API key
env:
ASC_KEY_BASE64: ${{ secrets.ASC_API_KEY_P8_BASE64 }}
run: echo "$ASC_KEY_BASE64" | base64 --decode > "$RUNNER_TEMP/AuthKey.p8"

- name: Build, sign, and notarize
env:
TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
ASC_KEY_PATH: ${{ runner.temp }}/AuthKey.p8
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
run: scripts/release.sh "${GITHUB_REF_NAME}"

- name: Publish release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "$GITHUB_REF_NAME" dist/PRMonitor.dmg dist/PRMonitor.zip dist/SHA256SUMS \
--title "PR Monitor ${GITHUB_REF_NAME#v}" --generate-notes --verify-tag

- name: Clean up signing material
if: always()
run: |
security delete-keychain "$RUNNER_TEMP/release.keychain-db" || true
rm -f "$RUNNER_TEMP/AuthKey.p8"
26 changes: 20 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
.firecrawl
.swiftpm
.DS_Store
DerivedData
.firecrawl/
# macOS
.DS_Store

# Xcode / SwiftPM build products
build/
.build/
DerivedData/
*.xcuserstate
xcuserdata/
PRMonitor.xcodeproj/project.xcworkspace/
PRMonitor.xcodeproj/xcuserdata/
.swiftpm/

# Release artifacts (attach to GitHub Releases instead)
*.app.zip
*.dmg

# Local tooling
.firecrawl/
.superset/

# Release output from scripts/release.sh
dist/
51 changes: 31 additions & 20 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,45 @@
# Contributing

Thanks for your interest in PR Monitor. Contributions are welcome.
Thanks for your interest in PR Monitor. Issues and pull requests are welcome.

## Getting Started
## Getting started

1. Clone the repo.
2. Open `PRMonitor.xcodeproj` in Xcode.
3. Run the `PRMonitor` target.
1. Clone the repo and open `PRMonitor.xcodeproj` in Xcode 26 or later.
2. Run the **PRMonitor** scheme (⌘R). The app appears in the menu bar.
3. Run the tests with ⌘U, or `swift test` from the command line.

## Development Notes
Debug builds use their own keychain item, so they never touch the credentials of an installed release build. The quickest way to sign in while developing is **Continue with GitHub CLI**.

- The app uses GitHub Device Flow for authentication.
- Tokens are stored in the macOS Keychain.
- Polling frequency is configurable in Settings.
> **Tip:** if a command-line build fails at code signing with "resource fork, Finder information, or similar detritus not allowed", the checkout is inside a folder that adds extended attributes (such as an iCloud-synced `~/Documents`). Pass `-derivedDataPath` (Xcode) or `--scratch-path` (SwiftPM) pointing outside it.

## Code Style
## Where things live

- Prefer SwiftUI best practices and native macOS patterns.
- Avoid introducing new dependencies unless there is a clear benefit.
- Keep background work off the main thread; UI updates should be on the main actor.
| Folder | Contents |
| --- | --- |
| `Sources/PRMonitor/Core` | Pure domain logic: status engine, notification planner, polling policy. No I/O. |
| `Sources/PRMonitor/GitHub` | GraphQL client, queries, the two-phase fetcher, and auth (device flow, `gh`). |
| `Sources/PRMonitor/Services` | `@MainActor` models and system integration. |
| `Sources/PRMonitor/UI` | SwiftUI views and design tokens (`DesignSystem.swift`). |
| `Tests/PRMonitorTests` | Swift Testing suites and fixtures. |

## Testing
Read [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) before larger changes.

There is no automated test suite yet. If you add one, please include clear run instructions.
## Guidelines

## Submitting Changes
- **Status rules belong in `StatusEngine`**, with a test. If a PR is reported wrong, first reproduce it with `PRMonitor --diagnose owner/repo` (Debug builds), then write a failing test using the builders in `Fixtures.swift`.
- **Keep Core pure.** No networking or UI in Core, and time-dependent functions take `now` as a parameter so tests control the clock.
- **Measure GraphQL cost** when changing queries. Detail fields are paid for on every changed PR.
- **Follow the platform.** Use standard controls, macOS text styles and the tokens in `DesignSystem.swift`. Don't put glass on glass, and don't animate hover.
- **No new dependencies** without a strong reason.
- Refresh screenshots with the Debug-only tools in `Sources/PRMonitor/App/`. They always use fictional preview data:
- `PRMonitor --showcase hero-light` (also `hero-dark`, `settings-light`, `settings-dark`) puts the real UI, on real Liquid Glass, over a designed backdrop and prints the rectangle to capture with `screencapture -x -R<rect> out.png`. These are the README images.
- `PRMonitor --snapshot <dir>` renders the panel, Settings panes and every menu bar icon state offscreen, for quick layout checks.
- `PRMonitor --diagnose owner/repo` checks status derivation against live repositories, using your `gh` token.

1. Create a branch from `main`.
2. Make your changes and verify the app runs.
3. Open a pull request with a clear description of the change.
## Submitting changes

If your change is significant, please open an issue first to discuss the approach.
1. Branch from `main`.
2. Make sure `swift test` passes and the app builds.
3. Open a pull request describing the change and how you verified it. Include before and after screenshots for UI changes.

For significant changes, please open an issue first to discuss the approach.
Loading
Loading