Port behavior and regression coverage into current modules; preserve current upstream behavior.
Port visibility preference, choice registration, live panel style and canvas override. Keep current animated camera and toolbar exclusions. Relocate misplaced persistence regression to prefs tests; retain current parity score counts.
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Added Alt guide rotation in current ruler/tool-input modules and engine Move Guide, retaining click-versus-drag selection, deletion on either ruler, colors, ruler-origin units and snapping. Selection follows the new orientation/index after a successful turn; original guide-turn/deletion regressions retained.
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Kept current stronger Hand modifier zoom implementation including sticky modifiers, primary-release ownership, middle-button pan and Free Transform exclusions; retained the original approved zoom/pan/release regressions alongside newer guards.
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Ported hanging punctuation into current line placement and kerning modules, preserving forced breaks, bounded edge-tip row merging, justified-last-line behavior, area exclusions and independent Roman orientation shaping; kept PSD/engine/UI regression coverage.
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Registered adjustment presets in current dialog modules, preserving current editor dispatch, explicit PhotoCraft preset provenance, Custom matching and discrete preview/history regressions.
CodeRabbit on #9775: the 12-of-181 count changed today, so ui-parity.md and gaps.md get the date in their status lines and a revision-history row, as AGENTS.md asks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflicts with main's batch #9880:
- psd file.rs / layer.rs: #5287 (PSB by encoded size, RLE tables widened on
promotion) against #6583's lazy reader. Main's new widening and length code
read `.data`, which #6583 leaves empty while the bytes stay in the source;
it now goes through `encoded()` / `encoded_len()` (also the auto-merged
lines in `rle_widening`, `encoded_len` and `LayerRecord::write`), and the
lazy reader sets `write_as_psb: false`. New test
`a_lazy_psd_promotes_to_the_same_psb_as_an_eager_one`: a lazily read PSD
writes the same PSB bytes as an eager one; it fails with a `.data` read.
- direct_select.rs / vector_ui.rs: #9321's Type Path and #6353's hidden
Work Path both kept.
- io tests main.rs: both modules. scorecard.md regenerated (12 of 181);
ui-parity.md and gaps.md follow it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CodeRabbit on #9775: Place::AutoEditable puts type, shapes, pastes, placed files and frames above a group whose lock would refuse them; ui-parity.md's status line and checklist row said every new layer goes into an open active group.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CodeRabbit on #9775: FontLibrary::finish wrote a failed type.font.favorite to the status bar as plain information; it now sets status_error like the other status writes. The regenerated scorecard counts 12 of 179 preferences that do nothing; ui-parity.md and gaps.md still said 178.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Main moved the engine commands into crates/engine/src/cmd/ and the
dialogs and panels into submodules. The PR's placement calls follow
them, and main's new insert sites (new_adjustment in adjust_params.rs,
Place in file_cmds/open.rs, shapes in vector_cmds/shape.rs) use
layer_place::insert_new like the PR's originals. The New Group dialog
moves to dialogs/new_group_ui.rs; the Layers panel flyout's New Group…
opens it.
Main's Session::precheck now also checks layer.new.group's and
layer.new.groupFromLayers' new params (name, color, mode, opacity,
position), so precheck_agrees_with_execute holds. Main had since added
"New Group…" to every catalog: the PR's duplicate rows are dropped.
Main's newer tests that made a layer with an open group active now
open the group last, as the PR did for the older ones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PR's follow-up commit appended the reworded entries instead of
replacing the first ones, so both versions were in the doc. Keeps the
"at least 53 pt tall" wording.
Co-authored-by: hectorviov <1501713+hectorviov@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebasing onto main put this PR's history row above main's newer rows
(CodeRabbit flagged the same on #9282).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeRabbit on #9282 (inline 4373668508): the rebase put this PR's
2026-10-24 row above main's 2026-10-25 rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeRabbit on #9154: the Hue Cube and Brightness Cube draw the ⚠ only when
the chips column is at least 53 pt tall; the slider modes always do. The
ui-parity row, history entry and status line now say so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Photoshop's Color panel shows the out-of-gamut warning and a chip of the
nearest printable colour under the foreground/background chips in its
Hue Cube, the default mode. PhotoCraft drew it only in the slider modes.
The Hue and Brightness Cube now draw the same alerts (a click takes the
nearest printable colour), when the field is tall enough to hold them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes#9263, #9265, #9268, #9270. Each of these adjustments gains a
Preset menu at the top of its editor (dialog and Properties), with the
preset names Photoshop uses and Custom once edited; a choice is one edit.
The settings are PhotoCraft's own, designed for each look: Levels
presets lift, lower or stretch the tones; Exposure presets are whole
stops; the Black & White and Channel Mixer "filter" presets follow what a
coloured lens filter does on black-and-white film. None is taken or
measured from another application's presets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brush-parity.md, gaps.md and ui-parity.md had 2026-10-25 revision rows
under a 2026-10-23/24 "Last updated" header; the headers now carry the
latest row. gaps.md (24 of 171), ui-parity.md and target-app-parity.md
(46 of 149) quoted stale counts of preferences that do nothing; they now
quote the scorecard's 12 of 178. target-app-parity.md still listed the
Camera Raw Calibration panel as missing, which landed on 2026-10-13
(#5128); the row now names per-camera colour for non-DNG cameras, as
gaps.md G14 does.
Found by CodeRabbit on #9386.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The open scroll brings the marked row itself into view, keeping the recent
group's top in view when the row fits below it: with Font Preview Size Huge
and 10 recents, recent #8 opened below the fold.
- The open scroll is armed for the first laid-out pass only, whether or not a
row matched: with a missing font or a mixed selection the scrolled list
jumped later, once the font loaded or the selection settled.
Co-authored-by: hectorviov <1501713+hectorviov@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
davidmann4's round-3 review of #8347: a marks row the box had to split
out of a run folded row after row into one line running past the edge by
almost the whole run, and a row merged into a justified paragraph's last
line kept the row above's spread instead of the last-line align.
- A marks-only row steps back only while it is at most two clusters (the
tip that hangs at the edge); a row above that already ends in a
hangable mark takes no more.
- A justified paragraph's last row keeps its own align (centered, right
or left): the placed clusters already carry the spread, and the merge
only moves x1/range.end.
- hanging_marks_of_a_split_run_keep_their_breaks pins that a run keeps
its rows and no line outgrows the box by more than two marks;
hanging_marks_stay_off_a_justified_paragraphs_last_line pins the
JustifyCenter/Right/All last lines. The old merged-row test now pins
the tip (a_stepped_mark_row_keeps_its_clusters_and_glyphs_in_step).
- 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>
* fix(ui): Properties Transform shows a shape's path, not its stroke
For a shape layer, Properties › Transform showed the pixel bounds, so a
400 × 300 rectangle with a 15 px stroke read W 416, H 316, X 192, Y 142,
and typing a width scaled those bounds rather than the path. As in
Photoshop, the fields now show and edit the path's geometry (W 400,
H 300, X 200, Y 150); pixel and other layers are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Properties: a shape's W/H takes edits under half a pixel
CodeRabbit finding on #9181 ("Apply fractional W/H edits below 0.5 px"): the
W/H field and transform_params ignored changes under 0.5 px, so a 400.25 px
path typed as 400 kept its old width, though the fields now show and scale
the path exactly. For a shape the threshold is half the field's last shown
decimal (focusing and leaving the field, which re-reads its rounded text,
still changes nothing); pixel layers keep half a pixel. New test:
a_shape_path_takes_a_width_edit_under_half_a_pixel (the edit is dropped
without the fix).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
A swatch recoloured the selected type layers only with a Type tool, and
shape fills only with a shape or Pen tool. Photoshop 2025 recolours both
whatever the tool (measured with the Move and Brush tools); its Color
panel keeps to the tool-based rule, which is unchanged here.
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>
- Tolerance defaulted to 50 and averaged alpha into the difference, so an
ordinary edit (e.g. #c03030 -> #d04848) was never painted back, and a stroke
that wrote nothing still left an empty History step. Photoshop's default is
0; the difference is now the larger of the mean colour change and the
alpha change (the Eraser keeps colour and lowers alpha), and a stroke that
writes nothing is an error instead of an empty step.
- Each tile copied a source window of tile +- halo per dab: a leftward stroke
or a curl across x = 64 read empty pixels there (seam), and a size-1000 click
copied ~5e9 floats (11.9 s). The rotated read stays inside the dab, so it now
samples the source directly; bilinear reads at pixel centres, so an
unrotated dab copies the history pixel instead of a 2x2 blend.
- The brush used its own history lookup (state 0 by default): it now takes
the History Brush's source (history.setSource, snapshots, refusal after a
canvas size change).
- TOOL-213-20 is partial (8 of 10 styles, Area scales spacing); docs follow.
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Dragging every component with Path Selection moves the whole path
(`shape.edit` move), so a live rectangle or ellipse stays live instead of
losing its corner-radius widgets through `path.moveAnchors`.
- Esc and switching tools end a Pen draft with `pen_end`: a refused commit
(a locked layer) keeps the path as the Work Path instead of retrying
`shape.create` and the error on every frame. Return still keeps the draft
to commit again.
- The first component always draws as Combine: `path.setOperation` leaves
it alone (naming only it is refused), and the menu and + / - neither add
a History step for it nor show another operation.
- Only Path Selection, which shows the component selection, changes the
selected components; the Pen tools set the next path's operation.
A swatch recoloured the selected type layers only with a Type tool, and
shape fills only with a shape or Pen tool. Photoshop 2025 recolours both
whatever the tool (measured with the Move and Brush tools); its Color
panel keeps to the tool-based rule, which is unchanged here.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The open font menu marked the current family's alphabetical copy, even
when it also headed the recent group, and opened scrolled to the top
whatever the family, so the mark could be far out of view. As in
Photoshop, the current family is now marked in the recent group when it
is a recent one, the menu opens showing it (the recent group at the top,
else the family's row at the top of the view), and the arrows walk on
from the marked row. Applies to the options bar and the Character panel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Photoshop's documented hang list carries weights: period, comma, quote,
apostrophe and hyphen hang 100%, colon, semicolon, ellipsis and dash hang
50%; ! and ? are not in the list (davidmann4). The drawn positions never
change: the weights only come out of the composed width.
- A marks-only row after a forced break keeps its row: the merge-back must
not cross the author's break (CodeRabbit).
- The merge-back reads its pair style from the mapped byte; the box slack
and the justify-all path take the shaped advance scaled once and the
(already scaled) trim on top, so horizontal scale no longer scales the
trim twice; area type keeps its planned rows (hanging applies to point
and box lines only).
- The Styles editor row label goes through tl!, the dock comment loses its
(#...), space-before keeps upstream's arrow order, tests get abs() and
exact centering assertions; hi.tsv carries the label.
With the Type tool on a type layer (editing it, or with it selected),
the options bar showed the layer's font, size, anti-aliasing and colour
but always the tool's default alignment and paragraph direction. As in
Photoshop, the Left/Center/Right and direction buttons now show the
target paragraph's; without a type target they show the tool defaults.
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>
Feedback on #7648 from testing a vector portrait workflow:
- Path Selection selects path components: a click selects the one under the pointer, ⇧-click
adds or removes one, a click off every component deselects, and a drag moves only the
selected components (one `path.moveAnchors`). Their anchors show solid, as in Photoshop.
- Path Selection gets the Path Operations menu. With components selected, the menu changes
their operation through the new engine command `path.setOperation` (Work Path, saved path,
shape layer or vector mask; one History step), and its icon shows their operation.
- `+` (Combine Shapes) and `-` (Subtract Front Shape) pick the operation with the Pen tools and
Path Selection, on the selected components and for the next path. `+` is ⇧= on a US layout, so
with these tools it no longer steps the blend mode.
- The Pen carries on an open path from its endpoint in Shape mode (the selected shape layer)
and on a targeted vector mask, as it already did on the Work Path and saved paths. Closing the
open path a stroked-only shape layer was made of gives it the options bar's fill and stroke,
as if it had been drawn closed.