Changelog
StorageScope release history. Download the latest from GitHub Releases.
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.
Requires macOS 14 or later · verify with SHA-256 · Privacy