Repository navigation
Conversation
Byte-index slicing on multi-byte UTF-8 strings panics when the slice boundary falls inside a character. Switch both treemap labels and the dir-tree name column to chars().take(n).collect() so truncation always lands on a char boundary. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A partial refresh completing after start_scan had been called would graft its old NodeId into the new tree, corrupting the tree structure and targeting the wrong path on Trash. Dropping the receiver in start_scan prevents the stale graft from ever landing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Files with nlink > 1 can appear under multiple paths in the tree. Previously every hard-link path added its full st_size to directory totals, causing the root size to exceed the volume's used space and making the "Hidden / Skipped" sentinel saturate to 0. Now the first path we see for a (dev, ino) pair is counted normally; subsequent paths for the same inode contribute 0 bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
perform_delete now returns early if the node is no longer alive (e.g. killed by a concurrent partial refresh), preventing a trash call to a path that no longer exists in the tree. remove_node gains the same guard so a second removal doesn't double-subtract size and counts from ancestors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Selecting the no-extension entry sets selected_extension to Some("").
Extension-less files return None from tree.extension(), so comparing
Some("") != None always dimmed them. Check for an empty string and
match against None instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Candidates were sorted by size, so the same category header would re-emit whenever a different category's item appeared between two items of the same category. Sort by (category, size desc) before the display loop so each header appears once. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The bare-name rule matched any directory named \"target\", flagging Maven targets, CMake build dirs, etc. with incorrect Rust messaging. Guard the match with a Cargo.toml existence check in the parent dir. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
collect_files(root).len() allocated a Vec of every file ID on every status-bar render frame. The root node already carries file_count. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Single and batch delete left view_root and zoom_stack pointing at dead nodes after trashing a directory the user had zoomed into, leaving the treemap blank. Reset view_root to the tree root and sanitize zoom_stack whenever the current view_root is no longer alive after a delete. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… children_sorted, let _ = new_id) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Spawned curl processes were dropped without waiting, leaving zombies in the process table for the app's lifetime. Reap each child on a background thread. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pasted assign blocks
…rmation A ConfirmTrash dialog opened before a rescan stored NodeIds from the old tree. After the new tree loaded, confirming the dialog would act on whichever path happened to share that id in the new tree. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… OOB panic hovered_dir held a NodeId from the previous tree. When the scan finished with a smaller tree the old id could be out of bounds, causing tree.is_alive(id) to panic. Also clears hovered_node in the Done handler for the same reason. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…iles metadata.len() reports EOF size, not allocated blocks. Docker.raw, APFS clones, and compressed files all appear larger than their disk footprint. Notes the upgrade path to st_blocks * 512 with a zero-guard for special files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… path integrity Previously a directory whose stat() failed was skipped entirely, so its children were reparented to find_closest_ancestor. Their tree-derived full_path was then grandparent/child instead of parent/child, making Trash and Reveal target the wrong path. Now we register a zero-size stub dir in the tree and dir_map so children are always attached to their real parent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Trash button in the cleanup window would have moved ~/.Trash into the system Trash. Use the dedicated Empty Trash… button instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Failures were silently dropped after an eprintln. Now they set batch_error_msg which is displayed in the toolbar status area for one render frame so the user sees what couldn't be trashed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ext menu Trashing the scan root is catastrophic; synthetic Free Space / Hidden / Skipped nodes have no real disk path. Both cases are now excluded from the context menu. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ename ConfirmTrash now stores PathBuf instead of the bare filename String. The dialog displays the complete path so the user knows exactly what will be moved to Trash before confirming. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers Trash exclusion, .rustup/toolchains and iCloud path detection, node_modules inclusion, and the find_candidates > 1 MB threshold. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scans a temp dir with a regular file and a subdir, verifies sizes and child counts aggregate correctly through the full walk pipeline. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Verifies that calling remove_node twice on the same node is a no-op: the second call neither underflows sizes nor panics. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ion modals Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…root Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd clear_descendants Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Raw ends_with("/Library/Caches") matches "/x/MyLibrary/Caches".
Check that the byte immediately before the matched suffix is '/'
(or the suffix spans the whole string) so only a real component
boundary counts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The old assertion checked category == "Trash" but no rule produces that category string, so it could never fail. Assert instead that no candidate's path ends with ".Trash". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Walking from the root itself could classify the scan root (e.g. ~/Library) as a cleanup candidate, which would be catastrophic. Start the walk from root's children instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
.gradle may contain gradle.properties with credentials; .m2 may contain settings.xml with credentials. Docker named volumes may hold database data that cannot be regenerated. Update descriptions so users know to review before deleting. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The context menu already refuses to offer Move to Trash for the scan root and synthetic nodes (__free_space__, __skipped__). Apply the same check to the Cmd+Backspace keyboard path so it cannot bypass that guard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the walker thread panics, the sender is dropped without sending Done, causing try_recv to return Err(Disconnected). The previous while-let-Ok loop silently exited, leaving scanning=true forever. Now an Err(Disconnected) clears scanning and the receiver. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
take() consumed the error after one render cycle, so it appeared for a single frame. Switch to as_ref() for display and clear it in start_scan so the message persists until the user re-scans. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Failed perform_delete and failed Empty Trash were only eprintln!d. Route both through batch_error_msg so the user sees the error in the status bar. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A lingering extension filter from the previous scan dimmed the treemap on the new one. Clear it in start_scan alongside the other selection state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
treemap/mod.rs already sorts children by size descending before building the LayoutItem slice. The second sort inside squarify.rs was redundant. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iterals Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <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.
Findings
Tests: 30 → 37 (+7 new)
Notable fixes
Crashes (H):
src/ui/treemap_view.rs+dir_tree.rs— byte-slice on non-ASCII names panicked the UI thread (e.g. "Příliš.pdf"); now char-safe truncationsrc/app.rs—hovered_dirnot reset on new scan → OOB index panic on first frame after scan completessrc/app.rs—pending_action(Confirm Trash dialog) kept stale NodeIds across a rescan → trashed wrong pathsrc/scanner/walk.rs— hard links counted once per link;<Hidden / Skipped>saturated to 0Safety (H/M):
src/cleanup.rs— suffix rules (Library/Caches,.cargo, etc.) matched anywhere in the path string;/x/MyLibrary/Cacheswas a false candidate; now component-boundary checkedsrc/cleanup.rs— scan root itself was listed as a cleanup candidate; its Trash button bypassed the root guardsrc/app.rs— Cmd+⌫ keyboard shortcut bypassed the scan-root/synthetic-node guard added to the context menusrc/cleanup.rs—.Trashoffered as a candidate; its Trash button moved~/.Trashinto the Trashsrc/cleanup.rs—.m2/.gradledescriptions didn't mention credentials (settings.xml, keys)src/cleanup.rs— Docker description didn't warn about named volumes with non-regenerable datasrc/cleanup.rs—targetrule flagged any directory namedtarget, not just Rust builds; guarded byCargo.tomlpresencesrc/app.rs—perform_deletehad nois_alivecheck; stale ConfirmTrash trashed wrong path after partial refreshsrc/scanner/tree.rs—remove_nodedidn't guard against double-remove; double-subtracted sizessrc/app.rs— zoom stack not sanitized on delete; trashing the zoomed-into dir left treemap blankCorrectness (M):
src/ui/treemap_view.rs— selecting "(none)" extension dimmed extension-less files instead of lighting themsrc/ui/cleanup_window.rs— category headers repeated across size-sorted list; now sorted(category, size desc)src/app.rs—collect_files(root).len()walked entire tree every frame; replaced withnode.file_countsrc/app.rs— batch/single trash failures onlyeprintln'd; now shown in status barsrc/app.rs—poll_scanignoredDisconnected; a crashed walker thread left UI stuck on "Scanning…"src/app.rs—start_scannow resetspending_action,hovered_dir,partial_refresh_receiver,selected_extensionsrc/app.rs— Confirm Delete dialog shows full path, not just truncated filename<Free Space>,<Hidden / Skipped>)New tests:
tests/cleanup_tests.rs— classify, find_candidates, .Trash exclusion, .rustup, iCloud, node_modulestests/walk_tests.rs— tempdir scan: file + subdir sizestests/tree_tests.rs— remove_node double-dead guardRefactors (6 commits)
confirm_dialoghelper for three trash confirmation modals (dedup ~100 lines)AppState::remove_trashedshares post-delete cleanup across single/batch (dedup ~20 lines)AppState::reset_dead_view_rootdeduped view/zoom cleanup across 3 call sitesaggregate_dirhelper deduped size-field writes acrosscompute_sizes,recompute_sizes_upward,graft_undermark_descendants_dead— non-recursive, replaces recursivemark_dead_recursive+clear_descendantsduplicationSkipped (architectural, out of scope)
st_blocksallocated-size (L) — OS-specific, needs careful sparse/special-file guards/System/Volumesscan root empty (L) — volume prefix skip logic needs restructure🤖 Generated with Claude Code