Buttons were egui's plain one, a fill set by hand where one had to stand out (a dialog's confirm,
About's Discord link), with no hover or press shade on a filled one and no shared sizes.
- `crate::button::Button`: what it is for (`Kind`: Primary filled with the accent, Secondary
with a neutral fill and a border, Danger filled red, Ghost with no fill until hovered), how
big (`Size`: Small 24, Medium 30, Large 36 points, each with its padding, text and icon size),
and what it shows (text, an icon before the text, or an icon alone, which takes a name for
screen readers and its tooltip).
- Hover and press darken it; disabled (or in a disabled part of the window) fades it and takes
no click, its tooltip or name still shown; a focused one shows an accent ring. A filled kind's
fill is darkened as little as needed for white to read at 4.5:1 (the light themes' accent and
the red would be too pale), in every state. Drawn within its own rectangle.
- Named for screen readers, registered for scripts and tests (`.widget(id)`). Its egui id comes
from the widget id (salted with `.id_salt`, e.g. the dialog's kind, so the keyboard's place
doesn't carry from one dialog to the next) or from its place in the ui, moved on so buttons
with the same text side by side stay apart.
- Dialogs' footers (Cancel, the confirm; at least 72 points wide) and About's Discord link use
it. The Export dialog's expected height in `tests_dialog_fit` grows by the 8 points of its
footer's 30-point buttons.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A design critique of the first run found the import review's "Import N Photos" a plain button at
the bottom left beside Cancel, though the welcome before it had taught that blue means go.
- Every dialog's footer is right-aligned: the confirm on the right, Cancel to its left, added in
that order so the keyboard goes through them as the eye does. The row spans the width the
window gives its content this frame, so it reaches the window's edge without widening it.
- The confirm is filled with the accent when it makes or brings in something (`confirm_style`);
Delete and Remove, and an informational dialog's Close, stay plain. Which dialogs only show
something is one list (`only_shows`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
import_reads_files_off_the_ui_thread_and_can_be_cancelled: the probe was
a 150 ms sleep judged by a 300 ms wall-clock frame budget, which the
loaded FreeBSD QEMU VM can miss. It now waits on the sleeping-NAS gate of
tests_offline, which can also let a counted number of reads through:
frames must go on while every read is held (a watchdog records a frame
that waited), one batch is let through and joins the catalog, then
Cancel and wake: the files being read finish and nothing new starts.
compare_at_one_to_one_gets_a_sharp_window_per_pane ("both originals stay
decoded"): the media cache takes its budget from the process-wide memory
budget, which settings_dialog_by_keyboard_and_clicks sets to 1 GB for a
moment (and left there when it failed on FreeBSD). That leaves a 512 MiB
share, too small for two 24 MP originals (288 MB each), so one pane's
was evicted. compare_at now gives its session the default 768 MiB share;
LIGHTCRAFT_MEMORY_MB=1024 reproduces the failure without this.
dropping_the_lock_frees_the_library_while_another_thread_spawns_processes:
"the spawning thread never got a child off the ground" when a spawn took
longer than the 2 s of lock cycles. The cycles now go on until at least
5 children were spawned during them (at most a minute).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
settings_dialog_by_keyboard_and_clicks: the Performance tab's GPU status
hint reads process-wide GPU state, which other tests flip through
app.gpu. Without a GPU driver (the FreeBSD VM) the reason is a long wgpu
message that wraps to two lines, while "disabled by the GPU rendering
preference" is one, so the controls below moved by 20 px between the
frame that located "button:settingsMemory-2" and the frame that clicked
it. The status is now one truncated line with the full text on hover;
gpu_status_never_moves_the_performance_controls checks the buttons stay
put (it fails without the fix when no Vulkan driver loads).
offline_originals_never_block_a_frame: the slow NAS probe was a 400 ms
sleep judged by a 300 ms wall-clock frame budget, which a loaded QEMU VM
missed (1.06 s). The probe now waits on a gate the test opens; frames
must finish while it is closed (a watchdog records any frame that had to
wait), so no timing budget is needed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preserve model setup and retry behavior alongside the upstream folder
opening callback. Keep the Linux opener asynchronous.
Co-authored-by: Codex <codex@openai.com>
Treat a zero-width feather transition as a hard edge so exact-radius pixels remain unchanged, including at zero opacity. Add actual image regressions for all spot modes, image corners and two clone output sizes.
Validation: both new regressions failed on the original implementation and pass here. Formatting, workspace Clippy, HEIF and a generated-photo app smoke test pass. The unfiltered workspace gate remains blocked by the existing all-masks overlay test tracked by #1599; later parity, layers, assets and WASM stages are pending. This is a source checkpoint, not a green release candidate.
Contributor: @eliets2