Commit Graph
2767 Commits
Author SHA1 Message Date
David MannandClaude Opus 5.5 e5f1ecf501 Merge main into fix/smart-filter-last-mask
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 22:24:50 +02:00
David MannandClaude Opus 5.5 221af61dea Merge main into fix/count-delete-group-active
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 22:22:55 +02:00
David MannandClaude Opus 5.5 48ed913526 Merge main into fix/video-fps-validate
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 22:19:12 +02:00
David MannandClaude Opus 5.5 0ea2684679 Merge main into fix/adjust-smart-object-locks
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 22:15:01 +02:00
David MannandClaude Opus 5.5 dc71a11d41 Merge main into fix/4809-dual-brush-size
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:25:44 +02:00
aiongg 0a328189aa Smart filters, data sets, video: re-read stored files only when local or on the document's share
Paths these features keep in the document go through the same resolver as
linked smart objects (relative to the document's folder; other computers,
devices and URLs refused):

- A Displace smart filter's `mapPath` and a Color Lookup smart filter's LUT
  `file`, re-read on every re-render: a path that is not followed leaves
  the pixels as they are, as for a missing map or LUT.
- Apply Data Set's Pixel Replacement images.
- Reload Frame of a video layer's footage.

Apply Data Set and Reload Frame say why a path is not followed.
2026-10-16 16:53:28 +00:00
aiongg 348499a5e0 PSD export: embed linked files only when they are local or on the document's share
Save As PSD/PSB, layered TIFF and Package as PSD read a linked smart
object's file to embed it. The path is resolved with the same resolver as
other linked-file reads, against the document's own file (the new
ExportOptions::document_path / PsdExportOptions::document_path, set by the
engine's saves and the app's Save As). A link that is not followed is
written as pixels with a warning that says why, as for a missing file.
Nested smart-object documents resolve against the same document.
2026-10-16 16:53:21 +00:00
aionggandDavid Mann 87918a6c85 Linked smart objects: follow stored paths only to local files or the document's share
A linked smart object's path comes from the document. Every place that
follows it now goes through one resolver (photocraft_io::doc_path, built on
craft-docpath): a relative path is taken from the document's folder, and
only local files or files on the share the document itself is on are
opened. Paths to other computers, device paths and URLs are treated like a
missing file: the layer keeps its last rendering.

- Link watch: such links are not stamped, and no background look starts
  when no link of the document is followed.
- Update Modified Content and other re-renders read only followed files;
  the error names why a link is not followed.
- Package lists such links as missing instead of copying them.
- Reveal in Finder for a linked layer shows only a followed file; the
  document's own Reveal is unchanged. The macOS invocation ends options
  with `--`, and Explorer is not asked to select a path containing a comma
  or a quotation mark.
- Place Linked stores a relative path as an absolute one.

The document path travels to smart-object renders in a thread-local scope
entered by Session::edit, command dispatch and background jobs.

Co-authored-by: David Mann <daaavid.mann@gmail.com>
2026-10-16 16:53:13 +00:00
UnemarsandClaude Opus 5.5 87fbb2018f Merge origin/main into content-aware-workspace
Conflicts:
- distort_ui.rs: main's `drop_foreign` (#5092, #5123) closes the Content-Aware Fill workspace
  too when another document becomes active or its own is closed, stopping its background
  preview as Cancel does; what Apply wrote stays. New test
  `the_workspace_is_dropped_when_another_document_becomes_active`.
- sr, tr, vi catalogs: both sides' rows. Dropped es's High, Low, Duplicate Layer and Contract,
  which main has now with the same translations.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 14:19:21 +02:00
