Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…anic Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lorMode import Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RevealInFinder and MoveToTrash were never built by any caller — the context menu performs both inline. Removing them makes the dispatch match exhaustive, so a future variant fails to compile instead of being silently swallowed by the catch-all arm. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- cleanup_selected pruning: collect-to-Vec then remove-in-loop is HashSet::retain, one line instead of twelve - Option::is_some_and replaces map_or(false, ..) in two predicates - collapse the nested hover-detection if into one condition Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Delete / empty-trash / batch-delete each hand-built the same modal: hidden title bar, center anchor, heading + detail label, right-aligned danger+ghost pair. Extracted to ui::widgets::confirm_dialog returning Option<bool> (None = undecided, Some = chose), next to the buttons it uses. Also flattens the Option<Option<_>> result plumbing into plain Option<NodeId> / Option<Vec<NodeId>> / bool. Only visual change: the single-delete dialog now uses the same 320/360 width as the other two. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tart_scan Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…an Done branch Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ot-owned) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-uploaded files) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… parent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…uping Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ror in status_message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Caches, .gradle Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…TRY=1 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ally happened Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
remove_node and clear_descendants each had their own walk marking descendants dead — one recursive with a per-directory children clone, one iterative. Both now share the iterative version (no clone, no recursion depth limit). Also drop a dead `let new_id = …; let _ = new_id;` in graft_under. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Six copies of the same four-line f64-rect → egui::Rect conversion in treemap_view collapse to a single helper. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ogressInfo Cmd+1/2/3 were three copies of the same three-statement body; now one loop over (key, mode). ScanProgressInfo's zeroed literal was written out twice — derive Default and spread it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ompute_sizes_upward Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on, dead-node skip) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…eption fires
/System/Volumes was listed in build_skip_paths, which caused the TCC-path
loop to skip everything under it — including the scan root — even when the
scan-root exception at line 124 had already decided not to skip. Scanning
/System/Volumes/Data produced an empty tree as a result.
The existing should_skip() guard (path.starts_with("/System/Volumes") &&
!path.starts_with(scan_root)) is the right place to handle this; the
skip_paths entry was redundant and wrong.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…g it find_candidates was classifying the scan root node itself — if the user scanned ~/Library/Caches the root would match "Application Caches" and the Trash button could delete the entire scanned directory. Fix: iterate the root's direct children instead of calling walk() on the root, so only descendants are candidates. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ce nodes is_volume_root() was firing for /usr, /usr/local, /System/Library/Caches, /System/Library/Assets, and /System/Library/AssetsV2 because they have a different st_dev from / (they are firmlinks into the Data volume) but were not listed in APFS_FIRMLINKS. A partial refresh on any of them would add a duplicate Free Space synthetic node and double the displayed disk total. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After a delete, the zoom_stack is pruned of dead nodes and if empty the scan root is pushed back as the fallback. If the scan root itself was the deleted node (possible before the cleanup.rs scan-root guard) the pushed ID was dead, leaving the UI in an invalid state. Guard both perform_delete and perform_batch_delete with is_alive(root). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Errors from both poll_scan and poll_partial_refresh were only printed to stderr; the user saw nothing in the UI. Add status_message assignments so the error appears in the status bar. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
starts_with("__") was used to identify __free_space__ and __skipped__
synthetic nodes, which would falsely match real file extensions like __bak,
causing them to be excluded from file_count and extension statistics.
Switch to exact equality checks in both child_totals and
collect_extensions_recursive.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CleanupCandidate.size was set at scan time and never updated. After deleting individual files inside a candidate directory the tree node's size is correct (remove_node propagates upward) but the cached value remained stale, so the batch-delete total overstated the space to be freed. Replace retain() with retain_mut() and refresh c.size from the live tree. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ot thread The comment said "background thread" but the implementation spawns an OS process (osascript) and returns immediately. Clarify that Ok(()) means the process started, not that the trash was emptied. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…of three filters Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… conditionally Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…l from synthetic nodes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nt ends Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nset Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…! wrapper Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…st_ancestor Re-parenting to a distant ancestor produced wrong full_path() results, which could cause Trash/cleanup to target the wrong file. Now entries whose parent is absent from dir_map are silently dropped. Deletes find_closest_ancestor entirely and adds a unit test for the guard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The jwalk process_read_dir callback on line 180 already gates every entry; the identical check inside the loop was dead code. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… wrap Casting buf.len() as u32 silently wraps at 4 GiB, corrupting full_path() results. try_from + expect panics immediately with a clear message so the bug is visible rather than silent data corruption. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ore layout recompute Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… instead of found flag Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nto is_some_and Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sweep 2026-10-04 — mac-dir-stat
5 cycles · 100 commits · tests: 36 → 43
Findings fixed (50 total: 9H · 28M · 13L)
perform_deletenois_alivecheck; trashes wrong path if node died while dialog was open/Users/Applicationsetc.) treated as volume roots; fake Free Space injecteddir_mapafter metadata fail; children get wrongfull_path→ Trash hits wrong fileremove_nodeno dead-node guard; double-call corrupts ancestor sizespoll_partial_refreshkeeps staleConfirmTrash/ConfirmBatchTrashids after graftwants_keyboard_input; search-clear opens trash dialogview_root/zoom_stack/ selection not sanitized after delete; deadview_rootremove_nodeon dead node — now idempotent; test addedNone≠"")poll_partial_refreshgrafts without checking target alive; setsselected_nodeto dead id/System/Volumesinskip_pathsblocked the scan-root exception → empty tree on Data volumeConfirmBatchTrashnot invalidated after graft/usr/localetc.); partial refresh on them doubled disk totalfind_candidatescould classify the scan root; "Trash All" would trash the entire scan rootzoom_stackfallback after cleanup batch-trash pushed dead root nodeeprintln;status_messagenever set__collided with real files likefoo.__bakempty_trashleft zombie; Finder failure silentperform_batch_deletenever resetview_root/zoom_stack; dead view_root after cleanup trashConfirmTrashon synthetic nodes (context menu was guarded, keyboard was not)treemap_viewhad the fix)perform_deletekept dead ancestors inzoom_stackopen/osascriptchildren never waited → zombie per clickpartial_cmp().unwrap()panics on NaN sizeset_varin test mutated env across parallel threadsstate_dir()(alreadypub(crate)in state.rs after cycle 1 dedup)/homeand/netlisted as APFS firmlinks (they are autofs; lookups can stall)should_skipinside loop body (same check already ran before dir_map insert)file_count(includes synthetic nodes) with stalescan_progress.dirsperform_batch_deletecleared selection only for exact id match; dead child nodes stayed selectedfind_closest_ancestorre-parented orphans;full_path()returned wrong path → Trash hit wrong fileStringArenaas u32cast silently wraps at 4 GiB → corruptfull_path; now panics loudlyContested / skipped (architecture-level)
find_candidateson UI thread (background task infra)graft_undermemory reclaimfull_pathfor entries found viafind_closest_ancestorwhen parent is missing (follow-up: now orphans are dropped instead of re-parented — safer)Tests
36 → 43 (+7): added tests for
remove_nodeidempotency,graft_under,clear_descendants,recompute_sizes_upward,find_candidates, scan-root orphan guard, and test isolation fixes (temp dirs, env races).Refactors (15)
platform::dialogs::pick_folderin toolbarstate_dir()infda.rsandflywheel.rsext_list.rs: iter/enumerate over index looptreemap/color.rs: lerp closuretreemap/mod.rs: deadgap > 0guard removedtoolbar.rs: chained display callui/cleanup_window.rs: single pass for batch selectionui/dir_tree.rs: frame built onceplatform/finder.rs:spawn_reapedhelper (was already fixed inline, now unified)ui/widgets.rs: reusefgfor label colorui/help_window.rs: reusetip()helperflywheel.rs: dropped unusedPathBufimport (only compiler warning)ui/treemap_view.rs: 3 simplifications (redundant guard, find loop, is_some_and)🤖 Generated with Claude Code