This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Do not make any changes until you have 95% confidence in what you need to build. Ask me follow-up questions until you reach that confidence.
RsyncUI is a SwiftUI-based macOS GUI for rsync. It organizes synchronization tasks, configures rsync parameters, and wraps the rsync CLI in a native macOS application. Requires macOS Sonoma or later. Bundle ID: no.blogspot.RsyncUI.
Use these skills for the relevant work in this repo:
/swift-concurrency— when writing or reviewing Swift concurrency code (actors, async/await, Sendable, task groups)/swift-testing-expert— when writing or reviewing Swift Testing framework tests/swiftui-expert-skill— when writing or reviewing SwiftUI views
Debug build (no notarization/signing):
make debugRelease build (archive → notarize → sign → DMG):
make buildARM64 release:
make -f Makefile-arm64 buildBuild output lands in ./build/. The release workflow requires a keychain profile named "RsyncUI" for notarization and team ID 93M47F4H9T for signing.
Tests use Swift Testing (Xcode 16+), not XCTest.
# Run all tests
swift test
# Run a specific suite
swift test --filter VerifyConfigurationTests
# Run a specific test
swift test --filter VerifyConfigurationTests/validLocalSynchronizationIn Xcode: Cmd+U. Test files live in RsyncUITests/.
Tests that share mutable state use @Suite(.serialized). Use SharedReference.shared directly in tests; reset it after with SharedReference.shared.reset().
SwiftLint is configured in .swiftlint.yml:
- Line length: 135
- Type body length: 320 lines
- Function body length: 80 lines
- Opt-in:
implicit_return,force_unwrapping,force_cast,sorted_imports,unused_declaration, and others
SwiftFormat is configured in .swiftformat targeting Swift 6 with OTBS (not Allman) braces.
Dead code is tracked via Periphery (.periphery.yml), indexed against the RsyncUI scheme.
RsyncUI/
├── Main/ # App entry point, root view, window setup
├── Views/ # All SwiftUI views, organized by feature
└── Model/ # Business logic, data, and execution
├── Global/ # Shared state (SharedReference singleton)
├── Storage/ # JSON persistence via actors
├── Execution/ # Rsync process orchestration
├── ProcessArguments/ # Rsync CLI argument construction
├── Output/ # Rsync output parsing
├── Schedules/ # Scheduled task logic
├── Snapshots/ # Snapshot support
├── Ssh/ # SSH key/config management
└── Deeplink/ # URL-based deep linking
SharedReference (in Model/Global/) is a @Observable singleton holding app-wide state: rsync version, active profile, and feature flags. Views receive it via the environment or direct reference. Local view state uses @State.
Shared JSON storage reads and writes flow through SharedJSONStorageReader and SharedJSONStorageWriter, while other storage helpers still use actors where needed. Views initialize via Task { } blocks, never blocking the main thread.
- ProcessArguments — builds the rsync argument array for a configuration
- EstimateExecute — runs a dry-run first, then the real sync
- RsyncProcessStreaming (SPM package) — streams live stdout/stderr to the UI
- ParseRsyncOutput / RsyncAnalyse (SPM packages) — parse and interpret output
- ProgressDetails — feeds parsed output into the progress view
All packages are owned by rsyncOSX and track main:
| Package | Purpose |
|---|---|
RsyncArguments |
Rsync argument generation |
RsyncProcessStreaming |
Streaming process output |
ParseRsyncOutput |
Output line parsing |
RsyncAnalyse |
Output analysis/stats |
ProcessCommand |
Process execution utilities |
SSHCreateKey |
SSH key generation |
DecodeEncodeGeneric |
Generic Codable helpers |
RsyncUIDeepLinks |
Deep link URL definitions |
Configurations, logs, and user settings are stored as JSON in the user's Application Support directory. Each profile maps to a subdirectory. The Storage/Actors/ layer wraps all file I/O in actors so reads and writes never race.
VerifyConfiguration (in Model/) validates a task before saving: checks that local and remote catalogs are non-empty, SSH fields are consistent (server ↔ username), trailing slash policy, and that snapshots/syncremote require rsync v3.