Components¶
Picturesque is a Cargo workspace plus a Svelte front end. Dependencies point inward toward the domain (ADR-0003).
flowchart TB
ui[ui/<br/>Svelte 5 + TypeScript]
desktop[picturesque-desktop<br/>Tauri 2 shell]
devserver[picturesque-devserver<br/>HTTP, development only]
infra[picturesque-infra<br/>SQLite, decoding, ort, geocoder, XMP, filesystem]
app[picturesque-app<br/>use cases + ports]
core[picturesque-core<br/>domain]
ui -- Tauri IPC --> desktop
ui -. HTTP .-> devserver
desktop --> app
devserver --> app
desktop --> infra
devserver --> infra
infra --> app
app --> core
infra --> core
Crates¶
picturesque-coreThe domain, with no I/O. Photo and metadata types, content-hash identity, path-template rendering and filename sanitization, series grouping, score combination and best-in-series ranking. Depends only on small utility crates.
picturesque-appUse cases (import, analyze, cull, export, settings) and the ports they need, defined as traits where they are consumed: catalog repositories, file system, metadata reader, image decoder, inference engine, geocoder, sidecar writer, clock. Owns the command and event types exposed to the UI, so both transports share one definition. Depends on
core.picturesque-infraImplementations of the ports: SQLite catalog (
rusqlite), image decoding (ADR-0005), ONNX inference (ort), offline geocoder, XMP reader and writer, file system and hashing. Native libraries and their types stay inside this crate.picturesque-desktopThe Tauri 2 application. Builds the infrastructure, wires it into the use cases, and exposes app commands as Tauri commands and events. Serves images through a custom protocol. Contains no business logic.
picturesque-devserverA development-only HTTP server exposing the same commands, events and image routes, so the real UI can run in a browser where no display is available (ADR-0010). Not shipped to users.
Front end¶
ui/Svelte 5 and TypeScript, built with Vite. An
apimodule is the only code that knows how commands reach the backend; it selects Tauri IPC inside the desktop app and HTTP otherwise. Views: timeline grid with series frames, loupe, compare, filters; activity (import folders, per-photo progress, log); settings.
Command API¶
The UI calls the backend by command name with JSON arguments.
picturesque_app::api::Api::call dispatches every command, so both
transports are thin adapters around one function:
The desktop shell registers a single Tauri command,
api, takingcommandandargs.The development server exposes
POST /api/commands/{name}(Running the UI in a Browser).
Api::call is synchronous; adapters run it on a blocking thread, so use
cases can do file and database I/O without depending on an async runtime.
Failures are an ApiError with a code (unknown_command,
invalid_arguments, internal) and a message.
Types that cross the boundary derive ts_rs::TS. Running the Rust tests
writes TypeScript definitions to ui/src/lib/api/generated/, which are
committed; make bindings-check fails when they are out of date. The UI
never hand-writes these types.
Boundaries¶
corenever depends onapp,infraor any framework.appdepends oncoreand on its own port traits; it never names a concrete database, decoder or model runtime.Types from
rusqlite,ort, libvips, LibRaw and Tauri do not appear inapporcoresignatures.The desktop shell and the dev server are thin: a command handler converts arguments, calls a use case, and converts the result.
Crate dependencies enforce the first two rules at compile time; the rest are enforced in review.
Background work¶
Long-running work (import, thumbnail generation, analysis) runs as jobs owned by the application layer. Jobs report progress through events, can be cancelled, and record enough state in the catalog to resume after a restart. CPU-heavy steps run on a bounded worker pool so the UI stays responsive.
Importing is a persistent queue of photos found in watched folders, processed one at a time; each photo is catalogued as soon as it is copied (ADR-0012).
Photo analysis (scores, hashes, AI models) is a queue of analyzers run over every photo’s preview, with results stored per analyzer version; photos on screen go first (ADR-0011).