Release History
Changelog
All notable changes to The Annex, organized by version.
v1.10.0
Fixed
- Command injection via shell interpolation — all shell commands now use Process with arguments array (runDirect/runDirectAsync) instead of string interpolation through bash -c, eliminating injection risk in hostnames, paths, share names, and custom rsync flags
- SyncJob mutations off main thread — rsync completion handler now wraps all @Published property updates in DispatchQueue.main.async to prevent SwiftUI data races
- Thread-unsafe line buffer in ShellHelper — lineBuffer in readabilityHandler is now protected by a dedicated bufferLock
- processQueue TOCTOU race — SyncEngine.processQueue() now collects all folders to start under a single lock acquisition instead of unlocking/relocking between iterations
- SMB mounts not using stored credentials — NASMonitor now retrieves passwords from Keychain via authenticatedShareURL(for:) and includes them in mount commands
- Force unwraps on timers and AppDelegate properties — timer!, syncTimer!, statusItem!, and menu! replaced with safe optional unwrapping
- Silent data loss on decode failures — all AppState load/save methods now log errors via NSLog instead of silently swallowing try? failures
- Dead code in NASMonitor — removed no-op expression
- DiscoveredNAS deduplication broken — hash(into:) and == now use hostname instead of per-instance UUID
- Stale connection quality when NAS goes offline — legacy fields are now cleared alongside per-device dictionaries
- Process returned before started in ShellHelper.runAsync — process is now started synchronously before returning
- IOKit nil safety — IOPSCopyPowerSourcesInfo() and IOPSCopyPowerSourcesList() return values are now nil-checked
- Keychain save failures silently ignored — failed saves now log an error to the activity log
- Log export failures invisible to user — export success/failure now logged to activity log
Added
- Shell escaping utility — ShellHelper.shellEscape() for safe single-quote escaping, plus runDirect() and runDirectAsync() methods
- Input validation — hostname validation, bandwidth limit clamping, dangerous rsync flags stripped
- Accessibility labels — status icons, pickers, toggles, and menu bar icon now have proper VoiceOver descriptions
v1.9.0
Changed
- Sync History chart — added time range picker and interval picker with proper time-bucketed aggregation and adaptive x-axis labels
v1.8.0
Added
- DMG installer — releases now include a styled .dmg with custom background, app icon, and Applications drop link
v1.7.1
Fixed
- Duplicate permission dialogs on launch — limits to 1 concurrent sync until first success, guards reconnect path against queuing
- CI test failure for runAsync output — drains remaining pipe data after waitUntilExit()
v1.7.0
Fixed
- Statistics reset on every restart — SyncEngine now reads/writes directly from AppState.shared.statistics
- Rsync stats never parsed — ShellHelper.runAsync now accumulates and passes through all output
- @Published mutations off main thread — added internal backing stores with main-thread-only @Published updates
- Sync counter could go negative — clamped to max(0, n-1)
- Shell command deadlock risk — reversed pipe read and waitUntilExit order
- Menu bar rebuilt on every log line — removed redundant buildMenu() calls
- Clear Logs didn't persist or update UI — now calls saveActivityLog() and objectWillChange.send()
Added
- WiFi network restriction — auto-sync skips when not connected to an allowed WiFi network
- AC power restriction — auto-sync skips when running on battery
- Custom rsync flags — user-defined rsync flags passed through to rsync
- Launch at Login — uses SMAppService (macOS 13+)
Changed
- Advanced settings fully persist — WiFi filter, allowed SSIDs, AC power, and custom rsync flags save to UserDefaults
- Removed dead code — stripped unused SyncSchedule and FileFilters
- Test suite updated — added 7 regression tests
v1.6.1
Fixed
- CI build failure — ChangelogView.swift and CHANGELOG.md resource copy were missing from CI workflows
- Compiler warnings — suppressed unused variable warnings
v1.6.0
Added
- What's New tab — in-app changelog viewer with collapsible version headers and color-coded section badges
v1.5.1
Changed
- "Check for Update" opens release page — now opens GitHub release page instead of directly downloading
v1.5.0
Fixed
- Auto-sync was never running — added syncTimer to AppDelegate
v1.4.0
Added
- Symlink mode on folder creation — toggle symlink mode when adding a new sync folder
- Startup state verification — verifies symlink state matches the actual filesystem on launch
- Safe folder deletion — removing a symlinked folder will unsymlink it first
v1.3.0
Added
- Symlink mode for macOS-protected folders — automatically detected and run in sync-only mode
- Clickable folder paths — local and NAS paths open in Finder when clicked
- Browse buttons — folder picker dialogs for both paths
- Startup recovery — folders stuck in "Transitioning..." are recovered on launch
- Update checker — automatic check for new versions on startup
Fixed
- App hang after overnight sleep — batched addLog() with 500ms coalescing
- App hang on Cancel — moved log() and process termination to background threads
- Sync restart after cancel — cancelling no longer re-queues the folder
- Update alert not showing — fixed race condition
Changed
- "Pause" replaced with "Cancel" throughout
- About view and README updated
v1.2.1
Fixed
- Update checker race condition — callback now fires after properties are set
- Startup update alert visibility — added delay and NSApp.activate
v1.2.0
Added
- Check for Updates — queries GitHub Releases API
- Startup update prompt — alert on launch for newer versions
- Manual update check — button in About tab
- Menu bar cancel — individual sync jobs cancellable from menu bar
Changed
- Rsync output processing batched for better performance
- rawLog changed from @Published to manual refresh
- ShellHelper.runAsync rewritten to use terminationHandler
v1.1.0
Added
- Symlink mode — replace local folders with symlinks to NAS
- Automatic unsymlink on NAS offline — local copies restored
- Automatic re-symlink on NAS online — changes synced and symlinks re-created
- Raw rsync output toggle in sync detail view
Changed
- Rsync output display now shows "Skip existing" messages cleanly
v1.0.0
Added
- Initial release
- Multi-NAS support with Bonjour/mDNS network discovery
- Queue-based sync engine with rsync integration (max 2 concurrent)
- One-way sync: Local to NAS with progress tracking
- Live NAS monitoring — connection quality, latency, disk space
- Auto-mount SMB shares when NAS comes online
- Configurable check intervals (30s to 30min)
- SwiftUI interface with General, Sync Folders, Activity Log, Statistics, Advanced, and About tabs
- Menu bar app with dynamic status icon
- Keychain integration for NAS credentials
- Bandwidth throttling and WiFi/power-based sync scheduling
- Preset folder configurations (Downloads, Documents, Pictures, Movies, Music, Desktop)
- Custom folder support with exclude patterns
- Activity log with search and export
- Statistics dashboard with transfer metrics and charts (macOS 13+)
- Annex personality mode with fun quotes
- CI/CD with GitHub Actions