Skip to content

v3.2.0: 2.4× faster treemap, 24% less memory, duplicate finder, Recycle Bin - #7

Merged
MrHakan merged 3 commits into
mainfrom
claude/overhaul-standalone-exe-bni8pv
Aug 12, 2026
Merged

MrHakan merged 3 commits into
mainfrom
claude/overhaul-standalone-exe-bni8pv

Conversation

@MrHakan

@MrHakan MrHakan commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Summary

Optimization pass driven by measurement, plus three functional additions. Everything below was benchmarked on a 7,300-file tree before touching the code and again after.

Performance

Before After
Hover one tile 242 ms ~0.2 ms
Treemap render 246 ms 103 ms (2.4×)
Tree memory 392 B/node 296 B/node (−24%)

The hover number was the real bug. Moving the pointer across a tile boundary re-rendered the entire map just to move the highlight outline. The outline is now a canvas item drawn on top of the cached image, so the image is never rebuilt.

The render win came from profiling, not guessing — and my first attempt actually made it slower (246→493 ms) before the profiler showed why:

  • Shading each tile allocated four temporary images plus two blends and a composite. Cushions are now built once per colour at a few resolutions, and each tile is a single resize from the nearest level. Scaling everything from one large source was still reading the whole source for a 20px tile.
  • Pillow re-rounds a list LUT in Python on every call — 4.2 million round() calls in the profile. The LUTs are bytes now.
  • Tiles under 8px are filled flat; the gradient can't be seen at that size.
  • Tile colour is memoised by extension instead of re-deriving the category per tile.

Memory

  • Node exists once per file on disk. Swapping the dataclass __dict__ for __slots__, and letting files share one empty children container instead of each allocating a list they can never use, cut 24% per node.
  • Thumbnail requests snap to size buckets, so resizing the window reuses decodes instead of re-decoding every image at its new exact size; JPEGs also decode at reduced scale via draft().
  • Thumbnail and cushion caches are bounded by total pixels, not entry count — a few large entries cost far more than many small ones.
  • The row-preview map is cleared alongside the rows it points at, and capped.

New features

  • 👯 Duplicates view — finds byte-identical copies in three widening stages (size → head/tail sample → full hash), so almost nothing is read twice. Shows exactly what keeping one of each would free, runs in the background, is cancellable, and its rows feed the existing zip/delete actions.
  • ♻️ Recycle Bin — deletions are undoable by default (Windows shell API, XDG trash elsewhere), with an automatic fallback and a setting for permanent delete.
  • 🖼️ Treemap PNG export alongside the CSV report.

Testing

  • 133 tests pass (up from 104), including new coverage for the duplicate finder (same-size-but-different content, mid-file differences that fool the cheap sample, cancellation), the trash helper (real move + restore metadata + name collisions), and each optimization, so they can't quietly regress.
  • Drove the new views through the real UI headlessly and confirmed hover does no re-render.
  • Compared the rendered treemap before and after to verify the cheaper path looks identical — no quality traded for speed.

Version 3.2.0.

🤖 Generated with Claude Code

https://claude.ai/code/session_011HEb3UbLzGGY4KuK54H45Q


Generated by Claude Code

Copilot AI lite review requested due to automatic review settings August 12, 2026 17:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

claude added 3 commits August 12, 2026 18:20
…export

Measured on a 7,300-file tree before changing anything, then again after.

Performance:
- Hovering the treemap re-rendered the entire map just to move the
  highlight outline: 242 ms of CPU for every tile the pointer crossed.
  The outline is a canvas item on top of the rendered image now, so a
  hover costs about 0.2 ms and the image is untouched.
- Treemap render 246 ms -> 103 ms. Two things were behind that. Shading
  each tile allocated four temporary images and ran two blends and a
  composite; cushions are now built once per colour at a few resolutions
  and each tile is a single resize from the nearest one. Tiles under 8px
  are filled flat, where the gradient cannot be seen anyway.
- Pillow re-rounds a list LUT in Python on every call, which showed up as
  4.2 million round() calls in the profile; the LUTs are bytes now.
- Tile colour is memoised by extension instead of re-deriving the
  category for every tile.

Memory:
- Node holds one instance per file on disk. Dropping the dataclass
  __dict__ for __slots__, and letting files share a single empty children
  container instead of each allocating a list they can never use, took
  the tree from 392 to 296 bytes per node (-24%).
- Thumbnail requests snap to size buckets, so resizing the window reuses
  decodes rather than re-decoding every image at its new exact size, and
  JPEGs are decoded at reduced scale via draft(). Both the thumbnail and
  cushion caches are now bounded by total pixels rather than entry count,
  since a few large entries cost far more than many small ones.
- The row-preview map is cleared with the rows it points at, and capped.

Features:
- Duplicates view: finds byte-identical copies by size, then a head/tail
  sample, then a full hash, so almost nothing is read twice. Shows what
  keeping one of each would free, runs in the background and is
  cancellable, and its rows feed the existing zip/delete actions.
- Deletions go to the Recycle Bin by default and are undoable; permanent
  delete is a setting. Falls back automatically when no bin is available.
- The treemap can be exported as a PNG alongside the CSV report.

Rendering output was compared before and after to confirm the cheaper
path looks the same. 133 tests pass, including new coverage for the
duplicate finder, the trash helper, and each optimization so it cannot
quietly regress.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011HEb3UbLzGGY4KuK54H45Q
CI on main is red on Windows: 103 tests pass and then one errors while
building its window, with "Can't find a usable init.tcl". Every GUI test
was constructing its own root window, and creating that many Tcl
interpreters in a single process fails intermittently on the hosted
runner partway through the suite. This change adds more GUI tests, which
would have made it more likely to hit, not less.

The window is now created once per session and reset between tests: scan
target, search, treemap stack, duplicate results, and the resize binding
that some tests unbind so they can drive the reflow directly. That takes
the suite from 20 root windows to 1, and runs faster for it.

The module-level Tk probe goes with it. The session fixture skips the GUI
tests when the display cannot be opened, which is what the probe was for.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011HEb3UbLzGGY4KuK54H45Q
A build-only dispatch (no tag) takes the version from the ref name, so on
a branch it became "claude/overhaul-standalone-exe-bni8pv" and
Compress-Archive failed on the '/' in the destination path. Tag releases
were unaffected, which is why it went unnoticed.

Strip anything that is not filename-safe before using it, so the
build-only path works from any branch.

Found by dispatching a build-only run to check the suite on Windows:
the tests and both PyInstaller builds succeeded and only the packaging
step failed.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011HEb3UbLzGGY4KuK54H45Q
@MrHakan
MrHakan force-pushed the claude/overhaul-standalone-exe-bni8pv branch from b0c40f2 to f199e88 Compare August 12, 2026 18:23
@MrHakan
MrHakan merged commit 9e4be91 into main Aug 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants