diff --git a/README.md b/README.md index b559491..3e0fb69 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,9 @@ pnpm dev:app # Frontend only pnpm dev:vite +# Browser-only UI Lab with mocked Tauri APIs +pnpm dev:ui + # Production build (CPU) pnpm build:app:cpu @@ -119,6 +122,9 @@ pnpm build:app:cuda pnpm build:vite ``` +For visual frontend work without launching Tauri or the Rust backend, see +[Phokus UI Lab](docs/ui-lab.md). + ## How it works 1. Add a folder from the sidebar — the Rust indexer walks it recursively. diff --git a/docs/ui-lab.md b/docs/ui-lab.md new file mode 100644 index 0000000..38a05ab --- /dev/null +++ b/docs/ui-lab.md @@ -0,0 +1,182 @@ +# Phokus UI Lab + +Phokus UI Lab is a browser-only development mode for visual work on the real +Phokus frontend. It runs the same `App.tsx`, Zustand store, components, and CSS +as the Tauri app, but installs Tauri JavaScript mocks before the app imports. + +This gives UI agents and contributors a stable browser target without launching +the Rust backend or a Tauri window. + +## Run It + +```bash +pnpm dev:ui +``` + +Then open: + +```text +http://127.0.0.1:1422 +``` + +The script runs Vite in the custom `ui` mode: + +```bash +vite --mode ui --host 127.0.0.1 --port 1422 --strictPort --open +``` + +The normal app development commands are unchanged: + +```bash +pnpm dev:app # Tauri app + Rust backend +pnpm dev:vite # frontend dev server for Tauri dev +``` + +## Scenarios + +UI Lab reads `?scenario=` from the URL. If no scenario is provided, it uses +`rich`. + +| URL | Purpose | +| --- | --- | +| `/?scenario=rich` | Default realistic library with folders, albums, ratings, favorites, tags, images, and videos | +| `/?scenario=empty` | First-run state with no folders or media | +| `/?scenario=busy` | Background workers with pending thumbnail, metadata, embedding, caption, and tagging jobs | +| `/?scenario=duplicates` | Duplicate Finder opened with duplicate groups already available | +| `/?scenario=album` | Gallery opened directly into an album | +| `/?scenario=errors` | Broken thumbnails, folder scan errors, failed embeddings, failed tagging, and metadata issues | +| `/?scenario=huge` | Large mock library for virtualization and layout checks | + +Examples: + +```text +http://127.0.0.1:1422/?scenario=duplicates +http://127.0.0.1:1422/?scenario=errors +http://127.0.0.1:1422/?scenario=huge +``` + +## How It Works + +`src/main.tsx` bootstraps the app asynchronously. In `ui` mode it imports +`src/dev/setupMockTauri.ts` first, then imports the real `App`. + +That order matters because static imports are hoisted. The Tauri mocks must be +installed before `App`, the store, the title bar, or visual components import +Tauri APIs. + +The mock setup installs: + +- `mockWindows("main")` for window APIs used by the title bar +- `mockConvertFileSrc("windows")` as a baseline Tauri file URL mock +- `mockIPC(..., { shouldMockEvents: true })` for `invoke`, `listen`, and `emit` + +The in-memory backend lives in: + +```text +src/dev/mockBackend.ts +src/dev/mockFixtures.ts +src/dev/mockScenarios.ts +src/dev/applyMockScenario.ts +``` + +Happy-path fixture media is generated from existing onboarding images and +website screenshots into: + +```text +public/dev-media/fixture-*.webp +``` + +The pack is deliberately small and deterministic, but varied enough for browser +screenshots: square, portrait, landscape, monochrome, tinted, framed, and +interface-like crops. Synthetic or deliberately broken fixture paths can still +use the `mock://` scheme described below. + +## Mock Media + +Visual components should use `mediaSrc(...)` instead of calling +`convertFileSrc(...)` directly: + +```ts +import { mediaSrc } from "../lib/mediaSrc"; + +const src = mediaSrc(image.thumbnail_path); +``` + +In normal Tauri modes, `mediaSrc` delegates to `convertFileSrc`. In UI Lab, +root-relative asset URLs such as `/dev-media/fixture-01.webp` are returned +as-is. Paths beginning with `mock://` are also served from `public/dev-media`: + +```text +mock://thumb-1.svg -> /dev-media/thumb-1.svg +``` + +Use `mediaSrc` when adding components that render thumbnails, album covers, +preview images, or video posters. + +## Adding A Mock Command + +If the browser console logs an unmocked command, add it to +`src/dev/mockBackend.ts`. + +Prefer returning realistic data for commands that affect visible UI. For actions +that would touch the machine, return a safe no-op: + +```ts +case "open_app_data_folder": + return null; +``` + +When adding fixture data, keep it shaped like the real store/Rust contracts. +Most frontend-facing types are exported from `src/store.ts`, so the mocks can +stay type-checked against the real UI. + +## Adding A Scenario + +1. Add the scenario name to `MockScenario` and `SCENARIOS` in + `src/dev/mockScenarios.ts`. +2. Adjust fixture creation in `src/dev/mockFixtures.ts`. +3. If the scenario should open a specific view, add that behavior in + `src/dev/applyMockScenario.ts`. +4. Visit `http://127.0.0.1:1422/?scenario=your-scenario` and check for console + errors. + +## What UI Lab Is For + +UI Lab is intended for: + +- visual iteration on the real app shell +- layout and responsive checks +- state-heavy views like Explore, Timeline, Duplicates, albums, and Settings +- AI-agent screenshot/browser inspection +- safe UI work without filesystem or Rust-side effects + +It is not a replacement for Tauri app testing. Validate native behavior with +`pnpm dev:app` when touching: + +- file or folder pickers +- filesystem permissions +- real thumbnail/video loading +- window drag, minimize, maximize, or close behavior +- Rust backend performance +- updater installation +- WebView2-specific behavior + +## Verification + +Useful checks after changing UI Lab: + +```bash +pnpm exec tsc --noEmit +pnpm build:vite +pnpm dev:ui +``` + +Then smoke-test at least: + +```text +http://127.0.0.1:1422/?scenario=rich +http://127.0.0.1:1422/?scenario=empty +http://127.0.0.1:1422/?scenario=duplicates +http://127.0.0.1:1422/?scenario=errors +http://127.0.0.1:1422/?scenario=huge +``` diff --git a/package.json b/package.json index fc358f8..d3d03e3 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "clean:app": "cd src-tauri && cargo clean", "dev:app": "tauri dev", "dev:app:cpu": "tauri dev -- --no-default-features", + "dev:ui": "vite --mode ui --host 127.0.0.1 --port 1422 --strictPort --open", "dev:vite": "vite", "dev:web": "cd website && pnpm dev", "format:app": "cd src-tauri && cargo fmt", diff --git a/public/dev-media/fixture-01.webp b/public/dev-media/fixture-01.webp new file mode 100644 index 0000000..cfb5d53 Binary files /dev/null and b/public/dev-media/fixture-01.webp differ diff --git a/public/dev-media/fixture-02.webp b/public/dev-media/fixture-02.webp new file mode 100644 index 0000000..3a3f308 Binary files /dev/null and b/public/dev-media/fixture-02.webp differ diff --git a/public/dev-media/fixture-03.webp b/public/dev-media/fixture-03.webp new file mode 100644 index 0000000..518ab7f Binary files /dev/null and b/public/dev-media/fixture-03.webp differ diff --git a/public/dev-media/fixture-04.webp b/public/dev-media/fixture-04.webp new file mode 100644 index 0000000..456e7a7 Binary files /dev/null and b/public/dev-media/fixture-04.webp differ diff --git a/public/dev-media/fixture-05.webp b/public/dev-media/fixture-05.webp new file mode 100644 index 0000000..b5d78a8 Binary files /dev/null and b/public/dev-media/fixture-05.webp differ diff --git a/public/dev-media/fixture-06.webp b/public/dev-media/fixture-06.webp new file mode 100644 index 0000000..be187f6 Binary files /dev/null and b/public/dev-media/fixture-06.webp differ diff --git a/public/dev-media/fixture-07.webp b/public/dev-media/fixture-07.webp new file mode 100644 index 0000000..d3ef32c Binary files /dev/null and b/public/dev-media/fixture-07.webp differ diff --git a/public/dev-media/fixture-08.webp b/public/dev-media/fixture-08.webp new file mode 100644 index 0000000..86429ba Binary files /dev/null and b/public/dev-media/fixture-08.webp differ diff --git a/public/dev-media/fixture-09.webp b/public/dev-media/fixture-09.webp new file mode 100644 index 0000000..4a53a55 Binary files /dev/null and b/public/dev-media/fixture-09.webp differ diff --git a/public/dev-media/fixture-10.webp b/public/dev-media/fixture-10.webp new file mode 100644 index 0000000..71e062b Binary files /dev/null and b/public/dev-media/fixture-10.webp differ diff --git a/public/dev-media/fixture-11.webp b/public/dev-media/fixture-11.webp new file mode 100644 index 0000000..2dafc30 Binary files /dev/null and b/public/dev-media/fixture-11.webp differ diff --git a/public/dev-media/fixture-12.webp b/public/dev-media/fixture-12.webp new file mode 100644 index 0000000..85d9cbe Binary files /dev/null and b/public/dev-media/fixture-12.webp differ diff --git a/public/dev-media/fixture-13.webp b/public/dev-media/fixture-13.webp new file mode 100644 index 0000000..458acd4 Binary files /dev/null and b/public/dev-media/fixture-13.webp differ diff --git a/public/dev-media/fixture-14.webp b/public/dev-media/fixture-14.webp new file mode 100644 index 0000000..a470726 Binary files /dev/null and b/public/dev-media/fixture-14.webp differ diff --git a/public/dev-media/fixture-15.webp b/public/dev-media/fixture-15.webp new file mode 100644 index 0000000..9b4a39a Binary files /dev/null and b/public/dev-media/fixture-15.webp differ diff --git a/public/dev-media/fixture-16.webp b/public/dev-media/fixture-16.webp new file mode 100644 index 0000000..33818a7 Binary files /dev/null and b/public/dev-media/fixture-16.webp differ diff --git a/public/dev-media/fixture-17.webp b/public/dev-media/fixture-17.webp new file mode 100644 index 0000000..dc7e679 Binary files /dev/null and b/public/dev-media/fixture-17.webp differ diff --git a/public/dev-media/fixture-18.webp b/public/dev-media/fixture-18.webp new file mode 100644 index 0000000..bd0708d Binary files /dev/null and b/public/dev-media/fixture-18.webp differ diff --git a/public/dev-media/fixture-19.webp b/public/dev-media/fixture-19.webp new file mode 100644 index 0000000..176d938 Binary files /dev/null and b/public/dev-media/fixture-19.webp differ diff --git a/public/dev-media/fixture-20.webp b/public/dev-media/fixture-20.webp new file mode 100644 index 0000000..d01d146 Binary files /dev/null and b/public/dev-media/fixture-20.webp differ diff --git a/public/dev-media/fixture-21.webp b/public/dev-media/fixture-21.webp new file mode 100644 index 0000000..c20a2e5 Binary files /dev/null and b/public/dev-media/fixture-21.webp differ diff --git a/public/dev-media/fixture-22.webp b/public/dev-media/fixture-22.webp new file mode 100644 index 0000000..477bdec Binary files /dev/null and b/public/dev-media/fixture-22.webp differ diff --git a/public/dev-media/fixture-23.webp b/public/dev-media/fixture-23.webp new file mode 100644 index 0000000..79f7fac Binary files /dev/null and b/public/dev-media/fixture-23.webp differ diff --git a/public/dev-media/fixture-24.webp b/public/dev-media/fixture-24.webp new file mode 100644 index 0000000..2bb3cdb Binary files /dev/null and b/public/dev-media/fixture-24.webp differ diff --git a/public/dev-media/fixture-25.webp b/public/dev-media/fixture-25.webp new file mode 100644 index 0000000..645fb8b Binary files /dev/null and b/public/dev-media/fixture-25.webp differ diff --git a/public/dev-media/fixture-26.webp b/public/dev-media/fixture-26.webp new file mode 100644 index 0000000..3b4631c Binary files /dev/null and b/public/dev-media/fixture-26.webp differ diff --git a/public/dev-media/fixture-27.webp b/public/dev-media/fixture-27.webp new file mode 100644 index 0000000..8083f34 Binary files /dev/null and b/public/dev-media/fixture-27.webp differ diff --git a/public/dev-media/fixture-28.webp b/public/dev-media/fixture-28.webp new file mode 100644 index 0000000..ac2be2b Binary files /dev/null and b/public/dev-media/fixture-28.webp differ diff --git a/public/dev-media/fixture-29.webp b/public/dev-media/fixture-29.webp new file mode 100644 index 0000000..a4af699 Binary files /dev/null and b/public/dev-media/fixture-29.webp differ diff --git a/public/dev-media/fixture-30.webp b/public/dev-media/fixture-30.webp new file mode 100644 index 0000000..347adb1 Binary files /dev/null and b/public/dev-media/fixture-30.webp differ diff --git a/public/dev-media/fixture-31.webp b/public/dev-media/fixture-31.webp new file mode 100644 index 0000000..e814fbd Binary files /dev/null and b/public/dev-media/fixture-31.webp differ diff --git a/public/dev-media/fixture-32.webp b/public/dev-media/fixture-32.webp new file mode 100644 index 0000000..3f27199 Binary files /dev/null and b/public/dev-media/fixture-32.webp differ diff --git a/public/dev-media/fixture-33.webp b/public/dev-media/fixture-33.webp new file mode 100644 index 0000000..71f00ad Binary files /dev/null and b/public/dev-media/fixture-33.webp differ diff --git a/public/dev-media/fixture-34.webp b/public/dev-media/fixture-34.webp new file mode 100644 index 0000000..42d7a58 Binary files /dev/null and b/public/dev-media/fixture-34.webp differ diff --git a/public/dev-media/fixture-35.webp b/public/dev-media/fixture-35.webp new file mode 100644 index 0000000..aa91feb Binary files /dev/null and b/public/dev-media/fixture-35.webp differ diff --git a/public/dev-media/fixture-36.webp b/public/dev-media/fixture-36.webp new file mode 100644 index 0000000..42bbd28 Binary files /dev/null and b/public/dev-media/fixture-36.webp differ diff --git a/public/dev-media/fixture-37.webp b/public/dev-media/fixture-37.webp new file mode 100644 index 0000000..e3ea474 Binary files /dev/null and b/public/dev-media/fixture-37.webp differ diff --git a/public/dev-media/fixture-38.webp b/public/dev-media/fixture-38.webp new file mode 100644 index 0000000..f098693 Binary files /dev/null and b/public/dev-media/fixture-38.webp differ diff --git a/public/dev-media/fixture-39.webp b/public/dev-media/fixture-39.webp new file mode 100644 index 0000000..8fe6fff Binary files /dev/null and b/public/dev-media/fixture-39.webp differ diff --git a/public/dev-media/fixture-40.webp b/public/dev-media/fixture-40.webp new file mode 100644 index 0000000..645dc2c Binary files /dev/null and b/public/dev-media/fixture-40.webp differ diff --git a/public/dev-media/fixture-41.webp b/public/dev-media/fixture-41.webp new file mode 100644 index 0000000..e32819f Binary files /dev/null and b/public/dev-media/fixture-41.webp differ diff --git a/public/dev-media/fixture-42.webp b/public/dev-media/fixture-42.webp new file mode 100644 index 0000000..614a0c3 Binary files /dev/null and b/public/dev-media/fixture-42.webp differ diff --git a/public/dev-media/fixture-43.webp b/public/dev-media/fixture-43.webp new file mode 100644 index 0000000..1cc52b3 Binary files /dev/null and b/public/dev-media/fixture-43.webp differ diff --git a/public/dev-media/fixture-44.webp b/public/dev-media/fixture-44.webp new file mode 100644 index 0000000..08cc36f Binary files /dev/null and b/public/dev-media/fixture-44.webp differ diff --git a/public/dev-media/fixture-45.webp b/public/dev-media/fixture-45.webp new file mode 100644 index 0000000..5618a23 Binary files /dev/null and b/public/dev-media/fixture-45.webp differ diff --git a/public/dev-media/fixture-46.webp b/public/dev-media/fixture-46.webp new file mode 100644 index 0000000..3a9a298 Binary files /dev/null and b/public/dev-media/fixture-46.webp differ diff --git a/public/dev-media/fixture-47.webp b/public/dev-media/fixture-47.webp new file mode 100644 index 0000000..60733e8 Binary files /dev/null and b/public/dev-media/fixture-47.webp differ diff --git a/public/dev-media/fixture-48.webp b/public/dev-media/fixture-48.webp new file mode 100644 index 0000000..4d255cc Binary files /dev/null and b/public/dev-media/fixture-48.webp differ diff --git a/src/App.tsx b/src/App.tsx index 3dfe52f..75de078 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -48,12 +48,16 @@ export default function App() { if (import.meta.env.PROD) { void checkForUpdates({ quiet: true }); } - loadFolders().then(() => { + loadFolders().then(async () => { void loadBackgroundJobProgress(); void loadCaptionModelStatus(); void loadDuplicateScanCache(); - void loadAlbums(); - return loadImages(true); + await loadAlbums(); + await loadImages(true); + if (import.meta.env.MODE === "ui") { + const { applyMockScenario } = await import("./dev/applyMockScenario"); + applyMockScenario(); + } }); let unlisten: (() => void) | undefined; subscribeToProgress().then((fn) => { diff --git a/src/components/DuplicateFinder.tsx b/src/components/DuplicateFinder.tsx index f7fe36e..c0e006d 100644 --- a/src/components/DuplicateFinder.tsx +++ b/src/components/DuplicateFinder.tsx @@ -1,8 +1,8 @@ import { useRef, useState } from "react"; import { useVirtualizer } from "@tanstack/react-virtual"; -import { convertFileSrc } from "@tauri-apps/api/core"; import { DuplicateGroup, useGalleryStore } from "../store"; import { FolderScopeDropdown } from "./FolderScopeDropdown"; +import { mediaSrc } from "../lib/mediaSrc"; function formatBytes(bytes: number): string { if (bytes >= 1_073_741_824) return `${(bytes / 1_073_741_824).toFixed(1)} GB`; @@ -68,7 +68,7 @@ function DuplicateGroupCard({ group }: { group: DuplicateGroup }) {
{group.images.map((image) => { const isSelected = selectedIds.has(image.id); - const src = image.thumbnail_path ? convertFileSrc(image.thumbnail_path) : null; + const src = mediaSrc(image.thumbnail_path); return (