Navigation map for AI agents. PicFetch is a Fyne desktop image viewer: one
binary, split into internal/... packages. Start here to find a file.
Standing rules (data flow, concurrency, conventions, build) live in
AGENTS.md — do not duplicate them here.
Entry point only. main.go parses the command line (launchArgs, see
internal/launch) before any side effect, dispatches the private similarity.WorkerMain
subprocess mode before desktop startup, calls openwith.Install (first
statement after that, see internal/openwith), skips GitHub-update predecessor
cleanup for Store-managed builds and explicit Explorer trials, asks launch.Options.ApplicationID to validate and select the app identity before
building the fyne.App, loads embedded
translations/*.json, converts CLI paths to URIs (argsToURIs), and calls
ui.Run. main_darwin_test.go asserts the graft landed — this is the only
test binary that links the Cocoa driver.
Development-only Git history films, reached through make movie and
make movie-test. run.sh captures one HEAD and its NUL-delimited log/tree
snapshots, builds the Docker tool image, and owns container cleanup. movie.py
replays ancestor changes with merge-tree reconciliation, checks the final file
set, and generates the Gource animation, titles, live counters/chart, original
score and verified MP4. The renderer receives exported history and an output
directory, with networking disabled; it does not mount the repository.
Dockerfile defines the tools and test_movie.py guards replay and timing.
Repository tooling for measured Linux race-test sharding. It parses complete
go test -json streams, emits deterministic package/top-level-test summaries,
plans a reviewable manifest from median durations with stable LPT tie-breaking,
checks that manifest against the build-selected Linux test inventory, derives
the fail-closed non-UI package partition, and emits exact shard filters. Its
capture path streams compact partition/package/test diagnostics while retaining
the raw events outside the repository; the generated assignment lives at
.github/testshards/internal-ui.tsv.
| File | Responsibility |
|---|---|
main.go |
summarize, plan, check, regex, partition, and capture command paths: complete event-stream validation, deterministic assignment, live build-selected inventory and parallel-call checks, exact anchored filters, exact-package subtraction, concise diagnostics, and raw preservation. |
docker-race.sh |
Public Make runner: unique host artifact directory, attached Docker lifecycle, raw streams and console retention, exit/memory/OOM diagnostics before cleanup. |
main_test.go |
Command-boundary fixtures for event streams and capture, deterministic planning, every manifest rejection, build-selected runnable forms, parallel-call refusal, exact filter generation, package partitioning, and Make contract expansion. |
Local content analysis shared by the viewer and its reproducible experiment.
client.go owns Client.Analyze (selected source paths, control channel, immutable Event callbacks),
asset discovery, the cancellable analysis subprocess, and private WorkerMain
dispatch. Each request captures the caller's encoded-file size limit; the worker
installs it before source reads. Explorer requests also capture configurable
item and serialized-map memory limits, with item admission checked by both
client and worker. analysis_protocol.go streams snapshot metadata, individual
items and hierarchy merges, enforcing the same aggregate memory budget at both
ends and publishing only a completed snapshot to the caller. Resource errors
survive subprocess cleanup for the settings-guidance toast.
analyze.go accounts for every input, captures source versions,
reuses canonical full oriented decoding, makes previews, and publishes a map
on manual request, optionally every 30 sources, and at completion.
facts.go captures versioned oriented dimensions, normalized extension and optional
EXIF camera/calendar-date facts from already-read sources; old cached representations
backfill these facts without inference. tags.go exposes catalogue identity for
rule compatibility.
Event.Measurements carries cumulative worker stage times, inference attempts
and publication counts as immutable values; grouping includes its named sub-stages.
control_unix.go/control_other.go own the pollable worker input descriptor.
worker_linux.go installs worker-only seccomp denial on x64/ARM64 synchronized across all
threads before reading requests; worker_other.go retains the macOS sandbox launcher.
worker_windows.go launches a hidden ordinary subprocess with closeable control
pipes; Windows does not install OS network denial and events keep OfflineVerified
false. offline.go exposes that distinction through EnforcesNetworkIsolation
and assembles the Linux filter for host-independent BPF decision tests.
cache_favorites.go owns the shared Favorite inventory, retained directory handles,
list versions and producer lease. Both producer admission and maintenance use it;
unknown membership remains inspectable without admitting unleased writes.
cache.go persists successful favorite representations in analysis beside
file-list.json/thumbs, validates source/model/preprocessing versions, and uses
directory handles plus file-list identity to avoid recreating removed favorites.
cache_payload.go reads one JSON document through a hard byte bound, then checks
its version, source identity shape, vector, digest and preview. General/Favorite
reads, general write validation and maintenance share that decoder.
Explorer's analyzer and search share the representation store. When the general
cache is enabled, both reuse and write compatible loose-image records; Explorer
also promotes compatible hits into newly saved Favorites. Explorer sends Favorite
membership even when its analysis preference is off, with a separate opt-out that
blocks both Favorite records and general fallback for those members.
Producer inventory preserves healthy Favorites alongside per-entry errors;
incomplete membership disables general reuse/writes for that producer. Cache maintenance
retains each Favorite directory and list version through inventory and rechecks
membership before stale removal. Inventory handles unreadable Favorite definitions
independently so healthy peers remain inspectable and cleanable.
encoder.go (cgo) sets the non-Windows telemetry opt-out before library loading,
disables telemetry through the runtime API before session creation, and owns
the pinned native SigLIP 2 session;
encoder_nocgo.go keeps cross-platform package builds available and reports that
inference requires cgo.
tags.go decodes the generated embedded tag-vectors.bin, validates its canonical
float32 digest against tag-catalog.json, and applies those text prototypes to
fresh and reused image vectors against 75 fixed subjects, without a text runtime;
scripts/tagvectors converts the retained JSON source before Make builds;
grouping.go owns independent 15D grouping and 2D layout fits plus canonical
cohort identities; hierarchy.go orders a centroid spanning tree in grouping
space for local granularity cuts. Repeated source paths share one assignment.
Display events retain this compact hierarchy and omit inference vectors, which
remain in the worker and favorite cache. The encoder and
grouping entry points are worker-only: the
native runtime is process-global and batch algorithms cannot be interrupted
in place. assets.go/assets.sha256 verify pinned local assets;
assets_install.go checks local availability and performs explicit, cancellable
HTTPS installation into a per-user cache. It bounds and hashes downloads,
extracts only named runtime/license files from tarballs or Windows x64/ARM64 ZIPs
and publishes verified files; Windows ZIPs include both pinned runtime DLLs and
skip debug symbols. Store builds verify/load DLLs beside the executable, ignore
model-cache runtime overrides, and download only model data. assets_package.go
verifies architecture-specific archives for MSIX staging and extracts both DLLs
and the upstream license, third-party notices and privacy document.
Asset availability is separate from analysis admission; analysis
never starts a download. offline.go
verifies actual TCP/UDP OS denial on macOS/Linux; files.go registers the driverless read-only
file repository. Production setup/analysis supports Intel/Apple Silicon macOS, glibc
Linux x64/ARM64 and Windows x64/ARM64; native library evidence collection remains macOS-only.
platform_darwin_amd64.go checks the native macOS product version before Intel
analysis/setup admission (13.4 minimum); platform_other.go leaves the other
platforms' existing admission unchanged. Unknown Intel OS versions fail closed.
Search uses search_rank.go for bounded exact cosine top-k and search.go for
the retained, reference-first worker session with progress and 100-source result
publications. search_client.go owns query/event IPC and observes writer and
subprocess completion; search_worker.go uses canonical decoding and compatible
Favorite representations, validates source versions before publication, and
retains vectors rather than previews between queries. A committed Favorite save
carries an acknowledged cache revision on the coalesced query lane. The retained
worker refreshes ownership and persists prepared members, regenerating previews
without inference; cache opt-outs still refresh ownership without writing Favorite
records. Transient search progress is limited to one update per 100 ms, while
ranked, failure and terminal events retain exact accounting.
The first general-cache capacity refusal reduces that producer's write scope
to Favorites only, preserving reads and in-memory preparation. Explicit Favorite
refresh carries that scope forward; new producers receive fresh admission.
Search reports pressure at session readiness so automatic eviction cannot
interrupt its preparation, including when a reference failed or was abandoned.
search_pipeline_test.go exercises retained queries through real cached source
preparation and complete-scope version validation. Its warm benchmark includes
store reopening, cache reads, validation and progressive ranking; the existing
session benchmark supplies prepared vectors directly. Both exclude native model
startup, subprocess transport and UI paint.
Narrow build-time selection of the ONNX Runtime Go binding used by similarity's
encoder and scripts/explorertags. binding.go uses upstream v1.36.0/API 29;
binding_darwin_amd64.go uses unmodified v1.25.0/API 23 through the explicit
internal/ortlegacy module alias in go.mod. Intel macOS pins Microsoft's last
official runtime 1.23.2 (macOS 13.4+); other platforms retain 1.29.0. Exactly one
binding may enter each binary because both wrappers define the same C symbols.
binding_test.go guards all six platform/architecture selections. Both files
require cgo; similarity retains its existing no-cgo encoder.
The independently MIT-licensed HDBSCAN subset from PhotoPrism, pinned with its
copyright/license and source provenance in LICENSE and README.md. It uses
only the standard library. hdbscan.go builds and condenses the mutual-reachability
hierarchy; labels.go owns core distances and worker iteration; union_find.go
tracks merge ancestry; common.go retains only required validation, distance and
error declarations. Similarity grouping calls it synchronously with one worker,
minimum cluster size four and minPts three (self plus two other neighbors).
Its adapter retains a single root cohort when no smaller cluster qualifies.
Reusable local preset definitions, stable identities and versioned rule matching
over immutable similarity facts. presets.go owns the rule contract and atomic,
cancellable global library storage; root composition supplies its directory.
The Explorer feature runs I/O on its separate preset workers and shared UI queue.
session.go owns an explicit native trial's exclusive evidence directory,
source-free causal records and final collection summary. Numbered analyses bind
input digests to received events, actual UI application and observed worker exit.
Session-unique event IDs link receipt to application and foreground-surface
observations; grid-result/current-image digests distinguish frozen browsing from
the updating map without retaining paths. Foreground MapView records pile count,
zoom/floor, camera center and viewport; map departure/return binds the camera to
its applied revision even when grouping changes behind a cohort. It also records cohort/map visits,
rejected truncated scans, and shutdown.
Identity gives trial launches separate Fyne preferences/session storage.
Collection has no implicit human quality verdict; UI application is not paint.
The module starts no goroutines.
Bounded local experiment reached through make explorer-setup,
make explorer-test and make explorer-evaluate. main.go owns the experiment
CLI/offline launch and the explicit -install entry point; make explorer-setup
calls it directly without requiring Bash, and setup.sh remains a compatibility
wrapper for the same verified Client.InstallAssets used by the viewer.
make explorer-download-test qualifies download/reuse without loading native code.
Platform runtime
archives are selected in internal/similarity/assets.go and extracted library
hashes remain in internal/similarity/assets.sha256.
evaluate.go uses the shared native encoder/grouping and adds per-stage
measurement and evidence files; files.go selects the bounded smoke corpus.
report.go and review.html produce local cohort/measurement artifacts;
memory_*.go measures native RSS; evaluate.sh retains each run/exit status.
TRIAL=search adds a local exact-cosine relevance experiment. search.go
validates the bounded reference/judgment manifest; search_evaluate.go owns
canonical decoding, version-checked preparation, ranking and measurements.
search_report.go/search_review.html retain a local interactive relevance
report with judgment export; search_cache.go runs a separate production
Favorite cold/warm baseline against the captured corpus. These experimental
rankings and timings do not establish a human usefulness verdict or production
search UI behavior.
Real-model tests require assets under the explorertrial tag and check the
platform's actual network policy; Windows must not claim OS denial.
make explorer-ui-test additionally exercises the production worker and
completed-map/Grid View round trip through the UI harness.
profile.go, reached with make explorer-profile, runs bounded cold/warm passes
through the actual similarity.Client with a temporary favorite cache, retains
aggregate event timings and a source-identity digest, then removes that cache.
Its completed summary observes both worker exits; no previews or source paths
are written to the profiling report.
native_darwin.go/native_other.go implement TRIAL=library: retain a native app
bundle and executable digest, launch under OS denial with its own process group,
forward cancellation for graceful shutdown then escalate only that group, observe
child/descendant exit, and sample aggregate process-group RSS once per second.
The normal app scans the supplied library without the smoke sampler. Launcher
logs, exit status and collection/qualification fields remain separate from the
app's numbered analysis records. It never scans source files outside denial.
Development-only regeneration of the embedded semantic tag vectors.
prepare_tokens.py uses the pinned Gemma tokenizer for fixed catalogue prompts;
main.go verifies the pinned text tower and runs it with the existing native
ONNX Runtime, producing readable normalized float32 JSON keyed by tag identity. The viewer requires neither
this text model nor Python. See README.md for reproducible commands/provenance.
Offline build-time conversion of authoritative tag-vector JSON into exact
little-endian float32 data. main.go validates identities, dimensions, norms
and the catalogue digest; -check guards generated-file freshness. Make builds
regenerate internal/similarity/tag-vectors.bin, which NewTagger embeds and
validates. The JSON and generated binary are both retained in the repository.
Development-only derivation of embedded illustrations and compact gaze atlases
from retained originals. main.go owns source/output paths and display pixel
targets; -check compares dimensions and decoded pixels without an encoder.
Generation uses local cwebp; the application keeps its existing image decoders.
scripts/updaternotices reconciles the six-target production updater dependency
union with its reviewed manifest.json, checks source-file hashes and generates
the bounded updater section of THIRD-PARTY-NOTICES.md. artifacts.go verifies
byte-identical license/privacy/notices inside finished ZIP/tar.gz archives and
both MSIX bundle payloads; CI runs this before release publication/Store upload.
packaging/tools.mk owns reviewed CLI versions and multiarchitecture image
digests consumed by Makefile and the release/Store workflows.
docs/packaging-inputs.md describes provenance and native artifact validation.
scripts/plistdoctypes derives macOS file associations from supported formats;
scripts/msixstage stages Store manifests/assets and guards packaging routes.
Microsoft Store release command: record binds the WACK-validated x64/ARM64
bundle to its tag and producing run; prepare freezes a GitHub-only approval
manifest; preview and check read release/Store data; submit and reconcile
consume that exact manifest to drive one approved release lifecycle. release
freezes the prior reconciliation and current submission under one approval,
advancing only after the exact prior receipt is confirmed published; standalone
operations keep their existing scope.
release.go admits exact artifacts, notes.go generates bounded plain-text
notes and preserves listing metadata, github.go discovers immutable build
artifacts and journals receipts in deployment payloads, microsoft.go owns
OAuth/submission/blob requests, and lifecycle.go resumes recorded operations.
approval.go binds review to artifact/notes/base/receipt identity;
environment-policy.jq checks the saved main-only required-reviewer policy.
main.go/contracts.go provide command parsing and per-invocation dependencies;
main_test.go exercises the command against persistent fake services.
.github/workflows/microsoft-store-publish.yml pins trusted main across preparation
and reviewer-gated jobs after Store builds or manual dispatch. Certification
observation and recovery are manually dispatched; reconciliation never admits a new
release. docs/microsoft-store.md covers setup and recovery.
Native Windows/macOS and explicit Microsoft Store validation. main.go
selects suites, verifies build-selected inventories, runs the full packages,
retains raw Go test events and requires named guards to run/pass without skips.
The macOS suite includes root main's Cocoa-linked delegate test; Windows includes
native Unicode transport, wallpaper and updater guards. CI invokes this command
and uploads its event files. main_test.go covers admission, selection, event
validation, process failures and workflow wiring through a per-call runner.
Feature owns the analysis/setup/editing workflow around the existing Map.
feature.go defines its injected runtime options, value observations, settings,
cohort snapshots, trial observations and close/stop/wait/settle contract.
workflow.go captures prepared source paths and Favorite/cache ownership,
controls native analysis, loads Favorite cohorts and rejects stale UI delivery.
lifecycle.go owns feature-local request tokens. setup.go owns first-use
acceptance, local asset checks and explicit cancellable download/retry.
cohort_workflow.go owns captured Unassigned-selection review and rollback;
preset_workflow.go/preset_fields.go own preset dialogs, matching and saves.
Analysis/setup/cohort I/O and preset work have separate tracked worker groups
and one drainable UI queue. Settle joins/drains/repeats and reports delivery
so root can repeat any resulting Grid work; SettlePresets observes preset
completion while analysis streams. Close cancels without joining on UI;
Stop ends admission and Wait joins workers off UI.
The consumer-side WorkflowHost carries window/input access, separate menu
and repaint notifications, cohort/exit/return transitions and presentation
facts for trial recording. Repaint is distinct from menu notification because
source reconciliation may still be updating Grid indexes when analysis retires.
Root's explorerInput retains duplicate preparation, selected Favorite identity,
trial launch admission and maximize consumption. No worker or workflow field
is exposed to the viewer. feature_test.go, setup_test.go and
cohort_workflow_test.go exercise the module without the full viewer harness;
root tests retain cross-feature transitions and native trial integration.
presets.go captures frozen candidate facts and applies reviewed preset membership,
protecting other groups and pending members. cohorts.go finds shared recognized tags in selected Unassigned sources,
matches chosen traits, and captures named current-map cohorts by source identity.
Manual assignments overlay immutable analysis results, survive publications and
stay separate from granularity cuts. Snapshot/restore preserves explicit favorite
members across partial analyses. Creation reveals the new cohort and rejects
stale/non-Unassigned sources.
tags.go owns the localized checkbox catalogue, clickable unique-source counts
opening captured tag-only grids, OR filtering of intact piles/Unassigned, and
cohort subject titles from up to two tags covering at least half the distinct
sources; explicit cohort names take priority.
The toolbar collapses/restores the sidebar. Choices survive map publications and
cohort visits; full exit clears them. Filtering starts no background work.
Map owns the clipped pan/zoom surface, opaque toolbar, Unassigned entry and
stable samples of up to fifteen distinct members per Pile, fitted thin frames,
Shift-scroll panning and a granularity slider cutting the supplied hierarchy.
Slider release rearranges piles and centers the selected source or arrangement
at the current zoom; background publications retain shared-source anchors.
Map.View captures source-free camera geometry on UI for explicit trial recording.
keys.go owns keyboard zoom, directional stack selection, source-bound selection
restoration and camera reveal; Enter opens the highlighted stack.
layout.go matches continuing cohorts by shared
sources, orients the initial projection to the window, and places new piles
with a minimum gap using nearby-cell collision queries and perimeter searches.
Its narrow Host opens the
full captured cohort, leaves the map, sends manual/automatic update controls, and
supplies current input modifiers.
The Map itself starts no workers; Feature delivers partial/final results and
optionally expands the camera for new stacks. Root maximizes the native window
at entry and coordinates Grid/image transitions; Feature retains the camera
and frozen cohort across them. internal/ui/grid/subset.go filters
cohorts by source path while retaining root indexes for existing operations.
Replaced pile images have their sources cleared and refreshed to release Fyne
renderer/texture references. Leaving Explorer clears the map; cohort round trips
retain it. map.go retains compressed sample sources for the complete map,
decodes piles within one pile-width beyond the viewport, and releases decoded
pixels/textures beyond two pile-widths. Distant pile renderers expose no child
objects, preventing Fyne's minimum-size walk from decoding them. Returning piles
restore their pixels before paint; selection redraws reuse unchanged previews.
Committed writes through filework.go and removals through viewer.RemoveFile
retire the analysis/map while preserving surviving cohort identities;
load.go keeps missing-file retries within the remaining cohort.
Reference-driven browsing over a captured original source scope. feature.go
owns immutable origin/current visits, the latest 20 successful reference visits,
Back/Exit and branching; session.go owns a retained native provider, queued
session/query/revision delivery, and observable suspension and shutdown. Root
supplies source identity, Grid presentation and origin restoration through a
narrow Host. The feature's Settle drains finite query delivery without waiting
for an idle retained worker; Stop followed by Settle joins retired producers.
internal/ui/visualsearch.go composes command admission, shared Explorer setup,
ranked Grid visits and complete presentation/restoration deliveries. Deferred Back
carries its saved Grid state until the surface can accept it; application reads
current preparation progress from the live search session.
Capacity pressure requests maintenance once at readiness, after the current
Favorite-save revision is acknowledged. The prepared worker then remains
available for reference changes under the existing revoked-lease protocol.
Image origins and Grid bookmarks use internal/fileidentity occurrence values
when merge mode repeats a source. Grid retains one index per collection generation;
image-origin lookup captures only the bookmarked path.
An image opened before the first publication retains its Grid anchor until the
first successful visit commits. Terminal search failures revalidate the captured
collection through root's tracked file-work lane before restoring the origin;
request, session and collection identities reject obsolete reconciliation.
internal/ui/sourcechange.go owns admitted source changes: search detaches its
origin before callbacks, then root reconciles the complete removal/write/recovery,
retires Explorer, updates Grid and restores the origin. Display failures receive
the selected origin index through their existing retry chain instead of starting
a competing load. Committed writes retain comparison for Grid origins and close
it when an image origin must be restored.
internal/ui/browsing.go captures the shared ranked/cohort command restriction and
one immutable ranked index order per action or preload pair; menus and duplicate
handlers consume the same subset restriction. Favorite capture exposes one
CurrentFiles snapshot to the naming/overwrite workflow.
internal/ui/searchoverlay.go observes dismissal of generic canvas overlays while
a result is pending, with one acknowledged UI callback and cancellable, tracked
worker completion through shutdown and the test harness.
internal/ui/grid/ranked.go owns ordered path-based Grid visits, captured
selection/filter/highlight/scroll state (including duplicate occurrence ordinals),
determinate progress and deferred
revision installation during marquee gestures. Rank revisions preserve the
collection's stable host indexes. The reference leads up to 30 other matches and
has a purple outline below ordinary selection/highlight feedback. Completion
hides the progress bar while retaining the result and counts.
Favorites AddFiles captures its list before naming and overwrite dialogs begin.
Ranked filtering and visit capture/restoration share a generation-bound source
index, so repeated result updates inspect only their bounded ranked identities.
The worker retains its top-k between preparation batches; a changed reference
resets ranking to one full prepared-scope pass.
Owns the Settings Cache tab and serialized inspection, retuning, cleanup and
automatic eviction. feature.go presents measured general/Favorite usage and
partial reports. operation.go owns explicit intent admission, coalesced usage
refresh/reserve requests, provider dispatch and accepted policy effects. Refreshes
wait behind mutations; automatic eviction and policy retirement survive view close. Persistence toggles commit independently of inspection
success, retire producers on UI and join their barriers even after Settings closes.
work.go captures providers/roots, joins predecessors, dispatches maintenance
and suppresses retired view callbacks. quiescence.go owns the UI handoff after
shared write-lease revocation: cancellation can abandon a queued callback, but a
claimed callback's reply and all retired producer barriers belong to completion.
Host reasons distinguish local policy retirement before inspection, explicit
record removal after lease revocation and automatic eviction. Cache
inspection holds only one queued progress callback, reading the latest count on
delivery, so inventory size cannot create an unbounded UI progress backlog. Root
composes the two-method Host in internal/ui/analysiscache.go, uses the Fyne
application cache root, persists accepted policy and disables new analysis
admission while maintenance owns the roots.
Automatic eviction notifies search through CacheWritesRevoked, permitting an
idle, fully prepared producer to retain its vectors after lease invalidation;
pending preparation/Favorite writes still join. The disk manager retains its
existing lock/epoch protocol and final committed-effect report.
Settings supplies the tab slot, confirmation window and Close notification without
sharing worker state.
internal/similarity/cache_store.go owns Favorite-first/general record reuse and
producer write scope: Explorer and search both begin with every enabled store;
capacity pressure can narrow an individual producer to Favorite-only writes.
Explorer reports that pressure only with its completed map, so root can run the
existing automatic eviction after preparation. Neither producer reaches into the
store to route a write.
cache_payload.go validates shared representations; cache_records.go confines
inventory to managed records and temporary files. cache_management.go reports
usage and implements conservative stale cleanup/general LRU eviction. Canceled
maintenance retains its measured remainder after a complete inventory under the
lease; an interrupted inventory reports its partial counts as incomplete.
cache_transaction.go owns observations, committed removal accounting and final
outcomes on every return path. Reconciliation respects cancellation. Missing source paths remain unavailable
without persisted volume identity, even when their parent directory exists.
A successful general-only retune persists despite incomplete Favorite usage.
cache_lease.go plus build-tagged cache_lock_* provide root epochs and native
advisory locks shared by Explorer/search subprocess writers and maintenance.
Old writers cannot publish after a cleanup; installed model/runtime assets are
outside these roots. The harness drains both new per-instance UI queues and
production shutdown joins both features after stopping admission.
The application. Unexported appState is the file-set model (scan/drop
order, displayed order, index, sort, merge) and publishes an immutable
dupes.Snapshot — file keys plus generation — atomically on every write to
files; viewer.Generation() reads the generation out of that snapshot
rather than a separate counter. Unexported viewer is the Fyne façade.
Construction order, overlay order, data flow, and concurrency: see
AGENTS.md. Features expose state; internal/ui composes them.
The concurrency invariant: see AGENTS.md § Concurrency and Fyne.
| File(s) | Responsibility |
|---|---|
run.go |
Run: restore startup viewer, start runtime (favstore.DefaultDir, position polling), register shutdown and CLI drop, enter the Fyne loop. Shutdown retires title/menu updates, cancels feature work and flushes preferences without rebuilding retired native menus. Store-managed builds skip GitHub update startup and staged-binary apply. Explicit trial startup reserves new evidence, isolates Favorites/presets/updates, disables update activity, auto-opens Explorer after the ordinary scan, and joins its signal watcher and workers before finalizing evidence. |
build.go |
buildViewer composes widgets and registerFeatures modules and snapshots distribution.StoreManaged onto the viewer. Overlay tail: copy selection, similarity map, grid, comparison (including its pointer shield), delete confirm, export prompt, toast. Desktop canvases also receive the chained comparison key-down hook for exact physical Ctrl+L; ordinary typed-key and shortcut wiring remains separate. |
startup.go |
loadStartupState / restoreStartupGeometry / buildStartupViewer — the one load→build→restore path shared by Run and tests. |
components.go |
Dropzone, scan, sort, and info-overlay constructors. Toast stays in toast.go. |
trane.go |
Welcome-screen Trane: hosts widgets.Gaze with a compact 17-cell atlas. Owns pointer/window-layout coordinates, scaled dead zone, immutable decode cache and magenta-spill correction within five source pixels of transparency. Hide/MouseOut forget pointer position and circle progress. A hover-only surface preserves input across the restore link; ten circles request Help.ShowFinis. No timers or background workers. scripts/appassets retains the used pixels from assets/trane/codex-pet/spritesheet.webp. |
explorer.go |
Root adapter for Explorer: captures duplicate-prepared sources, composes setup acknowledgment with preferences, maps frozen cohort identities to collection indexes, and coordinates map/Grid/image transitions. The feature owns workflow, dialogs, workers and delivery. explorerInput retains collection/launch/window policy only. |
features.go |
registerFeatures assigns help, EXIF, zoom, copy selection, grid, similarity map, comparison, mosaic window, deletion, slideshow, settings, then favorites. |
shortcuts.go |
wireGlobalShortcuts plus per-action shortcut wiring (open, favorites, clipboard, copy selection, comparison, delete, select-all, save, export, wallpaper). Comparison registers the native Cmd/Ctrl+D plus physical Ctrl+D when those differ. yieldingShortcuts blocks ordinary commands during comparison and otherwise yields Copy Selection; Open is admitted only far enough to show comparison's refusal. Copy Selection and clipboard bindings also defend their own direct entries. |
gesture.go |
Position-poller callback fans samples to winPos and spiralDrag; a recognised spiral calls the viewer's openSpiralForGesture. |
tunnel.go |
Both Spiral trigger doors; freezes the loaded main-order URI list through one installed duplicate-visibility snapshot, without preparing groups or narrowing to grid selection. |
windowtrack.go |
Main-window size tracker and position poller; widgetGeometry / prefGeometry translate preferences.WindowGeometry ↔ widgets.Geometry. |
windowmenu.go |
Window-menu action handlers (showViewer, showWindowExif, showWindowGrid, showWindowPictureFrame, showWindowHelp — grid/picture-frame mutual exclusion lives in the first two) plus refreshMainMenu / syncNativeMenuBar and the Darwin sync entry points. The Checked/Disabled matrix itself lives in internal/ui/menus. |
windowmenu_darwin.go |
After Show and every native rebuild, fold Window items into GLFW’s NSApp.windowsMenu and clear AppKit’s default Command mask on unmodified letter accelerators. |
windowmenu_notdarwin.go |
No-op twin of the Darwin native-menu merge. |
testdata/ |
Golden screenshots for the e2e suite. |
state.go |
Unexported appState. Only viewer accesses it. |
sourcechange.go |
Complete source-removal, committed-write, validation-recovery and analysis-policy transitions. Detaches search before callback delivery and restores browsing after collection/cohort/Grid reconciliation; display keeps retry ownership. |
lifecycle.go |
requestLifecycle / requestToken for root scan/sort/copy-selection and other root work. Display owns its load/GIF/SVG lifecycles internally. |
viewer.go |
Façade: title (baseTitle / gridTitle / comparison ownership / applyTitle), reset/close (clearToDropzone releases cached and recycled-cell images through grid.InvalidateContent), merge, Host vocabulary (CurrentFile, ShowImage, RemoveFiles, …). |
visibility.go |
dupeFileSet (adapts the viewer to dupes.FileSet by forwarding appState's published dupes.Snapshot); jumpIfHiddenExtra; pushHideDuplicates; the navigation helpers (nextVisibleIndex / firstVisibleIndex / lastVisibleIndex / randomVisibleOther) that read v.dupes instead of polling the grid overlay. |
keys.go |
handleKeyEvent / handleTypedRune, plus a chained desktop key-down hook that requests the ready-gated comparison link toggle on exact physical Ctrl+L without key-repeat flapping. Return immediately while Canvas().Overlays().Top() is set (Fyne dialogs/menus). Comparison owns all main-window typing: Escape closes it, F1 opens Help, 0 / 1 / + / - reach its shared camera or hovered photo pose, swipe-mode Left / Right / Home / End reach its divider, and every other key/rune stops before the still-open grid. Copy Selection: HandleKey consumes Escape/copy/navigation; unowned keys yieldCopySelection except modifier-only and zoom keys. |
menu.go |
buildMainMenu builds internal/ui/menus.Menus and assembles the bar: File, Favorites, Actions, Window, Help. yieldingMenuCallbacks enforces comparison isolation and Copy Selection yielding at callback entry. menuState() is the one function that builds the menus.State snapshot; syncMenus() applies it, pushes comparison/file availability into Favorites, and refreshes the native bar only when something actually changed. |
actionmenu.go |
Comparison-guarded Actions-menu handlers (setActionsSort, toggleActionsHideDuplicates, showActionsVariant, rotateActionsImage, …). The Checked/Disabled matrix lives in internal/ui/menus. |
drop.go |
handleDrop / applyScanResult / applyScannedFiles glue over filescan.Images / filescan.Siblings; scan lifecycle is viewer.scanOp. A non-empty drop is refused before any state change while comparison is active. |
openwith.go |
macOS "Open With" delivery: installOpenWithHandler / openInitialFiles / openFilesFromOS over internal/openwith, both routed through fyne.Do so a launch carrying argv files and a delivery makes one handleDrop. The combined pending set is cleared before that shared path refuses an active comparison, so deliveries cannot queue behind it. |
memlimits.go |
settings value, settingsState / ApplySettings, memory-limit get/set that retune caches and imaging.SetMaxEncodedBytes. |
theme.go |
Settings-facing appearance getter/setter; applies internal/appearance modes live. |
favthumbs.go |
Viewer glue for favthumbs.Sync and gridSink; captures the saved Favorite preview limit (default 1000) and retires active work on edits; per-request completion plus all-pass worker tracking and terminal shutdown cancellation. |
load.go |
ShowImage admission and display callbacks: requested/probed/presented/failure, cohort retry and neighbor choices; autoResizeToImage / resizeToImage / syncWindowToZoom (static-size gate). |
toast.go |
Self-dismissing notification card and ShowToast. |
info.go |
Persistent info overlay (I); EXIF link; RAW (preview) mark. |
asyncop.go |
asyncOpUI (lifecycle, active, done, spinner) — used only by scan and sort. |
sort.go |
toggleSort / SetSortMode / startSort / finishSort over filesort.Order; lifecycle is viewer.sortOp. |
rotate.go |
Rotation commands compose display intents with zoom/window/menu policy. displayedDimensions reads the display snapshot's oriented logical size. |
vector.go |
Forwards zoom scale and canvas pixel conversion to display; SVG work and policy live in display/vector.go. |
save.go, filework.go |
File > Save Changes captures pixels and source, owns cancellable workers and causal UI delivery, then reconciles the current resolved source. Committed stale Save/Export/Strip/mosaic writes invalidate cache revisions, thumbnails and duplicate facts; tracked background identity/info reads preserve unrelated views and retry after intervening commits. |
export.go |
File > Export image (promptExport / exportAs / runExport) via widgets.ChoiceCard + filepicker.ChooseSave. Carries an exportRequest (format plus imaging.ExportOptions) and reports the applied size limit in the suggested name and the toast. |
exportoptions.go |
The export prompt's extra rows (exportOptions, a widgets.ExtraRows): the export size limit rungs and the "Include camera metadata" widget.Check, reset to defaults on every open. Both Fyne controls grab canvas focus on tap; the checkbox hands it back from its own OnChanged (every effective tap toggles it), while the rungs are tappable labels because a radio item focuses without firing OnChanged when the value doesn't change. |
mosaic.go |
Cross-feature mosaic composition: snapshot the loaded collection, or explicit Grid selection/complete filtered result while Grid View is open; inspect displays, open the singleton workflow, and adapt generation/display refresh into its narrow Host. |
wallpaper.go |
Shared ordinary/mosaic wallpaper lifecycle: serialize work, export captured pixels to a global or hashed-target cache scope, call wallpaper.Set(Request), and sweep only copies no longer backing another scope. |
autoupdate.go |
Viewer-side update glue: maybeStartUpdateCheck gates the opt-in daily check; CheckForUpdatesNow adapts manual worker callbacks through fyne.Do with an inner staleness check; PerformUpdate records relaunch intent and requests quit; maybeShowWhatsNew consumes the cached update marker and opens this build's bundled release notes. Every update entry point refuses a Store-managed viewer. Policy, staging, and cache live in internal/ui/autoupdate. |
slideshow.go |
togglePictureFrameMode (closes grid first) plus shuffle/interval bindings. |
batch.go |
Routes delete/copy commands by the current subject: image-region selection, grid selection, or displayed image. |
session.go |
restoreSession glue over internal/session. |
clipboard.go |
Copy-path / copy-image glue over internal/clipboard; captures a displayed image and encodes PNG off UI. |
clipboardwork.go |
Per-viewer clipboard admission, cancellation, encoder and result queue; whole-image/grid workers finish before queued delivery, while the operation Signal includes its UI effects. Shared admission preserves region-copy routing and its existing completion contract. |
reveal.go |
Actions > "Reveal in file manager" (Cmd/Ctrl+R) and the info overlay's link of the same name: current-file path, own goroutine behind viewer.reveal, toast on failure. Glue over internal/filemanager. |
copyselection.go |
Viewer adapter for internal/ui/copyselection: availability, start/cancel, zoom Geometry to View, animation pause, clipboard worker, yieldCopySelection. Command entry yields through yieldingMenuCallbacks, yieldingShortcuts, handleKeyEvent, and handleDrop. |
compare.go |
Viewer adapter for internal/ui/compare: validates exactly two explicit grid selections, resolves ascending host indices to URIs, unfocuses the covered grid so desktop modifier hooks remain reachable, and loads through the canonical full-image cache/probe/decode path. That path preserves EXIF-corrected pixels, RAW previews, animation decoding/budget policy, encoded-input limits, and the original first frame; the feature deliberately freezes animation. The adapter owns the exact comparison-window title callback and reports failures without mutating the grid or file set. comparisonActive() is the composition layer's sole exclusive-mode fact; refuseOpenDuringComparison() owns the localized discard policy. |
internal/ui/display/animation.go, pause.go |
Own acknowledged GIF playback and acquisition-bound stable captures. The root composes Copy Selection and observes display completion. |
openfiles.go |
UI-owned native open admission. Each request captures the native chooser, tracks its worker and queues results through private chooserUIQueue. New requests, accepted drops, reset and shutdown invalidate old delivery; comparison is rechecked on UI. closeOpenChooser stops admission without waiting for a blocking native panel. Harness settlement waits all native workers, then drains results before scan/sort/load waits. |
| Package | Responsibility | Reaches back via |
|---|---|---|
internal/ui/zoom/ |
Zoom/pan of the displayed image. Geometry / HandleScroll / SetOnGeometryChanged are the presentation seam Copy Selection uses; this package does not import copyselection. Window growth is syncWindowToZoom in internal/ui. |
onChanged, modifiers, onScaleChanged. |
internal/ui/copyselection/ |
Transient Copy Selection mode: image-region geometry, overlay, and captured Source crop/encode. HandleKey reports whether the mode consumed the key. |
Copy, Ended, Scroll. |
internal/ui/grid/ |
Overview (G): GridWrap, thumb cache, decodepool, uiqueue.go, work.go (cancellable sessions and terminal Stop), search, badges, explicit host-index selection plus its change observer, marquee.go (drag rectangle → Targets()), browse-duplicates (Shift+D), and hashengine.go's per-session hashing pass that feeds internal/dupes. Each work session owns one decode queue: current Grid cells enter newest-first high priority, while duplicate hashing enters low priority; cancellation resolves queued callbacks without waiting for occupied source reads. Closing cancels reads, reopening resumes unfinished hide analysis, and revision-tagged cell claims isolate old completions. Captured FactWriters reject obsolete hashes, failures and sizes; groupwork.go coalesces cancellable grouping on one independently tracked worker so partial hiding progresses while source reads occupy the decode pool; it admits only current snapshots; inspect retarget waits for accepted groups. Shutdown Stop is terminal; Settle waits the decode pool and grouping worker, drains queued completions, and repeats. nav.go: setHighlight → HighlightChanged; scrollfollow.go follows wheel/trackpad movement with the ring and reconciles live filter reflows within the viewport. Reads the model; does not own it. |
10-method Host including Modifiers. |
internal/ui/compare/ |
Opaque main-window comparison surface: switchable gapless 50/50 and full-viewport swipe layouts compose two persistent photo transforms with one shared camera transform; each image has one reveal clip so swipe keeps aligned image coordinates. In Swipe, each pane input mirrors its current reveal even though the render viewport remains full-size, and reveal-local wheel coordinates are translated back into that viewport. The ready-gated top-left Unlink/Link control and physical Ctrl+L share ToggleLink; its adjacent status reports only the active unlinked target, while layout/Swap/Back stay in a separate top-right card. ToggleLink changes only input ownership and never changes rendered geometry. Linked pan/zoom moves the camera, linked 0 frames both current photo poses without rewriting them, and linked 1 returns the camera home. Unlinked pointer input and transform keys target the hovered or last-hovered photo; its 0 / 1 fit or show that photo at decoded-pixel size in the current camera. Photo centers and camera movement stop when an image edge reaches its pane center. Resize and layout preserve both photo poses and the camera; Swap deliberately clears divergence from the last-targeted visible pose before exchanging sources. A private paneRenderer scene seam keeps transforms independent from presentation; production owns two stable canvas.Shader objects while tests can inject the canvas reference adapter. Each immutable render source retains the canonical decoded frame, a long-edge-1024 overview, and a 64 MiB detail-tile cache. The planner uses physical display density and the actual side-by-side/swipe reveal, skips details when the overview is sufficient, and binds at most seven guttered detail tiles without shuffling stable sampler slots. One cancellable worker per pane generates tiles; publications are coalesced and marshalled through the feature's UIQueue. Pan/zoom changes shader geometry and uniforms without repainting the viewer root. Each SVG still gets a pane-local device-pixel raster, clamped by imaging.ClampVectorRaster, before entering the same overview/tile path. Settle covers load, vector, tile, and causal queued completions with reusable channel-epoch barriers. Fyne's software test painter does not render canvas.Shader, so deterministic pixel tests use the reference adapter; native runtime acceptance uses the GL painter. The feature also owns divider input, permanent chrome, ready-gated layout/link/Swap controls, the input shield, and the replaceable completion signal. It receives an ordered URI pair and never reads or mutates grid/viewer state. |
Loader plus Callbacks (Repaint, Closed, Failed, OrderChanged, Modifiers). |
internal/ui/mosaicwin/ |
Dedicated configuration/preview window with immutable command-entry sources/topology, accessible controls and focus order, distinct labels for identically named displays while preserving target IDs, cancellable generation lifecycle, progress.go coalesced coverage/live-canvas updates on the tracked worker/UIQueue, a determinate coverage bar, stale-result rejection, exact-result export, targeted wallpaper callback, and remembered geometry. Partial previews preserve a separate finished image for cancellation/failure; export actions stay disabled during generation and Cancel remains in the visible surface. It is a secondary window, not a main-window overlay. |
4-method Host (GenerateMosaic, InspectMosaicDisplays, SetMosaicWallpaper, AfterFileExported). |
internal/ui/deletion/ |
Shift+Delete confirm (widgets.ChoiceCard) then trash.Move. RequestFiles snapshots unique URI targets; successful moves reconcile by identity on UI across reorder/replacement. uiqueue.go owns completion dispatch; Close stops admission/unstarted moves and suppresses late callbacks, and Settle drains test completions. |
6-method Host, including ReconcileDeletedFiles. |
internal/ui/slideshow/ |
Picture-frame mode (P): full-screen, auto-advance, interval, winpos.Tracker capture/restore. uiqueue.go owns delayed advance dispatch; the worker waits for a buffered application acknowledgement, and Exit/Close cancel waits independently of UI. Kick discards an already queued timed advance. Close stops shutdown work without geometry restoration; Settle waits workers before draining stale test callbacks. |
2-method Host. Knows nothing about the grid. |
internal/ui/exifwin/ |
EXIF panel (E): metadata.go owns cancellable source reads, generation-checked tag/GPS/action presentation and content-based removal inspection/status and separate MetadataDone completion. stripwork.go owns cancellable removal, busy admission and a committed WriteResult in the Host notification. GPS map (tiles.go, tilework.go, startWarm): four shared workers, a 64-job queue, 256 expiring failure entries and a 16 MiB encoded-byte cache. Navigation/close cancels old reads and tile sessions; collapse cancels tiles. uiqueue.go owns result delivery and Settle waits/drains removal, metadata, warm and tile workers repeatedly. Shutdown Stop is terminal. Geometry via widgets.Singleton. |
4-method Host. |
internal/ui/help/ |
Manual, About, release notes/What's New (whatsnew.go), Help menu; embeds manual.md / manual_de.md and this build's release-notes.md. make release copies the canonical .github/release-notes.md into the bundle alongside the version bump. Both notes entry points share the bundled file and a singleton with a GitHub release-history link. releaseart.go replaces Markdown images before layout and loads GitHub-hosted HTTPS URLs on up to three background workers; releaseimage.go owns the allowlisted redirect policy and bounded HTTP fetch/decode. releasework.go cancels on close, stops admission on shutdown and exposes Wait/Settle with a per-instance UIQueue. Notes without images start no workers. Secret search phrase calls the viewer’s registered SetOnSpiral callback; finis in manual search opens the cursor-following companion (finis.go, embedded finis.webp), hosting widgets.Gaze with centered portrait geometry and its own hover surface. ShowFinis also serves welcome Trane; ten independent circles reveal the localized, wrapped bubble in finis_clue.go, whose click opens an empty focused manual search. |
New(app, title, art) plus optional event callbacks. |
internal/ui/spiral/ |
Full-screen shader easter egg. tunnel.go owns serial preview admission and three texture slots; playback.go advances bounded GIF frames on the existing UI clock with independent flight origins; flow.go owns cycles, batch variation and route selection; flight.go owns safe route geometry used for admission/retirement; shader.go renders depth, feather and translucent composition. uiqueue.go marshals preview/frame callbacks with session checks. H toggles local help; F1 invokes the viewer's manual callback. The shared centre stays within a resized viewport. |
Viewer supplies a frozen URI value to Show / ShowForGesture; Close cancels on UI and test Settle joins/drains off UI. Process shutdown does not join uninterruptible preview source reads. |
internal/ui/settingswin/ |
Settings: General/Appearance/Updates/Limits/Cache, update dialogs, snapshot seed, live apply, Singleton geometry. Show(State, storeManaged) replaces the GitHub update controls with Store-owned-update copy when applicable. |
Show(State, bool) + Host (ApplySettings, CheckForUpdatesNow, PerformUpdate). |
internal/ui/favorites/ |
Favorites menu and add/overwrite/manage/remove dialogs. New does no disk I/O; SetDir from Run. SetCommandsEnabled preserves Add's file availability while disabling both static and dynamically rebuilt menu entries during comparison. |
6-method Host. |
internal/ui/menus/ |
The stateful File/Window/Actions menu items and their whole Checked/Disabled matrix as Apply(State) (changed bool), a pure function of a value snapshot. ComparisonActive applies a final all-ordinary-items-disabled override while leaving Help available. Fyne-typed but viewer-free, unit-testable with no app. Menu-bar assembly, the Darwin native-bar fold, the real shortcut bindings, and every action the items run all stay in internal/ui. |
No Host: Apply(State) over a value snapshot built by menu.go's menuState(). |
internal/ui/autoupdate/ |
Shared serialized automatic/manual update worker: lazy verifier/client preparation, check/download progress events, same-process matching-stage reuse, all-worker settle, last-check-day persistence, staged apply/relaunch intent, the What's-New cache (whatsnew.go), and the apply-failure cache (applyfailure.go) — ApplyStagedUpdate writes it when update.Apply fails, and internal/ui reads and clears it on the next launch. Persisted stages carry a process-ephemeral authentication seal, so a stage from an earlier run is redownloaded and re-attested rather than trusted from the user-writable cache. Both UI caches are one JSON document each in app.Cache(), over the saveCacheJSON / loadCacheJSON / clearCacheJSON helpers in cache.go; a failed relaunch is deliberately not recorded, since it happens after the new binary is installed and verified. |
No Host: takes a context.Context and a staleness func per call (Start / StartManual), plus Persist and per-Updater verifier-factory seams — cancellation stays the viewer's own requestLifecycle, not promoted here. |
internal/ui/infoview/ |
The persistent info overlay (I key): its four widgets - text, the EXIF link, the reveal link, the card - the current file's raw facts (byte size, EXIF presence, RAW-preview flag), its own toggle preference, and formatFileSize. The EXIF link follows HasEXIF; the reveal link is shown with the card itself. |
No Host: Update(State) / Sync(bool, State) over a value snapshot built by info.go's infoState(). |
internal/ui/display/ |
Single-image surface publication, rotation/fades, source-bound observations and action captures. feature.go owns the surface, snapshots and worker settlement, capture.go saved baselines and stable captures, animation.go/pause.go playback and its acquisition gate, lifecycle.go private request tokens, vector.go sharpening, load.go complete navigation/retries/handoff and preload.go bounded speculation. One UI queue delivers workers; separate completion observations distinguish loaded/applied/stopped. |
Feature; root shares Surface() with zoom for geometry only. |
internal/ui/widgets/ |
Shared UI mechanics: ChoicePanel / ChoiceCard (+ its optional ExtraRows slot above the button row, Up/Down between them, Return offered to the focused row before it commits, and SetSelectionActive muting the button ring so only one mark is ever at full strength), TappableArea, Singleton (+ geometry memory), NewSizeTracker, focus-ring style. gaze.go extracts the compact single-row atlas and owns the 16-direction/neutral portrait presenter shared by Trane and Finis; callers own artwork preparation, hosting and face-relative coordinates. circlegesture.go recognizes timestamped head-relative pointer turns; hosts own independent instances, geometry normalization and lifecycle reset. |
Leaf aside from internal/winpos. |
internal/ui/assets/ |
Embedded viewer artwork, including ExplorerIntroPNG for first use. |
Leaf. |
Viewer-independent probe → decode → EXIF-orient → cache pipeline (JPEG, PNG,
GIF including animated, WebP, BMP, TIFF, ICO, XPM, AVIF, SVG, camera
RAW via embedded JPEG). RAW is preview-only (LoadedImage.Preview);
CanEncode is false. SVG is the only vector format (svg.go / vector.go).
Encode/write-back for a subset of formats lives in save.go; mutations.go serializes resolved destinations. Thumbnail caches carry favthumbs.Preview source versions for safe reuse after file replacement, and ByteCache.Capture binds in-flight cache writes to the pre-purge revision.
| File | Responsibility |
|---|---|
bytecache.go |
ByteCache[V]: goroutine-safe LRU by estimated bytes. Add admits foreground images even over budget; generation-bound CacheWriter.AddIfRoom admits display preloads only into remaining space without eviction or promotion. RefreshIfRoom lets Favorite warming replace stale keys, dropping only that key if the replacement cannot fit. AddIfFits keeps its existing individual-size gate and may evict. LoadedImage.DecodedBytes shares retained pixel/vector accounting with the mosaic repeat cache. |
loader.go |
LoadedImage, NewImgCache, ReadAndProbe, CaptureDateContext (cancellable metadata reads), DecodeLoaded (pixels), DecodeRecord (complete full-cache facts), LoadImage, IsSupportedImage, SupportedExtensions, MaxEncodedBytes / InputTooLargeError. |
ico.go |
Explicit ICO probe/decode dispatch, independent of the desktop driver's decoder registration: validates directory/payload spans and dimensions, selects the same single image for probe/decode, delegates PNG or normalized uncompressed DIB pixels to existing decoders, and applies icon transparency. |
raw.go |
Largest embedded JPEG from TIFF IFDs or SOI scan (CR3/RAF). |
svg.go |
SVG detection, logical-size floor (MinVectorWidth/Height = UI startW/startH), ClampVectorRaster / MaxVectorRasterPixels. |
vector.go |
Vector / ParseVector / ParseVectorContext / RasterAt. |
svg_limits.go |
SVG preflight limits encoded bytes, XML depth and conservative expanded work. Allows direct definition reuse, rejects use inside definitions, and estimates expanded source storage for the cache. |
exif.go |
Orientation tags from JPEG APP1, PNG eXIf, WebP EXIF, and TIFF IFD0 + ReadMetadata / Metadata (including GPS IFD). Metadata scans JPEG APP1, then TIFF IFD0, then ISOBMFF through the AVIF metadata parser, then RAW preview APP1. |
exififd.go |
Unexported IFD walker (walkIFD) and tag value helpers used by exif.go and raw.go. |
exifformat.go |
Unexported display formatters for exposure, focal length, and Exif dates (formatExposureTime / formatFocalLength / formatExifDate / parseExifDateTime). |
orientation.go |
ApplyOrientation, RotateSteps. |
resample.go |
Rolling-row CatmullRom downscaling for YCbCr sources in ScaleForExport; retains sixteen-bit source colors and float64 intermediates with bounded row storage. |
gif.go |
Cancellable animated GIF decoding/compositing, probeGIF, shared frame-count and memory admission (palettes, source/output pixels, frame objects and scratch), and logical-canvas restoration for a frozen partial first frame without decoding later frames. |
thumbnail.go |
LoadThumbnail / LoadThumbnailAndBounds and their context-bearing forms / NewThumbCache: same probe+decode, then downsample; LoadThumbnailAtEdgeContext supplies bounded static previews at a caller-selected ceiling; LoadThumbnailAndBounds also returns native ReadAndProbe size for hide-duplicates. Also FitEdge (the shared longest-edge rule) and ScaleForExport (CatmullRom, for exports) beside the unexported ApproxBiLinear scaleToFit thumbnails use. |
preview.go |
LoadAnimatedPreviewContext shares GIF compositing and static thumbnail decoding. Chooses a smaller edge to fit retained animation bytes, bounds native decode pixels/frame count before decode, and falls back to the static first frame when those limits are exceeded. Spiral uses a 512px ceiling and 16MiB per preview. |
dhash.go |
DifferenceHash / Hamming for grid hide-duplicates. |
jpegseg.go |
Unexported JPEG header-segment walker (walkJPEGSegments) used by exif.go and jpegexif.go, plus jpegFrameSize reading the SOF frame size the file's own dimension tags describe. Stops at SOS; does not walk entropy-coded scans (jpegLength in raw.go) or metadata removal (jpegprivacy.go). |
jpegexif.go |
Unexported metadata-preserving JPEG segment copying for save.go, plus the in-place TIFF patcher: orientation normalization, next-IFD unlinking, and the dimension-tag correction Save Changes and export apply across IFD0, the Exif SubIFD and the Interop IFD (reached through 0xA005) once the written frame stops matching them - patchIFDDimension rewrites what it can hold honestly, removeIFDEntries takes the rest plus the two coordinate tags no size can repair. Both refuse an IFD that does not wholly fit. |
jpegprivacy.go |
InspectJPEGMetadata, its clean/removable/unsupported result, complete marker/scan removal policy, bounded decoding, JFIF/Adobe reconstruction shared by inspection and the serialized lossless mutation. |
jpegremovalexif.go |
Bounded EXIF rendering-declaration validation and reconstruction: orientation, chroma positioning and enumerated color declarations only, with fresh directories and no source payload copying. |
jpegmemory.go |
Header-only admission for the metadata-removal working-memory budget: encoded copies, padded/subsampled component planes, progressive coefficients and validation decoding; shared cancellable marker-fill traversal for admission and complete removal parsing. |
jpegstructure.go |
Private qualification of 8-bit Huffman sequential/progressive frames, tables and complete component/coefficient scan progression. |
jpegentropy.go |
Removal-only Huffman syntax validation: exact scan/restart consumption, one-bit padding, MCU traversal and bounded progressive nonzero masks; pixel reconstruction remains with the standard decoder. |
jpegicc.go |
Bounded ICC chunk assembly and reconstruction of qualified RGB matrix/TRC and gray/TRC profiles; neutral descriptions and header identity, validated numerical transforms, no copied gaps/private tags. |
grouping.go |
Cancellable greedy complete-linkage grouping; equal hashes reuse their assignment, and narrow distances use verified 16-bit projection candidates. Membership/order remain unchanged; internal/dupes chooses native-pixel representatives. |
mutations.go |
Live file-identity transactions shared by Save/Strip/Export and external WithFileMutation participants such as Trash; case aliases share admission across atomic replacements, with cancellable admission/I/O and WriteResult commit identity. |
save.go |
SaveRotated, Export (+ ExportOptions: size limit, metadata omission and exact-path fallback encoder), CanEncode / CanEncodeExt, StripJPEGMetadata. dimensionTagsInvalidated decides whether the source's dimension tags still describe what is being written, by comparing the written bounds against the source's own frame header - so a resize, a viewer rotation and an Orientation 5-8 source all correct them; Save Changes and export share that policy, retaining tags when geometry is unchanged or the source frame cannot be read (subject to export's resize fallback). |
An imaging compile dependency with files only under nodynamic && !wasm2go.
Unsupported tag combinations cannot build the viewer or imaging-dependent tools.
Make, CI, packaging and nested test inventory/execution supply no_emoji,nodynamic;
the pinned AVIF dependency therefore selects embedded WASM/wazero and excludes its
automatic native library loader. imaging/avif_policy_test.go checks the dependency's
file selection on all six shipped platform/architecture targets.
Named file lists under a caller-supplied config directory. No UI. Saved path
metadata uses private directories/files. DefaultDir is the production path
helper and creates an isolated temporary fallback when user configuration is
unavailable.
| File | Responsibility |
|---|---|
favstore.go |
Save / Load / Count / DefaultDir; trash-backed remove. |
cohorts.go |
Favorite-owned named source memberships, preset links and explicit Unassigned overrides in version-2 cohorts.json; legacy arrays migrate on the next save; cancellable atomic writes bound to the observed file-list identity, with removed members filtered on load. |
Disk-cached grid previews under <favorite>/thumbs/. Sync is the
background pass; Sink is the caller’s in-memory thumb cache.
| File | Responsibility |
|---|---|
store.go / name.go |
On-disk lookup and filename scheme; ReadContext / WriteContext check cancellation through cache decode/encode and before atomic replacement. |
sync.go |
Cancellable Sync walk: memory → disk → decode, then Sink; bounds original-decode preparation to the configured number of unique source paths (default 1000) and serializes those decodes. Later entries reuse memory/disk previews without original reads. A completed pass prunes against the full Favorite membership. |
sweep.go |
Deletes stale preview files; Sync's membership scan and deletion walk check cancellation, preserving current tail previews and the offline-source guard. |
Last-open file set via Fyne’s app-scoped cache.
| File | Responsibility |
|---|---|
session.go |
Save, Load. |
GitHub-release check, SHA-256 + immutable release attestation verify, stage, apply.
| File | Responsibility |
|---|---|
update.go |
Client, AssetName, Newer, Due. |
github.go |
Releases + release-attestation HTTP. |
checksums.go |
VerifyHash (optional API digest). |
download.go / extract.go |
Fetch with optional DownloadProgress, hash and attest the archive, extract directly from verified memory bytes, and select payload paths/hashes from the archive-entry manifest. Persist Stage provenance under a process-local HMAC seal; restart requires redownload. |
attest.go |
GitHub Fulcio Sigstore Verifier + in-toto release policy. |
tufroot.go |
Offline 60-day expiry check and verified sync of embed/tuf-repo.github.com/root.json. |
apply.go / apply_unix.go / apply_windows.go |
Apply dispatcher with ApplyOptions, normal shutdown without relaunch, explicit Perform-update relaunch. Unix (apply_unix.go) writes <dest>.new beside the target, renames it into place, and rolls back through <dest>.old on failure. Windows (apply_windows.go) replaces the running executable in-process from a retained file handle that denies concurrent writes/deletes and whose bytes matched the authenticated stage digest, then verifies the installed destination against that digest; it never reopens the mutable cache path after validation. It used to run a generated <dest>.apply.cmd through cmd.exe, which Controlled Folder Access refuses outright regardless of cmd.exe's own Microsoft signature; that script is gone. |
swap.go |
swapBinaryFrom: the Windows in-process replacement and shared rollback/relaunch test seam, using an already validated handle. Renames the running executable to <dest>.old (the one replacement Windows allows on a running image), copies the validated handle over dest, SHA-256-verifies the copy against the authenticated digest, and tries to restore <dest>.old on any failure past the rename — the rename back is retried a few times and then falls back to copying the backup over dest, but if all of that is refused too, dest is left truncated or missing and the reported Op is restore. That is the one outcome PicFetch cannot recover from or even report on the next launch, since reading the record needs the executable that is broken. <dest>.old deliberately survives a successful swap — it is still this process's own running image — for the next launch to sweep (await.go). |
applyerr.go |
ApplyError (Op/Path/Err) and FailureReason (ReasonAccessDenied / ReasonVirusBlocked / ReasonSharingViolation / ReasonUnknown); ClassifyApplyError maps a failed Apply to the reason the next launch reports, preferring Windows errno classification (applyerr_windows.go) over the portable fs.ErrPermission fallback (applyerr_other.go). |
await.go |
AwaitPIDEnv (PICFETCH_UPDATE_AWAIT_PID) relaunch handshake. CleanupPredecessor, called from main.go before app.NewWithID, waits (bounded, 15s) for the process that installed this executable to exit before preferences are touched, then sweeps <dest>.new / <dest>.apply.cmd left by pre-2026-08-30 updates. SweepBackup, called from internal/ui startup once the Fyne app cache exists, removes <dest>.old — skipped when the last recorded apply failure has Op == "restore", the one state where the backup is the user's only intact executable. |
Standing UI preferences via Fyne Preferences (not the session cache).
SortMode is a string on disk (filesort.FromPref / Mode.PrefValue).
Secondary-window geometry is WindowGeometry structs.
| File | Responsibility |
|---|---|
preferences.go |
Save, Load, SaveLastUpdateCheckDay, State, WindowGeometry; persists normalized mosaic visual settings and secondary-window geometry, never mosaic sources or display IDs. |
Application-wide System/Light/Dark mode, independent of the viewer. Forced modes wrap Fyne's current theme and override only its color variant; returning to System restores the underlying theme so Fyne's OS appearance watcher keeps switching automatically.
| File | Responsibility |
|---|---|
appearance.go |
Mode, translated picker labels, stable preference values, and Apply. |
Compile-time distribution policy. StoreManaged is false for ordinary builds
and true only with the microsoftstore build tag. internal/ui snapshots it
when constructing a viewer so Microsoft Store packages cannot use the GitHub
self-updater while all other distributions retain the existing behavior.
Pure geometry: timestamped window positions in, spiral Result out. No
Fyne, no cgo. Positions are y-down (positive accumulated angle = clockwise
on screen). realdrag_test.go replays recorded title-bar drags.
| File | Responsibility |
|---|---|
wingesture.go |
Direction, Result, Config. |
detector.go |
Ring buffer, idle gap, one-shot armed latch. |
analyse.go |
Centroid, accumulated angle, sign consistency, radius-vs-angle fit. |
Fyne has no position getter and no move event. Get reads the native
handle. Tracker remembers the last good reading. Poll / PollAt sample
on a background goroutine and queue one native read at a time through
fyne.Do. Their Poller separates nonblocking Stop from actual Done /
Wait; cancellation discards queued reads without requiring UI to drain.
An already-started native read must return before completion. Main-window
tracking binds both stop and wait; widgets.Singleton retains unfinished
pollers across close/reopen for WaitForTracking.
| File | Responsibility |
|---|---|
winpos.go |
Get, Set, Maximize, Unmaximize. |
poll.go |
PollAt / Poll / Poller / PollInterval / GestureInterval; per-call native-read/dispatch seam and cancellation-aware acknowledgement. |
tracker.go |
Tracker atomics: Store / Get / Capture / Restore. |
darwin.go / windows.go / linux.go / other.go |
Platform position + maximize. Linux/Wayland: Get reports ok=false. |
OS integrations (clipboard, displays, filemanager, filepicker, trash,
wallpaper) use
dispatcher vars and build-tagged platform files; tests stub them via
internal/uitest — see AGENTS.md.
COM declarations and result handling shared by native Windows adapters.
desktopwallpaper_windows.go owns the IDesktopWallpaper GUIDs, vtable layout,
and activation constants. Portable monitor.go owns the HRESULT failure
predicate and MonitorAttached, which distinguishes an attached monitor
(S_OK) from a retained, detached entry (S_FALSE). internal/displays and
internal/wallpaper retain their native calls, COM lifetime, feature-specific
behavior, and error reporting.
Native attached-display inspection. Inspect(fyne.Window) returns an ordered
snapshot of opaque platform IDs, user-facing names, native-pixel bounds, and
the display containing the greatest part of the PicFetch window. macOS uses
NSScreen/CoreGraphics IDs, Windows uses IDesktopWallpaper monitor device
paths while skipping retained detached entries, Linux uses XRandR under X11 and
explicitly reports Wayland unsupported. The shared snapshot builder supplies
localized fallback names. Callers compare IDs but never parse them.
Viewer-independent mosaic generation. Its small public contract snapshots and
validates source URIs, native-pixel target size, deterministic seed, and visual
settings, then returns immutable rendered pixels. Layout, lazy canonical image
loading, coverage, frame/shadow geometry, and rendering stay inside the
package. Repeated sources use a generation-local 64 MiB byte cache; oversized
sources render without being retained. preparation.go bounds live rendering
scratch to 64 MiB separately from output canvases, layout coverage and decoded
sources/cache. Ordinary placements retain full Catmull-Rom preparation; oversized
working sets prepare visible regions of 256-pixel destination tiles, including
resampler/mask storage and bounded extra SVG rasterization. resample.go uses
separable Catmull-Rom filtering in 32-row bands, reusing converted source rows
and retaining full-precision premultiplied intermediates. The planner accounts
for every band/weight/source-row buffer; exceptionally large supports retain
the low-storage direct transform when the separable workspace would not fit.
Tiles preserve the original affine geometry and use clipped floating-point coverage.
GenerateWithProgress reports completed canvas coverage synchronously from the
existing layout/render pass; only successful final composition reports completion.
preview.go adds independent canvas snapshots after completed placements, capped
at a 960-pixel longest edge and four per second. It combines the repair/background
and primary layers directly at preview resolution into RGBA storage, selecting
the resampler's specialized loops; no full-size snapshot or extra worker is needed.
Generation without a callback does no preview work. Rotated masks
include a filter margin before clipping so canvas boundaries do not fade. The package has no Grid,
display-enumeration, picker, wallpaper, preference, or widget behavior.
PNG image data (CopyImage) and file-reference lists (CopyFiles).
| File | Responsibility |
|---|---|
clipboard.go |
CopyImage dispatcher + per-OS image copy. |
copyfiles.go |
CopyFiles dispatcher + Linux/Windows file-list copy. |
darwin.go / other.go |
AppKit NSPasteboard file list / stub. |
windows.go / notwindows.go |
hideConsoleWindow pair. |
Native open chooser (Choose) and save panel (ChooseSave). Linux/macOS
can pick folders; Windows is files-only.
| File | Responsibility |
|---|---|
filepicker.go |
Typed Choose (URI list) / ChooseSave (one URI), strict native result decoding, canonical-path Zenity framing and UTF-8 JSON PowerShell transport. |
darwin.go / other.go |
NSOpenPanel / NSSavePanel with a shared NSURL-to-JSON transport / stubs; darwin_test.go exercises the actual native serializer. |
windows.go / notwindows.go |
hideConsoleWindow pair. |
Move to Trash/Recycle Bin (Move). Tests use uitest.StubTrashMove.
| File | Responsibility |
|---|---|
trash.go |
Move dispatcher + Linux/Windows impls. |
darwin.go / other.go |
AppKit recycle / stub. |
windows.go / notwindows.go |
hideConsoleWindow pair. |
Set desktop wallpaper through Set(Request), where a zero target retains the
legacy global/all-screen action and an opaque display ID requests one screen.
UI writes a persistent PNG into its cache before calling this. A typed
TargetUnsupportedError distinguishes an honest platform limitation from a
native execution failure.
| File | Responsibility |
|---|---|
wallpaper.go |
Request (including Solo), Set dispatcher, typed limitation, legacy Linux/GNOME/KDE and Windows PowerShell global paths. Linux rejects a target before lookup or mutation unless it is the only attached display. GNOME writes picture-uri and the 42+ picture-uri-dark — the only key on screen in dark mode — skipping the dark key only when the schema genuinely lacks it and otherwise reporting its failure. hostSchemaEnv scrubs GSETTINGS_SCHEMA_DIR / XDG_DATA_HOME / XDG_DATA_DIRS so a snap-wrapped launcher cannot redirect a host desktop write at its own stale bundled schemas. |
darwin.go / other.go |
AppKit all-screen or exact preflighted NSScreen set / non-Darwin stub. |
target_windows.go |
Targeted IDesktopWallpaper: COM-thread lifetime, exact device-path validation, and single-monitor set. |
windows.go / notwindows.go |
hideConsoleWindow pair plus non-Windows target stub. |
Show one file selected in the current OS's own file manager, through
Reveal(path). macOS runs open -R - a LaunchServices binary rather than an
Apple Event, so unlike this repo's other macOS integrations it needs no cgo to
avoid an Automation prompt. Windows runs explorer.exe /select,"<path>" with
the command line built by hand, because os/exec's escaping would quote the
/select, prefix along with the path; explorer's own non-zero exit is
discarded, which is why the path is stat'ed first. Linux calls
org.freedesktop.FileManager1.ShowItems over dbus-send --print-reply (the
only portable way to get the file selected) and falls back to xdg-open on
its parent directory.
| File | Responsibility |
|---|---|
filemanager.go |
Reveal dispatcher, the three platform paths, fileURI, and the portable explorerCmdLine. |
windows.go / notwindows.go |
applyExplorerCommandLine pair: SysProcAttr.CmdLine on Windows, no-op elsewhere. |
macOS "Open With", Dock drop, open -a, and double-clicked associations —
none of which put files in argv for a bundled .app. AppKit turns the
kAEOpenDocuments Apple Event into a delegate call, which Install grafts
onto GLFW's delegate class. That event fires inside glfw.Init(), before
SetOnStarted, so Deliver buffers until SetHandler installs the
viewer's handler and flushes in the same critical section.
| File | Responsibility |
|---|---|
openwith.go |
The queue (Deliver / SetHandler) and URIsFromFileURLs. |
openwith_darwin.{go,h,m} |
Install / DelegateRespondsToOpen + the application:openURLs: / application:openFiles: graft. |
openwith_notdarwin.go |
Both report false; other OSes use argv. |
Recursive image gather for drop/open, plus a non-recursive sibling listing when the user opened a single file.
| File | Responsibility |
|---|---|
filescan.go |
Images(ctx, uris, max, progress) (recursive); Siblings(ctx, file, max, progress) (parent dir only, opened file seeded first); symlink-cycle + per-call dedupe. |
Command-line flag parsing into the Options value ui.Run applies at startup.
Options.ApplicationID owns pre-app Explorer trial offline validation and
isolated identity selection; the ordinary app ID passes through unchanged.
Hand-rolled rather than flag, so flags may appear anywhere among the paths;
rejects an unknown flag, ignores macOS's -psn_*, and validates --sort
against the preferences.SortBy* vocabulary. No Fyne import.
| File | Responsibility |
|---|---|
launch.go |
Options, Options.ApplicationID, Parse, Usage, ErrHelp; the flagSpecs table every flag is declared in. |
Five orderings the S key cycles, plus Label (lang.L) and preference
string translation (FromPref / PrefValue).
| File | Responsibility |
|---|---|
filesort.go |
Mode, Next, Order, Label, FromPref / PrefValue. |
occurrence.go owns immutable path-plus-ordinal bookmarks and a captured index
for exact occurrence lookup. Grid and image visits share this contract. Missing
occurrences return absence; each caller chooses its fallback. The package has
no Fyne or filesystem I/O and does not identify source-content versions.
Integer index set + range anchor for grid multi-select. No Fyne import.
| File | Responsibility |
|---|---|
selection.go |
Set, Toggle, Add, Range. |
Which files in a file set duplicate which others. Fyne-free — reached
through a FileSet interface with string keys, not fyne.URI —
Snapshot() Snapshot is FileSet's only method, handing back an
immutable {keys, generation, key→index} view rather than answering a live
count and key lookup — and its only project import is internal/imaging.
Owns dHashes and native pixel sizes keyed by file (generation-scoped:
WipeIfStale on a fresh drop, AdoptGeneration on an incremental
shrink), the Hamming distance threshold, the installed group snapshot
(representative = highest native pixel count, lowest index on a tie), the
hide-duplicates and inspect modes, and the visibility queries (IsVisible
/ NextVisible / FirstVisible / LastVisible / VisibleIndexesExcept)
plain navigation asks. A caller testing many indices reads the model once
via Visibility(), a frozen hide-flag-plus-groups value with its own
HiddenExtra / Visible / RepresentativeOf / Size. internal/ui
owns the Model and implements FileSet; internal/ui/grid reads and
feeds it (hashing pass, browse, badges) but does not own it.
| File | Responsibility |
|---|---|
dupes.go |
Model, FileSet, hash/native facts, generation (WipeIfStale / AdoptGeneration, read through a Snapshot), distance clamp, OnChange observers. |
facts.go |
FactWriter captured before source work; generation and reset admission share each fact mutation's lock. Reads ignore a mismatched namespace so explicit adoption can retain established facts. |
groups.go |
Groups snapshot, GroupingKey, ComputeContext / guarded Install / reusable Rebuild, GroupSize / RepresentativeOf / Members. |
snapshot.go |
Snapshot: the immutable {keys, generation, key→index} view every Model method reads through. |
visible.go |
Hide/inspect modes (SetHideDuplicates, BeginInspect / ClearInspect / InspectMembers), IsHiddenExtra, Visibility, and the visibility/navigation queries. |
Bounded demand-started workers + per-key in-flight claim. A lifecycle-bound
Queue removes its pending jobs on cancellation and invokes their callbacks
with failed admission; it keeps current interactive jobs ahead of low-priority
background work without creating a goroutine per queued source. Grid thumbs:
Pool[*fyne.Container, thumbClaim]. Display preloads: Pool[string, struct{}].
Cell staleness stays in grid/thumbs.go.
| File | Responsibility |
|---|---|
decodepool.go |
Pool[K,V], Claim / Release, Begin / Queue.Go / Queue.GoLow, one-job Go, Wait. |
One-shot “this background op finished” signal. Named wait helpers vs
drain: see AGENTS.md § Concurrency and Fyne.
| File | Responsibility |
|---|---|
completion.go |
Signal (Begin / Wait / Begun / Current) and Handle. |
Test-only fixtures and OS-seam stubs. Never imported from production files.
| File | Responsibility |
|---|---|
uitest.go |
Temp URIs, synthetic images (including animated GIF, EXIF-oriented/GPS/dimension-tagged JPEG, RAW preview, SVG), ExifIFD0HasTag for asserting on a written file's tags, ApproxEqual. |
stubs.go |
StubChooser, StubSaveChooser, StubClipboardCopy / CopyFiles, StubTrashMove, StubWallpaperSet. |
uiqueue.go |
Drainable UIQueue. |
reader.go |
Instance-owned ReaderURI sources, ReadCloser callbacks and DirectoryURI listings for holding reads across cancellation; one immutable repository adapter registered before workers start. |
Wait helpers (waitUntilLoaded, dropAndWait, …) stay in
internal/ui/harness_test.go.
main.go owns //go:embed translations/*.json and lang.AddTranslationsFS.
filesort.Label and internal/filepicker are the lang.L call sites
outside the UI tree. main_test.go
checks locale parity and that en.json is an identity map. String rule:
see AGENTS.md.
fyne.LogError is used in main.go, internal/ui glue, and
internal/session. Viewer-independent packages return errors. Ignore rule:
see AGENTS.md.
- "How is an image loaded/decoded/cached?" →
internal/imaging/loader.go(raw.gofor camera-RAW previews). - "How is image memory bounded, and where are the limits set?" →
internal/imaging/bytecache.go+internal/ui/memlimits.go+gif.go+loader.goMaxEncodedBytes. - "Where does the EXIF panel live?" →
internal/ui/exifwin. - "Who owns the Hypno Spiral?" →
internal/ui/tunnel.gofreezes sources forinternal/ui/spiral; Help emits the secret phrase callback and the gesture uses the same viewer-owned window. - "Why is the log not full of
tile fetch error?" →internal/ui/exifwin/tiles.goquietPendingTiles/tileLogFilter. - "Why doesn't the EXIF window's map freeze the app while it loads?" →
exifwin/tiles.go+startWarm/syncLoading. - "Why is the info overlay's 'Show EXIF data' link missing?" →
info.gosyncInfoOverlayVisibility+internal/ui/infoviewCard.Sync/HasEXIF. - "How does the persistent info overlay (I key) work?" →
internal/ui/infoview+info.go. - "Why is a menu item greyed out (Checked/Disabled)?" →
internal/ui/menusApply+menu.gomenuState/syncMenus. - "Where is a photo's GPS position read, and where is it shown?" →
internal/imaging/exif.goparseGPSIFD+exifwinLocation section. - "How is EXIF orientation handled?" →
internal/imaging/exif.go+orientation.go. - "How is a camera RAW file shown?" →
internal/imaging/raw.go+LoadedImage.Preview+load.go/info.go. - "How does drag-and-drop / folder scanning work?" →
internal/filescan.Images/filescan.Siblings(single-file case) +drop.gohandleDrop. - "How is an image shown/preloaded/animated?" →
internal/ui/display; rootload.goowns navigation/window policy. - "Which keys do what?" →
keys.go(handleKeyEvent/handleTypedRune) +shortcuts.go. - "How do I find one file by name in a big drop?" →
internal/ui/grid/search.go+keys.gohandleTypedRune. - "How does hide-duplicates work?" →
internal/dupes(the model: grouping, hide/inspect modes, visibility —BeginInspect/InspectMembers/IsHiddenExtra/NextVisible/Visibility) +internal/imaging/dhash.go(the hash) +internal/imaging/grouping.go(cancellable indexed complete linkage) +internal/ui/grid/hashengine.go(the pool-driven pass that fills the model) +grid/dupes.go(browse). Escape reopen:internal/uireopenVariantGrid. - "How do I act on several images at once?" →
internal/selection+grid/selection.goTargets+grid/marquee.go+batch.go+deletion.RequestFiles/clipboard.CopyFiles. - "How does zoom/pan work?" →
internal/ui/zoom; keys inkeys.go; window resize inload.gosyncWindowToZoom. - "How does an SVG stay sharp when I zoom?" →
internal/imaging/vector.goRasterAt+svg.go+internal/ui/display/vector.go+ root density forwarding and zoomSetLogicalSize/onScaleChanged. - "How does rotation work, and how is it saved to disk?" →
internal/ui/display(frames/rotation state) +internal/ui/rotate.go+internal/ui/save.go+internal/imaging/save.go. - "How do I write an image out in a different format, at a smaller size, or without camera metadata?" →
internal/ui/export.go+internal/ui/exportoptions.go+filepicker.ChooseSave+imaging.Export/ExportOptions. - "How do I open the current image's folder in Finder/Explorer/my file manager?" ->
internal/ui/reveal.go+internal/filemanager+shortcuts.gowireRevealShortcut+internal/ui/infoviewRevealLink. - "How does 'Set as Wallpaper' work?" →
internal/ui/wallpaper.go+internal/wallpaper. - "How is an image mosaic sourced, generated, previewed, exported, and targeted to a display?" →
internal/ui/mosaic.go+internal/ui/mosaicwin+internal/mosaic+internal/displays+internal/ui/wallpaper.go. - "How does the slideshow / picture-frame mode work?" →
internal/ui/slideshow+slideshow.go. - "How does delete work?" →
internal/ui/deletion+internal/trash+shortcuts.go/batch.gorequestDelete. - "How are native file dialogs implemented?" →
internal/filepicker+openfiles.go/export.go. - "How is the last session saved/restored?" →
internal/session+session.gorestoreSession. - "How do in-app updates work?" →
internal/update+internal/ui/autoupdate(serialized automatic/manual checks, staging, apply intent, What's-New cache, apply-failure cache) +internal/ui/autoupdate.go(maybeStartUpdateCheck/CheckForUpdatesNow/PerformUpdate/maybeShowWhatsNew/maybeShowUpdateFailure) +settingswin(manual dialogs) +help/whatsnew.go(the window). Automatic checks are off by default (preferences.CheckForUpdates) and stage silently. Apply remains OnStopped: normal shutdown installs without relaunch; explicit Perform update adds a post-apply relaunch. On Windows that relaunch starts the new executable withPICFETCH_UPDATE_AWAIT_PIDset to the installing process's PID;update.CleanupPredecessor(internal/update/await.go), called frommain.gobeforeapp.NewWithID, waits on that PID before preferences are touched, unsets the variable, and sweeps leftovers from pre-2026-08-30 updates. Ifupdate.Applyfails,ClassifyApplyError(internal/update/applyerr.go) records the reason viaautoupdate.SaveApplyFailure, andmaybeShowUpdateFailureexplains it on the next launch with a button to the releases page — releases are unsigned, so Controlled Folder Access can still deny the write even topicfetch.exeitself. GitHub TUF bootstrap expiry:tufroot.go. - "How are GitHub release notes written?" →
todos.md## Done+scripts/releasenotes+make release+.github/workflows/release.ymlbody_path. - "How are Linux race-test shards measured and assigned?" →
scripts/testshards+.github/testshards/internal-ui.tsv+ the measured CI sharding plan. - "How is a WinGet publish gated after Release?" →
.github/workflows/winget.yml+scripts/wingettag(vX.Y.Z allowlist;workflow_runmust berelease.ymlon a published tag). - "How are Microsoft Store updates submitted and reconciled?" →
scripts/storepublish+.github/workflows/microsoft-store-publish.yml+docs/microsoft-store.md. - "How does a macOS Open With reach the viewer?" →
internal/openwith(queue + Objective-C graft) +main.goopenwith.Install+internal/ui/openwith.go+run.goSetOnStarted. - "How does the packaged macOS app declare file/folder associations (Open With)?" →
internal/imaging/loader.goSupportedExtensions+scripts/plistdoctypes+Makefilepackage-mac. - "How do Favorites work?" →
internal/favstore+internal/ui/favorites+shortcuts.go+viewer.OpenFavorite. - "How are favorite previews cached on disk?" →
internal/favthumbs+internal/ui/favthumbs.go+favorites+gridthumb accessors. - "Where is the File menu / Settings window?" →
menu.gobuildMainMenu+actionmenu.go+settingswin+viewer.settingsState/ApplySettings+viewer.closeFiles. - "How are preferences (sort order, appearance, merge mode, slideshow interval/shuffle, folder-scan cap, window-size cap, static window size, window size/position, favorite-preview-cache toggle, similarity cache/automatic update/fit settings, check-for-updates checkbox) persisted?" →
internal/preferences+startup.go+features.go+windowtrack.go+run.gocurrentPreferences. - "How does Light/Dark/System appearance work?" →
internal/appearance+internal/ui/theme.go+settingswin. - "How do the Settings and EXIF windows come back where I left them?" →
widgets.Singleton.Remember/Geometry/StopTracking+winpos.Poll+preferences.WindowGeometry. - "How is the window's on-screen position read back, since Fyne has no getter for it?" →
internal/winpos+windowtrack.gostartWindowPosPolling. - "How can dragging the window open something?" →
internal/wingesture+gesture.go+openSpiralForGesture/spiral.ShowForGesture. - "How does copy-image-to-clipboard work?" →
internal/clipboard+clipboard.go. Batch file copy:copyfiles.go+batch.gocopySelection. - "How does Copy Selection (image-region copy) work?" →
internal/ui/copyselection(Source/Encode,HandleKey) +copyselection.go(pause, clipboard worker,yieldCopySelection) +menu.goyieldingMenuCallbacks+shortcuts.goyieldingShortcuts+ zoomGeometry/HandleScroll+ overlay order inbuild.go. - "How does two-image comparison open, preserve raster/vector fidelity, render through stable overview/detail shaders, identify/swap sides, switch side-by-side/swipe, toggle photo editing with physical Ctrl+L, route camera/photo/divider input, isolate commands, load, settle, and return to the grid?" ->
internal/ui/compare(renderer.goscene/source seam,shader.gostable GPU panes and tile lifecycle,tile.goreveal-aware planning/cache/generation, plus surface/chrome/reveal clips/layout/two photo transforms/shared camera/hover target/divider/input shield/load/vector lifecycles/UI queue) +internal/ui/compare.go(selection, focus release, canonical loader, title, active-mode fact, and open refusal) +features.go/keys.go/shortcuts.go(physical Ctrl+D, toggle hook, transform-key, divider-key, and command routing) +favorites/menus/menu.go(disabled state and guarded entries) + open paths indrop.go/openfiles.go/openwith.go+ overlay order inbuild.go. - "How does the grid overview / thumbnail generation work?" →
imaging/thumbnail.go+grid/grid.go+grid/thumbs.go+grid/hashengine.go+grid/nav.go+grid/uiqueue.go. - "What decides the window title?" →
viewer.gosetTitle/applyTitle/HighlightChanged+load.go+grid/nav.go. - "How do I write a test that needs an image / a viewer?" →
internal/uitest+newTestViewer/newTestUI+dropAndWaitinharness_test.go. - "How do I add or translate a user-visible string?" →
lang.Lat the call site and the same key in everytranslations/bundle. SeeAGENTS.md. - "Why isn't feature X its own package?" →
AGENTS.md. - "How/where are errors reported, and when is it OK to ignore one?" →
AGENTS.md.
Update this file in the same change when a package is added, removed,
renamed, or files move between packages. Cells are locators (path, symbol,
order) — one sentence. Standing rules go in AGENTS.md, not here.