A slideshow template is the Playback (not "prepare previews"), Layout /
Options / Backdrop, Overlays and Titles settings as one versioned JSON
document (format lightcraft.slideshowTemplate, version 1).
- Engine: slide_template (SlideLook, Template, built-ins, user template
files in <settings>/slideshow-templates read through each section's own
merged() validation; size and count caps; newer versions refused; bad
files reported and skipped; file names sanitised so ids never leave the
folder). Applying replaces the four settings in one step and keeps the
replaced ones as "previous" for the session (prefs have no undo).
- Five built-ins of our own: Plain Black, Gallery Wall, Edge to Edge,
Cinema Wide, Shooting Data (no Lightroom names or designs, no images).
- Commands slideshow.templates.list/apply/save/update/rename/delete/
import/export; slideshow.preview {template} previews without applying.
- UI: Template Browser at the top of Settings > Slideshow (built-in, user,
previous settings; click applies, hover previews on the selected photo,
right-click update / rename / export / delete; save and import), file
dialogs on the desktop; strings in the nine locales.
- Docs: parity row (partial), whats-new, control protocol and MCP.
Intro and ending title screens (Lightroom Classic's Titles panel): each on
or off, a background colour and, centred, the Overlays identity plate or a
line of text, in the plate's colour or an override colour, at a scale, for
its own duration (default: the slide duration).
- Playback timeline: titles are items (Item::Title) of Playback; the intro
plays once before the first photo, the ending after the last photo when
repeat is off; random order never moves them; arrows step across them;
fades, pause and readiness work as for slides; n of N counts photos only.
- Engine: slide_title (TitleSettings in prefs.json, TitleCompose drawing
through the slide composer and overlay text renderer), slideshow.titles
get/set, slideshow.preview {title}.
- UI: titles composed on a render worker (Renderer::request_title), the
control bar shows Intro / End, Settings > Slideshow > Titles section and
a Photo / Intro / Ending preview choice; strings in the nine locales.
- Docs: parity row (partial), playback note, whats-new, control and MCP.
The crop tool's rotate pointer always bulged up, which only reads right
above the box. It now turns to bulge away from the nearest point of the box
outline: right beside the right edge, down below it, left beside the left
edge, diagonally off a corner, and following a box that is already rotated.
icons::paint_turned draws an icon turned about its centre; icons::paint is
unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- release.yml: new linux-riscv64 job (ported from GridCraft): cross-compiles on
ubuntu-24.04 against ports.ubuntu.com riscv64 libs, packages a tar.gz with
package.sh --formats tar, smoke-tests lightcraft-cli under QEMU. The release
job needs it, so its tarball lands in the draft and SHA256SUMS.
- packaging/linux/package.sh: CROSS_ARCH/CROSS_TARGET/CROSS_COMPILE/EMULATOR
support (target dir, cross strip, emulated --version).
- README Downloads and docs/releasing.md list the riscv64 tarball.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
In the side-by-side and top/bottom Before/After layouts a click in the
Before view was mapped to image coordinates through the After view's
rectangle, so the White Balance Selector sampled the wrong point (at Fit,
the photo's edge). The click tools in general_interaction now use the
Before view's image rectangle when the pointer is in the Before view's
pane; the pane decides, since zoomed in an image reaches past its pane.
New headless test tests_before_after.rs clicks the same image point in
the After and Before views (side by side, top/bottom; Fit and 100 %) and
expects the same white balance. Without the fix: Before 8202 K / +116,
After 2710 K / +33.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In Compare a filmstrip click made the clicked photo active without
showing it, so the Edit panel and the rating keys acted on a photo on
neither side (a headless test rated the hidden photo 2 -> 3 stars). The
click now puts the photo into the active side (the candidate after
entering Compare, the select after clicking the select's photo); a photo
already on the other side swaps the two. The active photo is always one
of the two shown. Other views keep the usual selection clicks.
New UI command compare.set {id, side?: "select" | "candidate"} for the
same through the control channel and MCP.
New headless tests in tests_compare.rs: the active photo stays on screen
and no hidden photo is rated (Fit and 100 %), and replace / swap /
explicit side / unknown side.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lightcraft-web-<version>.zip carried Cargo host build output next to the site: deps/ (81 files, 90.6 MB), build/ (149 files, 22.4 MB) and .fingerprint/ (297 files), ~113 MB that the app never references and that leaked build paths and crate names to anyone hosting the zip.
Cause: xtask wrote the bundle to <target>/web (SITE in packaging/web/package.sh) while building with --profile web. Cross-compiling with --target wasm32-unknown-unknown makes Cargo write the selected profile host artifacts (build scripts, proc-macros, deps/, build/, .fingerprint/) to <target>/<profile>/, so they landed in the bundle directory and package.sh copied the whole tree into the zip.
Fix: write the bundle to <target>/web-site, a directory that is not a Cargo profile name, and point SITE at it. The wasm32/web/ target artifacts and the web profile are unchanged, so the rust-cache keys and the CI web job keep working.
Guard: package.sh now fails if deps/, build/ or .fingerprint/ exists in $SITE, and a unit test asserts the bundle directory is not a Cargo profile name, so the collision cannot come back silently.
Reference (#793)
The White Balance Selector button in the Edit panel only armed the tool,
so in Compare (which doesn't handle on-image tools) it did nothing while
W opened the photo in Detail with the selector armed. The button now runs
tool.wbPicker like W; a second click still disarms it.
New headless test tests_wb_selector.rs clicks the button in Compare and
expects Detail with the selector armed on the photo that was active; it
fails without the fix (the view stays Compare).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Canon EOS R50 C-RAW files reach QP 168 (quant 44, step 102, measured about 100). The quantization step and the adaptive QP map range accept it; anything above stays rejected. Bumps RENDER_CACHE_VERSION since such files change from the embedded preview to a raw decode.
view.back (Esc) closed dialogs, the keyword painter, full screen and an
armed on-image tool, but not the crop tool (a right panel, not
app.ui.tool), so Esc with the crop tool open went straight to the grid.
It now closes the crop tool like C does (the crop stays); the next Esc
goes back to the grid.
Esc that ends an edit in a text field (the crop tool's Angle field) also
ran Back: egui drops the field's focus as Esc arrives, before
shortcuts::handle checks egui_wants_keyboard_input(). The app now
records whether a text field had the keyboard at the end of the last
frame (LightcraftApp::text_focus); that Esc is then left to the field,
like one that closes a popup.
New headless tests in tests_crop_tool.rs; both fail without the fix
(the existing escape_in_the_angle_field_keeps_the_angle fails with only
the crop step).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The frame dropped the flips together with the crop (flip_h: apply_crop &&
...), so the crop tool's uncropped view (render and screen <-> image
mapping) ignored Flip H / Flip V until the tool was closed. A flip isn't
part of the crop: the frame now keeps it without the crop. Cropped
renders and exports are unchanged.
The crop overlay's dimming paired the photo's corners with the crop
box's corners in data order; flipped, the box reaches the screen
mirrored and the photo was dimmed inside the box. It now takes the box's
corners in screen order (flipped_corners). Box, handles, drags and guides
already go through the frame's mapping.
Tests: geometry uncropped_frame_keeps_the_flips (fails without the fix)
and detail flipped_crop_box_corners_come_in_screen_order; headless
screenshots for H, V and H+V checked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Engine: slide_overlay (OverlaySettings in prefs.json, the new slideshow.overlays
command): identity plate (text, or a PNG/JPEG file with its transparency; size,
colour, opacity; render behind image), rating stars (own vector stars), up to 16
text overlays with tokens ({caption}, {exposure}, {file}, {date}, {focal}… plus
every rename token; unknown tags show as typed and are reported) and a drop
shadow, each at one of nine anchors of the slide or photo plus an offset, kept on
the slide; the export watermark on the photo. Sizes relative to the slide's
shorter side; validated and clamped.
- Drawn by the engine as a second pass over lightcraft_slideshow::compose_with
(new behind hook for "render behind image"); the watermark's text renderer moved
to engine text.rs (TextLayout, Inter + craft-fonts CJK) and is shared; glyphs
off the image are skipped so huge text stays cheap.
- rename::expand_tokens_with lets overlays add tokens without changing rename.
- Playback composes slides when overlays are on; slideshow.preview returns each
overlay's box. Settings > Slideshow > Overlays section with live preview.
- Docs: parity row (partial), whats-new, control/MCP docs, nine locale catalogs.
- Tests: tokens, nine anchors, star count, plate image opacity and missing file,
text shadow, resolution independence, hostile input, big-slide cost, CJK with
craft-fonts, prefs persistence, preview and headless playback pixel checks.
Zoomed in, each view's photo reaches past its pane. The whole-frame renders
were drawn unclipped (only the window renders were clipped to their pane),
so the After's magnified whole-frame render covered the Before pane's margins
and the gap between the panes with a blurred picture, and the Before side
looked like it didn't fill its pane. Both whole-frame renders are now clipped
to their pane, like the windows over them; the Before / After labels stay
under the pane instead of moving off screen with the photo's bottom edge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New crate lightcraft-slideshow (L1, no UI): SlideSettings (margins linked or
independent, guides, aspect preview, zoom to fill, stroke border, cast shadow,
backdrop colour, colour wash, background image; lengths relative to the slide's
shorter side; validated and clamped) and a CPU compose() used by playback, the
Settings preview and slideshow.preview.
- Engine: slideshow.layout (partial merge, saved in prefs.json) and
slideshow.preview {id?, width?, height?, path?} (PNG to path or base64);
RenderJob::with_slide composes on the render worker; the background photo is
rendered once and shared.
- Playback: with a non-default layout each slide is the composed screen-sized
slide, prepared ahead like before; the default layout plays as before.
- Settings > Slideshow: Layout, Options and Backdrop sections and a debounced
live preview of the active photo's slide (with margin guides).
- Tests: composer geometry/pixels/hostile input, command get/set/persist/preview,
headless playback of a composed slide and of the default layout.
Progress docs per craftrules standards/progress-docs.md: target-app parity, gaps, roadmap with alpha gate, readiness by audience (full / mainstream / essentials) with Opus 5.5 hours, model-by-model RAW coverage against Adobe's camera list (docs/raw/), and format, hardware, UI and localization parity docs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Their confirm does nothing, so they join About, Shortcuts and Settings in the
footer's informational list: one Close, no Cancel / OK. System Info keeps Copy
to Clipboard; Esc still closes them.
A stationary hold recorded one pointer sample per frame and each sample laid
another dab at the same spot, so flow accumulated with hold time and the
feathered edge hardened. brush_dabs now lays no dab for a sample that hasn't
moved from the last one, and the canvas no longer records repeated samples at
the same position. Moving strokes and passes back over an area are unchanged.
Return presses OK in Choose Edit Settings to Copy and Paste Selected
Settings when no widget has the keyboard focus (a Tab-focused button or
checkbox takes Return itself, as egui does). Esc already cancelled them;
tests now cover both keys.