Commit Graph

7 Commits

Author SHA1 Message Date
Brandon Thomas b718d9248a M10.9: Export with Previous (⌥⇧⌘E), export dialog remembers the last options (prefs.json); Show in Finder (⌘R); New Folder… (⇧⌘N)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:10:46 -04:00
Brandon Thomas 9886d49823 Merge branch 'worktree-agent-a69dcec0f0c61bc39'
# Conflicts:
#	crates/engine/src/media.rs
#	crates/ui-egui/src/render.rs
2026-10-01 09:39:30 -04:00
Brandon Thomas 5fd5384bfb M5.4a: instant first view — cached view render / embedded raw preview, then the full render
- engine: QuickJob — what to show the moment a photo opens: the loupe's last render for the
  current settings (kept by Session::loupe_job under a size-independent "view" key in the
  preview cache, memory + disk with a library; the disk JPEG is written off the render
  thread via PreviewCache::put_deferred), else the embedded camera JPEG of a raw that still
  has its import look (import::has_import_look), else a thumbnail-level render.
- Grid / filmstrip: an unedited raw without a thumbnail shows its cached thumbnail, else its
  embedded preview (Slot::ThumbQuick) at once; the real rendered thumbnail follows at
  background priority (same cache keys, so it still ends up matching the develop render;
  edited photos render directly).
- Loupe (detail, compare, filmstrip selection): Slot::Preview holds the stand-in until
  Slot::Main has the photo; the full render then replaces it in place (no blank frame).
- files::embedded_preview_srgb + PreviewLoader hook (desktop/CLI/MCP and web).
- ui.inspect → loupe {photo, source: render|cached|embedded|small|thumb|none, pending}.

Real app (24 MP raws, loaded machine): first image 15–90 ms (thumbnail, then embedded
preview), full render 0.2–0.5 s (was 1.0–2.4 s with nothing shown before it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:18:50 -04:00
Brandon Thomas 8f2058a124 M0.13: web render workers — pipeline renders off the main thread
Renders run in dedicated module Web Workers, each a second instance of the app's wasm module
(index.html compiles it once and posts the WebAssembly.Module to worker.js). A job crosses as
JSON (WireJob: source, settings, request); the worker reads originals and cached thumbnails from
browser storage itself, keeps decoded sources and per-view stage caches, and transfers RGBA back.
Jobs follow the worker that decoded the photo; the loupe waits for it. No wasm threads or
SharedArrayBuffer needed; falls back to inline rendering if no worker starts (?workers=0).

Thumbnail disk cache on the web: thumbs/<key>.jpg in OPFS/IndexedDB with an index on the main
thread, pruned to the desktop budget (2 GB → 80 %, LRU).
ui-egui: RenderOffload hook in the Renderer (queue/priorities/dedup stay in the UI).
engine: RenderJob.origin + MediaCache::origin_ref; preview: encode_jpeg/decode_jpeg public.
xtask web: copies worker.js; --serve sends COOP/COEP/CORP (documented in docs/web.md).

Headless Chrome, cold load + grid scroll, 24 demo photos: main-thread busy 12.4 s → 0.7–1.1 s,
long tasks 23 (max 1.1 s) → 0; grid filled in 6.3–8.4 s vs 13–16 s; slider job 4 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:36:15 -04:00
Brandon Thomas baa30ca590 M0.12: web persistence — library, edits and imported originals survive a reload
The catalog Store on the web is an in-memory mirror (apps/lightcraft-web/src/files.rs) loaded
from browser storage at start-up and flushed in the background in modification order (so a
snapshot always lands before the log reset that follows it). Storage is OPFS when writable from
the main thread (createWritable), else IndexedDB (?store=idb forces it); ?reset clears it.
Imported originals are stored as originals/<content hash> and referenced as
web/<hash>/<name>; the main thread keeps a bounded memory cache and loads misses (and the
active photo) asynchronously. view.json and ui.json are saved every second when changed.

Engine: Session::open_library_in(LibraryStores) opens a library from any Store (presets, view
and prefs go through it too); Session::save_view; Copy imports into a non-disk library add.
JS automation API: lightcraft.command(id, paramsJson) (+ web.stats).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:18:01 -04:00
Brandon Thomas 3ef4eb0687 M9.2: preset files — .lcpreset import/export (groups preserved), XMP presets via crs: mapping
- engine presets.rs: .lcpreset JSON format {format: "lightcraft.preset", version, presets[]},
  parse_preset_file (.lcpreset file / bare preset / array, or XMP), folder expansion,
  Session::add_presets (identical presets skipped, id clashes get fresh user.* ids, built-ins
  never replaced).
- crs::preset_from_xmp: crs: fields → partial settings, crs:Name/crs:Group (fallback: file name /
  "Imported Presets").
- Commands preset.import {paths} and preset.export {path, ids?, group?}; UI commands
  file.importPresets / file.exportPresets (File menu, with native open/save dialogs on desktop),
  Presets panel "⋯" menu (Import Presets…, Export User Presets…) and group context menu
  (Export Group…).
- Tests: export → import round trip keeps groups/settings, duplicates skipped, clashing ids,
  malformed files reported, hand-written XMP preset imports and applies.
- docs/xmp-interop.md: preset file section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:05:26 -04:00
Brandon Thomas 2d2bc2ad8f M0.11: web build (WASM)
apps/lightcraft-web runs the egui UI in the browser (eframe web, glow/WebGL2):
procedural demo library on load; in-memory imports via file picker
(Cmd+Shift+I) and drag-and-drop (MemStore + bytes-based engine hooks);
export goes through app.export and downloads in the browser; `?bench` logs
first-frame and slider latency. `cargo xtask web [--serve [port]] [--dev]`
builds with the matching wasm-bindgen CLI into <target>/web/; the web app is
part of the CI wasm check. Wasm fixes: lazy profiling Instant in pipeline,
web-time clocks in ui-egui (render timings were 0 on wasm). Docs: docs/web.md.
No persistence on web yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:01:18 -04:00