Skip to main content
Release History

Changelog

All notable changes to The Annex, organized by version.

v1.11.0

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