Skip to content

Releases: frathe/picfetch

v0.2.12

Choose a tag to compare

@github-actions github-actions released this 30 Aug 01:30
Immutable release. Only release title and notes can be modified.
a98f92c

What's Changed

New Features

  • Added manual update checks in Settings: Check now bypasses the automatic
    daily gate, shows checking/current/download/error states, verifies and
    stages updates, and offers Later or Perform update to apply and
    relaunch with What's New on the updated launch.

Internal

  • Qodana's false-positive suppressions are confirmed in CI, not just under
    GoLand's engine: at 210fee5 (run 33270269940), the summary CSV counts
    the 12 findings covered by the 8 //goland:noinspection comments exactly —
    GoMaybeNil 2, GoBoolExpressions 3, GoRedundantConversion 1,
    GoErrorStringFormat 2, GoVarAndConstTypeMayBeOmitted 4, summing to 12 —
    and none of the 12 appear anywhere in qodana.sarif.json's results, whose
    only ruleId values are DuplicatedCode and GoTypeAssertionOnErrors:
    12-of-12 suppressed. The other 18 suppressions, the GoUnusedParameter _
    renames, never produced a CSV row at all — the blank identifier removes the
    finding before the inspection pass counts it, rather than suppressing a
    counted one. The earlier prediction that the next CI run would show "30
    fewer problems (155 -> 125 on a full scan)" was wrong, and not because the
    suppressions failed: 155 was an IDE Project Default scan and 125 assumed CI
    would run that same profile, but CI runs qodana.starter instead, so an
    IDE Project Default total and a CI qodana.starter total were never
    comparable in the first place.

  • CI does not under-report duplication against a full scan — the old framing
    had it backwards. Qodana emits one SARIF result per duplicate cluster,
    not per fragment: at 210fee5 the SARIF holds 33 DuplicatedCode results
    occupying 71 location slots, and the slots are not disjoint because 8
    fragments each belong to 2 clusters (55 fragments in exactly 1 cluster, 8
    in exactly 2: 55×1 + 8×2 = 71), so 71 slots reduce to 63 distinct
    fragments. The summary CSV counts 75 DuplicatedCode rows, and the gap
    closes completely rather than leaving a mystery: 75 CSV rows = 71
    test-file fragments + 4 production fragments already suppressed at source
    in the orientation pixel loops (see Orientation transforms below), and 71
    − 8 serialisation losses = 63 SARIF fragments, so the 12-fragment
    CSV-to-SARIF gap is 8 + 4 with nothing left unexplained. At 210fee5 the
    IDE finds 71 DuplicatedCode fragments and CI finds 63; CI's 63 are a
    strict subset of the IDE's 71, and the 8 fragments CI is missing — 7 in
    internal/imaging/loader_test.go and 1 in internal/update/tufroot_test.go
    — are accounted for by a genuine Qodana serialisation failure that the CI
    run recorded itself as 3 DuplicatesProblem "Can't find duplicate problem
    in db" warnings naming exactly those 2 files and no others. All 33
    clusters at 210fee5 were test-only — every fragment in every cluster
    lives in a _test.go file — so qodana.yaml now excludes those files from
    DuplicatedCode by explicit path; run 33274422606 at ed3d4e6 confirmed
    it, returning 0 SARIF results.

  • Making the DuplicatedCode exclusion actually take effect needed three
    attempts, but the mechanism itself was never broken — only the pattern.
    exclude: with a paths: glob compiles into a real scope:
    log/effective.profile.xml shows <scope name="qodana.yaml.exclude.DuplicatedCode" level="INFORMATION" enabled="false" /> nested inside the DuplicatedCode inspection element
    itself, proving the entry reached the engine — but a compiled, disabled
    scope that matches no file suppresses nothing. Both "**/*_test.go" (run
    33273666731 at d38f6e8) and "**_test.go" (run 33274030031 at
    f481c4a) failed exactly that way: 33 DuplicatedCode results both times,
    unchanged from baseline. The second glob is the dialect JetBrains uses for
    its own built-in scopes (glob:**.md, glob:**.test.ts), so it was not a
    wild guess, and it still did not match. Neither failure was a delivery
    problem — each run's log/qodana-config.json echoes back the exact
    pattern that run was given, and neither run's idea.log shows a cache hit
    or restore. Explicit file paths work: with qodana.yaml's paths:
    replaced by the 30 flagged test files listed by path, run 33274422606 at
    ed3d4e6 returned 0 SARIF results. That the CSV's DuplicatedCode count
    fell to 4 rather than 0 on that run is the proof the exclusion narrowed the
    inspection instead of disabling it outright — a full disable would have
    zeroed the CSV too, not just the SARIF.

  • Update-check lifecycle ordering is restored: after its existing gates,
    maybeStartUpdateCheck prepares the verifier/client through the
    instance-owned Updater verifier-factory seam before updateOp.begin(),
    then calls Updater.Start. The focused
    TestUpdateCheck_VerifierFailurePreservesLifecycle regression test proves
    a verifier failure preserves the revision and prior token/context, leaves
    the client nil, and never begins completion. Moving begin above
    EnsureClient makes that test fail on the lifecycle preservation; restoring
    the intended order passes.

  • buildMainMenu's exact help.SetOnManualOpened(view.syncMenus) observer
    registration is covered by
    TestBuildMainMenu_ManualOpenedObserverSyncsWindowHelp. The test scopes
    theme.DefaultTheme() to this case because the shared Fyne test theme
    cannot render the manual's complete Markdown font combinations; it identifies
    and closes the newly created manual window and restores the original theme
    during cleanup. Removing the observer registration makes the test fail,
    leaving Window → Help enabled after the manual opens; the restored
    registration passes.

  • Favorites no longer un-merges the macOS native menu bar. The original note
    here had the diagnosis half wrong, so for the record: SetHasFiles was
    never an unguarded site - syncMenus folds on the very next line,
    deliberately - SetDir is covered by the startup fold at run.go:45, and
    there is no rename path at all, since favstore exposes only Save and
    Remove. The two real paths were adding a favorite (favorites.go:249)
    and deleting one (manage.go:362), both reaching refreshMenu with
    nothing to fold the bar afterwards. favorites.Host gained
    RefreshMenus, which the viewer implements as refreshMainMenu;
    refreshMenu calls it instead of f.menu.Refresh, and SetHasFiles now
    only flips Disabled. The invariant is structural rather than documented:
    internal/ui/favorites never calls fyne.Menu.Refresh, so
    refreshMainMenu is the only place a main-bar menu is re-published.
    Pinned at both ends - three tests in favorites that the feature makes
    the call (and that SetHasFiles does not), one in internal/ui that the
    viewer's method reaches refreshMainMenu. Fyne's test driver records
    nothing when the bar is re-published: MainMenu.Refresh re-hands the same
    pointer to test.window.SetMainMenu, which just stores it, and both
    syncNativeMenuBar steps dead-end on NSApp's nil menus in a test binary.
    The only trace is refreshMainMenu reading the window's menu, so the
    viewer test observes that through a decorator on the per-viewer v.win.
    All four verified by mutation.

  • Small mechanical Qodana fixes, five categories. imaging/save.go and
    exifwin/tiles_test.go compare errors with errors.Is now;
    update/extract.go and plistdoctypes/doctypes_test.go deliberately keep
    err == io.EOF, because the inspection does not flag the documented
    tar.Reader.Next / xml.Decoder.Token loop terminators and the bare
    sentinel is what those APIs promise. raw_test.go drops a redundant
    []byte conversion (copy takes a string source). apply_test.go's
    local real became target, filename string included, so it no longer
    shadows the builtin. The two scripts mark their ignored Fprintf results
    _, _ =, matching scripts/plistdoctypes/main.go. The spiral FPS
    backdrop colours were the only non-trivial one: they were positional
    color.NRGBA literals written out in both overlays.go and
    overlays_test.go, so they moved into package-level
    fpsGoodColor/fpsWarnColor/fpsBadColor that both sides name. That
    alone would have gutted the test - it would compare a var against itself
    and only catch a wrong threshold - so TestFPSBackdropColorValues was
    added to pin the values, and verified by mutation: bumping
    fpsGoodColor's G to 121 fails it while the threshold test still passes.

  • Doc-comment openings: GoCommentStart wants the documented element's name
    as the comment's first token, followed by whitespace - an optional leading
    A/An/The is the only thing allowed in front. Punctuation glued to
    the name breaks it - a slash, a comma, or a colon all fail even when the
    comment already opens with the right word, which is why Dir/SetDir and
    MouseIn, MouseMoved, and MouseOut were both flagged. The slash-joined
    pairs became the X and Y form zoom.In/Out,
    widgets.FocusGained/FocusLost and imaging.MinVectorWidth/Height
    already used; the three widgets/style.go group comments now lead with
    the first constant in their const/var group; and internal/ui's five
    file-level notes gained a blank line before package ui, which detaches
    them from the package clause - the form 69 other files in the tree already
    use, with run.go keeping the one real package doc.
    spiral.ShowForGesture and Show shared one comment block that opened by
    describing Show, so it was split: go doc Spiral.Show printed nothing
    before and prints its paragraph now. Also normalized the six unflagged
    sites with the same shape in load.go and preferences.go; the
    inspection skips struct fields and mid-comment references, so those were
    consistency rather than findings. Verified through GoLand's inspection
    engine over all 13 files, which is the same engine Qodana...

