Alt-drag, Cmd+Opt-drag and Free Transform on a copy duplicate with
`inPlace: true` for every layer, but the flag only changes anything for
an artboard. With in-place duplicates now refused on strict .atn export,
a recorded copy of a plain layer could no longer be exported, although a
plain Duplicate replays it exactly. Recording now drops `inPlace` when no
copy is an artboard, as `layer.select` already rewrites its recorded
params.
Refs #7200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Photoshop's Duplicate Layer has no in-place flag, so `layer.duplicate
{"inPlace": true}` was exported as a plain Duplicate and replayed with
the artboard copy beside the original. Strict export now fails on it and
skipUnsupported reports it, as for a Close with a document target.
Fixes#7200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main's 589941a5 (#6600) refuses to replace a read-only file before anything
is written. This branch's test wrote to a 0o444 file and expected the save to
succeed, so the merge of the two failed on Linux:
cannot save .../doc.psd: the file is read-only; the original file was
not changed
No bug on either side: the two changes disagreed about what a read-only save
should do, and main's answer is the deliberate one. The test now asserts the
refusal, that the original is untouched, and that the attribute and mode survive
— which is what it set out to check. The writable cases alongside it still cover
the metadata copy itself.
The windows-latest failure was the roll-up gate reporting this same failure, not
a second one.
* docs: add user docs for PhotoCraft
Adds docs/user/ (published at getartcraft.com/docs/photocraft), the docs
check and publish workflows (copied from craft-launcher), and a User docs
rule in AGENTS.md. Checker: 0 error(s), 0 warning(s).
Source notes per page:
- index.md: README (intro, status, Crafting Apps table), website
crafting-apps.ts photocraft entry, links.rs (Discord, issues), menus.rs
(Help > Report an Issue).
- getting-started/index.md: menus.rs and menu_catalog.rs labels, panels.rs
(Layers, Properties, History), dialogs.rs (Create button).
- getting-started/install.md: README Downloads and Get started sections,
gh release view v0.7.0 asset list, craft-launcher catalog.rs (lists
PhotoCraft).
- guides/open-and-save-files.md: apps/photocraft/src/services.rs (open and
save filters), README Formats and PSD sections, docs/file-format-parity.md.
- guides/adjust-colors-with-adjustment-layers.md: menu_catalog.rs, panels.rs
(footer button, adjustment list), lib.rs (Properties shows new layer).
- guides/select-and-mask.md: menu_catalog.rs Select and Layer Mask menus,
state.rs tool names, retouch_ui.rs (Select Subject button and message).
- guides/add-and-style-text.md: state.rs (Horizontal Type Tool, T),
type_tool.rs, menu_catalog.rs Layer Style and Type menus, dialogs.rs.
- guides/export-an-image.md: export_dialog.rs (labels, formats, options).
- troubleshooting/taskbar-turns-black.md: issues 3550, 3864, 3430, 3144,
PR 3308 (git: not an ancestor of v0.7.0), prefs_ui.rs (System title bar).
- troubleshooting/text-moves-when-editing.md: issues 3795, 1317, PR 3819
(open).
- troubleshooting/get-help.md: menus.rs, links.rs, gpu_status.rs (System
Info content).
- faq/*: README (status, license badge, offline, formats), LICENSE-MIT and
LICENSE-APACHE, docs/file-format-parity.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(user): apply final review fixes
Part 1: verified = latest release tag policy in docs/user/AGENTS.md; released-pages sentence in root AGENTS.md. Part 4 items 1-9: remove x86 row and TGA resolution, formats, new photocraft-does-not-start page, mask label, taskbar workaround, wording, Affinity read-only, index.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(user): use the standard section title
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(user): describe PhotoCraft 0.7.0 startup behaviour
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(user): tighten PhotoCraft startup wording
Drop the unsourced "slower" claim for CPU mode and give the exact
Preferences path on Windows and macOS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: pin the docs checker to a commit and its SHA-256
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: drop the docs workflows until the checker is on artcraft-services main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: re-verify against 0.8.2
- taskbar and PSD text pages: both fixes ship in 0.8.2; tell users to
update and drop the "not in a release yet" wording and workarounds
- does not start: 0.8.2 fixes the Intel Iris Pro/Monterey Device(Lost)
case; OpenCore Legacy Patcher Macs still have no fix
- install: Windows 10 or later, 32-bit Windows builds, RISC-V deb/rpm,
Wayland paste and XWayland notice
- open and save: Krita and Paint.NET filters, Krita/PCX/FITS formats,
save progress, JPEG/WebP encoding kept on Save
- export: TGA Resolution, multi-layer Export As and Quick Export
- text: Shift-click starts new text over existing type
- CLAUDE.md is now a regular file pointing to AGENTS.md
- verified: 0.8.2 on every page
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop the root AGENTS.md docs rule; keep the PR to docs/user/
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(user): drop the stale missing-tools count from the Photoshop FAQ
Re-verified the page against v0.9.0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(filters): Lens Blur reads its depth from an alpha channel, listed in Depth Map › Source as in Photoshop (#7400)
* test(filters): Lens Blur depth from an alpha channel by name or index, its errors, and the Source menu (#7400)
Copy refused fill and adjustment layers ("the active layer has no
pixels"). With no pixel selection they are now taken whole, like smart
objects, type, shape and group layers, and Paste recreates them in this
or another document. An adjustment layer's clip spans the canvas, so
paste placement and the OS-clipboard mirror stay consistent.
Scorecard UI-217-23 note, gaps.md G9 and target-app-parity.md updated;
the row stays partial (a single pixel layer still copies pixels only).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Edit > Preferences > Enhanced Controls > Enable Gestures (enhancedControls.touchGestures)
was saved but read by nothing, so it was hidden from the dialog. Now, with it off, two
fingers on a touch screen no longer zoom the canvas or the Navigator, nor turn the view.
The mouse and trackpad behave as before, including a trackpad pinch or Rotate event that
arrives while fingers rest on the screen.
- wheel_nav: pinch_factor() drops egui's multi-touch zoom when gestures are off and keeps
the trackpad's own Event::Zoom (Windows' folded Ctrl + wheel pinch included);
read_with() takes the preference.
- rotate_view: trackpad_delta() only falls back to the multi-touch rotation while gestures
are on; Rotate events always count.
- canvas, Navigator: pass the preference through.
- prefs: touchGestures leaves HIDDEN_UNTIL_IMPLEMENTED, so the dialog shows it.
- Tests: synthetic two-finger pinch and twist, on and off, and the trackpad events with
gestures off.
- docs/scorecard.md regenerated: settings that do nothing 26 -> 25 of 168.
* Curves: Cmd/Ctrl-click on the image adds a point at the pixel's tone (part of #7325)
With Image > Adjustments > Curves open, a Cmd-click (Ctrl-click off the Mac) on
the image adds a point to the channel shown in the dialog, at the pixel's tone
and at the curve's current output there; Shift adds one point on each of Red,
Green and Blue. Only the press adds, an armed eyedropper still wins, and the
control channel's ui.pointer does the same with command: true.
* Curves: Cmd/Ctrl-click adds the point with the Hand tool too; Space and middle button still pan
The Hand tool took the Cmd-click press for a pan, so no point was added; the
press now counts before the Hand. On the control channel, ui.pointer with
space or the middle button pans as the mouse does instead of adding a point.
* Curves: the Cmd/Ctrl-click point's comment says where the curve can still change
* Type: right-click on committed text opens the type layer menu (#7324)
With the Type tool, a right-click only opened a menu while text was being
edited; over committed type nothing opened. The menu was keyed on the edit
session alone (`editing_text`).
Over committed type, a Type tool (not a Type Mask tool) now opens
TYPE_LAYER_MENU: the type edit menu's layer rows, without the edit rows
(Undo, Cut, Copy, Paste, Select All), which would act on the document, and
without Faux Bold/Italic, which act on the characters being edited. Type
outside the selected layers becomes the target first, as a click with the
Type tool makes it. The control channel's right button takes the same path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Type layer menu: the clicked type becomes the active layer in a multiselection (#7324)
The structural rows (Rasterize Type, Convert to Shape, Create Work Path, the
paragraph/point conversions) act on the active layer. When the right-clicked
type was already selected but another selected type layer was active, they
acted on that other layer. Make the clicked type the active layer while
keeping the selection (layer_multi_cmds::set_selection), so the formatting
rows still act on every selected type layer. Found by CodeRabbit on #7430.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
No textual conflicts, but the merge did not compile. main's 589941a5 added a
second AtomicFs test impl using copy_permissions, while this branch renames that
trait method to copy_metadata. Git merged both cleanly because they sit in
different parts of the file, giving a tree whose trait and impl disagree:
E0407, E0046 and E0599 in photocraft-format's lib test. That one error is what
failed all four CI jobs.
main's new impl is renamed to copy_metadata to match.
Verified with --features heif, which is what CI builds with and what I had
previously failed to pass locally: clippy --workspace --all-targets --locked
--features heif -- -D warnings is clean, and the workspace tests pass.
* fix(print): the last print's settings are kept in the preferences, so Print opens with them after a relaunch (#7410)
* test(print): the last print's settings survive a relaunch for Print and Print One Copy (#7410)
When the crop quad's horizon crossed the layer and most of the layer
(or its centre) lay beyond it, warp picked the source's majority side
and skipped every output pixel, so the crop came out blank. A clip
wholly on one side of the inverse horizon only ever samples that side,
so draw it; a straddling clip keeps the majority rule (#4863).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The target size was read with as_u64 only, so 100.0 (json.dumps of a
float, many agent clients) was refused as not a whole number, while the
crop rectangle and image.imageSize accept it. Fractional, negative,
zero and out-of-range values are still refused.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(shortcuts): plain , and ; bindings follow the typed character on AZERTY
The positional match for , and . (so Shift+Cmd+. works on Nordic/German layouts)
applied to every shortcut. On AZERTY, ; sits on the US comma key, so ; fired the
command bound to , (brush hardness up/down did the same thing). Only shortcuts
with Cmd now follow the key position.
* contributors: credit Byxis by their GitHub profile name
The synchronous filter-preview path (web, and sessions with background
jobs off) flattened each changed preview, uploaded it and recorded it,
then flattened it again for publish_filter_preview, which uploaded and
recorded it a second time. Flatten once, as the background worker does,
and let publish_filter_preview do the upload and the perf record.
This removes one full composite and one texture upload per changed
preview, and the frame's refresh time no longer counts the preview twice.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Found while testing crates/format/Cargo.toml:19 (serde_json features), used for the manifest in crates/format/src/manifest.rs and store.rs
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
* fix(actions): stop resumed playback when a dialog's worker fails
* test(actions): reject suffix after failing background dialog filter
* fix(actions): propagate the failed worker result through dialog confirmation
* fix(dialogs): return action worker failures to callers
* Collapse the nested if in actions::resume (clippy::collapsible_if)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(remove): cancel waiting strokes when their target changes
* fix(remove): store pending target identity with waiting strokes
* test(remove): cover queued strokes across layer and channel switches
* Tests: format retouch_ui.rs and drop the unreachable! arm clippy denies
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(fade): detect uniform mask changes and blend full canvas
* test(fade): cover uniform mask inversion and Fade across bit depths
* style(fade): keep default-pixel comparison readable
* Format edit_menu_cmds.rs with rustfmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Typing or pasting over a selection now takes the style of the first selected character instead of the one before it, so retyping a smaller second line no longer takes the first line's size from the line break. Fixes#7413.
* file.saveACopy refuses the document's own file without "overwrite" (#7437)
Save a Copy through the engine (control channel, MCP, serve, headless
action playback) silently replaced the open document's own file when
given its path, so a lossy re-encode could not be undone. It now returns
BadParams when `path` is the active document's file (same text, ./ or ..
spelling, hard link, case on Windows via same_file) unless the params
pass "overwrite": true, and nothing is written. The desktop dialog writes
through the shell after the OS confirms a replace, so it is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* same_file: compare file identity on Windows, so a hard link is the same file
On Windows `same_file` compared canonical path strings, so a hard link to
the input (another name for the same file) passed the new convert guard
without --in-place. It now compares the volume serial number and file index
(the `same-file` crate, already in the dependency tree; std's file_index is
unstable), as unix compares dev and inode, falling back to the
case-insensitive canonical path when a file won't open. The engine's
same_file test now checks hard links on every platform. (Review on #7454.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* saveACopy own-file test: hard link on every platform, doc state unchanged after a refusal
Each refused copy (same path, ./ spelling, hard link) now also checks the
document keeps its path, revision and unsaved state. The hard link is no
longer unix-only: same_file compares file identity on Windows too. (Review
on #7456.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(type): ↑/↓ on the focused font field step through the families after a pick, as in Photoshop (#7407)
* test(type): the font field keeps the focus after a pick and ↑/↓ step the families (#7407)
* fix(type): font menu rows keep their size when hovered or selected, so the list no longer jumps (#7406)
* test(type): hovering a font row moves no row and keeps the menu width (#7406)
an_authenticated_connection_outlives_the_authentication_deadline started
its 200 ms deadline after it spawned the thread that writes the first
line. When the OS started that thread late, the first read failed with
TimedOut at crates/automation/src/security.rs:447. On a loaded machine
the photocraft-automation lib tests failed 5 of 30 runs this way.
The test thread now writes the first line before the deadline starts,
and the limit is 1 s. The sender thread writes the second line after
the test thread has called authenticated() and 300 ms after the
deadline, so the second read still waits past the deadline. The test
still fails when authenticated() is not called. The product's
AUTH_DEADLINE is unchanged.
the_frame_loop_keeps_running_during_a_slow_job_and_esc_cancels_it
stepped frames until 400 ms of wall time and 10 frames had passed, and
then looked for the job's status message, "Filtering tiles", in the
progress dialog. The job's worker reports that message as its first
statement, on a thread of its own. The dialog appears after 250 ms
whether or not the worker has run, and it reads "Working…" until the
message arrives. On a loaded machine the worker can start after the
400 ms window, and the test then fails with "the job's status
message".
The test now steps at least 10 frames and until the dialog shows
"Filtering tiles", through a new helper, frames_until, which fails the
test when the condition does not hold within 60 s. It also checks that
the job is still running at that point. The helper has the same
signature as the frames_until in #3422.
* Preferences: the settings fill the resized dialog, buttons at the bottom (#7420)
#5170 made Preferences resizable, but its contents still sized themselves
from the app window (width capped at 1040, settings area at 680 tall), and
the dialog opened at a 960x900 default taller than that. So it opened with
empty space below the buttons, and enlarging it left the settings and the
buttons stuck in the top-left corner.
The settings now take their width and height from the resizable area,
leaving room for the button row, and Preferences opens at 1040x800. The
settings area stays a few points short of the space it is given: egui grows
the area to fit its contents, so asking for more would make the dialog grow
every frame.
* Preferences: a rendered test drags the resize grip and checks the buttons stay in the corner (#7420)
Opens Preferences in a kittest harness, drags its grip wider and then smaller, and checks the button row stays in the frame's bottom-right corner each time and that the dialog keeps its size afterwards. Fails with the sizing from before the fix.
* Preferences resize test: the buttons must also stay inside the frame
A button row past the frame's bottom or right edge gave a negative gap and passed; now each gap must be 0 to 24 pt.
#2455 stretched the pro Color panel's SV field and hue strip to the dock
group's height, but only the legacy dock told the field that height. The
shared docking renderer from #3097 drew the panel without it, so once the
layout was customized the field fell back to 120 pt. Both docks now offer
the group body's height through one helper.
`composite_ms` measures encoding and submitting a composite, not running it: on
a 24 MP refresh it read 7.7 ms for work the GPU spent ~171 ms on, so nothing in
the app could see a regression that moved work onto the GPU.
A timestamp is written on the GPU timeline at the start and end of every command
encoder a refresh submits, and the spans are summed and read back
asynchronously. Nothing waits, so a measurement arrives a frame or two later and
carries the pixel count of the refresh it belongs to. It is reported as a new
`Option<f64>` `perf.gpu_ms`; `composite_ms` keeps its meaning, so its consumers
are untouched. At 24 MP it reports 83.9 ms against a waited wall clock of
88.4 ms, where the app previously said 3.1 ms.
What it leaves out, documented in the module: queue-level uploads, which wgpu
runs in its own command buffer ahead of ours, and the end-of-batch writeback
after the last timestamp. That is ~5 % of the reported time here and
proportional to the composite, against the ~22x of the number it replaces.
Unavailable rather than wrong: without TIMESTAMP_QUERY and
TIMESTAMP_QUERY_INSIDE_ENCODERS the timer is never built and `gpu_ms` is None —
on older drivers, the CPU compositor, wasm, and Apple silicon, whose GPUs
support counter sampling only at stage boundaries. `PHOTOCRAFT_NO_GPU_TIMING`
suppresses both the timer and the feature request.
Pass-boundary timestamps would cover Apple silicon and need only
TIMESTAMP_QUERY, but they capture 70-78 % of the encoder-level span here and
the shortfall grows with document size, so they were measured and rejected.
D3D12 is exercised: device creation, a 100 ns/tick period and the full
write/resolve/map round-trip succeed with no validation errors, including
across a flushed three-buffer refresh. Numbers throughout are from an
integrated Intel GPU on Mesa/Vulkan and a software D3D12 adapter; no vendor
Windows driver and no Apple hardware was tested.