You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SaneClip is a macOS clipboard manager that captures clipboard history, applies rules/transforms, and provides Touch ID protected access. It also supports snippets, URL schemes, Shortcuts, and widgets.
Non-goals
No SaneApps clipboard sync server and no ad-tech analytics. Optional iCloud sync uses the customer's iCloud account, and privacy-preserving operational counts never include clipboard content.
No clipboard capture for transient or password-manager types.
No network dependency for core clipboard functionality.
System Context
Menu bar app with hotkeys and popover UI.
Clipboard capture via NSPasteboard polling (timer-based).
Touch ID via LocalAuthentication for protected access.
Widgets via App Group shared data.
Sparkle for update checks (direct download builds).
No GitHub DMG: DMGs are hosted on Cloudflare R2, not in GitHub.
Architecture Principles
Local-first: all data stored on device.
Safety: sensitive sources and transient pasteboard types are ignored.
Predictable persistence: JSON files in Application Support + UserDefaults.
Modular services: capture, rules, security, and UI are separate.
Core Components
Component
Responsibility
Key Files
ClipboardManager
Capture, history, pinning, widget updates
Core/ClipboardManager.swift
ClipboardRulesManager
Normalize and sanitize content
Core/ClipboardRulesManager.swift
SnippetManager
Snippet storage + placeholder expansion
Core/SnippetManager.swift
TextTransformService
Paste transforms
Core/TextTransformService.swift
SensitiveDataDetector
Detect sensitive content patterns
Core/Security/SensitiveDataDetector.swift
AutoPurgeService
Time-based auto deletion
Core/Security/AutoPurgeService.swift
HistoryEncryption
Optional AES-GCM history encryption
Core/Security/HistoryEncryption.swift
URLSchemeHandler
saneclip:// command entrypoints
Core/URLScheme/*
WebhookService
Optional outbound webhook posting
Core/Webhooks/WebhookService.swift
Data and Persistence
History: ~/Library/Application Support/SaneClip/history.json (encrypted if enabled).
Excludes transient clipboard types and known password manager bundle IDs.
Network requests are optional (webhooks, Sparkle updates).
Build and Release Truth
Single source of truth: .saneprocess in the project root.
Build/test: ./scripts/SaneMaster.rb verify (no raw xcodebuild).
Release: run ./scripts/SaneMaster.rb release_preflight, then
./scripts/SaneMaster.rb appstore_preflight for App Store lanes, then the
guarded SaneProcess release.sh --full --version --notes --deploy pipeline.
Direct channel:
DMGs uploaded to Cloudflare R2 (not committed to GitHub)
Sparkle feed configured in SaneClip/Info.plist
Lemon Squeezy handles direct licensing
Direct download + Sparkle is canonical; Homebrew is optional only when intentionally published and the Basic/Pro gate is verified
App Store channel:
StoreKit + App Store updates
no external licensing path in the App Store build
Setapp channel:
separate -setapp bundle ID
Setapp entitlement/update path
no Sparkle
no Lemon Squeezy activation UI
no donate/sponsorship UI in the Setapp build
launch-at-login should remain explicit user choice in the Setapp lane instead of being silently treated as channel-default behavior
Channel rule:
direct Lemon Squeezy business stays in place
Setapp using Stripe does not replace the website/direct flow
Known Setapp gotchas:
Setapp packages must stay universal: main app, extensions, and MPSupportedArchitectures must include both arm64 and x86_64; setapp_package and setapp_upload --validate-only enforce this before upload
Setapp packages that sign iCloud/app-group/keychain entitlements must embed a matching Contents/embedded.provisionprofile; notarization and spctl can pass while LaunchServices still fails with launchd/RBS error 163 if the profile is missing
The final Setapp ZIP must be expanded, quarantined, and opened on the Mini before upload; static signing checks alone are not release proof
Setapp final ZIP validation must reject Sparkle framework residue, Lemon
Squeezy/license-key/checkout strings, and donation/direct-download copy in
the uploaded archive
Setapp listing screenshots are manifest-backed owned-site app-in-use
screenshots and must pass 16:10 / 1280x800 validation before upload
if the macOS Setapp lane ships widgets or extensions, the extension bundle-ID family must be audited with the Setapp lane instead of assumed to inherit safely
Setapp public release notes must be user-facing. Review-team comments,
icon geometry, archive/signing details, and direct-channel licensing/update
terms belong in private review comments or email, not in Release notes.
Testing Strategy
Unit tests in Tests/.
Use ./scripts/SaneMaster.rb verify for build + tests.
Manual checks: clipboard capture, Touch ID prompt, widgets.
Channel checks:
direct = Sparkle + Lemon Squeezy path
App Store = StoreKit + App Store-safe UI
Setapp = Setapp entitlement/update path with no Sparkle/direct-pay drift
Risks and Tradeoffs
Clipboard polling can miss extremely fast changes.
Encrypted history increases CPU overhead on save/load.
Widgets rely on App Group access and shared file writes.