feat(dupe): persist reviewed groups; synchronized zoom/pan; sharpness tiebreaker (#21, #57, #67) #127

Merged
ValleyGeek merged 3 commits from 0.1.45/issue-21-57-67-dupe-workbench-depth into main 2026-06-22 01:52:43 +00:00
Member

Summary

  • #67 — reviewed-group state is now persisted to the working DB (reviewed_dupe_groups table, WORKING_DB_VERSION 1→2 with an additive migration) instead of living only in memory, where it reset every session. The row for a removed/cleaned-up dupe tag is deleted wherever dupe tags get removed, so a later group that recycles the same tag name starts unreviewed.
  • #57 — each comparison tile in the Duplicates Multi view now renders through its own QGraphicsView/QGraphicsScene instead of a plain scaled QPixmap, so zoomed/panned content is clipped to that tile. Wheel-zoom and drag-pan on any tile broadcast relative (multiplicative/fractional) deltas to every other visible tile, so small and large images in a duplicate group zoom/pan proportionately to each other instead of drifting apart.
  • #21pick_keep_best_path()/keep_best_delete_paths() gain an optional root kwarg: when given, ties on pixel count are broken by a Pillow-based edge-variance sharpness score (FIND_EDGES + variance) instead of falling straight through to date/path — implemented without an OpenCV dependency per AGENTS.md's non-goals (see DR-025).

Test plan

  • ruff check / ruff format --check
  • pytest (including a dedicated WorkingDB schema-migration test)
  • PR-prep version bump 0.1.45 → 0.1.46

Refs #21, #57, #67. (#57's zoom/pan gesture and #21's sharpness display both get follow-up polish in the final PR of this chain — fit-on-resize/visible-border fixes, left-click-drag pan, and surfacing the sharpness score in the UI for every image instead of only Keep Best's internal use of it.)

Stacked on #126 — part of the 11-PR sequential chain into main.

## Summary - **#67** — reviewed-group state is now persisted to the working DB (`reviewed_dupe_groups` table, `WORKING_DB_VERSION` 1→2 with an additive migration) instead of living only in memory, where it reset every session. The row for a removed/cleaned-up dupe tag is deleted wherever dupe tags get removed, so a later group that recycles the same tag name starts unreviewed. - **#57** — each comparison tile in the Duplicates Multi view now renders through its own `QGraphicsView`/`QGraphicsScene` instead of a plain scaled `QPixmap`, so zoomed/panned content is clipped to that tile. Wheel-zoom and drag-pan on any tile broadcast relative (multiplicative/fractional) deltas to every other visible tile, so small and large images in a duplicate group zoom/pan proportionately to each other instead of drifting apart. - **#21** — `pick_keep_best_path()`/`keep_best_delete_paths()` gain an optional `root` kwarg: when given, ties on pixel count are broken by a Pillow-based edge-variance sharpness score (`FIND_EDGES` + variance) instead of falling straight through to date/path — implemented without an OpenCV dependency per AGENTS.md's non-goals (see DR-025). ## Test plan - [x] `ruff check` / `ruff format --check` - [x] `pytest` (including a dedicated WorkingDB schema-migration test) - [x] PR-prep version bump 0.1.45 → 0.1.46 Refs #21, #57, #67. (#57's zoom/pan gesture and #21's sharpness display both get follow-up polish in the final PR of this chain — fit-on-resize/visible-border fixes, left-click-drag pan, and surfacing the sharpness score in the UI for every image instead of only Keep Best's internal use of it.) Stacked on #126 — part of the 11-PR sequential chain into `main`.
Bumps WORKING_DB_VERSION 1 -> 2, adding a reviewed_dupe_groups table
(tag_name PRIMARY KEY) with an additive migration for existing v1
databases. DupeContainer now reloads its viewed-group set from the DB
on every refresh_from_db() (instead of starting empty each session)
and writes through immediately when a group is opened. The row for a
removed/cleaned-up dupe tag is deleted wherever dupe tags get removed
(remove_dupe_tags_with_single_member, delete_dupe_tags_by_kind), so a
later group that recycles the same tag name starts unreviewed, per the
issue's explicit requirement.

pick_keep_best_path()/keep_best_delete_paths() gain an optional `root`
kwarg: when given, ties on pixel count are broken by a Pillow-based
edge-variance sharpness score (FIND_EDGES + variance) before falling
back to date/path, instead of the OpenCV Laplacian variance issue #21
suggested -- AGENTS.md rules out an OpenCV dependency by default. See
DR-025. Omitting `root` reproduces the exact pre-#21 ordering.

Threading notes: all new DB methods follow the existing WorkingDB
short-lived-connection pattern (open/use/close per call via
self.transaction()). sharpness_score() does synchronous file I/O on
the main thread only inside _on_dupe_keep_best(), a small, bounded,
user-initiated one-off action (typically 2-10 images per group, and
only the images already tied on resolution are read) -- consistent
with how that handler already worked synchronously before this change.
Each comparison tile now renders through its own QGraphicsView/Scene
(_DupeZoomPanView) instead of a plain scaled QPixmap on a QLabel, so
zoomed/panned content is clipped to that tile and never bleeds into
its neighbors. Wheel-zoom and middle-button-drag-pan on any tile emit
relative deltas (a multiplicative zoom factor, or a fraction of that
view's own scrollbar range) that DupeMultiGrid rebroadcasts to every
other visible tile. Because each tile establishes its own 1.0x
baseline via fitInView and the broadcast is multiplicative/fractional
rather than absolute, small and large images in a duplicate group
zoom/pan proportionately to each other instead of drifting apart.
Zoom never goes below each tile's own fit-to-cell baseline.

Panning uses the middle mouse button (not left-drag) so it never
competes with the existing single/double/right click gestures (mark
others DELETE, open Single viewer, mark this one DELETE) already
wired to each tile. The DELETE outline, loading/"unreadable"
placeholder states, and caption row are unchanged -- only the loaded-
image rendering surface and its interaction model changed.
docs: PR-prep for issues 21, 57, 67 - bump to 0.1.46
All checks were successful
CI / lint (push) Successful in 7s
CI / test (push) Successful in 1m39s
CI / appimage (push) Successful in 8m20s
CI / build-appimage (push) Successful in 0s
CI / windows-exe (push) Successful in 11m42s
CI / build-windows-exe (push) Successful in 0s
4a69435d9e
ValleyGeek changed target branch from 0.1.44/issue-55-58-72-87-grid-nav-zoom to main 2026-06-22 01:50:53 +00:00
Claude force-pushed 0.1.45/issue-21-57-67-dupe-workbench-depth from 4a69435d9e
All checks were successful
CI / lint (push) Successful in 7s
CI / test (push) Successful in 1m39s
CI / appimage (push) Successful in 8m20s
CI / build-appimage (push) Successful in 0s
CI / windows-exe (push) Successful in 11m42s
CI / build-windows-exe (push) Successful in 0s
to 4638af90e4 2026-06-22 01:51:47 +00:00
Compare
ValleyGeek deleted branch 0.1.45/issue-21-57-67-dupe-workbench-depth 2026-06-22 01:52:43 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ai-collab/bulk-image-organizer!127
No description provided.