Add a RAW settings tab with orthogonal controls: preview source (embedded
first / always render), rendering brightness (original / brightened, #369),
and a per-pair display source so RAW+JPEG pairs can prefer the JPEG/HEIC
(#331). The policy is carried per request (never stored in global decoder
state) through the asset protocol and thumbnail commands, and folded into
the thumbnail cache key (mode x brightness) so switching re-renders lazily.
The viewer gains a bottom-left RAW button showing the current source that
cycles embedded -> rendered -> brightened and switches to the paired
JPEG/HEIC; the label updates optimistically on click with a deferred
spinner. Editing and export keep the rendered pipeline for pixel parity.
Localizes the new strings across all 11 locales and drops the orphaned
settings.raw keys left by the earlier single-mode design.
Drain active database connections before migration, validate WAL checkpoints, and share the move and restore-default flow. Report old-file cleanup failures with translations for all supported locales and add migration regression tests.
Extract the panel-splitter highlight into shared app.css classes so every
draggable divider looks the same: a gradient bar that is opaque at the
center and fades to transparent toward both ends (theme primary, via
color-mix to avoid a gray fade; solid fallback under @supports).
Reveal is driven by opacity (a background-image gradient cannot be
transitioned): hovering fades the bar in after a 200ms delay so a cursor
passing over the divider does not flash it, leaving fades out immediately,
and dragging reveals it at once.
Applied to the left-panel and info-panel vertical splitters, the left-panel
collection splitter, and both DedupPane horizontal splitters.
Multi-select actions now live entirely in the always-visible selection
panel and are left-click only:
- Expose "Open in external app..." in More actions, popping the same
ContextMenu submenu (apps + Manage app) the right-click menu used.
- Disable "Open in external app" when the selection mixes images and
videos, since there is no single external-app target.
- Remove the now-redundant multi-select right-click context menu and its
Thumbnail -> GridView -> Content event chain; right-click in select
mode is a no-op. The single-file right-click menu is unchanged.
In select mode the file context menu gains a "Refresh file info" action
that re-reads metadata from disk for every selected file, with a
cancellable progress dialog (processed / succeeded / failed). The backend
command takes the library id + file id (never a frontend path), re-checks
the active library under a lock, and invalidates the thumbnail and CLIP
embedding when content, dimensions, or orientation change. Calendar,
Camera, and Location sidebars reload in place so the active filter
survives even if its last file moves out of the category.
Small RAW images could appear shifted toward the bottom-right when the viewer container was larger than the image. RAW placeholders and full previews can have different intrinsic dimensions, while centering, zoom and rotation use the stored imageSize. Minimum dimensions alone allowed the rendered image box to diverge from those calculations.
Set explicit width and height from imageSize and override the default image max-width constraint. This keeps the rendered layout box consistent with the dimensions used by the viewer transforms, including when a RAW placeholder is replaced by the full preview.
Validation: frontend production build and git diff --check passed. The user confirmed that manual testing passed after the change.
Adds a complete Dutch translation of the UI and README.
src-vite/src/locales/nl.json — all 1307 keys of en.json, placeholders unchanged
Registered nl in main.js and in the language pickers (Welcome + Settings)
i18n/README.nl.md + link added to the language bar of all READMEs
Note: utils.ts calls date-fns format() without a locale, so month and day names in date strings stay English for all non-English languages. Passing the matching date-fns locale would fix that; happy to help if you want.
Restart Live/Motion Photo hover playback from the beginning instead of
resuming the position left by the previous hover. Reset currentTime before
play only for playOnActivate previews, preserving normal video pause/resume.
Use the daisyUI bg-base-200 class for fullscreen viewer backgrounds. When
Follow theme is selected, omit the transparent inline background that would
otherwise override this class and expose the main window underneath.
Explicit background colors and non-fullscreen behavior remain unchanged.
Validation:
- User verified repeated Live Photo hover playback and fullscreen backgrounds.
- Playback logic checks confirmed restart-at-zero and unchanged normal video behavior.
- Frontend production builds and staged diff whitespace checks passed.
Clicking the inline filename input blurred it (the root mousedown.capture
refocused the content pane), and Enter — the macOS file.rename shortcut —
reached the global shortcut channel and popped the rename dialog over the
in-progress edit.
- activateContentPane: skip refocus when the pointer target is editable
- App: suppress global-keydown while FileInfo's inline rename is active
- clickRename: bail when an inline input is active
- rename input: stop Enter/Esc from bubbling to the content shortcut handler
Remove the settings selector, persisted default, setter, cross-window event synchronization, and translations. Keep Quick Preview as the default double-click behavior and preserve modifier-assisted opening in a new window.
- Add fullscreen controls to Quick Preview and filmstrip, with a floating
toolbar and close button shared with ImageViewer fullscreen layouts.
- Persist the preview fullscreen preference and restore it when reopening
Quick Preview; closing the preview preserves the preference, while an
explicit fullscreen exit saves the windowed state.
- Make Escape close fullscreen Quick Preview directly and return fullscreen
filmstrip to its embedded layout. Exit fullscreen before showing file info.
- Serialize native fullscreen operations across preview instances to prevent
cleanup from an old preview interfering with a newly opened preview.
- Restore native WebView keyboard focus after fullscreen transitions, including
after preview disposal, and add the required Tauri capability. Preserve
pending focus recovery when queued operations have nothing to do.
- Hide badges in fullscreen, retain overlay navigation, update fullscreen
icons, and remove the duplicate Video.js fullscreen button.
- Recalculate image layout immediately on container resize without animating
from stale coordinates, while retaining manual zoom transitions.
- Add six regression tests covering reopen races, focus recovery, preference
persistence, disposed queued previews, and no-op operation cleanup.
Validation: frontend production build, six preview-window regression tests,
and staged diff whitespace checks passed. Fullscreen image layout
was also verified by the user.
Move "Clean unused thumbnail cache" out of Settings > Advanced into the
Manage Libraries dialog, where it becomes a per-library action rather
than a current-library-only one.
Backend:
- LibraryInfo gains thumbCacheSize; get_library_info scans the library's
cache dir (jpg/png) so each row can show its cache footprint
- AThumb::clean_unused_cache takes Option<library_id>: pooled connection
for the current library, transient read connection for any other
- clean_unused_thumbnail_cache command is now async + spawn_blocking so
heavy directory IO no longer freezes the main thread (project convention)
Frontend (ManageLibraries):
- Replace the 4 hover icon buttons with a single IconMore context menu
(Rename / Reorder / Clean cache / Hide / Remove), matching the
CollectionTray and Tag panel pattern; stats stay always visible
- Add reorder mode: clicking Reorder arms one row with a drag handle and
enables VueDraggable, disarmed by outside pointerdown (CollectionTray parity)
- IconMore fades/slides in after a 200ms hover delay; selected row shows it
immediately. Space is always reserved so rows never shift
- "New library" header button becomes an icon-only TButton with tooltip,
matching AlbumList's add-album button
- Per-library in-flight guard prevents double-clean; stats refresh in place
ContextMenu z-index raised 500->700 (submenu 510->710) so menus opened
inside a ModalDialog (z-600) are no longer covered. Toast stays on top (950).
i18n: add manage_libraries.{clean_cache,cache_cleaned,cache_size} and drop
settings.advanced.{clean_unused_thumbnails,clean_unused_thumbnails_hint,
clean,thumbnail_cache_cleaned} across all 9 locales (1292 keys each, aligned).
Grid tab now covers Grid, Filmstrip and Map sections, so "Grid" no
longer describes its scope. Renamed to "Layout" / "布局" (and localized
equivalents) — the internal settings.grid namespace and section_grid
sub-header are preserved so the tab still contains a "Grid" section
alongside "Filmstrip" and "Map". Chosen over "View" to avoid clashing
with the adjacent Viewer tab in en/es/ko/pt where the two words would
be nearly identical.
Also cleans up translation issues surfaced by a full 8-locale audit
against en.json:
ru.json:
- Add 3 missing keys introduced in 17faa5ba (map marker size settings)
- Fix mistranslations from 06fe990d: subject.items.pets (Животные →
Питомцы), import_organize.destination (Цель → Назначение),
menu.tag.delete_message (restore "Its" and correct case),
recovery.message_with_file (restore Lap brand name)
Cross-locale term consistency (minority variants aligned to majority):
- de: Standort → Ort (Location)
- ja: 位置 → 場所 (Location), マップ → 地図 (Map),
作成日 → 作成日時 (Created at)
- ko: 순서 → 순서 변경 (Reorder), 캘린더 → 달력 (Calendar),
길이 → 재생시간 (Duration)
- pt: Mover para lixeira → Mover para o lixo (Move to trash),
Mostrar informações → Mostrar info (Show info),
Classificação → Avaliação (Rating)
- ru: Местоположение → Место (Location), Порядок → Изменить порядок
(Reorder), Обычный → По умолчанию (Default font size),
Общие → Общее (General, singular neuter), Фото → Фотографии (Photos)
- zh: 位置 → 地点 (Location), 重新排列 → 重新排序 (Reorder),
清除 → 清空 (Clear), 基本信息/常规 → 通用 (General)
Structural audit passes: all 8 locales have 1293 keys, 0 missing,
0 extra, 0 placeholder mismatches, 0 HTML tag mismatches, key order
matches en.json.
Replace the modal dialog with the same inline-rename pattern used by
collections: clicking "+" immediately creates a placeholder row and
focuses an empty input. Enter with a typed name commits (rename API),
Esc / blur with an empty input rolls back (delete API), Enter with an
empty input keeps the input open so a stray keystroke cannot discard
the pending row.
Header tag count uses a computed that excludes the in-flight
placeholder, so the number only reflects committed tags. Placeholders
are also cleaned up on panel unmount to prevent leaks when the user
switches sidebars mid-create; library switches clear state without
deleting to avoid hitting an unrelated row in the new library.
Removes ~145 lines of modal editor code (saveEditor / duplicate
detection / editor state) and reuses existing i18n keys
menu.tag.new_group and msgbox.new_tag.title for both the placeholder
row name and the input placeholder text — no new locale strings.
Add an optional positionKey prop to ModalDialog that persists x/y in
localStorage under lap.dialog.pos.<key>. Position is restored on next
mount (clamped to viewport) and saved once on unmount, so drag hot path
stays zero-IO. First open with no saved data still centers as before.
Wired for 11 main-window dialogs (manage-libraries, album-edit,
import-organize, smart-album-edit, move-to, file-conflict,
index-recovery, tag-picker, edit-tag, edit-collection, edit-comment).
Cross-window components (MessageBox / TaggingDialog /
AddToCollectionDialog) forward the prop; independent-window call sites
in Settings and ImageViewer keep the original centered behavior.
- new settings.grid "Map" section with marker size preset (48/64/88/120)
- scale marker icon and cluster spacing from the configured size
- cache invalidation re-computes clustering on size change
- shared MAP_MARKER_SIZES constant + i18n across all locales
- collapse showFilmStrip + viewMode into a single viewMode (grid|filmstrip|map)
- make the three toolbar buttons mutually exclusive with active highlighting
- grid button cycles style only when already active; filmstrip/map are select-only
- keep viewMode unchanged when entering the map cluster temp view
- disable grid/filmstrip/slider in temp view; map button exits temp view
- remove filmstrip enable checkbox and "open" settings section
Keep nearby photos clustered at every zoom level, including detailed
photo views. Use hierarchical clustering with a 48px screen-space radius
to limit overlap between 64px thumbnails.
Move cluster index construction to a Web Worker and cache overview and
detail indexes separately. Reuse unchanged detail data, coalesce map
updates, and ignore stale asynchronous results.
Add 200ms opacity transitions when clusters merge or expand. Wait for
thumbnail loading before fading in and preserve unchanged markers.
Buffer viewport rendering and detail queries to reduce regrouping near
map edges. Fall back to overview clusters when the detail result exceeds
500 photos rather than displaying a truncated result.
Resolve cluster membership to exact photo IDs before opening the grid,
including original GPS cells for overview clusters, so neighboring
clusters do not contribute unrelated photos.
Validation: production build and git diff checks passed. Temporary
clustering tests were removed as requested. Final map interaction changes
have not been visually verified in the running app.
Prevent video hover previews from starting when the host GStreamer stack
explicitly reports a missing autoaudiosink, addressing the Arch AppImage
WebKitGTK WebProcess crash.
- Register check_gstreamer_available and cache its result with OnceLock.
- Probe gst-inspect-1.0 autoaudiosink with LC_ALL=C and inspect the missing
element diagnostic instead of treating every nonzero exit as absence.
- Preserve playback on non-Linux systems, missing probe tools, abnormal
exits, and other inconclusive probe failures.
- Share a lazily initialized probe promise across frontend hover previews.
- Await availability before loading video in Thumbnail and FileInfo.
- Invalidate pending preview requests when hover ends, the file changes,
or the component unmounts, and recheck after rendering the video element.
- Avoid redundant per-component reactive availability state.
Validation:
- Frontend production build passed after the final simplification.
- GStreamer probe regression test passed, covering explicit absence,
success, signal termination, loader errors, and unknown failures.
- Whitespace checks passed.
- Arch and Mint AppImage runtime verification remains outstanding.
Add a Group > Tag hierarchy to the tag sidebar and tagging dialog.
Keep a protected Default group first and migrate existing tags into it.
- Add database migration 17 for tag groups, membership, persistent ordering,
and safeguards for the Default group.
- Support creating, renaming, reordering, and deleting groups, plus moving
tags between groups. Deleting a group preserves its tags in Default.
- Filter files by tag group and count distinct visible files, honoring
existing file visibility filters.
- Use inline renaming for sidebar groups and tags, with duplicate-name
validation and cleanup when the edited row disappears.
- Match the sidebar search input and count/menu layout to existing views,
reuse menu icons, and keep group styling free of color configuration.
- Group tags in the tagging dialog, support duplicate-tag location, and
refresh tag metadata across views when tags change.
- Prevent edits during initial tag loading and recover submission after
catalog or selection loading errors without discarding pending changes.
- Persist group selection and collapsed state and add localized text.
Validation:
- Frontend production build passed.
- Six Rust tag-group tests passed, covering migration, ordering, membership,
deletion safeguards, deduplication, and visibility filters.
- Targeted loading/recovery logic checks passed.
- Staged whitespace checks and locale JSON validation passed.
Adding an album nested inside (or containing) another album creates overlapping folder trees that break subfolder refresh/sync. Reject such adds in both the backend and the album editor, with a localized toast.
- Re-check has_collections and bump collectionVersion when files are removed from collections, so the bookmark badge and FileInfo membership clear correctly.
- Refresh content and re-check membership when a collection is deleted (Content and ImageViewer).
- Show the collection bookmark in MediaViewer status badges and fold culling into the favorite/rating badge as a trailing icon.
- Welcome screen: show the package app name and shared app description, and add a language selector; drop the hardcoded welcome.title/description keys.
- Persist the left panel state in config.leftPanel.show (default collapsed) and derive showPanel from it instead of an in-memory ref.
- Settings: reorder the similarity-threshold row and apply language on mount.
Add an album context-menu import workflow for recursively copying supported media from a selected source folder. Files retain their original names and can be organized by day, month, year, or a single destination folder.
Provide an in-dialog destination folder tree, progress reporting, cancellation through the button or Escape, and immediate database/content updates without starting an album-wide scan. Preserve Apple adjustment sidecars and roll back copied files when indexing fails.
Reject source and destination folders that are identical or nested to prevent recursive imports, and add localized UI strings for the workflow.
Replace the manual folder refresh workflow with a dedicated recursive subfolder reconciliation command. Preserve inode-based path migrations, remove stale folder records and thumbnail cache entries, and refresh the sidebar tree without scanning file records or regenerating thumbnails.
Keep folder tree shape cached independently of directory mtimes so later file synchronization still detects external file changes.
fix(content): avoid duplicate sidebar content queries
Separate explicit sidebar count updates from selection-driven content reloads, with request guards for concurrent or failed count queries.
Exclude coverFileId alongside cached counts from content refresh dependencies. Compare watched values before refreshing so replacing the smart album array with unchanged query inputs does not reload grouped results.
Preserve refreshes for query, grouping, sorting, rename, and explicit item activation changes. Verified Vue watcher behavior and the Vite production build.
Add a configurable 160/320/640 px browse-only filter that hides files only when both dimensions are below the selected threshold, while preserving scanning and indexing totals.
Apply the filter consistently to content queries and sidebar aggregates, including Library, Albums, Tags, Collections, Calendar, People, Camera, Lens, Location, smart albums, and search results.
Replace persisted aggregate badges with live grouped counts where appropriate, retain lazy counts only for explicit item activations, and prevent runtime count refreshes from causing redundant library-state writes.
Skip unreadable images during face indexing so failed decoder inputs do not remain perpetually unprocessed.
Replace calendar display settings and icon toggles with Years, Months, and Days tabs. Add Camera and Lens tabs with group counts, and remove obsolete configuration, events, and translations.
Add a configurable Calendar display mode, defaulting to a Year → Month → Day hierarchy. The hierarchy follows the Location and Camera sidebar pattern, shows counts at every level, restores the selected path, uses localized long month names, and preserves the existing calendar grid as an alternative mode.\n\nReorganize Settings so Browse contains album, file-grouping, calendar, and sorting preferences; place Grid immediately after it. Rename the former Browse settings group to Grid and the former Library settings group to Browse across templates, tooltips, option helpers, locale keys, and tab metadata.\n\nUse calendarDisplayMode consistently for the persisted setting, setter, option helper, and cross-window event. Synchronize all locales and validate with a production frontend build, locale-key consistency checks, and diff whitespace checks.
Add a reclaimable duplicates summary with a Delete all action and reuse the shared deletion confirmation flow for selected and bulk cleanup. Unify dedup deletion behind one request, preserve permanent-delete behavior, Live Photo components, and Apple sidecars, and avoid returning large ID lists for bulk operations.\n\nAdd similar-photo result totals, refresh them after mutations without repeating the aggregate query while paginating, and synchronize the new copy across locales.\n\nMake trash deletion resilient to virtualized null file slots and prevent duplicate submissions by disabling the confirmation actions and showing a loading indicator until deletion finishes.