- Cache Levels 1 in Preferences turned off the 250 ms recount throttle,
because "forced" was derived from max_level == 1: only an Uncached
Refresh skips it now.
- The proxy factor became 2^(level-1) for every count, so the Levels,
Curves and Threshold dialogs counted a 6000x3000 image on an 8x proxy
instead of main's 7x (a 36 MP one 16x instead of 9x). Uncapped counts
use main's factor again; only a Cache Levels cap halves by powers of two.
- The panel opens in Compact View, as in Photoshop, and Compact View
counts the entire image whatever Source Expanded View was left at.
- The clipping corner marks are gone: Photoshop's Histogram panel has none.
- ui.inspect: "preview" counts only the active document's preview, and
"current" compares the source and cache level too, not only the
revision (a Source change left it true until the recount landed).
- ui-parity row lists two more differences (selection, Selected Layer
with a dialog preview); cargo fmt.
Co-authored-by: mathildalabs <347373177+mathildalabs@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged with our fix-up pr-3297-fixup (1de1e9eb58) on top of the author's head 053e1e0d03.
Conflicts resolved: channel_cmds.rs routed() keeps the PR's PAINT_IDS list plus the batch's
paint.artHistoryBrush; paint_cmds.rs Paint Bucket params keep the batch's "mode" and the PR's
"filterMask" target; docs/scorecard.md takes the batch's numbers with FILE-215-3 done.
Redone on the PR head 053e1e0d0 (the author's main merge); the earlier
fix-up f7af09d0f carried the first six points.
With a smart object's filter mask targeted, every routed command got
"target":"filterMask" and the filter-mask precondition instead of its own:
- Cmd+T and Edit > Transform > Flip/Rotate failed ("can't transform a
filter mask on its own"); now the smart object transforms, as in
Photoshop (lone_target -> Ok(None), and transforms get no target).
- Move (layer.translate) re-rendered the whole filter stack on every
nudge, and failed for contents that can't render.
- Convert for Smart Filters became enabled and nested the smart object;
Apply Image, Last Filter, Liquify, the lens filters, Vanishing Point,
the render filters and Desaturate ignore the target.
`follows_filter_mask` now leaves these out, in the shell, in
inject_target, in the precondition and in the re-render hook; Paste
keeps its own precondition.
- With the filter mask shown (Alt-click), clicking the layer, layer-mask
or vector-mask thumbnail (or ui.set maskTarget/vectorMaskTarget/
filterMaskTarget:false) left the view on (and ui.set maskTarget:false
left the filter mask targeted), so strokes kept landing in
the filter mask; those now end the view (CodeRabbit, panels.rs).
- ui.set filterMaskTarget:true on a layer without smart filters, or with
a single channel targeted, cleared the mask target and answered ok; it
is now refused before anything changes (CodeRabbit, control.rs).
- Curves on a targeted filter mask showed colour channels and the smart
object's histogram; it is now a gray plane with the mask's histogram
(GraySurface::FilterMask).
New on this head:
- Filters with a parameter schema (Gaussian Blur, High Pass, ...)
refused "target":"filterMask" ("`target` must be pixels, mask,
quickMask or {channel}"), so blurring the filter mask, the usual way to
feather it, failed from the menu, the dialog and agents. The filter
validation now accepts it (blurring_the_filter_mask_feathers_it).
- Delete with a selection and the filter mask targeted ran Edit > Clear
on the smart object ("not a pixel layer"); it now fills the filter mask
with the background colour there, as Photoshop does. With no selection
Photoshop deletes the layer, which Delete already did
(delete_with_the_filter_mask_targeted_edits_the_filter_mask).
- docs/control-protocol.md: the ui.set line lost its closing parenthesis
in the main merge.
Co-authored-by: Valentin Klein <271991477+vangel76@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
panels.rs: the PR's snapshot rows next to main's History Brush source
and scroll-to-current; ui-parity.md: main's status line.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Open wrote PhotoCraft's crs: subset over any existing sidecar, on by
default: a Lightroom or Bridge IMG_0001.xmp lost its crop, masks, rating
and keywords, even when the raw was only opened with Open in Camera Raw
off (FredApps' review). Now a sidecar is only written when the settings
changed, never over one whose x:xmptk isn't PhotoCraft's (a notice says
it was kept, camera_raw_xmp::is_photocraft_xmp), and never for a control
or MCP request, which has no authority to write beside the user's files.
A write error goes to a notice instead of a status line the develop
overwrote at once.
The Camera Raw Defaults' colour space, 8-bit and smart-object steps ran
before the Camera Raw step, so with the default Adobe RGB every raw opened
with the dialog's settings applied to other pixels than it previewed. They
now follow the step (also when a background re-develop lands), as
Camera Raw converts to the workflow space last. Main already sets the raw
resolution at import (#7811), so the PR's Image Size step is dropped.
Two raws opened back to back with raw-stage edits: the second one's
background re-develop replaced the first one's pending entry, so the first
never got its Camera Raw step, conversions or unmodified mark. Pending
re-develops are now a list keyed by job. Sidecars are read only from
regular files of at most MAX_XMP_BYTES (a pipe or a huge file named
IMG.xmp no longer blocks or fills memory), and a conversion that fails
after the Camera Raw step is reported instead of leaving the dialog open
for a second Open that would apply the step twice.
Tests: a_foreign_sidecar_is_never_overwritten,
a_rating_only_sidecar_is_left_alone,
opening_without_the_dialog_does_not_rewrite_the_sidecar,
a_control_request_never_writes_the_sidecar,
a_sidecar_write_error_stays_reported,
the_camera_raw_step_runs_before_the_workflow_conversion (inline and
background), two_raws_opened_back_to_back_both_finish_their_open,
sidecars_are_read_from_small_regular_files_only,
is_photocraft_xmp_reads_the_toolkit_marker; the existing
sidecar round trip now uses a PhotoCraft-written fixture.
Co-authored-by: Fabio Lissi <3223836+FabioLissi@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
prefs.rs keeps main's demosaic default and the PR's write_xmp_sidecars, and
unhides the Camera Raw Defaults the PR makes live; the raw-open RawOpen built
without the dialog gains main's developed_with/demosaic; i18n and docs as
unions; docs/scorecard.md is regenerated in the fix-up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Say that `photocraft --help` lists the flags and their variables, that
a flag wins and an empty variable is unset, and what is a usage error;
that a bad --present-mode or --frame-latency is one while a bad
variable is still ignored; list PHOTOCRAFT_IN_WINDOW_MENUS; and that a
token with a token file is a usage error for the control server too.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
With the environment fallbacks, clap's conflicts_with on the token pair
made a stale exported PHOTOCRAFT_CONTROL_TOKEN and
PHOTOCRAFT_CONTROL_TOKEN_FILE a usage error for every mcp and serve, though
headless mcp and stdio serve read no token. As on main, the pair is now
refused only by serve on TCP and mcp bridging, still as clap's either/or
usage error naming each half as it was given.
An automation root on the command line with PHOTOCRAFT_CONTROL_PORT set
is a usage error too: the variable would switch mcp to bridging, which
drops the roots, with only a note on stderr.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Say that PHOTOCRAFT_CONTROL_PORT is the fallback for serve --port and
mcp --bridge (with the stderr notice and the empty-is-unset escape),
and list the CLI's variables on the book's CLI page, which also says
the automation root variables stay the desktop app's.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Step 4 of the issue: the control token flags and the automation roots
move to photocraft-automation's new args module as two clap structs,
which mcp and serve flatten and the desktop app can flatten in step 5
without moving them again. clap is a plain dependency of the crate.
- ControlArgs holds --control-token and --control-token-file;
AutomationArgs holds the roots, and AutomationArgs::workspace opens
them (replacing the CLI's automation_workspace).
- The port value parser is photocraft_automation::args::control_port,
with its unit tests, for serve --port and --bridge now and the
desktop app's --control later. It ignores surrounding spaces and
refuses anything else with a typed NotAPort.
- --bridge parses to Bridge::Port or Bridge::Addr: it takes a bare
port, meaning 127.0.0.1:<port>, or any value containing a ':', which
the bridge client then validates. Anything else is a usage error
(exit 2) instead of a bridge error.
- 127.0.0.1 addresses are built from Ipv4Addr::LOCALHOST.
mcp_bridge_takes_a_port_or_an_address is new: `--bridge nope` is a
usage error, and `--bridge 1` bridges to 127.0.0.1:1, which a tool
call names when nothing listens there.
No new environment behaviour: the token variables are still read at
run time through security::token_inputs, and the roots stay flag-only.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts resolved by keeping both sides:
- ui-egui control.rs: UI_SET_FIELDS has main's updatesOpen and the
histogramPanel, histogramRange and histogramRefresh fields (42).
- automation server.rs: the ui_set description names all of them.
- docs/ui-parity.md: main's header, with the Histogram panel entry
added to its change list.
- docs/scorecard.md: regenerated with `cargo xtask scorecard`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Compact, Expanded and All Channels views; the channel is kept across views.
- Channels per colour mode, plus Luminosity and Colors (overlaid, mixed where
channels overlap); Show Channels in Color and Show Statistics in the panel menu.
- Statistics for the level under the pointer or a dragged range: Mean, Std Dev,
Median, Pixels, Level, Count, Percentile, Cache Level.
- Source (Entire Image, Selected Layer, Adjustment Composite) for multilayer
documents.
- Adjustment previews counted live, with the original outlined over them.
- Cached-data warning and Uncached Refresh; Preferences > Performance > Cache
Levels (1-8) caps the cache level.
- ui.set histogramPanel / histogramRange / histogramRefresh, ui.inspect histogram.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An agent's ui.menu.invoke reached the main window's menus while a full-window workspace was
open: window.document.N switched documents and the workspace closed, dropping its work.
Photoshop makes these dialogs modal.
menus::modal_allows now refuses other menu commands while one is open (distort_ui::
modal_workspace), so ui.menu.invoke and the macOS menu bar wait for OK or Cancel. The
control channel's Camera Raw continuation is generalised: the open workspace's own
{"ui": {...}} requests still pass, unless another dialog, a discard prompt or a crop is
pending. The menus' cache key includes the open workspace, so opening or cancelling one
without input rebuilds them. engine.execute (MCP run_command) is not a menu click and is
not gated.
Puppet Warp, Perspective Warp and Vanishing Point are on-canvas modes here, with the
in-window menus live, so they stay out.
The bridge forwards Replace Contents to the app, which runs it on its
active document, so a given `index` was silently dropped and could replace
another document's contents. It is now refused before anything reaches the
app, as bridge previews and saves already do; documented in the schema and
control-protocol.md. A fake-app test checks the indexed call never arrives.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- crates/ui-egui/src/layer_style.rs: values_of keeps the PR's method/smoothness keys and source-stop ends, and main's (#7072) unconditional From/To and geometry fields.
- crates/compose/src/cmyk_tests.rs (no textual conflict): add the PR's new `interpolation: None` field to main's CMYK test GradientOverlay literal.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflicts:
- crates/ui-egui/src/i18n/{cs,de,el,es,fr,hu,id,it,ja,ko,nl,pl,pt-br,ru,sr,tr,uk,vi,zh-hans,zh-hant}.tsv: kept every key of both sides (main's block, then the PR's Color panel block); no duplicate keys.
- crates/ui-egui/src/panels.rs: took the PR's removal of color_picker/hsb_sliders/rgb_sliders (moved to color_panel_ui.rs); kept main's color_field_height doc comment; main's #5842 slider recolour tests ported to color_panel_tests.rs for the new HSB/RGB slider modes; main's black-HSB test dropped as the PR's hsb_sliders_take_values_on_black covers it.
- crates/ui-egui/src/color_panel_ui.rs (no textual conflict, adapted): set_color now goes through apply_color::foreground_changed with the gesture (was type_tool::foreground_changed), so every mode (sliders, ramp, alerts, wheel) recolours selected type/shapes tool-gated in one undo step per gesture; panel() calls apply_color::track_focus.
- crates/ui-egui/src/color_panel_tests.rs (adapted): new every_slider_mode_recolours_the_selected_shape test.
- docs/gaps.md, docs/ui-parity.md: union of both sides' status lines, rows and revision history; the PR's done row replaces the old "missing" Color panel row.
- docs/scorecard.md: generated, took b7-base's version.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflicts resolved:
- crates/ui-egui/src/i18n/hu.tsv: kept base's Bevel & Emboss keys, then the PR's update-check keys
- crates/ui-egui/src/i18n/ru.tsv: kept base's Bevel & Emboss keys, then the PR's update-check keys
- crates/ui-egui/src/i18n/sr.tsv: kept base's added sections, then the PR's update-check keys
- crates/ui-egui/src/i18n/tr.tsv: kept base's Bevel & Emboss keys, then the PR's update-check keys
- crates/ui-egui/src/menus.rs: kept base's saved-workspace entries, then the PR's Help › Check for Updates reordering
- docs/gaps.md: base status line with the PR's G17 clause prepended; PR revision row above base's rows
- docs/target-app-parity.md: base status line (updated 2026-10-22) with the PR's clause prepended; PR revision row above base's rows
- docs/scorecard.md: generated, took b7-base's version
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The rpc test only checked the returned layer id, so a no-op that echoed
{"layer": id} passed, its undo check passed on doc.place's own step, and
the bad-layer case asserted on an empty reason. It now checks the smart
object's file name and rendered centre pixel (42 -> 200 after the
replace, back to 42 on undo, 200 on redo), that every refusal changes
nothing, and that layer 999999 fails with "no such layer".
The bridge row in docs/control-protocol.md claimed doc_replace_smart_object
maps onto layer.smartObjects.replaceContents; the desktop refuses that
command (ambient path), like doc_place's file.placeEmbedded. The row now
says so and points to the headless / workspace server method.
Co-authored-by: VishnuByrraju <112673081+VishnuByrraju@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Main moved about 1,860 commits since the PR's last merge. i18n catalogs keep both
sides' rows; control.rs keeps updatesOpen next to main's strokeTrace (39 fields);
Services keeps check_updates next to main's place_inbox; Cargo.lock is main's,
re-resolved for ureq and semver; docs/gaps.md, docs/target-app-parity.md and
docs/scorecard.md are main's (the PR's entries come back in the next commit).
Co-authored-by: ZhuanTou33212 <285931757+ZhuanTou33212@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's Color panel HSB/RGB slider switch (#7172, #7317) is replaced by this
branch's panel-menu modes, which include RGB Sliders and HSB Sliders; its
control test now sets colorPanel.mode. The panel takes main's shared
photocraft_color HSB conversions and ui.set's f32-range checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
crates/ui-egui/src/lib.rs: replay_save doc comment names both Export As and JPEG; replay_save uses the PR's FormatChoices::from_step, after main's export_dialog::replay branch.
docs/file-format-parity.md: header keeps main's text plus the JPEG Options item (last updated 2026-10-19); TIFF row is main's, JPEG row is the PR's; change-log rows once each, newest first.
docs/gaps.md: header keeps main's text plus the G10 item (last updated 2026-10-19).
crates/ui-egui/src/lib.rs: union in the PhotocraftApp fields, the initialiser and set_wgpu (main's window_device/window_lost/window_recovery_retry and set_available, the PR's gpu_render_state/gpu_fault).
docs/control-protocol.md: ui.gpu.simulateLoss line takes the PR's text and keeps main's destroy: true sentences.
crates/paint/src/brush_tests.rs: both sides added tests at the same spot; kept main's four smoothing tests and added the PR's three pose_axes tests after them.
docs/brush-parity.md: kept main's header and added the PR's change item.
docs/gaps.md: kept main's header, added the PR's change item; G4 progress notes and the change-log row kept from both sides.
docs/control-protocol.md: kept main's eyedropper line (with the document.sampleColor wording) and added the PR's Brush Pose line before it.
docs/control-protocol.md: kept main's table rows (document.inspect with snapshots, layer.list) and added the PR's four-floats wording to document.pixel.
openingRaw is {name, path, demosaic}; ui.demosaic is accepted only while a raw
opens; rawDefaults.demosaic is the method the app's raw opens develop with; Open
re-develops when the chosen method differs from the one the open used.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>