Commit Graph
190 Commits
Author SHA1 Message Date
David Mann 7c9d6ac344 Merge PR #8150: fix(automation): doc.replaceSmartObject replaces Smart Object contents from the read root (#8061) 2026-10-22 23:40:19 +02:00
David Mann fff01d8918 Integration: stack #7335 (its merge onto main, conflicts resolved) 2026-10-22 23:40:11 +02:00
David Mann 9e7b35b716 Integration: stack #6055 (its merge onto main, conflicts resolved) 2026-10-22 23:40:11 +02:00
David MannandClaude Opus 5.5 6c7e920d5a Merge PR #7335: Gradient Overlay: add Method selector and retain editable source stops
- 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>
2026-10-22 23:39:14 +02:00
David MannandClaude Opus 5.5 945a10dc7c Merge PR #6055: Color panel: Photoshop's panel-menu modes, the same in every theme (#6000)
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>
2026-10-22 23:39:04 +02:00
David MannandClaude Opus 5.5 f56147655d Merge PR #5485: Add opt-out startup release checks and manual update status
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>
2026-10-22 23:38:46 +02:00
95c3af68c3 Integration: prove doc.replaceSmartObject replaces and undoes (#8150)
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>
2026-10-22 21:36:42 +02:00
aiongg 5857afd121 Integration: track t11
# Conflicts:
#	apps/photocraft-cli/src/lib.rs
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/el.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/fr.tsv
#	crates/ui-egui/src/i18n/hu.tsv
#	crates/ui-egui/src/i18n/id.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/nl.tsv
#	crates/ui-egui/src/i18n/pl.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/sr.tsv
#	crates/ui-egui/src/i18n/tr.tsv
#	crates/ui-egui/src/i18n/uk.tsv
#	crates/ui-egui/src/i18n/vi.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
#	docs/gaps.md
2026-10-22 15:38:31 +00:00
aiongg 446b74a8de Integration: track t07
# Conflicts:
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/de.tsv
#	crates/ui-egui/src/i18n/el.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/fr.tsv
#	crates/ui-egui/src/i18n/hu.tsv
#	crates/ui-egui/src/i18n/id.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/nl.tsv
#	crates/ui-egui/src/i18n/pl.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/sr.tsv
#	crates/ui-egui/src/i18n/tr.tsv
#	crates/ui-egui/src/i18n/uk.tsv
#	crates/ui-egui/src/i18n/vi.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
#	crates/ui-egui/src/layer_shortcut_tests.rs
2026-10-22 15:38:21 +00:00
aiongg 82cf3c828c Integration: track t01
# Conflicts:
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/de.tsv
#	crates/ui-egui/src/i18n/el.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/fr.tsv
#	crates/ui-egui/src/i18n/hu.tsv
#	crates/ui-egui/src/i18n/id.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/nl.tsv
#	crates/ui-egui/src/i18n/pl.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/sr.tsv
#	crates/ui-egui/src/i18n/tr.tsv
#	crates/ui-egui/src/i18n/vi.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
#	docs/file-format-parity.md
#	docs/gaps.md
2026-10-22 15:38:14 +00:00
6f56a4d750 Integration: merge main, resolve conflicts (#5485)
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>
2026-10-22 16:36:42 +02:00
Khawar Zamman WaniandClaude Opus 5.5 cb56c99fbd Merge main into feat/6000-color-panel-modes
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>
2026-10-22 09:34:24 +05:30
Yonghyuk Choi bea003acd8 Rebuild unrecorded Gradient Overlays from their end colours 2026-10-22 12:38:20 +09:00
Yonghyuk Choi dc029b93c5 Merge main into Gradient Overlay method branch 2026-10-22 12:38:20 +09:00
aiongg dfde38919e Merge PR #7883: Save As JPEG: JPEG Options dialog (quality 0–12, Baseline / Optimized / Progressive) (FILE-215-19)
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).
2026-10-22 02:27:46 +00:00
aiongg a77b078589 Merge PR #7417: Camera Raw: choose the demosaic method when a raw opens
crates/io/src/lib.rs: kept main's import_psd (and its call in import_stages); import_stages takes the PR's ImportOptions argument.
crates/io/src/raw.rs: the PR's demosaic summary on main's developed_document(name, bytes, ...) call.
docs/scorecard.md: ours (regenerated at combine).
2026-10-22 02:25:41 +00:00
aiongg 48f391e912 Merge PR #6897: GPU fallback: Retry GPU after an error rebuilds the GPU canvas now, no restart (#6577)
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.
2026-10-22 02:25:08 +00:00
aiongg f271b0e07d Merge PR #7051: Save: ask before replacing a file that an added or lowercased extension names 2026-10-22 02:24:07 +00:00
aiongg b2ae473eab Merge PR #6415: Save for Web: Width, Height, Percent and Resample in Image Size, linked in proportion (#6388) 2026-10-22 02:24:04 +00:00
aiongg c08a6ad9cf Merge PR #5317: Brush Pose: preserve independent Tilt X/Y overrides
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.
2026-10-22 02:22:51 +00:00
aiongg 6afb66de55 Merge PR #3775: Control protocol: document.pixel / document.sampleColor reply ranges (#3666)
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.
2026-10-22 02:21:55 +00:00
aiongg e8c68586c6 Merge PR #5644: Layer Mask Display Options: set the layer mask rubylith's colour and opacity
docs/scorecard.md: kept ours (regenerated at combine).
2026-10-22 02:21:54 +00:00
UnemarsandClaude Opus 5.5 c695493153 Control protocol: the open-time Camera Raw dialog's demosaic choice
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>
2026-10-21 20:44:44 +02:00
aiongg 94d28535d3 Merge PR #6464: Survive a lost window GPU device: draw nothing, save unsaved work for recovery, close (#3438, #1215) 2026-10-21 18:15:02 +00:00
Higgs32584andClaude Opus 5.5 f916c98536 Merge upstream/main into revive/4330-jpeg-options
Conflicts only in the parity docs: status lines take main's text with the
JPEG Options entry (FILE-215-19) on top; scorecard takes main's rows with
FILE-215-19 done and File compatibility 10 / 12 / 5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 12:56:32 -04:00
Marco Arnulfo 562601eb47 Save for Web: Width, Height, Percent and Resample in Image Size, linked in proportion (#6388) 2026-10-21 11:40:38 +02:00
VishnuByrraju 6b99ac877c fix(automation): doc.replaceSmartObject replaces Smart Object contents from the read root (#8061)
layer.smartObjects.replaceContents takes an ambient path and is refused in
serve/mcp. Add the capability-scoped sibling of doc.place: the file is read
beneath --automation-read-root and handed to the command's own core, as an
RPC method (doc.replaceSmartObject) and an MCP tool (doc_replace_smart_object).
2026-10-21 11:04:23 +05:30
aiongg 4699d3605c Merge PR #7026: Automation: place and layer/data-set export by index keep the active document (#7025) 2026-10-21 05:04:01 +00:00
Khawar Zamman Wani 7aaed6de0d Merge remote-tracking branch 'origin/main' into feat/6000-color-panel-modes 2026-10-21 10:09:04 +05:30
Higgs32584andClaude Opus 5.5 92d1ff42f9 Merge upstream/main into revive/4330-jpeg-options
Main added DDS saving (#5993): DDS Options, and FormatChoices carrying the
Targa and DDS answers through every save path (this branch's SaveChoices
carried Targa and JPEG). Resolved by keeping main's name and making it
the one struct for all three answers.

- lib.rs: FormatChoices { tga_bits, dds, jpeg } with from_params (strict),
  from_step (lenient JPEG read of a recorded step), apply and record;
  main's separate block is folded in. Both prompts (dds_options and
  jpeg_options) are kept everywhere: save_to asks Targa, then DDS, then
  JPEG Options; save_options_open covers all four. save_in_place takes
  main's (path, choices) signature and records the plain-Save step with
  plain_save_step for JPEG plus the Targa/DDS answers (save_in_place_with
  folded into it). journal_as_plain_save relabels a parked Targa, DDS or
  JPEG prompt and keeps tgaBits/dds*/quality/jpegFormat when dropping the
  path. replay_save reads choices with from_step.
- SAVE_OPTIONS_OPEN names all four prompts; DDS Options uses it too.
- targa_options_ui: main's plain-Save step (no path) through this
  branch's write_answered_save. JPEG Options gets main's Enter-on-a-
  focused-Cancel fix.
- view_cmds Save a Copy asks Targa, DDS, then JPEG Options; write_copy
  takes FormatChoices.
- control.rs: both app.save tests (JPEG and DDS) kept.
- engine file_cmds: SaveOpts keeps jpeg_format and dds; param docs list
  both jpegFormat and ddsCompression/ddsMipmaps. CLI: both --jpeg-format
  and --dds-compression/--dds-mipmaps. codecs re-exports JpegFormat and
  DdsCompression/DdsOptions.
- docs/book: both entries kept (control-protocol app.save lists both).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:43:26 -04:00
dotCooCoo eeb4f5cc1e Merge main into fix/save-added-extension-asks-before-replacing 2026-10-20 10:25:03 -07:00
Guillem Hermida 872748fb5b Codecs: save DDS textures, uncompressed or BC1–BC3, with optional mipmaps (#1327) (#5993)
DDS textures can now be saved, not just opened: uncompressed (BGRA, BGRX, L8, A8L8; lossless) or BC1/BC2/BC3 (DXT1/3/5) from a clean-room block encoder, with an optional box-filtered mip chain, in the legacy header. Save As and Save a Copy ask with DDS Options; scripts pass `ddsCompression`/`ddsMipmaps` (file commands, control `app.save`, RPC `doc.save`, MCP `doc_save`/`doc_export`), and the CLI takes `--dds-compression`/`--dds-mipmaps`. Targa and DDS answers travel together as `FormatChoices` through the Save As flow.

Review follow-ups:
- File › Save behind DDS or Targa Options records a plain Save with its answer, and replaying `file.save` applies it (#3531).
- The block encoder finds the principal axis from every channel, so a channel that varies on its own can't hide the others' variation.
- DDS joins the shared round-trip suite; the header is built before the output buffer is allocated.
- Enter on a focused Cancel in DDS, Targa or TIFF Options cancels instead of saving.
2026-10-20 11:02:39 -06:00
e647e102f3 Save As JPEG: JPEG Options dialog (quality 0–12, Baseline / Optimized / Progressive) (FILE-215-19)
Save As and Save a Copy to a JPEG now ask for JPEG Options before writing,
as Photoshop does. Re-creates #4330 on current main and folds in the
unique parts of #5580 (#5563).

- codecs: `JpegFormat` (Baseline, Optimized, Progressive) on
  `EncodeOptions`. Progressive writes SOF2. Baseline Optimized fits the
  Huffman tables only at 4:4:4 (Gray, CMYK) or with Progressive: fitted
  tables make the baseline scans non-interleaved, which zune-jpeg 0.5.15
  decodes wrongly with 4:2:0 chroma (zune-image#419), as Save for Web
  already works around.
- engine: a `jpegFormat` save param (file.saveACopy, Batch, Image
  Processor, Layers to Files and every SaveOpts user; anything but the
  three names is BadParams before writing) and a shared 0–12 -> 1–100
  `jpeg_quality()` that maps NaN/inf to 100.
- ui-egui: `jpeg_options_ui` (Quality 0–12 with Low/Medium/High/Maximum
  presets, Format Options), parked like Targa Options. It starts at the
  opened JPEG's own quality (`file_encoding`, #4032), else the Export
  preference; left there, OK writes that quality exactly and the step
  records no `quality`. `SaveChoices` (Targa bits + JPEG) replaces the
  bare `tga_bits` through file.save / file.saveAs / app.save, replay and
  Save a Copy; automation never prompts. `write_answered_save` is shared
  with Targa Options. `park_at` + `Start` let any save seed the prompt.
  A File › Save that confirms the document's own flat JPEG is still
  journalled as a plain `file.save` (#3531).
- CLI: `--jpeg-format`.
- i18n: five new strings in all 20 catalogs (#4330's duplicate de/es/zh
  `Low`/`High` rows dropped: main has them since #4008).
- Docs: book raster-formats JPEG bullet, control protocol, file-format
  parity, gaps G10, target-app parity; scorecard FILE-215-19 done.

Co-authored-by: SckraT <70698629+SckraT@users.noreply.github.com>
Co-authored-by: PierrunoYT <95778421+PierrunoYT@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:28:38 -04:00
Korg1ch 8c2cfdba6a fix(linux): keep crash-recovery autosaves in $XDG_STATE_HOME, out of the config folder (#6612) (#6941)
* fix(linux): keep crash-recovery autosaves in $XDG_STATE_HOME, out of the config folder (#6612)

* test(linux): cover the XDG state recovery folder (#6612)
2026-10-19 13:32:14 -04:00
Khawar Zamman Wani 77650501e8 Merge remote-tracking branch 'origin/main' into feat/6000-color-panel-modes
# Conflicts:
#	crates/ui-egui/src/dock.rs
#	docs/gaps.md
#	docs/scorecard.md
2026-10-19 14:31:04 +05:30
00xJ0 9e83b41f45 control-protocol: CPU rendering lasts the session only after a device loss (#6897 review) 2026-10-18 17:01:19 -07:00
Marco Arnulfo 8736098d38 Curves: Cmd/Ctrl-click on the image adds a point at the pixel's tone (#7473)
* 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
2026-10-18 17:23:16 -04:00
JulienandClaude Opus 5.5 bbfeae98e4 Type: right-click on committed text opens the type layer menu (#7324) (#7430)
* 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>
2026-10-18 17:23:05 -04:00
00xJ0 73123e621a GPU fallback: Retry GPU after an error rebuilds the GPU canvas now, no restart (#6577)
After an error on a device that still works (out of memory, validation), Retry GPU builds a new GPU canvas on the same render state with a fresh health flag, and the views go back to the GPU. After a loss or a failed GPU start it still schedules the retry for the next launch. The dialog says which. Two new strings, translated in all 20 catalogs.
2026-10-18 12:53:50 -07:00
00xJ0 4615a8bc26 Survive a lost window GPU device: draw nothing, save unsaved work for recovery, close (#3438, #1215)
When the window's own wgpu device was lost (a driver reset, e.g. after sleep), egui-wgpu could
not draw another frame: a frame with shapes panicked inside it ("Failed to create staging buffer
for index data", #1215, exit 101, unsaved work lost), and a frozen window's close button opened
an unsaved-changes prompt nobody could see (#3438). The #243 fallback only covered the canvas.

- DeviceHealth::device_lost (wgpu reported the loss, not a recorded fault) and ::probe (a
  non-blocking poll that keeps running after a recorded fault).
- RecoveryStore::flush: finish queued autosaves and wait for them.
- gpu_status::check_window, at the end of every frame: once the window's device is lost the
  frame draws nothing; the first time every unsaved document is written to crash recovery
  (Services::flush_autosaves) and the app closes without asking. The next launch reopens them.
- The app watches the window device on the CPU canvas too; ui.gpu.simulateLoss {destroy: true}
  destroys the real device to drive the path.

Verified on an RX 6900 XT, Vulkan and DX12: destroying the device in the running app exited 101
with the egui-wgpu panic before, and now exits 0 with the document recovered on the next launch.
2026-10-18 12:53:48 -07:00
oRE-o 98b9298d10 Clarify Gradient Overlay source loss when saving styles 2026-10-18 21:13:58 +09:00
oRE-o 8fb5d94d2b Gradient Overlay: expose interpolation methods and retain source stops 2026-10-18 18:36:30 +09:00
Khawar Zamman Wani 3a5e7cf0b0 Merge origin/main into feat/6000-color-panel-modes 2026-10-18 14:39:02 +05:30
David MannandClaude Opus 5.5 e1c119200b Automation: place and layer/data-set export by index keep the active document (#7025)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:37:18 +02:00
dotCooCoo a78e9f05fc Save: ask before replacing a file that an added or lowercased extension names
The save dialog asks before replacing only the file it answers with.
After it closes, pick_save can change that path: it gives a name
without an extension the suggested file's extension (#5039), and
Lowercase Extension changes CAT.PSD to CAT.psd. The save then wrote to
the changed path at once, and a file there was replaced without a
question. On Linux this happens when the Swatches panel exports to a
folder that holds Swatches.aco (the one-type portal dialog shows and
asks about "Swatches"), when CAT.PSD is typed where CAT.psd exists, and
in rfd's zenity fallback dialog.

When the path written differs from the dialog's answer and a file
exists there, the save now asks "“{name}” already exists. Replace it?".
Replace writes the file, and ui.dialog.confirm returns the save's
result. Cancel writes nothing and ends the save like the dialog's
Cancel, and Save in the close prompt of an untitled document returns
to that prompt. PhotoCraft counts the file dialog as open until the
question is answered.

A name that reaches the same file, as CAT.PSD and CAT.psd do on
Windows and macOS by default, is not asked about again. The new
same_file service decides this: the desktop app compares device and
inode on Unix and the canonical paths on Windows. The web has no file
stamps and does not ask. The two new strings have rows in all 20
catalogs.
2026-10-17 03:08:09 -07:00
Korg1ch 4c881326ea fix(windows): keep crash-recovery autosaves out of the roaming profile (#5648) (#6412)
* fix(windows): keep crash-recovery autosaves out of the roaming profile (#5648)

* test(windows): cover the local recovery folder and moving old autosaves (#5648)
2026-10-16 20:19:06 -06:00
Khawar Zamman WaniandClaude Opus 5.5 dcc5387388 Merge main into feat/6000-color-panel-modes
Resolve conflicts: keep both sides' new i18n strings, combine the
ui-parity change note, regenerate docs/scorecard.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:32:29 +05:30
augrclkandGPT-6 ec32464195 Merge upstream main into fix/brush-pose-tilt-axis-overrides
Retain both progress histories and integrate the existing Brush Settings changes with upstream. Preserve the response-curve editor, options-label scrubbing and independent preset regression tests where they overlap.

Co-authored-by: GPT-6 <noreply@openai.com>
2026-10-16 18:23:34 +03:00
Trade-MottoesandClaude Opus 5.5 8c9c5fccf8 Layer Mask Display Options: set the layer mask rubylith's colour and opacity
The rubylith (`\`, ⇧⌥-click on a mask thumbnail) was always red at 50 %. Its colour and
opacity are now Photoshop's Layer Mask Display Options: one setting for every mask, kept
across restarts and not an undo step, as Photoshop does it. They live in the preferences as
`layerMaskOverlay` (`#rrggbb`, 0..100 %, red at 50 by default) and are set through
`channel.options {"channel":"mask","color","opacity"}`, which with no fields reads them back
(prefs.set and prefs.reset work too).

In the UI, as in Photoshop: double-click the layer mask's row in the Channels panel or pick
Layer Mask Display Options… from its menu, Mask Options… on the Layers panel mask thumbnail,
or Channel Options… with the mask targeted (`\` counts). Each opens the Channel Options dialog
with just Color and Opacity.

The rubylith is still drawn over the picture as it looks, mask applied, as Photoshop draws
it. The channel view's texture key carries the options, so changing them redraws it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 14:03:46 +00: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