921 Commits
Author SHA1 Message Date
julyx10 b3d7c7577f feat(raw): preview source, brightness, and pair display options (#331, #369)
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.
2026-10-01 20:09:47 +08:00
julyx10 9c4f71601c fix: safely migrate database storage and report cleanup failures (#354)
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.
2026-09-30 19:13:50 +08:00
julyx10 6be1bdb957 fix: reload visible thumbnails after deletion in grouped view (#325)
Reset the visible-range cache before rebuilding grouped rows so an unchanged viewport fetches its data without requiring a scroll.
2026-09-30 18:17:25 +08:00
julyx10 e8cbd26058 style(splitters): shared gradient indicator with delayed hover fade-in
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.
2026-09-29 18:00:01 +08:00
julyx10 3d5eb5406b refactor(selection): consolidate multi-select actions into the panel
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.
2026-09-29 16:47:43 +08:00
julyx10 5116fc9cdf feat(fileinfo): add "Refresh file info" to multi-selection (#368)
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.
2026-09-29 15:44:35 +08:00
julyx10 ffe058eaab fix: center small RAW previews using explicit layout dimensions
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.
2026-09-29 11:43:26 +08:00
Daniel Benner c474735045 fix: keep full image from being replaced by the thumbnail when navigating quickly (#367)
Great!  Thanks for the PR!
2026-09-29 11:01:18 +08:00
julyx10 da1b2878c3 feat(imageviewer): bidirectionally sync current image with main-window selection (#328) 2026-09-28 20:25:03 +08:00
OPGERUIMD97 1f36083bc1 feat(i18n): add Dutch (nl) translation (#364)
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.
2026-09-28 18:06:59 +08:00
julyx10 62671501cf fix: restart Live Photo previews and use opaque fullscreen backgrounds (#347)
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.
2026-09-28 11:41:11 +08:00
julyx10 bc813bd8da fix(fileinfo): stop inline rename blur and Enter opening the dialog
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
2026-09-28 10:48:34 +08:00
julyx10 25f41f357e refactor: remove thumbnail double-click action setting (#347)
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.
2026-09-27 18:50:34 +08:00
julyx10 b4a8e3a377 feat: unify preview fullscreen controls and lifecycle (#347)
- 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.
2026-09-27 18:42:48 +08:00
Tomasz Gospodarczyk 02fa7aa7c8 feat: add Polish (pl) locale and README translation (#343) 2026-09-25 22:23:39 +08:00
julyx10 47ea34b087 feat: add per-album exclusions and scan preview (#322, #332) 2026-09-24 17:49:05 +08:00
julyx10 67cda34420 fix: persist preview toolbar pinned position 2026-09-20 19:54:56 +08:00
julyx10 309d6c5f7d fix(db): detect per-library corruption and prompt to switch library (#326) 2026-09-20 19:41:52 +08:00
julyx10 318ab6a8ce feat(libraries): per-library thumbnail cache cleanup in Manage Libraries
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).
2026-09-18 18:37:54 +08:00
julyx10 75739dabf1 chore(i18n): rename Grid settings tab to Layout and align translations
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.
2026-09-18 16:29:34 +08:00
julyx10 782180132c feat(tag): inline editor for new tag / new group
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.
2026-09-18 14:59:57 +08:00
julyx10 3f9c83aa29 feat(ui): remember main-window dialog positions across sessions
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.
2026-09-18 11:54:40 +08:00
julyx10 3c70cb9386 feat: arbitrary-angle straighten with ruler slider in crop mode (#318) 2026-09-17 20:21:22 +08:00
Dmitry Yerokhin 06fe990db3 Update ru.json (#320)
Updated Russian translation.
2026-09-17 09:28:17 +08:00
julyx10 17faa5baae feat: add configurable map photo marker size (#307)
- 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
2026-09-16 20:07:39 +08:00
julyx10 64299b6b42 feat: unify grid, filmstrip and map as mutually exclusive view modes (#315)
- 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
2026-09-16 17:57:51 +08:00
julyx10 34f0425fcc Improve tag and collection editors and show tag group in content titles 2026-09-15 18:56:41 +08:00
julyx10 20094dc5c1 fix: improve photo map clustering and transitions (#307)
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.
2026-09-14 19:13:00 +08:00
julyx10 ab71ea5c08 fix: guard video hover previews against missing GStreamer sink (#313)
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.
2026-09-14 15:09:15 +08:00
julyx10 eaf21c711b feat: organize tags into groups (#304)
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.
2026-09-14 10:35:54 +08:00
julyx10 95a9335707 chore: release v0.3.2 2026-09-12 21:48:26 +08:00
julyx10 d15d9ade32 fix(album): reject albums that overlap an existing album
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.
2026-09-12 20:39:17 +08:00
julyx10 b7adb0d9a5 fix(collection): keep collection badge in sync on remove/delete
- 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.
2026-09-12 18:50:11 +08:00
julyx10 caac20dd7b fix: stop slideshow when leaving filmstrip or entering map view 2026-09-12 18:17:30 +08:00
julyx10 6d1adb321e feat: add counterclockwise view rotation and align calendar labels 2026-09-12 17:44:40 +08:00
julyx10 40fedbfd85 feat(welcome): app name, language selector, and persisted left panel
- 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.
2026-09-12 17:18:11 +08:00
julyx10 e926908983 feat: restore random sorting (#303) 2026-09-12 13:18:37 +08:00
julyx10 8a78533853 feat(sidebar): keep item count visible next to the more menu 2026-09-11 20:23:18 +08:00
julyx10 242a69fcc8 feat(import): rework import-and-organize with resume, content dedup, and RAW support 2026-09-11 20:12:13 +08:00
julyx10 0dddb47440 chore: remove unused screenshots and replace empty-state PNG with search icon 2026-09-09 19:48:33 +08:00
julyx10 71c20aee49 fix(keyboard): add page navigation and prevent rename reopening (#296) 2026-09-09 18:36:46 +08:00
julyx10 e9ccb36cc7 feat(video): zoom at cursor and clamp pan like the image viewer 2026-09-09 17:18:09 +08:00
julyx10 45d5fd57b3 feat(import): add date-organized folder import (#86)
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.
2026-09-08 21:01:40 +08:00
julyx10 923c391eb8 refactor(folders): refresh subfolder hierarchy
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.
2026-09-08 13:58:45 +08:00
julyx10 0a201aa640 fix(smart-album): prevent duplicate content refreshes
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.
2026-09-07 16:26:40 +08:00
julyx10 0b90a65650 feat(browse): add small file filter (#258)
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.
2026-09-07 16:04:18 +08:00
julyx10 3a7e26fbfd fix(ui): defer actions until mouse release
Run Album Info confirmation and cancellation on mouseup, and remove the duplicate mousedown close action from Media Viewer.
2026-09-05 21:37:40 +08:00
julyx10 c748db0950 feat(sidebar): unify calendar and camera tabs
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.
2026-09-05 20:30:26 +08:00
julyx10 dca03ce89c feat(calendar): add hierarchical date navigation
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.
2026-09-04 21:02:07 +08:00
julyx10 1043bd0b54 feat: improve duplicate cleanup workflow (#291)
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.
2026-09-04 18:46:37 +08:00