Commit Graph

224 Commits

Author SHA1 Message Date
LyAhn 2bc4a98164 fix(frontend): derive THEME_OPTIONS from an exhaustive AppTheme record
github/actions/ci GitHub Actions CI finished: failure
The quick theme switcher's option list was a hand-maintained array that
could drift from the AppTheme union. Derive it from a
Record<AppTheme, string> instead, so adding a theme forces this
mapping to be updated too.
2026-07-06 20:42:06 +01:00
LyAhn 6923777345 test(frontend): cover mediaSrc, changelog, and timeline grouping
Unit tests for three previously-untested pure functions: mediaSrc's
UI Lab path handling, getChangelogForVersion's version normalization,
and groupImages' date bucketing/sorting.
2026-07-06 20:41:42 +01:00
LyAhn 96e62cb7c1 perf(frontend): invalidate duplicate-scan cache concurrently and safely
Cache invalidation across affected folders after a bulk delete ran
sequentially through awaited invoke calls, duplicated between
gallerySlice and duplicateSlice. Extract a shared
invalidateDuplicateScanCaches helper that batches the calls with
Promise.allSettled, logging failures instead of throwing — a
best-effort cache refresh should never turn an already-successful
delete into a rejected promise for the caller.
2026-07-06 20:41:16 +01:00
LyAhn 42564a93e0 fix(backend): escape LIKE wildcards and harden curl arg passing
Filename/tag search treated literal % and _ in query text as SQL
wildcards; escape them (with ESCAPE '\') before building LIKE patterns.
Also pass -- before the URL in curl invocations so a URL starting with
a dash can't be misread as an option.
2026-07-06 20:40:37 +01:00
LyAhn b8d009c973 fix(frontend): sync sidebar pause menu with per-worker background task state
github/actions/ci GitHub Actions CI finished: success
Only require a worker to be paused if it currently has pending work for
that folder, instead of demanding all four worker flags be true. The
background tasks panel only lets you toggle stages it shows, so the old
check stayed out of sync after pausing everything visible.

Also grants start-dragging and start-resize-dragging window permissions.
2026-07-06 00:47:13 +01:00
LyAhn dcc1612802 merge: add automated test coverage
github/actions/ci GitHub Actions CI finished: success
Merge the Tauri test feature branch into main.

Adds Vitest coverage for frontend formatting, path, and store helper behavior, plus Rust unit coverage for database, vector, storage, indexer, thumbnail, and AI tag filtering logic. Also wires the new unit test scripts into the package commands and documents the test layers.
2026-07-05 21:26:32 +01:00
LyAhn 2c699a5aac docs(claude): document the new unit-test layers and commands
CLAUDE.md claimed the Playwright smoke tests were the only tests; add
the Vitest and cargo-test commands and describe the three test layers,
including the shared db::test_support fixture for DB-touching Rust
tests.
2026-07-05 21:20:24 +01:00
LyAhn ca5c500e18 test(backend): cover storage, indexer, thumbnail, and tag-filter logic
storage.rs: thumbnail worker clamping across parallelism levels, the
adaptive-profile EMA transitions, and UNC-path fallback detection.
indexer.rs: supported-media extension matching plus media-kind and MIME
mapping. thumbnail.rs: fit_dimensions aspect-ratio math with extreme
ratios and is_jpeg extension checks. ai_tag_filter.rs: padding and
mixed-separator normalization edge cases.
2026-07-05 21:19:50 +01:00
LyAhn 782cf0ea08 test(backend): add in-memory SQLite test harness with db and vector tests
A shared test_support module in db.rs provides the fixture: sqlite-vec
registered via auto-extension, an in-memory connection with foreign keys
on, and both migrations applied - no refactoring of production code
needed since every query function already takes &Connection.

db.rs coverage: folder idempotency, upsert_image update semantics
(favorite/rating preserved, AI tag state invalidated), the get_images
filter matrix with pagination and count_images agreement, tag
merge/rename/delete, user-tag precedence over AI tags in update_ai_tags,
album CRUD with FK cascade, the embedding job queue (backfill, retry,
consistency repair), tag search, and delete_folder cascades.

vector.rs coverage: pack/unpack round-trip, embedding upsert/delete with
dimension validation, and find_similar_image_ids ranking on both the
global KNN and folder-scoped brute-force paths.
2026-07-05 21:19:42 +01:00
LyAhn 5004a2d01a test(frontend): add Vitest unit tests for store helpers and utilities
Wire Vitest into the existing Vite config (scoped to src/**/*.test.ts so it
never collides with the Playwright suite in tests/) and add test:unit,
test:unit:watch, and test:rust scripts.

Covers the pure logic layer: parseSearchValue prefix parsing, all twelve
sort orders through mergeImages, merge/dedup/window helpers, filter
matching, gallery request tokens, localStorage-backed initial settings,
folder-picker path utilities, and the duplicate/lightbox/gallery/video
formatters. Fixture factories live in src/test/factories.ts.
2026-07-05 21:19:31 +01:00
LyAhn 9a282dda86 docs(claude): document UI Lab, Playwright e2e tests, and tooling scripts
CLAUDE.md still claimed no test suites existed. Update it for the
current repo state:

- add pnpm test:e2e commands (full run, single file, single test) and
  note the suite runs against UI Lab mocks, not the Tauri app
- new UI Lab section: src/dev mock layer, ?scenario=/?changelog= params,
  mediaSrc rule, and the mockBackend requirement for new commands
- add dev:ui and format:all to the command list
- note website/ as a separate Vite project and document the
  changelog:add script syntax
2026-07-05 20:48:16 +01:00
LyAhn b23212ea1c refactor(backend): extract download and onnx_runtime modules from captioner
github/actions/ci GitHub Actions CI finished: success
captioner.rs had grown into two things: the (currently disabled)
Florence-2 captioner plus generic infrastructure the live tagger
depends on. Split the neutral parts out:

- download.rs: resilient curl downloader (resume, stall detection)
  and NuGet package extraction
- onnx_runtime.rs: shared ONNX Runtime/DirectML DLL manifest,
  provisioning, and ort init, with runtime_dir() as the single
  definition of the DLL location (kept inside the caption model dir
  so existing installs do not re-download)

Also dedupes the tagger-side copies of the DLL list and four
hardcoded caption-model path literals, and rewords tagger runtime
errors that wrongly told users to download the caption model.
2026-07-05 20:21:47 +01:00
LyAhn a791f112f5 fix(downloads): invoke system curl portably instead of curl.exe
Spawn bare `curl` (which Windows still resolves to curl.exe via
CreateProcess, and macOS/Linux ship natively) and write the size-probe
output to the platform null device instead of the Windows-only NUL,
removing the hard Windows dependency from the model download path.
Spawn quirks (console-window suppression) are centralized in a new
curl_command() helper.
2026-07-05 20:21:23 +01:00
LyAhn 7020a6b6cf style(whats-new): reformat WhatsNewModal via pnpm format
github/actions/ci GitHub Actions CI finished: success
2026-07-05 15:02:27 +01:00
LyAhn bf38fac30d test(e2e): wire Playwright to the UI Lab with scenario smoke tests 2026-07-05 15:02:26 +01:00
LyAhn 79e2e28979 refactor(dev): move DemoPanel into src/dev
DemoPanel is dev-only tooling like the rest of the UI Lab mock code, so it
lives with it now instead of among the real UI components. Import path in
App.tsx updated; the DEV gating (and production tree-shaking) is unchanged.
2026-07-05 13:38:52 +01:00
LyAhn aa3fe2062d feat(ui-lab): add first-run and update scenarios
github/actions/ci GitHub Actions CI finished: success
Add separate UI Lab scenarios for true first-run onboarding and post-update What's New checks. Keep empty-library fixtures reusable across mock data, tagger readiness, worker pause defaults, and launch-time version state so browser verification can exercise the real flows.
2026-07-04 21:41:34 +01:00
LyAhn af1a443a64 feat(ui-lab): preview What's New entries via ?changelog=
Add a UI-Lab-only (mode === "ui", dead-code-eliminated from real builds)
URL override for the What's New modal: ?changelog=unreleased shows the
in-progress notes before they ship, ?changelog=small serves a synthetic
hotfix-sized entry to exercise the compact layout (including a no-lead
bullet), and ?changelog=<version> previews any released entry. Documented
in docs/ui-lab.md alongside the ?scenario= table.
2026-07-04 21:15:17 +01:00
LyAhn 302a3151ef feat(whats-new): adapt modal layout to release size
Small releases keep the compact single-column list; releases with more than
20 bullets across 2+ sections switch to a wider two-pane layout with a
section nav rail, so no pane is a marathon scroll. The threshold lives in
RAIL_THRESHOLD; single-section entries always stay single-column.
2026-07-04 21:14:45 +01:00
LyAhn d7c3cbd683 docs(changelog): fill unreleased gaps and give Fixed notes a pain-first pass
Add the user-facing changes that had no entry: the Settings page reorg, the
AI-tag reset flow, the onboarding tagger-model choice + per-model confidence
thresholds, and the lightbox tagger-readiness fix. Fold two more papercuts
(search-field button alignment, selectable menu labels) into the papercuts
bullet.

Rework several Fixed entries to open with what the user used to suffer
rather than the fix, Discord-patch-notes style.
2026-07-04 21:14:29 +01:00
LyAhn 2d567c0810 Merge refactor/modularize-components: modular components, store slices, shared menus, prettier
github/actions/ci GitHub Actions CI finished: success
- Zustand store split from one god file into per-feature slices under
  src/store/ (library, gallery, search, explore, albums, duplicates,
  tagger, captions, settings, app) with shared helpers and event wiring.
- All large components modularized into per-feature subfolders:
  Lightbox, SettingsModal, Sidebar, ExploreView, FolderPickerModal,
  BackgroundTasks, Toolbar, Gallery, Timeline, DuplicateFinder,
  VideoPlayer, and BulkActionBar.
- Context menus, dropdowns, and dismissal handling consolidated into a
  shared menu system (src/components/menu/); dead MenuBar removed;
  shared icons extracted into icons.tsx.
- New: add-to-album submenu on the image right-click menu.
- Fixes: menu labels no longer text-selectable, duplicate tile React
  keys, mock IPC results cloned to match real invoke semantics, tile
  tooltip and folder picker glitches.
- Tooling: prettier added with format/format:rust scripts, one-shot
  format of the frontend, LF line endings enforced via .gitattributes.
2026-07-04 20:31:47 +01:00
LyAhn 5ff3ff53a6 chore: enforce LF line endings via .gitattributes
Prettier enforces endOfLine: lf, but core.autocrlf=true was converting
working-copy files to CRLF on checkout, which would flip format:check
red after every branch switch. eol=lf keeps LF in the working tree;
binary assets and Windows scripts are exempted.
2026-07-04 20:26:05 +01:00
LyAhn 827e1a8ecf style: format frontend with prettier
Mechanical one-shot pass of pnpm format over src/, tests/, tools/, and
root configs. No functional changes; build and type-check verified.
2026-07-04 20:23:32 +01:00
LyAhn 32c6ae09d6 chore: add prettier with format scripts
Prettier config (no semi, single quotes, 100 cols, tailwindcss plugin)
scoped via .prettierignore to frontend code only; markdown, docs, CI
workflows, website/, and src-tauri/ are excluded. Scripts follow the
toolchain split: format/format:check (prettier), format:rust(:check)
(cargo fmt), format:all for both.
2026-07-04 20:23:19 +01:00
LyAhn 44179c83a7 refactor(bulk-action-bar): modularize component
Break down the monolithic BulkActionBar component into smaller, highly cohesive modules to improve maintainability and separation of concerns.

- Extract distinct UI regions and popovers into dedicated components (`BulkSelectionSummary`, `BulkAlbumPopover`, `BulkDeleteConfirm`, `BulkRatingPopover`, and `BulkTagPopover`).
- Abstract shared panel states and configurations into a dedicated `types.ts` file within the `bulk` directory.
- Simplify the main `BulkActionBar` component to act as a clean presentation orchestrator.
2026-07-04 19:49:22 +01:00
LyAhn 4fcc42f356 refactor(video-player): modularize component
Break down the monolithic VideoPlayer component into smaller, highly cohesive modules to improve maintainability and separate concerns.

- Extract UI sub-components into dedicated files (`VideoControls` and `ControlButton`).
- Abstract complex playback state, event listeners, scrubbing, and keyboard shortcuts into a custom `useVideoPlayer` hook.
- Move time formatting utilities to a dedicated `format.ts` file.
- Relocate shared constants and types to dedicated files (`constants.ts` and `types.ts`).
- Simplify the main `VideoPlayer` component to act as a lightweight presentation orchestrator.
2026-07-04 19:41:46 +01:00
LyAhn aa3d843a4b refactor(duplicate-finder): modularize component
Break down the monolithic DuplicateFinder component into smaller, highly cohesive modules to improve maintainability and separation of concerns.

- Extract empty, loading, and intro states into dedicated components (`DuplicateFinderEmptyStates`).
- Extract the complex header and action logic into `DuplicateFinderHeader`.
- Move `DuplicateGroupCard` to its own dedicated component file.
- Relocate formatting utilities like byte conversion and relative time strings to a dedicated `format.ts` file.
2026-07-04 19:36:07 +01:00
LyAhn 01faec9155 refactor(gallery): modularize gallery and timeline
Break down the monolithic Gallery and Timeline components into smaller, highly cohesive modules to improve maintainability and separation of concerns.

- Extract core UI elements into dedicated components (`ImageTile`, `TruncatedFilename`, `ScrubberYearBlock`).
- Separate loading and empty states into dedicated files (`GalleryEmptyState`, `TimelineEmptyState`).
- Move complex timeline grouping, row virtualization mapping, and duration formatting logic into utility files (`timelineModel.ts` and `format.ts`).
- Abstract shared data structures into `types.ts` to cleanly decouple domain logic from the presentation layer.
2026-07-04 19:30:54 +01:00
LyAhn 3242897a3b refactor(toolbar): modularize component
Break down the monolithic Toolbar component into smaller, highly cohesive modules to improve maintainability and separation of concerns.

- Extract distinct UI regions into dedicated components (`ToolbarTitle`, `ToolbarSearch`, `SortControl`, `ZoomControl`, `ToolbarFilters`, and `FilterPill`).
- Abstract complex search state, debouncing, event listeners, and autocomplete logic into a custom `useToolbarSearch` hook.
- Relocate search value formatting and sort configurations to dedicated utility files (`searchValue.ts` and `sortOptions.ts`).
- Simplify the main `Toolbar` component to act as a clean presentation orchestrator.
2026-07-04 19:23:02 +01:00
LyAhn 8f424773d2 refactor(folder-picker): modularize component
Break down the monolithic FolderPickerModal component into smaller, highly cohesive modules to improve maintainability and separate concerns.

- Extract UI sub-components into dedicated files (`FolderRow`, `StagedFoldersPanel`, and `StatusLine`).
- Move path manipulation, string formatting, and breadcrumb utilities into a dedicated `pathUtils.ts` file.
- Abstract complex local state, API calls, and event listeners into a custom `useFolderPicker` hook.
- Clean up `FolderPickerModal` to act as a lightweight presentation layer.
2026-07-04 19:14:32 +01:00
LyAhn 2149b4cad5 refactor(bg tasks): modularize component
Break down the monolithic BackgroundTasks component into smaller, highly cohesive modules to improve maintainability and readability.

- Extract complex UI components into dedicated files (`BackgroundTaskSummary`, `ExpandedTaskPanel`, `TaskStagePill`, `TaskProgressBar`, `BackgroundTaskActions`, and `FailedWorkerItemRow`).
- Move task construction, duplicate scan formatting, and progress calculation logic into a dedicated `taskModel.ts` utility file.
- Relocate shared interfaces and constants to `types.ts` to cleanly decouple data structures from the presentation layer.
2026-07-04 19:04:06 +01:00
LyAhn 2901425f42 refactor(sidebar): modularize Sidebar
Break down the monolithic Sidebar component into smaller, maintainable modules to improve readability and separation of concerns.

- Extract `NavItem`, `FolderItem`, and `AlbumItem` into dedicated UI components.
- Modularize domain logic by introducing `LibrarySection` and `AlbumSection`.
- Abstract complex drag-and-drop and ordering state into custom `useFolderOrdering` and `useAlbumOrdering` hooks.
- Move shared constants and types to a dedicated `types.ts` file.
2026-07-04 18:57:36 +01:00
LyAhn 1074c875a3 fix(menus): prevent menu label text from being selectable 2026-07-04 18:30:25 +01:00
LyAhn 58ecb03070 refactor(explore): extract components from ExploreView into src/components/explore/
To de-godify \ExploreView.tsx\, the inline components \ClusterCloud\, \ExploreLoadingPanel\, \TagAtlas\, and \TagManageList\, along with shared constants in \layout.ts\, have been extracted into independent files. This improves modularity and maintainability.
2026-07-04 18:09:41 +01:00
LyAhn 2c0b928bf5 refactor(ui): split SettingsModal into feature slices under src/components/settings/
Extracts the various settings sections (General, Media, Updates, Storage, AI Workspace) from the massive SettingsModal.tsx into distinct sub-components. Introduces a shared.tsx file for common settings UI primitives (SettingsGroup, SettingsItem, StatusPill, etc.) to improve code maintainability and separation of concerns.
2026-07-04 16:46:41 +01:00
LyAhn fe312e7678 refactor(lightbox): split Lightbox into smaller hooks and components
Deconstruct the Lightbox.tsx god component into feature-specific hooks (slideshow, region selection, navigation, media details) and separate UI components (viewport, slideshow view, details panel). Extracted shared types and formatting logic into utility files.
2026-07-04 16:33:54 +01:00
LyAhn 52ac2543ec refactor(store): split store.ts into feature slices under src/store/
Break the 3,244-line monolithic store into a Zustand slice-per-feature
layout (types, helpers, librarySlice, gallerySlice, searchSlice,
exploreSlice, albumSlice, duplicateSlice, taggerSlice, captionSlice,
settingsSlice, appSlice, events) combined in index.ts. Components keep
calling useGalleryStore(s => s.field) against the same flat state
object — no component changes required. Completes the de-godify effort
started with the menu/Dropdown/Sidebar extractions and icons.tsx.
2026-07-04 15:53:52 +01:00
LyAhn fbf8a7878c refactor(ui): extract shared icons into icons.tsx
The ten most-duplicated inline SVGs (Check, Close, ChevronDown/Right,
Plus, Photo, Folder, Warning, Star, Play) become shared components --
34+ copies across 15 files replaced. Stroke icons take a per-site
strokeWidth since weights legitimately differ by context; icons that
appear once or use variant path data stay inline.
2026-07-04 12:29:14 +01:00
LyAhn 4d41f3744f fix(duplicates): key duplicate tiles on the outermost mapped element
The key sat on the button nested inside the Tooltip wrapper, so React
warned about missing keys for every duplicate group tile.
2026-07-04 12:21:04 +01:00
LyAhn c27662dd74 refactor(ui): extract AlbumPicker from BulkActionBar
The album popover body (album list + create-new-album form) moves to
its own component. The host supplies onPick, which both an existing
album row and a freshly created album route through -- BulkActionBar
adds the selection and closes the panel. Ready for reuse anywhere else
an album needs picking (e.g. the context menu submenu later).
2026-07-04 12:11:36 +01:00
LyAhn ee2a1b204e refactor(sidebar): extract InlineRename, InlineConfirm, and NavItem
FolderItem and AlbumItem each carried their own copy of the in-place
rename input (state, select-on-open effect, commit/cancel handling) and
the Confirm/Cancel pair for destructive actions. Both are now shared
components: InlineRename mounts fresh per rename (an external name
change mid-rename no longer clobbers typing), InlineConfirm is the
compact red/gray pair. The four copy-pasted nav rows collapse into a
local NavItem component.
2026-07-04 12:11:26 +01:00
LyAhn 6806703363 fix(dev): clone mock IPC results to match real invoke semantics
Mock handlers return references straight into the in-memory db (e.g.
`return db.albums`), so store updates like set({ albums }) kept the same
array identity across loads and Zustand never notified subscribers --
the sidebar album list froze after creating an album from the bulk bar.
Real invoke() deserializes fresh JSON per call, so production was never
affected. structuredClone in the shim restores that fidelity for every
mock command at once.
2026-07-04 12:11:00 +01:00
LyAhn 90fd6f4fed chore(ui): remove dead MenuBar component
MenuBar has had no importers since the Toolbar superseded it -- its
last real touch was the multi-folder picker work. Delete it and update
the CLAUDE.md component list (which also drifted: Timeline/ExploreView
existed but were missing, TagCloud never split out) plus a pointer to
the shared menu primitives.
2026-07-04 07:22:32 +01:00
LyAhn 053a2bd846 refactor(ui): consolidate dropdowns into one shared Dropdown
ThemedDropdown, Toolbar''s local SortDropdown, and FolderScopeDropdown
were three hand-rolled implementations of the same select pattern. They
are replaced by a single generic Dropdown (src/components/menu/) built
on MenuPanel/MenuItem, with solid/ghost/compact trigger variants and
Object.is value comparison so number|null folder scopes work alongside
string unions. Call sites drop their `value as X` casts.

MenuItem gains an `active` state plus stable menu-panel/menu-item class
hooks, and the subtle-light CSS that previously dressed only the folder
scope dropdown (feature-scope-*) now themes every menu surface --
dropdowns, context menus, and submenus alike.
2026-07-04 07:22:22 +01:00
LyAhn 54016df830 refactor(ui): adopt useDismissable in ColorFilter and BulkActionBar
Replaces the hand-rolled outside-pointerdown listeners with the shared
hook. Both popovers now also close on Escape, and listeners only attach
while a panel is actually open.
2026-07-04 07:22:09 +01:00
LyAhn 5d46ee5b94 feat(gallery): add-to-album submenu on the image right-click menu
Right-clicking an image in the Gallery or Timeline now offers an
"Add to Album" submenu listing all albums with their counts — the first
use of the new SubMenu primitive. Filing a single image away no longer
requires starting a multi-select.
2026-07-03 23:58:18 +01:00
LyAhn 83081928f6 refactor(ui): unify context menus into a shared menu system
Five hand-rolled context menu implementations (image tiles, sidebar
folders, sidebar albums, title bar theme switcher, plus duplicated
close-listener effects in Gallery/Timeline) are replaced by shared
primitives in src/components/menu/:

- useDismissable: one outside-pointerdown + Escape dismissal hook
- Menu.tsx: MenuPanel chrome, MenuItem (danger/disabled/checked/hint),
  MenuSeparator, MenuLabel, and SubMenu with viewport edge-flipping
- ContextMenu: portal-rendered wrapper that measures and clamps to the
  viewport, fixing menus rendering off-screen and the latent
  fixed-inside-transform bug under framer-motion Reorder items

The image right-click menu moves to ImageContextMenu.tsx, shared by
Gallery and Timeline.
2026-07-03 23:54:28 +01:00
LyAhn e374ff6b02 fix(ui): fix tile tooltip + folder picker
- Tooltips now shows their correct px size on hovering

- Remove trailing `\` from drive letter in folder picker UI
2026-07-03 23:27:14 +01:00
LyAhn 3ab9357d6f perf(explore): reduce tag cloud refresh pressure
github/actions/ci GitHub Actions CI finished: success
Debounce Explore tag refreshes while AI tagging is active so the tag cloud catches up after worker activity settles instead of continuously re-querying.

Optimize the tag cloud aggregate query by fetching representative thumbnails in one pass and adding a supporting tag/image index.
2026-07-03 22:43:41 +01:00
LyAhn fe65bc6f38 feat(settings): reorganize preferences pages
Split Settings into General, Media, Updates & Setup, Storage, and AI Workspace pages so update/setup and maintenance controls are easier to reach.

Move runtime check results beside the model runtime controls instead of under model location.
2026-07-03 22:43:41 +01:00