Changelog
StorageScope release history. Download the latest from GitHub Releases.
v0.8.0 — Incremental Scans and Review Integrity
Incremental rescanning reuses unchanged scan state and rescans changed folders, with a full-scan fallback when saved state or event history cannot be trusted. Search navigation now follows displayed results and reveals matches in collapsed Folder Tree branches.
- All Sizes in Duplicate Review includes smaller unverified same-size groups while keeping them separate from verified duplicates.
- Trash review reports candidates that disappear before confirmation; rollback attempts continue across earlier moves after a later failure.
- Duplicate hash-cache byte bounds and deleted-cache recovery are fixed, with regression tests for hard links and changed files.
- The final local audit passed 251 tests across 26 suites. Synthetic duplicate-verification measurements and their limits are in the full release notes.
The downloadable DMG is ad hoc signed and not Apple notarized. Its binary is for Apple Silicon Macs running macOS 14 or later; Intel has not been validated for this release.
v0.7.1 — Window Polish and Keyboard Navigation
Visual and navigation polish release. StorageScope now opens with more practical window dimensions, keeps dense Settings and cleanup controls visible, and improves keyboard-first movement across Cleanup Review, item tables, and Folder Tree reveal flows.
Window and layout polish
- Roomier windows — main app and Settings defaults better fit the current multi-pane interface.
- Cleaner storage views — Overview, sidebar, type breakdown, duplicate review, and tree surfaces were tuned for stronger hierarchy and steadier row spacing.
- Better empty and loading states — dense scan surfaces stay readable before, during, and after scans.
Keyboard navigation
- Cleanup Review navigation — keyboard selection and movement now support review-heavy cleanup flows.
- Folder Tree reveal behavior — reveal actions keep selection, expansion, and detail focus aligned.
- Table focus stability — item rows and keyboard-driven selection remain predictable across filtered views.
Tests
- Added app-level coverage for keyboard selection, Folder Tree reveal behavior, and tree keyboard navigation state.
v0.7.0 — Scan Control and Privacy
Scan control and privacy release. Adds a folder exclusion list so scans can skip node_modules, .git, and cache directories entirely; results now stream into list views live during a scan instead of only appearing at the end; cancelling a scan keeps its partial results browsable instead of discarding them; scans can be paused and resumed in place, with a live items/sec and elapsed-time readout; and a new redaction toggle masks file/folder names in the UI for screen-sharing. Interface rendering also got faster: cached type breakdowns, cheaper size bars, and less main-thread disk I/O.
Scan control
- Folder exclusion list — Settings gains an on/off toggle plus an editable list of excluded folders (defaults:
node_modules,.git,Library/Caches,~/Library/Application Support). Excluded folders are skipped entirely during traversal, not just hidden from results. A right-click "Exclude This Folder" action is available from list views. - Streaming results — Largest Files/Folders, Old Large Files, and other ranked views populate and re-sort live while a scan is still running, instead of showing "No Items" until it finishes.
- Cancel keeps partial results — cancelling an in-progress scan leaves the last streamed snapshot browsable instead of resetting back to an empty state.
- Pause/resume — a scan can be paused and resumed from the toolbar (in-memory only; does not survive quitting the app).
- Rate/elapsed display — the scan header shows a smoothed items/sec estimate and elapsed time while scanning.
Privacy
- Redaction toggle — a new Settings section masks file and folder names/paths across the UI with stable, per-session generic placeholders (e.g. "File 3.mp4", "Folder 1") while keeping sizes, dates, and counts real. Trash/move/reveal actions are unaffected since only display text is masked.
Interface performance
- Canvas size bars — the proportional size bars in Tree Explorer, Type Breakdown, and the Overview storage map are now drawn with a shared
Canvasview instead of a per-rowGeometryReader, removing an extra layout pass for every visible row on large expanded trees. - Cached type breakdowns — the File Type Breakdown and Category Mix lists cache per scan + search query instead of re-filtering every extension on every render while typing.
- O(1) Expand All enablement — the Tree Explorer toolbar no longer re-walks the entire retained tree on every selection or expansion change to decide whether "Expand All" applies; container IDs are cached per scan.
- Cached volume capacity — the sidebar Volumes section no longer performs synchronous disk I/O on every render to show "free of total" capacity; values cache per volume and refresh with the volume list.
- Lighter list dividers — row lists no longer allocate an enumerated tuple array per render just to skip the trailing divider.
v0.6.0 — Visual Refresh
Visual refresh across the entire app, plus performance work on row diffs. The welcome screen was extracted into its own module and now has a hero layout; the inspector was redesigned with a tinted icon header and clearer button hierarchy; list rows adopt Equatable so SwiftUI can skip recompute on large scans.
Visual refresh
- Welcome screen — full-width hero spanning both columns, spring transition when a scan starts/completes. Module extracted into
WelcomeView.swift(PR #90). - Inspector redesign — tinted icon header (rounded rect background matching file type), two-column attribute grid, clear primary/secondary/trash button hierarchy.
- Card radius 12 — all card backgrounds bumped from 8 → 12 pt, inner bars remain concentric.
- Storage Map — single unified card with dividers between rows, hover highlight per row.
- Hover states — all interactive list rows (Storage Map, Largest Files/Folders, Old Large Files, Folder Tree, Cleanup Review, Duplicate Review) highlight on hover.
- Metric cards — opaque material, tabular figures for numeric values.
- View transitions — switching between smart views fades smoothly instead of snapping.
- Sidebar Grant Access buttons — slimmed to compact icon + label style.
- Settings window — taller default size + scroll view so the Duplicate Hash Cache section isn't clipped.
Performance
StorageItemRowandDuplicateItemRowadoptedEquatableso SwiftUI skips diff/recompute for identity-equal rows.CleanupCandidateRowadoptedEquatableand hoisted per-row state, reducing redundant diff work on the cleanup-review lane.DuplicateItemListrender-path refactor extracted rows toDuplicateFileRowfor per-row@Statehover tracking.
Documentation
- README overview screenshot refreshed to the current app UI.
Internal
- WelcomeView lifted into its own file (PR #90); welcome and empty-state branch now owned by
DetailView. - Removed
ensureAncestorsExpandedfrom find-next navigation; auto-expand-on-Cmd+G reverted.
v0.5.3 — Performance measurement slice
Measurement-only release. No behavior changes, no scan-pipeline optimizations — this slice exists to validate (or invalidate) the bottleneck hypotheses in the v0.5.3 → v0.6.0 performance plan before any optimization commits. The premise: most proposed optimizations are waste unless the underlying bottleneck is real, so we measure first.
Signpost coverage added
verify_on_demand— wrapsFileSystemScanner.verifySizeGroup(_:)and reports item count for each on-demand Verify Now tap. Surfaces how long each Verify Now call takes vs. the cached path.persist_on_demand— wraps the backgroundDuplicateHashCache.persist()detached task triggered after on-demand verify. Measures the JSON encode + disk write cost the main actor skips.cache_invalidate_derived— event signpost fires every timeScanStore.invalidateDerivedCaches()clears the cleanup / reclaim-plan / old-large / duplicate caches, annotated with the active view name. Used to detect excessive invalidation during cleanup-review interactions.cache_invalidate_items— event signpost oninvalidateItemsCache(), also annotated with active view. Validates P-3 (cache gap) priority.lookup_build— wrapsStorageScan.buildItemLookupand reports retained/largest/old/duplicate/verified/cleanup counts. Validates P-21 (eager map build vs lazy).cleanup_candidates_build— wrapsScanAccumulator.cleanupCandidates(...)sort + filter pass, captures the gapScanBenchmarkReport.textalready admits exists betweentotalDurationandduration.
ScanBenchmark fixture scaling
StorageScopeBenchmark --items <n> [--depth <d>] [--duplicates <0..1>]— new flags produce scaled synthetic fixtures (10k / 100k / 500k items). Items distributed across a directory tree ofdepthlevels (~120 files per leaf dir);--duplicatesis the fraction emitted as content-identical pairs (so the verify path actually fires).- Backward compat:
--syntheticwith no--itemsstill builds the v0.5.0-era curated 7-file fixture, sobuild_and_run.sh --fixture-scanworkflow is unchanged. --keep-fixtureprints the fixture path so Instruments can be attached for a manual re-scan at fixture scale.
v0.5.2 baseline reports
- Three baselines captured under
docs/perf-baselines/v0.5.2/: curated (24 items, 0.01s), 10k (10,353 items, 6.90s), 100k (105,277 items, 185.51s). 500k skipped on the dev machine — would need ~36 GB disk vs ~33 GB free. Seedocs/perf-baselines/v0.5.2/README.md. - Bench script (
swift run StorageScopeBenchmark --help) now documents the scaled-fixture workflow so the validation workflow is reproducible.
Validation
swift test— 90 tests across 10 suites pass./script/public_upload_audit.sh— passesbash ./script/build_and_run.sh --verify— bundle launches cleanly- Scaled fixtures regenerate and benchmark cleanly at 10k and 100k items locally.
See PLAN.md for the full v0.5.3 → v0.6.0 performance plan, the 23 ranked bottleneck hypotheses (P-1..P-23), and the decision rules that gate which optimizations actually ship based on the numbers measured above.
v0.5.2 — Inspector, File Types colors, Duplicate Review copy
Three more tour-driven fixes: Inspector no longer shows the root scan as a (misleading) default selection, File Types bars use per-category tint so length is visually comparable at a glance, and the Duplicate Review subtitle is de-jargoned.
Inspector
- Removed the root-scan fallback in
ScanStore.selectedItem. Previously, when no item was explicitly selected, the inspector fell back toscan.rootItem(the entire scan root folder) — making the inspector look like it was “stuck on the root” on every view, and masking the fact that selection is explicit. It now returnsnilso the existing “No Selection — Select an item to inspect actions and metadata” empty state appears, which is what should have surfaced in the first place.
File Types
- Per-category bar tint. Both the Category Mix bar chart and the per-extension bar chart used a single color (teal for categories, blue for extensions), which made the visual length difference hard to register when bars were short (the Archives@16 GB vs Installers@572 MB bars looked near-identical proportionally). Each
FileTypeStat.Categorynow maps to a deterministic system color (Archives → orange, Audio → pink, Developer → purple, Documents → blue, Databases → red, Fonts → indigo, Images → green, Installers → teal, Video → yellow, Virtual Machines → brown, Other → gray). Bar lengths were already linear with an 8pt floor — no scaling change, just better perceptual differentiation.
Duplicate Review
- Subtitle de-jargoned. “Verified duplicates are content-hashed. Same-size candidates remain separated as review leads.” → “SHA-256 confirmed copies are safe to remove. Size-matched candidates need manual review.” Same meaning; fewer formal-CS terms.
Validation
swift test— 90 tests across 10 suites pass./script/public_upload_audit.sh— passesbash ./script/build_and_run.sh --verify— bundle launches cleanly
v0.5.1 — Post-release UX polish
Targeted fixes for issues raised during the v0.5.0 UI tour: Folder Tree expand behavior, persistent-header context-sensitivity, sparse-result view layout, and Keeper badge explainability.
Folder Tree
- Disclosure triangle is its own toggle. Previously the whole row mutated both selection and expand state, and double-clicking a folder opened it in Finder instead of drilling in-app. The chevron is now a dedicated
Button; the row body only selects. Deprecated the double-click-to-Finder shortcut on tree rows (Reveal/Open remain in the row's context menu). - Empty-tree accessibility hint clarified: rows that can expand say “Click the disclosure triangle to expand or collapse”; leaf rows still say “Selects this item.”
Context-sensitive header
- FilterBar removed from Overview / File Types / Cleanup Review. The Display + Scan Options strip was ~120pt of dead chrome on views that don't use it. The bar now appears only when the active view actually consumes the size or sort picker.
- Size picker gated per-view. Hidden on Overview / File Types / Cleanup Review where the threshold aggregates nothing.
- Sort picker gated per-view. Tree is hierarchical; Overview / File Types / Cleanup Review have no column sort the user can flip from the bar.
- Removed Scan Options group from the filter bar. “Old after N days” and “Hidden” were already covered by Settings → Scan Options; their presence in the bar was redundant. (The Old-after stepper was re-added only on Old Large Files, since the threshold directly drives that view.)
Old Large Files
- Threshold hint card when results are sparse. A new
OldLargeFilesThresholdHintinline card surfaces when 1–5 files qualify: copy acknowledges the small set, secondary line guides the user to lower the size filter or “Old after N days” then Rescan, and a Rescan button is wired up for one-tap action. - List minHeight scales with content. The scroll area no longer reserves 420pt when only a handful of rows exist — drops to ~220pt for ≤5 items so the empty canvas below sparse results is gone.
Duplicate Review
- Keeper badge tooltip. The green “Keeper” capsule now carries a help-tag explaining what keeper means and how to reassign it (right-click another copy → “Set as Keeper”). Row accessibility now appends “, keeper” to the row label and the hint explains the swap path.
Validation
swift test— 90 tests across 10 suites pass./script/public_upload_audit.sh— passesbash ./script/build_and_run.sh --verify— bundle launches cleanly
v0.5.0 — Search + Performance Upgrade
Search and performance upgrade. 14 PRs (#75–#89) shipped under the batch plan plus one bonus UI improvement (#82, Open Recent submenu). 15 PRs total across search, performance, and docs.
Search
- Multi-word AND search: split queries by whitespace and require every term to match (#84). "Documents report" matches both
Documentsas a path segment ANDreportas a substring of name. - Path-segment matching: terms match
/-split segments of the full path, so "Documents" matches/tmp/work/Documents/file.txtwithout needing "Documents" to be a contiguous substring (#84). - Substring highlight: matched substrings in the file list and Folder Tree render in accent color via a new
HighlightedTextSwiftUI view struct (#84). - Search-result count badge: pill-shaped "N matches" badge in StorageItemTable header when a search is active (#86). Distinguishes nil (no search) from zero matches so the empty-state below can tell them apart.
- Search empty-state distinction:
FilterRecoveryViewnow distinguishes "no scan", "no matches" (search active, zero hits), and "filtered empty" (filter chips, zero hits) with state-appropriate iconography and copy (#87). - Cmd+G find-next navigation: Mail/Finder convention. Cmd+G advances through search-result rows and wraps around; Cmd+Shift+G reverses (#88). Sets
selectedItemIDso the matched row takes focus. Menu items gate on search-active. - Search recents persistence:
SearchRecentsStoremirrors the RecentsStore ring-buffer pattern; recent terms surface as.searchSuggestionson the search field with Clear Recents (#89). Persists across launches via UserDefaults.
Performance
os_signpostinstrumentation on scan phases:enumerate,verify,persist, andindexspans wrap the hot paths so Instruments → Points of Interest can profile (#81). Zero behavioral change — no-ops when no profiler is attached.- Cache
duplicateGroups+verifiedDuplicateGroupsperscanFinishedAt, mirroring the existingcleanupCandidatescache pattern (#79). Avoids re-walking size-group / verified-group aggregation on every Duplicate Review view transition. - Background-persist
RecentsStorewrites off the main actor (#80). Heavy JSON encode + UserDefaults write detaches to.utilitypriority; @Published entries still updates synchronously so SwiftUI sees immediate change. Matches the v0.3.0hashCache.persist()pattern. - Extend
ScanBenchmarkReportwith per-phase durations:enumerateDuration,verifyDuration,persistDuration, andtotalDurationcomputed property (#83).duplicateVerificationDurationkept as deprecated alias for backward compat.
UI / chrome
- Bonus:
File → Open Recentsubmenu (#82). Mirrors macOS convention for non-document apps — lists recently-scanned folder paths so users can re-launch a scan without reaching for the sidebar. Dynamic rebuild on each menu open viaNSMenuDelegate; gates on!store.isScanning. - Keyboard-shortcuts.html docs page (#76) documents every v0.4.5 + planned v0.5.0 hotkey (Cmd+G/Cmd+Shift+G originally listed as "Coming in v0.5.0"; now shipping).
- Architecture.html docs page (#75) documents the ScanStore tier-2 sub-store extraction pattern + filterBinding bridge + derived cache invalidation, with an ASCII store hierarchy diagram.
v0.4.5 — UX Polish Release
This update focused on UX polish and standard macOS conventions. 39 PRs (#33–#72) shipped since v0.4.0.
Filter UX
- Individually dismissible filter chips — click the x on one pill instead of clearing all (#39).
- File-type filter separated from search query so the chip reads "Type: mp4" instead of misleading "Search: mp4" (#46); auto-clears on non-applicable view switches (#47); visually focuses the row in Type Breakdown (#62).
- Instant chip drop when the search field empties (#45).
- Context-aware toolbar search prompt per active view ("Search folders and paths" on Tree, "Search extensions" on File Types, etc.) (#64).
- Selected sidebar view persists across launches (#63).
- Scan duration formatter shows "1 min 30 sec" instead of "1 min" for sub-10-minute rescans (#54).
- Recent scan relative timestamps stay current via
TimelineViewinstead of freezing (#56). - Modified column shows relative time for recent files (#66).
Standard macOS menus
- View menu + Cmd+1–8 hotkeys for sidebar Smart Views (#41).
- Edit menu Find → Cmd+F focuses the search field (with graceful fallback on macOS 14) (#69).
- View menu Enter Full Screen → Cmd+Ctrl+F (#70).
- Window menu Close → Cmd+W (#52).
- Help menu links to GitHub repo + issues (#59).
Item / trash review interactions
- Double-click-to-open across item rows, opens file in its default app (#43).
- Trash review rows gain an Open button alongside Reveal + Remove (#53).
- Trash review section headers have Reveal All (#50) + Remove All (#67).
- Gate the Open button on inaccessible items with help tag (#60).
- Expand All / Collapse All in the Folder Tree header (#61).
- Clickable sortable column headers with chevron direction indicator in StorageItemTable (#65); Modified cycles Newest ↔ Oldest; VoiceOver values speak direction (#68).
Sidebar / welcome / window chrome
- Sidebar scan-entry buttons disabled mid-scan (#40); toolbar Choose Folder + Recent Scans + Volumes gated (#49).
- Volume free-space surfaced as subtitle ("247 GB free of 1 TB") (#51).
- Recent Scans quick-launch on the WelcomeView (#57).
- Tappable Overview insight cards (Largest File / Folder / Oldest Large File) select the item (#58).
- Scan-canceled acknowledgment banner with contextual rescan action, made dismissible (#44, #55).
- Dynamic window title reflecting the scanned root: "StorageScope — <folder>" (#48).
- Reclaim Plan action labels disambiguated (#42).
ScanStore sub-store extractions (provisional-for-v0.5.0)
- RecentsStore (#34), OnDemandVerificationStore (#35), FilterStore (#38) extracted from ScanStore via the lazy-var + closure-injection pattern. See the new architecture page for details.
Accessibility
- VoiceOver sort direction in column header a11y values (#68).
- File Type Category Mix rows labeled with category + bytes (#71).
Quality-of-life dead-code / micro-cleanup
- First-run permission card body →
.footnote(#33). - Inline StorageFormatless.kindLabel — remove needless indirection (#36).
- Remove dead
import AppKitfrom ScanStore (#37).
v0.4.0 — Foundation Release
Foundation for the UX arc that followed in v0.4.5. App Sandbox + security-scoped bookmark plumbing, transactional Trash, and the documented build / release pipeline.
.cardBackground()/.selectionBackground()SwiftUI ViewModifier extensions for consistent card styling across views.DuplicateHashCache.purgeStale— auto-evict stale hash cache entries based on file mtime + path.script/public_upload_audit.sh— switchedrg→grepfor portability + added allowlist for legitimate runtime env-var references.script/notarize_dmg.sh— scaffolds the notarization pipeline (NOTE: never actually submitted to Apple's notary service in this release — first submission remains a manual maintainer step).- Contributor docs scaffolded.
- Various foundation cleanups.
v0.3.0 — Scan performance and duplicate review completion
Parallel sibling-directory enumeration, adaptive hash concurrency scaled to available cores, keeper comparison sheet, on-demand "Verify Now" for same-size candidate groups, persisted hash cache management in Settings, and a sweep of Trash flow and review UX improvements folded in from v0.2.0.
- Scanner recurses folder children in parallel with deterministic ordering.
- Duplicate verification scales up to 8 hash workers based on core count.
- Verified duplicate groups gain a side-by-side keeper comparison sheet.
- "Verify Now" out-of-band hashes candidate groups beyond the auto budget.
- Settings gains a duplicate hash cache section with a guarded Clear Cache.
- Async batch Trash with progress, debounced search, unified trash confirmation.
v0.1.1 — Trash confirmation sheet and scan slices
SwiftUI Trash confirmation sheet, focused scan-state slices, localization groundwork, inspector keyboard focus, and extra filesystem safety tests.
- SwiftUI trash confirmation sheet for verified-only and mixed-risk batches.
- Focused scan state slices to narrow observation and re-render scope.
- Localization groundwork and inspector keyboard focus improvements.
- Additional filesystem safety tests.
Apple Silicon · macOS 14+ · verify with SHA-256 · Privacy