itsF4LCON 1302e08791 Arrange: the shown tiles stay put when a visible document is activated (#6017)
With more documents than tiles, the run of shown documents was worked out from the active
index alone, so after activating document 2 in Two-up, a click into the visible document 1
moved the tiles back to [0, 1]. The run is now kept from frame to frame and slides only when
a hidden document becomes active.
2026-10-16 12:04:34 +02:00
itsF4LCON 5b5fc9a80e Merge remote-tracking branch 'upstream/main' into fix/6017-arranged-tiles 2026-10-16 12:02:40 +02:00
itsF4LCON a6211253f8 Merge remote-tracking branch 'upstream/main' into fix/6085-text-caret-contrast 2026-10-16 11:37:08 +02:00
itsF4LCON 73e6671893 Merge remote-tracking branch 'upstream/main' into feat/6278-replace-color-result 2026-10-16 11:37:07 +02:00
highperfocused 4d6f3f4d1e style: cargo fmt (review on #6499) 2026-10-16 11:31:52 +02:00
jonathanwxh-cellandClaude Opus 5.5 9c914a9fd2 Refuse Affinity spot colours nested more than 4 levels deep
paint::color followed a spot colour's RGB equivalent and base colour with
no depth limit. Colours are shared objects, so a spot colour that links to
itself overflowed the stack and aborted the import, and a chain whose levels
link to the next through both took exponential time. Stop after 4 levels of
spot colours defined through spot colours, with a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:58:41 +08:00
Marco Arnulfo f59b75b058 Character and Paragraph panels: size, leading, baseline shift and indents follow the Type units preference (fixes #6491) 2026-10-16 09:35:06 +02:00
PerchAnton 3f216a0cd1 fix(engine): reject numeric overflow before document edits 2026-10-16 10:28:57 +03:00
highperfocused a08992c9d9 Desktop: a control/automation flag without its value is a usage error (#6429) 2026-10-16 09:14:24 +02:00
JGF 2a952f164c Windows/Linux: open files in the running PhotoCraft instead of a second instance (#3708) (#3755)
Fixes #3708; part of #1540 (Flatpak needs its own follow-up: each sandbox has its own loopback).

A second launch hands its files to the running instance over an authenticated loopback channel (random port, 32-byte token in a private instance.json, constant-time check, bounded clients, requests and paths). Files are acknowledged only once the UI has taken them; the instance shuts the hand-off down before a CPU-compatibility relaunch; it runs only in the per-user settings mode. --new-instance and PHOTOCRAFT_NEW_INSTANCE=1 opt out.

Thanks to @MrCloudy2 (#2358), whose Linux version first handled the relaunch and per-user endpoints, and @Desayuno64 (#5273), whose shutdown flag and read deadline informed the final design.
2026-10-15 22:48:30 -07:00
Tanish Manku d12be8ecd0 test(ui-egui): canvas_16f and rotation preview tests run on the app's wgpu device (#6454)
Both harnesses built their device with egui_kittest's .wgpu() (egui's default limits) instead of
gpu_canvas::wgpu_setup() (the adapter's own limits, what the app requests), as #5954 fixed for the
background_redraw and navigator tests. On that device:
- canvas_16f's sixteen_bit_gradient_on_screen and thirty_two_bit_preview_exposes_values_above_one
  panicked in egui-wgpu ("Failed to create staging buffer for index data", renderer.rs:984), every
  run, single-threaded too;
- arbitrary_rotation_preview's two tests failed "the fixture must use the GPU canvas".
Both binaries pass on the app's device.
2026-10-15 22:33:14 -07:00
Gert Pellin d8a23d3ec3 Automation: a control token authenticates in either letter case (#6425)
validate_token accepts uppercase hex and server_token keeps the token
lowercased, but authentication_reply passed the client's token to
token_matches unchanged, which compared bytes exactly. A client sending
the token exactly as configured (--control-token or a token file in
uppercase) was refused; only the bundled bridge, which lowercases, got in.

token_matches now folds ASCII letters on both sides, still without an
early exit. Only A-Z fold, so a non-hex byte never matches a hex digit.
2026-10-16 07:30:48 +02:00
58f855a40f Save As on Linux: the file type picked sets the extension (#6121, #4328, #4394) (#6151)
* Save As on Linux: the file type picked sets the extension (#6121, #4328, #4394)

The GTK file chooser behind the desktop portal keeps the typed name when
another file type is picked, and rfd 0.17.2 reads only the file name from
the portal's answer, so Untitled-1.psd with PNG picked was written as a
PSD. On Linux, Save As now asks the portal's FileChooser.SaveFile itself
(over the zbus already used for the portal), reads current_filter, and
gives the name that type's extension: an extension PhotoCraft saves is
replaced, none or another is appended. If that names an existing file, the
dialog opens again under the new name so the portal asks before replacing
it. rfd remains the fallback without a portal, and this can go once an rfd
release reads current_filter (PolyMeilex/rfd#341).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Save As on Linux: keep typed extensions, open on the stem, follow the request handle

Review follow-ups, checked on real GNOME 46 and KDE Plasma 6.7.5 sessions
(Wayland and X11):

- An extension the user typed is kept, as in PolyMeilex/rfd#341: only a
  name with none, or still with the extension the dialog opened with, takes
  the picked type's. GTK reports the type even when it is untouched, so
  art.png with the PSD type left alone was saved as art.psd.
- The dialog opens on the stem (test), as on Windows (#4407): with
  test.psd in the name and PNG picked, GTK asked to replace test.psd,
  which the save leaves alone. A name that then exists is asked about
  again under that name; the rule compares against the suggested name, so
  a test.psd GTK completes back still becomes test.png.
- Response 2 (GNOME's dialog closed with Esc) is a cancel: it fell back to
  rfd, which opened a second Save As.
- The answer is awaited on the handle SaveFile returns, which a portal
  older than 0.9 picks itself (the Request documentation).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style(automation): format smart filter refresh closure

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: skylar taylor-barrick <hi@skylar.cc>
2026-10-15 22:47:17 -06:00
Tanish Manku 6368e6a851 GPU memory follow-ups (#5973 review): fit cleared on device loss and on the CPU canvas; measured when switching to a document whose canvas is current; notice names the 3/8 card cap; VRAM override documented; perf rows count memory like the app (#6458) 2026-10-15 21:37:05 -07:00
Tanish Manku 6d3443c098 GPU memory: discrete cards aren't charged for the document; GPU Memory preference; GPU Memory Full status (#5973)
* GPU memory: a graphics card's own memory isn't charged for the document; GPU Memory preference; GPU Memory Full in the status bar

Many layers hit a cliff: past what the GPU memory budget leaves for layer pages, every edit
that redraws the whole document (opacity, visibility, a nudge) goes to the CPU compositor.
Measured on 3000x2000 full-canvas layers (RTX 5090, 31 GB RAM, Memory Usage 8 GB): 150
layers took 2.6 s per such edit, 300 layers 6 s. The budget was Memory Usage minus the
document's pixels and History, which is right where the GPU shares system memory, but a
discrete card's memory doesn't hold the document, so 150 layers of 6 MP (3.6 GB) squeezed
the budget below what they needed.

- Automatic budget: discrete adapters are no longer charged for the document's pixels
  (integrated and software adapters still are). 150 layers: 2.6 s -> 25 ms.
- Preferences › Performance › GPU Memory (MB, 0 = Automatic; performance.gpuMemoryMb) sets
  the budget outright. 300 layers with 12 GB: 6 s -> 45-72 ms.
- The status bar shows "GPU Memory Full" while the active document's layers don't fit
  (both status bar styles); hovering gives the numbers, clicking opens Preferences ›
  Performance. Its Graphics rows show the budget and what the layers need. The slow CPU
  redraw notice gives the numbers and points at GPU Memory.
- Headless: the preference is a normal pref (prefs.set), and ui.state's perf.timings
  reports gpuLayerFit {doc, needed, budget} next to gpuBudgetMb.

Next (owner-approved plan): after #5370, read the card's real limit from the driver
(wgpu memory budget thresholds) and shrink on out-of-memory; then redraw the visible
area on the GPU first when layers still don't fit.

* GPU memory: typed discrete check; the status and budget follow a GPU Memory change at once (measure_fit); perf rows P60/P61 for whole-document edits among 150 full-canvas layers under the app's budget

* GPU memory on top of #5234: memory_budget takes the card's memory (vram) and the GPU Memory value; the document's pixels are charged unless the card's own memory is known; a set GPU Memory is capped at the card's share; benches use video_memory
2026-10-15 21:22:46 -07:00
FinnNkandClaude Opus 5.5 297520b9c8 perf(gpu): cap the GPU memory budgets by a discrete GPU's own memory (#5234)
* perf(gpu): cap the GPU memory budgets by a discrete GPU's own memory

The compositor and canvas budgets came from system RAM only, so a 16 GB card in a 96 GB machine planned for up to 8 GB of compositor pages plus 24 GB of display textures. Detect a discrete adapter's dedicated memory (DXGI on Windows through winsafe, amdgpu sysfs on Linux) and cap each budget at three eighths of it. PHOTOCRAFT_VRAM_MB simulates a smaller card. Fixes #4536.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(gpu): keep a 16-bit canvas on the GPU in 8-bit when floats don't fit

With the canvas budget capped by a small card's memory, a 16-bit document under the float-canvas pixel limit could need more than the budget as Rgba16Float and fall back to the CPU canvas although its Rgba8Unorm set fits. Choose the float canvas only when it fits the budget, which is now shared atomically so format_for can read it while the renderer is locked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(gpu): detect a card's memory once per adapter, and test the Linux reader

video_memory built a DXGI factory each time it was asked, three times at
startup; it now remembers the answer per (vendor, device), so a GPU retry
that picks another adapter still gets its own. The amdgpu sysfs reader
takes the drm folder as a parameter and is tested against a temporary
folder: the matching card's VRAM, a card without the file, garbage ids, a
malformed size, no match and no folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:05:51 -07:00
Tanish Manku 57437ea1a9 fix(ui-egui): canvas textures fit the device's texture limit; GPU tests hold the GPU lock on the app's device (#5954)
- The CPU canvas texture and the crop's beyond-canvas bands were reduced to fit MAX_TEXTURE (4096)
  whatever the device allows. On a device with a smaller limit (2048 on some older GPUs and WebGL
  contexts) a 2600 px document was uploaded at full size: egui asserts and the backend can't
  create it. They now fit the smaller of the two. Test: cpu_canvas_texture_fits_the_device_limit.
- background_redraw and navigator GPU tests built their harness with egui's default wgpu limits
  instead of the app's wgpu_setup (the adapter's own limits), and the GPU canvas lost the device
  on it ("Device is lost", every run on an RTX 5090). They now use wgpu_setup.
- Eleven tests built a wgpu device without photocraft_testkit::gpu_lock, which testkit requires so
  devices aren't created concurrently; they now hold it.
2026-10-15 21:05:39 -07:00
skylar taylor-barrick 7053f703a5 Merge pull request #2933 from 1Git2Clone/ci-wallclock
CI: cut wall time, starting with the Windows test job
2026-10-15 21:38:18 -06:00
skylar taylor-barrick fd1faf4e13 Merge pull request #6381 from dylanmtaylor/ci/shared-freebsd-workflow
ci: delegate FreeBSD workflow to craft-common-actions and add nix check
2026-10-15 21:34:22 -06:00
skylartaylor 8f58ea4629 ci: integrate current main while retaining strict test gates 2026-10-16 05:25:24 +02:00
Higgs32584andClaude Opus 5.5 5c0e40784a rustfmt; merge main (CI Format step)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:29:42 -04:00
Higgs32584 36236b908a Merge remote-tracking branch 'origin/main' into fix/4962-web-save-dirty 2026-10-15 22:28:59 -04:00
Higgs32584andClaude Opus 5.5 c972acf98f rustfmt: format after merging main (CI Format step)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:16:12 -04:00
Higgs32584andClaude Opus 5.5 76f29f67d5 rustfmt: format after merging main (CI Format step)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:16:03 -04:00
Higgs32584 3eef8af459 Merge remote-tracking branch 'origin/main' into fix/help-search-arrows-space 2026-10-15 22:09:43 -04:00
Higgs32584 f46bd2a907 Merge remote-tracking branch 'origin/main' into fix/1056-path-selection-canvas-menu 2026-10-15 22:09:40 -04:00
Dylan M. Taylor 0cc161e2a8 ci: delegate FreeBSD workflow to craft-common-actions and add nix check 2026-10-15 22:04:09 -04:00
Higgs32584andClaude Opus 5.5 7bc556ec02 Web: a requested download no longer marks the document saved (#4962)
The web writer only triggers an anchor download, which the user can cancel
and which reports nothing back, yet Save treated it as a confirmed write:
the document was marked clean and the leave-page guard disarmed. Services
gains `write_unconfirmed` (set by the web shell); a save through such a
writer names the document but keeps its changes unsaved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:54:09 -04:00
PerchenkoAnton 3cb3580a46 fix(psd): recover layered imports with damaged merged data (#6170) 2026-10-15 19:14:05 -06:00
David MannandClaude Opus 5.5 0ce7e00fee Brush: the Dual Brush tip keeps its proportion at tiny main sizes (#4809)
Photoshop scales the Dual Brush diameter by the main size ratio, unrounded,
with a 5000 px cap and no lower floor: main 100 / dual 20 resized to 1 px
gives a 0.2 px dual tip, and back at 100 px it is 20 px again. PhotoCraft
already scaled the dual tip (#4856) but floored it at 0.5 px, so a trip to
1 px and back left a 50 px dual tip. Drop the floor (rendering bounds the
diameter itself), keep the cap, and leave a non-positive or NaN result alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 03:06:07 +02:00
skylar taylor-barrick b630d7cb9a Security: enforce the automation read boundary on stored smart-filter params (#5684)
* Security: enforce the automation read boundary on stored smart-filter params

Smart filters store a command id and params inside the document, so a
crafted .pcraft could resolve stored ambient paths (displace mapPath,
color-lookup LUT files) at render time without the authorize gate:
synchronous smart-object rendering has no Session argument, spawned job
threads drop the requesting session's authority, and a trusted UI
render could warm the process-wide stack cache with a stage the gate
never saw.

Thread the requesting session's gate through a thread-local in jobs.rs
(entered around command execution, reinstalled on spawned workers and on
job apply, restored on drop), re-check stored filter commands in
render_in before source resolution and any cache lookup and in
apply_filter_to_surface, and re-authorize layer.smartFilter.setParam
with the complete merged params after merging stored document params —
including edits whose request contains no path and actions.play steps.
Denial is conservative: the document is unchanged and no worker starts.

Regression tests in the automation crate cover rerender, cached-stage
reuse, merged-param edits, action playback, background jobs, and filter
transfer, for both the displace map and color-lookup LUT vectors.

* Review: carry render authority through Session::edit and surface denials

Session::edit is the engine's universal mutation entry (linked-file
refresh, transforms, place); renders reached through it ran with no
authorization scope installed, so stored smart-filter params read
ambiently there even after the execute-path fix. Install the scope in
Session::edit as well, keyed on the session's authorize gate.

apply_filter_to_surface also conflated a denied filter with an unknown
one: authorize_render_command's Result was discarded with .ok()?, so a
denial silently rendered unfiltered pixels and the stage entered the
process-wide stack cache. The signature becomes
Result<Option<Surface>>: Err for denials (callers propagate), None for
ids that aren't pixel filters (pass-through semantics unchanged), and an
unreadable map keeps its deliberate pass-through. apply_smart_filter and
apply_smart_filters propagate instead of cur.clone().

Two tests: a refresh driven outside command execution (Session::edit)
is denied and leaves the document unchanged; a denied render caches no
stage. Nine authorization tests pass, engine suite and strict Clippy
clean, panic hunt green.

* ui_set description: name the layerFilter field main added

The Layers-panel filter dropdown (#5048) added layerFilter to the
control method's UI_SET_FIELDS without extending the ui_set tool's
description, so the drift guard (mcp.rs, ui_set_description_names_every_
accepted_field) fails on Windows CI for any branch built on main. Name
the field with its patch shape: by (kind, name or mode), kinds (a list
of layer kinds), name (matches part of the layer name), mode (a blend
mode name). The 40 MCP tests pass; strict Clippy clean.

* wasm size: compose and algo join cms at opt-level "z" to clear the 24 MiB gate

Main's browser build is 171 KB over the 24 MiB wasm gate in CI
(25,337,343 bytes), so every branch built on current main fails the
web size check. Demote three pixel crates in the wasm-release profile:

- cms: transforms are built once and cached (transform::cached),
  so "z" costs little runtime (measured -42 KB).
- compose: the browser app composites per frame on the GPU (crates/gpu,
  WebGPU/WebGL2), so the CPU compositor is not the browser's hot path
  there (measured -122 KB).
- algo: filters run per pixel on every interactive filter application.
  "z" there is the lever of last resort (measured -331 KB), taken
  because the gate is enforced in CI; if browser filters feel too slow,
  restore 3 here and reclaim the bytes from a feature-gated decoder.

Final size 24,811,949 bytes locally, 354 KB under the gate; the native
profiles are untouched. If a maintainer prefers keeping algo at 3, the
alternative is raising the self-imposed 24 MiB early-warning limit
(Cloudflare's hard cap is 25 MiB) - a policy call in packaging/web/
package.sh, not a code fix.
2026-10-15 18:19:26 -06:00
itsF4LCON dedf89feae Type: move the caret test next to its helper, away from the file's end 2026-10-16 01:33:24 +02:00
Higgs32584andClaude Opus 5.5 8237d76170 Help search test: take the first Levels result and move the cursor to the end
"levels" now finds two results (Image › Adjustments › Levels… and the
Levels adjustment layer), and one → only reaches level|s, so the test
needs a second → before typing the space.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:28:58 -04:00
itsF4LCON 67602017f8 Merge remote-tracking branch 'upstream/main' into feat/6278-replace-color-result
# Conflicts:
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
2026-10-16 01:21:27 +02:00
itsF4LCON a76d3295ba Type: the text caret shows on a white background (part of #6085)
In the Photoshop themes the caret was a 1.5 pt white line with a 0.75 pt
black hairline inside, so on a white background only a faint grey line
showed. It is now a solid 1 pt black line inside a 3 pt white halo, readable
on any background, in every theme.
2026-10-16 00:38:48 +02:00
Tanish Manku 2b3d05636f Follow-ups to #5801/#5859: precheck docs name the group nesting limit; precheck test covers valid ids and null; Free Transform refusals translated; stranded-backup error tested (#6365) 2026-10-15 15:32:08 -07:00
Higgs32584andClaude Opus 5.5 944d0cf722 Path Selection: the canvas right-click opens the path menu, as the Pen's does (#1056)
The Path Selection and Direct Selection tools had no canvas menu, so Make Selection, Fill and
Stroke Path, Free Transform Path, Delete Path and the combine and style rows needed the Pen tool
or the Paths panel. Like Photoshop, their right-click now opens the path menu for the selected
path, with the Pen menu's rows and the same enablement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 18:27:56 -04:00
itsF4LCON 0752cd8365 Merge remote-tracking branch 'upstream/main' into feat/6278-replace-color-result
# Conflicts:
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
2026-10-16 00:27:03 +02:00
Tanish Manku fdaa9b8316 Free Transform: New Layer applies the transform first; tests for switching tool, layer or New Layer (#5665, #5667) (#5859)
* Tests: Free Transform applies when another tool, layer or New Layer is picked (#5665, #5667)

Whole-app regression tests for the paths #5665 and #5667 report (a moved box, and a placed image moved or untouched; toolbar tool click, Layers panel row click, New Layer). They pass on main, so these issues don't reproduce here; the tests pin Photoshop's behaviour (apply, never silently cancel) while the reporters narrow down their environment.

* New Layer while Free Transform is open applies the transform first (History: the transform, then New Layer; a placed layer's step no longer swallows New Layer); tests: a real placed fixture, the New Layer button, History order

* Free Transform + New Layer: check the command can run before applying the transform; a refused transform keeps the box and the placed layer and returns the refusal

* Free Transform + New Layer: Session::precheck validates the call (params shape, New Layer's values, precondition) before the box is applied

* Free Transform + New Layer: commit-first only for PRECHECKED commands; precheck shares dispatch's admission checks (named-layer precondition, jobs, hidden target) and checks a named layer exists; Group from Layers documents its layer param; test: precheck agrees with execute

* Free Transform + New Layer: precheck resolves a layer given by name as the command does; Layer via Copy/Cut leave PRECHECKED (their failures show only when they run); rebased (lib.rs mod lines)
2026-10-15 15:26:13 -07:00
Tanish Manku 8376363d8c Shortcuts without the UI: shell commands and tools in the engine, reserved keys, set files (#5801)
* Shortcuts without the UI: shell commands and tools in the engine, reserved keys, set files

A retro against "everything is drivable headless; the UI is a layer" found the shortcut
work leaning on the UI. Fixed:

- The shell's command table (UI_COMMANDS: View › Zoom In, the panels…) moves into the
  engine as data (shell_commands::SHELL_COMMANDS; menus::UI_COMMANDS is now an alias), with
  the tools (shell_commands::TOOLS: id, Photoshop name, default key; a test keeps it equal
  to the toolbar). edit.keyboardShortcuts binds, lists, imports, exports and checks them for
  conflicts with no UI: a headless .kys import takes tool keys, and an export is the whole set.
  Tools may share a key (Shift cycling); a command given a tool's key takes it from them.
- Reserved system keys are refused by the engine (the dialog uses the same check) and left
  out of imported sets.
- importKys / exportKys / summarize take {"path": …} to read or write files, like
  actions.save; the automation gate refuses those paths for MCP.

* Keyboard shortcuts: bindings commit only after their exports are written (a bad target changes nothing); docs: the command knows the tools

* Keyboard shortcuts: both export targets are resolved and checked before either file is written; a failed later write removes the earlier one

* docs: reserved keys per platform

* Keyboard shortcuts export: files are staged next to their targets and moved into place only when all are written; a failure leaves the user's files untouched

* Keyboard shortcut exports all or nothing: unique create_new staging and backups beside each target, restored on any failure; the same target twice refused; docs: Ctrl+Shift+Esc

* Shortcut exports: a failed backup move removes its placeholder; undo restores backups without deleting first and names any it couldn't; read-only targets refused up front
2026-10-15 15:26:09 -07:00
Tanish Manku b09aeae2e2 Low-Latency Drawing preference (vsync off, opt-in), plus present-mode overrides for testers (#1343) (#5274)
* PHOTOCRAFT_PRESENT_MODE / PHOTOCRAFT_FRAME_LATENCY launch overrides for latency A/B tests (#1343)

Linux traces put the remaining stroke latency in presentation queueing (the app's frame fits
in one refresh; the rest waits on the swap chain), and an NVIDIA user cured it by forcing
vsync off. Testers can now compare without rebuilding: PHOTOCRAFT_PRESENT_MODE (fifo,
mailbox, immediate, autovsync, autonovsync) and PHOTOCRAFT_FRAME_LATENCY (1-3), applied over
the platform default. Bad values are logged and ignored. The active mode is logged at start,
kept in GpuInfo.present and shown in every stroke trace line.

RTX 5090, Vulkan, 120 Hz: default autovsync/latency 1 waits 6 ms per 8.3 ms frame; mailbox
renders every 2.3 ms with 0.2 ms wait, so each vsync shows the newest frame.

* docs: present mode and frame latency overrides

* Present mode overrides never request a mode the driver may lack (Mailbox crashed on X11/NVIDIA)

* Preferences › Performance › Low-Latency Drawing, on by default: no vsync wait while drawing (#1343)

Testers' traces put the ~50 ms stroke lag in the swap chain's vsync wait. With vsync off it
disappears (X11/NVIDIA; Windows/NVIDIA via the driver), and a shorter queue doesn't help. The
surface now presents with AutoNoVsync by default: wgpu picks Immediate, else Mailbox, else
Fifo by what the driver offers, so no unsupported mode is ever requested. Turning the
preference off brings vsync back (smooth, but late). It applies at the next launch, like the
other graphics settings. Where only Immediate exists the canvas may tear while it changes;
frames render as fast as input arrives during a stroke and stop when idle.

* Runtime and launch controls: ui.set strokeTrace, --stroke-trace, --present-mode, --frame-latency

The retro's remaining gap: the stroke trace and the presentation overrides were env-only. The trace now switches at runtime over the control channel (ui.set {"strokeTrace": true|false|"stderr"|path}) and with --stroke-trace; --present-mode / --frame-latency sit beside their env vars and win over them. Bad values are reported and ignored; none fails a launch.

* docs: strokeTrace beside the other ui.set fields

* docs: regenerate the scorecard after rebasing onto main

* Low-Latency Drawing: frames capped at 240 fps without vsync (after #4472); ui.set strokeTrace takes on/off only (a CSV path is launch-only); tearing documented as usual (Immediate); pref hidden on web; doc comment order

* stroke_trace: from_value doc says ui.set passes only on or off

* Low-Latency Drawing ships off (opt-in), per the owner's decision

* docs: present mode default is vsync (Low-Latency Drawing ships off)
2026-10-15 15:23:47 -07:00