Reveal
Reveal is a personal native macOS photo workflow app designed around one loop: ingest, cull, develop, and publish to Obsidian/Garden with minimal friction.
What it is
- A menu-bar + window app for high-volume photo sessions.
- A file-first system: originals stay untouched, metadata stays in sidecars, and the app index is disposable.
- A stage-based architecture: Ingest -> Develop -> Print, with plugin seams for each stage.
Core rules
- Archive layout is
YYYY/YY-MM-DD/, based on capture date. - Keep original filenames on import and export.
- The folder on disk is the source of truth, not app-internal state.
Current capabilities
- Import from mounted cards to configured photo locations.
- Connect directly to the macOS System Photo Library through Apple Photos: browse albums, edit non-destructively in Reveal, and export without a manual import or copying the library into the archive.
- Browse and cull with grid/masonry, loupe, rating, and quick collection tools; layout mode (uniform vs masonry) and margin scale are cleanly wired through CullView to PhotoGrid.
- Develop with per-photo settings and engine-driven rendering.
- Export to desktop, Obsidian daily note, or story note flows.
- Story mode with per-folder markdown composition and photo inclusion toggles.
- Thumbnail loading now falls back to a lightweight develop preview when an embedded RAW JPEG thumbnail is unavailable, so grid cells do not stay blank.
- The animated grain layer is disabled in the cull/story UI, and window-controls offsets are exposed as CSS tokens for quick adjustment.
- The app shell now consumes Standard semantic tokens directly (
--color-*,--shadow-*) instead of local palette aliases. - Native macOS traffic lights are now restyled on show/resize: explicit top-left offset plus subdued idle tint.
- Thumbnail protocol accepts embedded JPEG and bitmap previews, with a developed JPEG fallback.
- The contact sheet uses an app-specific grid class and explicit row flow. This prevents Standard’s global
.gridutility from forcing every photo into one horizontal row, which previously left only the first few photos visible with the remaining story controls extending off-screen. - Reveal remembers the last browsed directory and reopens there in Grid mode on launch; startup uses the saved path even when a slow or temporarily unavailable network volume has not returned it in the sidebar index, and the previously active Develop or Story mode never hijacks startup.
- Reveal registers JPG/JPEG as macOS viewer file types. Opening one from Finder or another app routes it directly to the single-photo viewer, while the traffic-light controls remain transparent until the pointer enters their control group.
- Single-photo file opening reports sidecar or recipe-load failures in the app instead of leaving an unhandled IPC rejection during a reload or transient filesystem interruption.
- Tauri event-listener registration tolerates Vite replacing the development webview before Rust returns callback IDs, preventing harmless single-photo hot-reload races from appearing as unhandled rejections.
- Temporary NFS directory-read failures are reported as a recoverable folder-tree diagnostic instead of unhandled promise rejections; grid geometry diagnostics stay out of the error channel.
- Grid cells request visible thumbnails eagerly; virtualization limits the mounted request set.
- Develop mode shows the existing
.reveal.jpgor embedded camera preview immediately while RAW development continues. Developed results are cached locally by source file and recipe, the latest render becomes the grid’s.reveal.jpg, and its versioned thumbnail URL refreshes after every completed edit. Mode switching no longer schedules the same initial render twice. - Grid selection follows desktop conventions: plain click replaces the selection, Command-click toggles individual photos, Shift-click selects a range, and Command-Shift-click adds a range.
Ccopies the active photo’s development recipe andVapplies it to the selection while refreshing each developed grid preview. Dalways enters the active photo’s single-image Develop workspace, reopens the development panel, and restores Develop’s remembered focus-mode preference.Greturns to the contact sheet and closes the development panel without clearing that preference; once already in Grid mode,Gtoggles the folder sidebar.- The development engine is explicitly
Nonewhen a photo has no saved engine settings. In that state the grid ignores developed caches and shows the camera JPEG or RAW preview; selectingSpektraor changing a control creates development settings, while selectingNoneclears them without removing ratings, captions, or tags. - The sidebar brand controls cancel the floating card’s top inset, keeping their window-chrome baseline identical to the collapsed-sidebar rail.
- Development-panel controls use unified segmented tab navigation with Phosphor icons, sleek collapsible group headers, fixed-width tabular numeric values, and custom toggle switches without layout overlaps.
- The Develop panel has no native traffic lights and uses macOS utility-window behavior: it stays available while moving between Reveal windows, hides when the application loses focus, and returns when Reveal becomes active again. Its left-aligned close control hides only the panel, leaving the single-photo Develop workspace active; its native lifecycle has explicit show/close capabilities.
- Develop INFO reads the original RAW pixel dimensions from camera metadata without triggering a full image decode.
- The main window controls rest as faint monochrome circles in Grid and Story modes and switch to macOS red/yellow/green only while the pointer is directly over an individual circle. They disappear entirely in the single-photo Develop mode; their actions remain native, while the original AppKit buttons stay hidden to prevent titlebar-wide color activation.
- Focus mode now matches the Swift app’s native backdrop exactly: full-strength macOS HUD blur, complete desaturation, a uniform 10% black wash, and all-screen coverage dynamically ordered below the lowest visible Reveal window. The Develop panel is elevated to floating utility-window level, keeping all app chrome and secondary palettes above the backdrop. Global shortcuts (including O for focus mode) and pointer presence remain responsive across all windows, with a 220 ms native debounce preventing flickers while moving between windows.
Fpresents the selected photo in native full-screen without changing the active workflow mode. It shows an immediate preview, upgrades developed photos to a 2560 px render, temporarily hides the Develop panel, supports arrow navigation and0-5ratings, and exits throughF,G, Space, Escape, or a click.- Loupe inspection now stages the camera/developed preview without leaving Grid, displays EXIF and decoded-preview dimensions, toggles to 100% with a click, and supports constrained pointer panning. Space opens or closes Loupe; Return starts or stops its 3.2-second slideshow, and Shift-Return does the same in Full Screen.
- The REVEAL wordmark owns keyboard-shortcut discovery, while the sidebar gear and
Command-,open a dedicated macOS Inset-Grouped settings panel with interactive toggles, smart folder badges, date presets, and AI culling configurations. - The photo grid features high-contrast focus rings and subtle print-mat elevation on keyboard navigation, the sidebar tree uses clean vertical import indicators and high-contrast active rows, and the top rail provides live star filter badges and quick layout controls.
- Reveal installs native macOS Reveal, Edit, Import, Develop, Export, and Window menus. Their commands dispatch into the same workflow state as the in-window controls; unavailable Swift-only operations remain visibly disabled rather than failing silently.
- The single-photo Develop canvas mirrors Swift’s smart whitespace: compact windows let the photo nearly fill the available area, then the surrounding mat grows continuously until it reaches a golden-ratio frame on large canvases. The mat owns the size constraint and the image only owns its intrinsic aspect ratio, so
padding: var(--space)remains equal on all four sides for landscape and portrait photos. - Viewer photos use the simple
img-inside-divmodel: the1.5 × --radiusphoto mat supplies exactly one--spaceof padding on every side without a visible outer border or shadow, while Standard’s semantic--shadow-insetadds a subtle printed-surface impression inside the image. The mat remains ready for a future Polaroid-like caption. - Actions in the top rail, including library indexing, card import, publishing, and export, use the same quiet monospace treatment as the FRAMES count instead of appearing as rounded buttons.
- The empty canvas, grid background, and top rail act as native drag regions for moving the Tauri window, while photo cells, menus, forms, and other interactive surfaces explicitly remain clickable.
- Window dragging uses Tauri’s native
startDraggingpermission and API on empty space and grid background between photos, while delegated pointer handling excludes the sidebar, photo cells, menus, forms, and controls. - Grid photos provide the Swift-style right-click menu for Finder reveal, Preview opening, installed external editors, collection membership, desktop development, and vault publishing. Development actions remain unavailable until the photo has an active engine.
- Folder and catalogue rows provide native-feeling context actions for Finder and scoped reindexing. Reindexing a folder reconciles only that subtree and preserves the active catalogue root.
- Hovering the folder control while the sidebar is hidden reveals a temporary folder-browser card; a short dismissal delay lets the pointer cross into it, while clicking the control still pins the full sidebar.
- The render queue records each export as one coherent job with current photo, aggregate progress, completion or failure state, and cancellation. Cancellation stops safely between photos so an in-flight render can finish without corrupting its output.
- The Rapid (Digital) engine now offers dual tone-mapping paths: AgX Filmic (photographic rendering) for natural film-like color and contrast, or LogC Cinematic (log encoding) for neutral, cinema-style rendering. The “LogC” toggle in the engine controls switches between these workflows — when enabled, LogC encoding replaces AgX tone mapping for a flatter, more controllable tonal response suitable for grading and LUT stacking. This enables workflows like: Raw → exposure/contrast/white balance → apply pre-LUT (e.g., TheBRIM) → toggle LogC → apply post-LUT (e.g., Film Unlimited 2383) → finalize.
- Obsidian integration is modular and optional via
obsidian_enabledin preferences. When disabled, Obsidian vault paths and daily-note actions stay hidden from the interface and context menus to keep the workflow clean for users who only need traditional photo exports; when enabled, it connects to the configured vault (defaulting to~/Documents/Atelier) and safely verifies directory existence before daily note writes.
Build & install
./install.sh— rebuilds and deploys to~/Applications/Reveal.app(bundle idco.utopie.reveal, deep-link schemereveal://). Run this to actually see frontend, icon, tray and window-chrome changes: they are baked at bundle time (or gated behindisTauri), so the installed app keeps showing the previous build until you re-run it. Always trigger./install.shautomatically after making changes to the app so the live installation is immediately updated. It refuses to build while an instance is running — bundling under a live app leaves you with white pages, and stale instances stack pixel-perfect and swallow clicks. No launch-at-login agent is registered.- The Swift app (
Sources/,Package.swift,engine/— read-only, this rewrite doesn’t touch them) and itsinstall-app.sh/LaunchAgent (co.utopie.reveal) have been retired now that this Tauri rewrite has taken over the name, bundle id and Garden deep-link. The Swift source is left in place in case it’s ever needed again, but it’s no longer installed. install.shbundlesreveal-decode‘s native dependencies (libraw, lcms2, and their own runtime deps — libjpeg, libomp) intoContents/libs/viadylibbundler(brew install dylibbundler) and rewrites the executable’s link paths to@executable_path/../libs/.... Without this the binary dynamically links absolute/opt/homebrew/opt/...paths and only runs on a Mac with the exact same Homebrew libraries installed — confirmed 2026-08-03 viaotool -Lduring a portability audit. Ifdylibbundlerisn’t installed, the step is skipped with a warning and the build stays dev-machine-only.- The active Tauri build compiles the isolated PhotoKit bridge with the Xcode command-line tools and links Apple’s system frameworks. Info.plist supplies the photo-library permission explanation. The retired Swift app is not involved.
Connected Apple Photos library (macOS)
Apple Photos is another catalogue in the existing sidebar, alongside filesystem libraries such as ffp-production. It uses the same catalogue headers, directory rows, disclosure triangles, selection styling, and small refresh icon—not a source panel, album dropdown, or separate interface. Click its name to browse all visible still photos, or expand it to connect without changing the current grid. macOS permission is requested on that first interaction, never by the initial availability check or automatic source restoration. There is no import step, archive destination, or full-library copy. The connector uses PhotoKit’s System Photo Library, not arbitrary .photoslibrary packages; it never reads Photos’ private database or requests library mutations.
- User folders and nested albums retain their PhotoKit hierarchy and ordering through
PHCollection.fetchTopLevelUserCollectionsand recursivefetchCollectionsInCollectionList. Public still-photo smart albums appear alongside that tree; Hidden, Recently Deleted/private types, and video-only smart albums are excluded. Empty user folders/albums remain visible. Clicking a folder browses its descendant albums with assets deduplicated by PhotoKit identifier. These read-only rows cannot receive file drops, rename/move operations, or filesystem context actions. - Browsing loads frame metadata in pages of 200 with Load more photos in the top rail, and fetches previews only for displayed frames. Folder queries gather descendant asset identifiers to deduplicate membership before paging. Date ordering applies to the library query; rating filters and selection/export operate on loaded frames. The catalogue’s small Refresh Apple Photos icon updates the tree/counts and, when active, the current grid. Disclosure state uses stable collection IDs, survives refresh/renaming and normal/peek sidebar remounts, and honors explicit collapse even around the active album. The last selected source and collection reopen on launch when already authorized; removed collections fall back to the catalogue root. The sidebar is also available without a filesystem catalogue.
- Refreshing or reordering the same collection preserves the rating filter, chosen grid layout, multi-selection and focused asset by PhotoKit ID, the active Develop photo/recipe, and the current workflow mode. Metadata paging retains the previously loaded range and continues as needed to find selected assets after reordering; it does not pre-download originals. Removed or newly filtered assets leave the selection safely. Refreshing alone does not pull a manually scrolled grid back to its selection, and grid remounts restore the viewport; changed focus stays visible in the virtual grid. Navigating to another collection may reset filter/selection/scroll, but does not overwrite the chosen layout.
- Opening a frame shows a larger PhotoKit preview. Developing or exporting transparently retrieves its original resource, including an iCloud download if needed. A visible transfer indicator supports cancelling the active download; failures report an error and can be retried. Unavailable/deleted assets and denied permissions do not fall back to modifying or importing another file.
- RAW resources (including RAW+JPEG pairs) use the supported original RAW when available. HEIC, JPEG, PNG, and TIFF originals are converted by ImageIO into an oriented, color-managed 8-bit sRGB TIFF working image, then decoded into linear RGB for Reveal’s engines. This first connector is an SDR still-photo workflow: it does not preserve HDR gain maps, Live Photo motion, or Apple’s existing adjustment stack.
- Asset references use stable PhotoKit identifiers (
apple-photos://...), not filenames. Ratings, captions, and recipes are atomically stored as XMP under Reveal’s application-dataapple-photos/edits/<asset-id>/directory, separate from downloaded originals and thumbnails in the application cache’sapple-photos/directory. Duplicate filenames and album membership do not merge edits. Back up application data to retain these edits; removing the application cache while Reveal is closed does not remove them. Working copies are downloaded only when used and retained for reuse, not automatically mirrored for the whole library. - Grid, Loupe, Full Screen, the separate Develop panel, recipe copy/paste, JPEG exports, and daily-note exports use the connected source. Original export names are retained where available; collisions receive a numbered suffix rather than overwriting another Apple Photos export. Photos itself remains unchanged, and Reveal’s adjustments are not synchronized back to Apple.
- Finder/external-editor actions, filesystem moves, folder-based Story collections, and AI culling are unavailable for connected assets. Export first when a workflow needs ordinary files. Videos and hidden assets are excluded. Access is controlled by System Settings > Privacy & Security > Photos; a denied request can be retried after changing that permission.
Apple Photos cache controls
On macOS, Settings includes cache usage, the saved cache limit, the amount currently in use, and a Clear cached copies… action. The preferences key apple_photos_cache_limit_gib is a whole number from 1 through 64 GiB, default 4 GiB. Changing the field is a draft until Settings is saved through save_preferences; invalid values reject before saving, and accepted changes apply the limit and schedule safe pruning. Usage and clear-operation results are derived state and are never written into the preferences bag.
apple_photos_cache_statusreturns{size_bytes, limit_bytes, in_use_bytes}.apple_photos_cache_clearreturns{removed_bytes, remaining_bytes, protected_bytes}.- Both commands are asynchronous and return an error string on failure. The frontend only enables the section when the existing Apple Photos availability check reports support; reading usage and clearing cache do not request PhotoKit permission.
- Clearing requires the shared
@stnd/ui/AlertDialogconfirmation. Cancel/Escape performs no clear and does not save settings; confirmation keys cannot activate the Settings save shortcut underneath it. Clearing removes disposable working copies and cached previews, not durable XMP recipes, ratings, captions, or saved developed previews in application data. Originals may need downloading again. Busy files are explicitly skipped and reported inprotected_bytes, with a retry suggested after active work finishes. - Read, clear, validation and save errors stay visible in shared
Alertsurfaces without closing Settings or silently saving preferences. Cache refresh/clear buttons are disabled during active operations. If clearing succeeds but the follow-up status request fails, the UI distinguishes those outcomes.
Automatic eviction is least-recently-used at source-revision directory and per-asset previews directory granularity, using durable last-used markers. Cleanup runs at launch and limit changes; releases coalesce cleanup for approximately 500 ms. Native source access holds an RAII cache lease through the entire decode, render, or export operation. Thumbnails remain pinned through their native write and subsequent file read. In-use files are protected from both automatic eviction and explicit clearing, so usage may temporarily exceed the configured limit while work is active. Background cleanup failures emit the existing app-error event; explicit status and clear failures reject their commands. Cache cleanup never touches durable application-data edits or saved developed previews.
Shared interactive UI contract
Reveal imports stateful primitives through the public @stnd/ui/*.svelte subpaths. Ordinary buttons, inputs, native selects, cards, and photo-specific layout controls remain semantic HTML styled with @stnd/styles and the Reveal theme; there is no shared Button component.
- Photo and folder context actions:
ContextMenu,ContextMenuItem,ContextMenuItemCheckbox, labels, and separators own viewport measurement, overflow, disabled items, keyboard navigation, dismissal, and focus. The localmodules/menus/ContextMenu.sveltecontains only photo action composition and callbacks. Installed editors are an accessible Open with section in the same scrollable menu, replacing the hover-only submenu. Ratings are checkable menu rows. Delegated grid/folder handlers only select the target and supply pointer coordinates. - Rating/sort and date-folder presets:
Dropdownand its items own opening, focus, keyboard navigation, and item dismissal. Grid layout and Garden account:Popoverowns the anchored form/control surface, outside-click/keyboard dismissal, and trigger semantics. Grid geometry, account verification, and settings values remain Reveal state. - Settings, keyboard shortcuts, and catalogue notes:
Dialogprovides a native modal dialog, backdrop dismissal, Escape, background inertness, and focus restoration. Settings retains only its save shortcut and form content. The app-wide photo shortcut handler does not compete with shared menus/dialogs. Native window drag dispatch excludes dialog/menu surfaces. - Filesystem tree identity: expansion, manual collapse and ancestor discovery use full catalogue-qualified paths, never relative folder names. The
.v2sidebar expansion keys intentionally start fresh for this pre-release change; two roots containingShared/2026no longer affect one another. Explicit collapses persist across refresh, remount and restart, including when the selected folder is a descendant of a secondary catalogue. - Keyboard actions: story open, pin and unpin controls are separate native buttons inside semantic list items, with no bubbling row activation. Development reset labels are compact native buttons: Enter/Space or assistive activation resets the control, pointer double-click retains the existing convention, and a single pointer click does not reset. Disabled development controls are also disabled for keyboard users.
- Apple Photos transfer feedback, account errors, and render failures:
Alertprovides the common banner; Reveal supplies live-region semantics and cancel/retry/dismiss handlers. Apple Photos browsing itself uses the existing catalogue/tree renderer, with no source widget, select, or Combobox. - Intentionally local: the nonmodal render-queue monitor remains an app-owned floating surface so rendering never blocks photo work or steals focus. Shared
Panelis an independently pinned screen-edge drawer, not a replacement for that monitor, the folder browser, or native Develop/import webviews. SharedScrolleris a document reading-progress bar, not a scroll-container primitive. Photo grids, RAW/development controls, folder trees, story composition, native menus, traffic lights, and IPC remain app-owned. PhotoKit access, direct develop/export, metadata-by-asset-ID, and the separate cache are unchanged.
UI validation
Run pnpm --dir apps/reveal check and pnpm --dir apps/reveal build from the repository root. Unit tests run with node --test packages/ui/menu.test.js apps/reveal/tests/applePhotosTree.test.mjs apps/reveal/tests/applePhotosBrowsing.test.mjs. With pnpm --dir apps/reveal dev running, node --test apps/reveal/tests/ui.test.mjs exercises the real browser shell plus synthetic photo/folder/settings/queue fixtures: catalogue connection, nested selection, independent filesystem expansion, read-only rows, refresh/rename identity, paging cancellation, scroll/remount preservation, story/reset keyboard controls, and cache confirmation/validation/error paths. Cache fixtures record the expected command names through injected callbacks and assert that usage/clear does not call save_preferences or persist usage fields. The suite requires an existing Playwright installation and Chrome; REVEAL_PLAYWRIGHT_MODULE can point to its index.mjs when it is not on Reveal’s module path (for the repository’s pnpm store: $PWD/node_modules/.pnpm/node_modules/@playwright/test/index.mjs). The fixtures are test-served HTML, not production routes, and never install a Tauri bridge, request Photos permissions, read user images, or perform native IPC. Browser validation does not replace the required ./install.sh native rebuild.
On macOS, xcrun clang -fobjc-arc -fblocks -mmacosx-version-min=11.0 apps/reveal/tests/apple_photos.test.m -framework Photos -framework AppKit -framework ImageIO -framework CoreGraphics -o /tmp/reveal-photos-test && /tmp/reveal-photos-test validates the actual native traversal against synthetic collections. This isolated test process replaces its collection/asset fetch methods before traversal; it never queries a real library. Coverage includes nested folders, duplicate titles, empty albums, deduplicated folder membership, still/hidden filtering, and the smart-album allowlist. Run the Rust library regression suite with cargo test --manifest-path apps/reveal/src-tauri/Cargo.toml -p reveal --lib.
Giving this to someone else
Portability audit, 2026-08-03 — current state and what’s still open:
- Architecture: Apple Silicon only.
install.sh/pnpm tauri buildproduce an arm64-only binary on this machine. It will not launch on an Intel Mac (no error, just won’t run). A universal build (pnpm tauri build --target universal-apple-darwin) needsrustup target add x86_64-apple-darwinplus an x86_64 build of libraw available topkg-config(this Mac’s Homebrew is arm64-only) — not yet set up. - Not code-signed or notarized. The build is only ad-hoc signed (the default the linker/dylibbundler apply automatically) — no Apple Developer ID, no notarization. Any transfer that sets the macOS quarantine flag (AirDrop, a download link, Messages) will trigger Gatekeeper’s “cannot verify this software is free of malware” warning. To open it anyway, the recipient should:
- Try right-click (or Control-click) the app → Open → Open in the dialog.
- If that’s not offered, or macOS still blocks it: System Settings → Privacy & Security, scroll to the Security section, and click Open Anyway next to the Reveal warning — then confirm once more when relaunching.
- The proper fix (no more warnings for anyone) requires an Apple Developer ID ($99/yr): sign with
codesign --sign "Developer ID Application: ...", notarize viaxcrun notarytool submit, and staple the ticket. Not done yet — ad-hoc only, by choice, until that’s worth setting up.
- First launch can use a folder or Apple Photos. A fresh install has no filesystem catalogue root. The empty contact sheet offers “Ajouter un dossier” (also Import → Add Library Folder…), while macOS users can connect the Apple Photos catalogue directly from the sidebar without adding a folder.
- Distribution mechanism:
bundle.targetsnow includes"dmg"(2026-08-07) —pnpm tauri buildproduces bothReveal.appand a.dmgundersrc-tauri/target/release/bundle/, no manual zipping needed. Still ad-hoc signed only (see above), still arm64-only, and there’s no CI release pipeline yet — each.dmgis built and uploaded by hand. - Checked, fine as-is: all bundled assets (LUTs, film profiles —
data/) resolve viaresource_dir()and travel with the bundle; everystd::process::Commandshells out to stock macOS binaries only (osascript,open,mdfind,diskutil); AI cull defaults off (ai_cull_enabled→false) so a new user won’t trigger unexpected API calls; the Garden/vault path defaults to~/Documents/Atelierper-user, not hardcoded.
Architecture (short)
Index(SQLite) is a derived cache and can be rebuilt at any time.Contact Sheetreads from the index and drives browsing performance.- Editing/develop settings are persisted to sidecars and mirrored to index fields for speed.
- Story content is stored in folder-local markdown (
<date>.md) with frontmatter + body.
Product direction
- Keep the daily workflow fast and simple.
- Keep deep technical controls available but out of the way.
- Keep plugin seams clean so engines can evolve without rewriting the app shell.
Roadmap (active)
- Remember scroll position per folder.
- Add
qto add/remove from quick collection and expose it in filters. (In the Tauri port:qtoggles the frame in the story/quick-collection, with a “Collection Rapide” filter in the star menu and an Add/Remove context action.) - Improve export border rendering to a cleaner paper-white finish with subtle paper texture/grain. (
reveal_engine::paper_border— a warm fine-art white with a faint, deterministic per-pixel grain instead of clinical #FFFFFF;PAPER_TINT/GRAIN_AMPare tunable, andcargo run -p reveal-engine --example borderpreviews the matte on a synthetic photo.) - Continue moving remaining blocking I/O paths off the main thread. (Architecturally handled in the port: every filesystem/decode command runs in Tauri
spawn_blocking, off the webview thread.) - Unify keyboard dispatch ownership to reduce duplicated event handling. (The port routes every shortcut through a single
onKeyhandler in+page.svelte, so there is no second dispatch path to reconcile.)
Reveal 2.0 (Tauri) — Swift app parity
The Swift app (Sources/) remains the daily tool and the interface specification: copy it rather than reinterpret it. Current web/Rust port status:
Parity work is tracked by behavior rather than isolated screenshots: every window, panel, menu, shortcut, state transition, persistence rule, progress/error state, and native macOS interaction must either match Swift or document an intentional redesign.
Already ported — floating sidebar (catalog tree, status dots, story markers, live indexing count), icon rail (star filter, sorting, column/format/margin popover), print-frame cells (shadow/ring selection, never accent), incremental NFS scan with automatic boot indexing, I/D/E development panel (EXIF, stars, clickable values, double-click reset), reveal://thumb thumbnails, catalog note, and Story mode (composer, THÈME panel, pinned/recent notes, two-mode sidebar swap).
Remaining work in priority order
- Virtualize the grid with geometry-based windowing in
PhotoGrid.svelte(uniform layout only; masonry remains non-virtualized for day-sized folders). - Sort by capture date through the
capture_atcolumn (EXIF via libraw during scans, with automatic backfill for existing rows). - Publish a single photo through
publish_photo(develop at 2048 px, deduplicated upload, one-image note) and the red development-panel button. - Add the Swift-style cell context menu for Finder, Preview, external editors, collections, Develop, and Vault publishing.
- Separate Preferences from shortcut discovery and install native Reveal, Edit, Import, Develop, Export, and Window menus.
- Show the real Garden account in the sidebar bottom row: paste-key sign-in verified against
/api/me, initial-avatar + username when signed in, stats and sign-out in a popover, and publishing now prefers the signed-in key over the vault plugin config. - Add 100% click zoom, constrained panning, decoded dimensions, and an EXIF overlay to Loupe.
- Add right-click context menus for catalogue and folder rows with Finder and root-preserving scoped reindex actions.
- Show pixel dimensions in INFO from RAW header metadata without a full decode.
- Add a visible batch render queue with coherent job state and safe cancellation between photos.
- Drag a photo (or the whole selection) from the grid onto a folder row in the sidebar to move it there — the RAW plus its
.xmpsidecar and any developed<stem>.jpgtravel together (rename on the same volume, copy+remove across volumes), refusing to overwrite; the hovered folder shows an accent drop-target ring, and both source and destination folders reindex afterward. (Scoped to in-library moves rather than an OS drag-out to Finder.) - Add sidebar peek-on-hover while hidden.
- Complete the card auto-import flow: a live rail import chip (DIN/mono count, accent fill, stop button that halts the copy between files — the in-flight temp+rename always finishes, so the archive never sees a torn file), and an “Éjecter
" rail button after a real card import that runs diskutil ejectand clears itself when the card is pulled. - Add Story composer insertion with
![[name.jpg]]from the grid: the composer now carries a horizontal film-roll of the folder’s frames (the Swift Conteur grid), and clicking a frame drops its![[stem.jpg]]embed at the editor caret — consecutive embeds group into one Garden row — with in-story frames marked by an accent ring and check. The footer shows a live publish/export status line matching the SwiftpublishStatus. - Reach Story-mode parity with the Swift reference: a THÈME panel in the sidebar’s STORY tab writes Garden theme tokens (
color-{dark,light}-{background,foreground},color-{dark,light}-accent,font-header/font-text) into the note’s frontmatter — defaults stay unspecified so the note inherits the Garden default until you touch a control, and the light side plus both foregrounds derive from the dark side. ÉPINGLÉES (drag-reorderable) and RÉCENTES story-note lists live in the sidebar, with pin state stored in the note (pinned/pinned-at) rather than app prefs so it travels with the file — reorder rewritespinned-atacross the pinned set (60 s stride) so the chronological sort reproduces the displayed order. The sidebar body swaps between the folder tree (FRAMES) and THÈME + pinned/recent (STORY), matching the SwiftfolderBrowser; DÉVELOPPER/Exporter/PUBLIER moved from the StoryView header into the THÈME section. One Swift element is intentionally omitted this round: the floating CONTEUR picker (n), since the inline composer film-roll already covers it. - Complete the final keyboard-shortcut parity pass against the Swift table: added
+/−grid zoom (columns) with⇧+/⇧−stepping the margin,⌘Dto deselect all, and repurposedrto the Swift “Reveal” action (develop + export the selection — the render queue still opens automatically and stays menu-reachable). The in-app shortcuts overlay was rewritten to mirror the SwiftShortcutsPanelgroups. Two Swift entries have no web equivalent and are intentionally omitted:n(floating Conteur panel — the web composer uses an inline film-roll) and⌥R(menu-bar-app global summon).
Changelog policy
Large engineering diaries, investigation notes, and long-form implementation history should live in dedicated docs or logs, not in this README.