Read more

v0.2.11

Choose a tag to compare

@github-actions github-actions released this 28 Aug 21:27
Immutable release. Only release title and notes can be modified.
4cc8bb5

What's Changed

New Features

  • Changed a few labels to make them less confusing

Bugfix

  • Hashing a folder no longer crashes when the file set shrinks underneath
    the pool. The duplicate model reads an immutable snapshot of the file
    set instead of the live slice — the old live Count()/KeyAt(i) pair
    could index past the end, reproducible in about a second on a 48-file
    drop under -race.
  • A file the scanner produced no URI for no longer panics the hashing
    pass.
  • A crash when pressing 0 after rotating an image and returning to the
    drop zone. clearToDropzone cleared the frames but left the rotation
    set, and the 0 key is handled ahead of the navigation guard, so
    resetRotation indexed an empty frame slice. Reproducible in four
    keystrokes: open, R, Escape, 0.

Internal

  • internal/dupes.FileSet is a single Snapshot() method; every Model
    method takes one snapshot at entry.
  • Arrow keys with inspect or hide-duplicates on no longer rescan the whole
    file set per step (InspectSource is an O(1) map lookup; the
    hidden-extra walk takes the model mutex once per call, not once per
    candidate).
  • A new hashing pass no longer inherits the previous pass's hide-apply
    throttle floor.
  • Deleted grid/dupes.go's rememberHashFail and hashFailedOf, two
    duplicate-hash wrappers with no callers anywhere in the tree; the
    hashing pass already calls dupes.Model.PutFailed/Failed directly.
    Dropped the stale hashFailedOf mention from hashengine.go's
    wrapper comment.
  • internal/dupes now exports Visibility(), a frozen read of the
    hide flag plus the installed group snapshot taken in one model-mutex
    acquisition, with HiddenExtra/Visible/RepresentativeOf/Size
    methods for testing many indices against it without re-locking per
    index. applyVisibleFilter's filter pass and jumpIfHiddenExtra now
    take one such read instead of one (in fact two) model-mutex
    acquisitions per file.
  • Split the viewer god object from 87 fields to 55: four new
    subpackages, each reading a value State snapshot built in exactly one
    internal/ui function rather than a Host interface —
    internal/ui/menus (the 20 File/Window/Actions menu items and their
    whole Checked/Disabled matrix as Apply(State), −19 fields),
    internal/ui/autoupdate (the release check/download policy, staged-
    update lifecycle, and What's-New cache, −4 fields), internal/ui/infoview
    (the persistent info overlay's widgets and text formatting, −6 fields),
    and internal/ui/display (decoded frames, frame index, view-only
    rotation, and crossfade, −3 fields). A Host for menus alone would
    have needed roughly a dozen methods, which is why these four read a
    snapshot instead. Also lands needs_refactoring.md item 5 (menuState()/
    syncMenus() replace the per-site Checked/Disabled push and
    HighlightChanged's four-boolean hand-diff with one recompute-and-diff
    entry point, called from 16 choke points instead of the previous 23 call
    sites) and the rest of item 6 (appState now evicts the image cache
    itself on file removal via an onRemove hook, wired in build.go).

Full Changelog: v0.2.10...v0.2.11

v0.2.10

Choose a tag to compare

@github-actions github-actions released this 27 Aug 23:12
Immutable release. Only release title and notes can be modified.
21b3630

What's Changed

Bugfix

Trane drag and drop

  • macOS "Open With", dropping a file on the Dock icon, open -a, and
    double-clicking a file already associated with PicFetch now actually open
    it in the app — every one of those was silently ignored before, whether
    PicFetch was already running or being launched cold by the click itself.
    These paths also now cover the app's whole supported format list, not just
    the seven extensions Finder used to offer them for: HEIC, AVIF, TIFF, SVG,
    ICO, BMP, and every RAW format PicFetch reads are all included now, and a
    folder can be dropped on the Dock icon the same as a file.

Internal

  • Extracted the duplicate-visibility model out of internal/ui/grid into a
    new, viewer-independent internal/dupes package: dHashes and native pixel
    sizes keyed by file, generation-scoped wipe-vs-adopt, the Hamming
    threshold, the group snapshot (Compute/Install), the hide-duplicates
    and inspect modes, and the visibility queries (IsVisible/NextVisible/
    FirstVisible/LastVisible/VisibleIndexesExcept) plain navigation
    needs. The viewer now owns the model and answers arrow-key/Home/End/
    shuffle questions directly from it (internal/ui/visibility.go) instead
    of polling a closed grid overlay. internal/ui/grid keeps presentation
    and browse-duplicates, and boxes its hashing pass behind a new
    hashEngine type (grid/hashengine.go) rather than more fields on
    Overview.

Full Changelog: v0.2.9...v0.2.10

v0.2.9

Choose a tag to compare

@github-actions github-actions released this 27 Aug 10:29
Immutable release. Only release title and notes can be modified.
4e5b8f3

What's Changed

Bugfix

  • Reject zip/tar entries that are not filepath.IsLocal during update extract (GitHub CodeQL go/zipslip).

Internal

  • Drop deprecated tar.TypeRegA in update extract (still accept the historic NUL regular-file typeflag).
  • Install CI's Linux GUI packages in CodeQL so internal/winpos/linux.go can compile.

Full Changelog: v0.2.8...v0.2.9

v0.2.8

Choose a tag to compare

@github-actions github-actions released this 26 Aug 18:48
Immutable release. Only release title and notes can be modified.
e3fc3b6

What's Changed

New Features

  • When only a single image is selected or dropped,
    the left and right arrow keys now move to the next/previous image in the same folder.

  • Loop duplicate variants after picking one from the grid.
    Return from the variants grid kept losing the chosen extra to the
    highest-resolution stand-in, and arrows then skipped the rest of the
    group. Committing a variant now inspects that file, wraps arrows inside
    the group, and uses Escape to walk back to the variants grid.

Bugfix

  • When duplicates are hidden, the highest resolution image will now be selected by default.

Internal

  • make release writes GitHub release notes from this Done section (empty categories dropped) and appends the Full Changelog compare link.

Full Changelog: v0.2.7...v0.2.8

v0.2.7

Choose a tag to compare

@github-actions github-actions released this 26 Aug 13:53
Immutable release. Only release title and notes can be modified.
ee61af9

What's Changed

New Features

  • Rectangle selection in the grid overview: click-and-drag draws a selection rectangle and picks every thumbnail the rectangle touches. A click without dragging still opens the image. Hold Shift or Cmd/Ctrl while dragging to add to the current selection instead of replacing it. Esc during a drag cancels it; the grid stays open after you release.

Full Changelog: v0.2.6...v0.2.7

v0.2.6

Choose a tag to compare

@github-actions github-actions released this 26 Aug 11:48
Immutable release. Only release title and notes can be modified.
4ed9085

What's Changed

Features

  • Automatic updates: Opt-In via App-Settings, checks once a day

Full Changelog: v0.2.5...v0.2.6

v0.2.5

Choose a tag to compare

@github-actions github-actions released this 25 Aug 16:33
Immutable release. Only release title and notes can be modified.
d19b5ad

What's Changed

Bugfix

  • Fix fyne install by declaring the app icon in FyneApp.toml

Full Changelog: v0.2.4...v0.2.5

v0.2.4

Choose a tag to compare

@github-actions github-actions released this 25 Aug 15:41
Immutable release. Only release title and notes can be modified.
436e375

What's Changed

Trane opening menu

New Features

  • New Window and Actions menus: Functionality previously available only through keyboard shortcuts is now also accessible from the new Window and Actions menus.

Bugfix

  • The Remove Metadata button is now hidden when no metadata exists.

Internal

  • Several small refactorings.

Full Changelog: v0.2.3...v0.2.4

v0.2.3

Choose a tag to compare

@github-actions github-actions released this 24 Aug 17:25
Immutable release. Only release title and notes can be modified.
5004b74

What's Changed

Trane comparing files

New Features

  • Hide extra copies of the same shot (D). Uniques stay visible; each group keeps one representative (the earliest file in the current order). Remaining cells show a count badge. The full set stays loaded — this is not a duplicates-only gallery. Closing the grid leaves hide on, so arrows, Home/End, and picture-frame advance keep skipping extras until you press D again

  • Browse every copy of the current shot ((Shift + D). The grid lists that group only, including extras D would hide. A unique shot does nothing. While thumbnails are still being hashed, a toast says the images are being analyzed and the group appears when that finishes

  • Duplicate match distance in File → Settings… (0–32, default 6; lower is stricter, 0 is an exact thumbnail hash). Changing the slider while extras are hidden regroups immediately. Re-saved and downscaled copies of one picture match at the default; cropped versions are not detected

  • Two files count as the same shot only when every pair in the group is close enough. A chain of similar-looking photos does not merge into one giant group, and solid-color images are left ungrouped

  • The count badge is a bold number on a black chip in the top-right of the cell, inset so the highlight ring does not run through it
    / search stacks with hide-duplicates. While the search bar is open, D is a letter, not a toggle. Esc clears one layer at a time: selection, then search, then browse, then hide, then the grid.

Bugfixes

  • Pressing D on a large folder no longer freezes the window until hashing finishes. Hiding duplicates shows immediately, extras drop out as hashes land, and the highlight stays on the same file instead of jumping to the top

Internal

  • Grid thumbnail and hash completions marshal through a per-instance UI queue so tests can drain them instead of racing Fyne's test driver

Full Changelog: v0.2.2...v0.2.3