Merge PR #7115: fix(atn): map Apply Layer Comp steps by name (#7101)

Conflicts:
docs/gaps.md: header line is ours plus the PR's missing change item (later Last updated date); change-log rows kept both, newest first
docs/file-format-parity.md: same
This commit is contained in:
aiongg
2026-10-22 02:26:51 +00:00
4 changed files with 124 additions and 3 deletions
+15
View File
@@ -2187,6 +2187,20 @@ fn duplicate_to(id: &str, p: &Value) -> Option<Descriptor> {
Some(d)
}
/// Only a comp name in the active document is portable across action playback.
fn apply_comp_from(d: &Descriptor) -> Mapped {
match get_ref(d, "null") {
Some([ReferenceItem::Name { class, name }]) if trimmed(&class.class_id) == "compsClass" => {
step("layerComp.apply", json!({"comp": name.to_string_lossy()}))
}
_ => Mapped::Unsupported { harmless: false },
}
}
fn apply_comp_to(_: &str, p: &Value) -> Option<Descriptor> {
Some(desc("null").with("null", name_ref("compsClass", jstr(p, "comp")?)))
}
fn play_from(d: &Descriptor) -> Mapped {
match get_ref(d, "null").map(target) {
Some(Target::Name(n)) => step("actions.play", json!({"action": n})),
@@ -3473,6 +3487,7 @@ const EVENTS: &[Event] = &[
ev!("save", "save", "Save", "file.save", &[]),
ev!("Rvrt", "revert", "Revert", "file.revert", &[]),
Event { code: "Ply ", name: "play", label: "Play", ids: &["actions.play"], from: play_from, to: play_to },
Event { code: "", name: "applyComp", label: "Apply Layer Comp", ids: &["layerComp.apply"], from: apply_comp_from, to: apply_comp_to },
Event { code: "Stop", name: "stop", label: "Stop", ids: &[STOP_MESSAGE], from: stop_from, to: stop_to },
];
+100
View File
@@ -66,6 +66,105 @@ fn sample() -> AtnFile {
}
}
#[test]
fn layer_comp_apply_imports_plays_and_undoes() {
let name = "合成 𠮷";
let data = set_of("Comps", vec![("Apply", vec![item("applyComp", "Apply Layer Comp", Some(desc("null").with("null", name_ref("compsClass", name))))])]);
for depth in [8, 16, 32] {
let mut s = Session::new();
let loaded = s.execute("actions.load", json!({"data": data})).unwrap();
assert_eq!(loaded["unsupported"], json!([]), "{loaded}");
assert_eq!(s.actions.list[0].steps, [("layerComp.apply".into(), json!({"comp": name}))]);
s.execute("file.new", json!({"width": 8, "height": 8, "depth": depth})).unwrap();
s.execute("layerComp.new", json!({"name": "Other"})).unwrap();
let before = s.active().unwrap().doc.clone();
let played = s.execute("actions.play", json!({"action": "Apply"})).unwrap();
assert_eq!(played["ran"], 0, "{played}");
assert!(played["failed"]["error"].as_str().unwrap().contains(name), "{played}");
assert_eq!(*s.active().unwrap().doc, *before, "a missing comp must not apply another comp");
let comp = s.execute("layerComp.new", json!({"name": name})).unwrap()["comp"].clone();
let layer = s.active().unwrap().active_layer.unwrap();
s.execute("layer.setProps", json!({"visible": false})).unwrap();
s.execute("layerComp.new", json!({"name": "Hidden"})).unwrap();
let played = s.execute("actions.play", json!({"action": "Apply"})).unwrap();
assert_eq!(played["ran"], 1, "{played}");
assert!(played.get("failed").is_none(), "{played}");
assert!(s.active().unwrap().doc.layer(layer).unwrap().visible);
assert_eq!(json!(s.active().unwrap().doc.last_applied_comp), comp);
assert!(s.undo());
assert!(!s.active().unwrap().doc.layer(layer).unwrap().visible);
assert!(s.redo());
assert!(s.active().unwrap().doc.layer(layer).unwrap().visible);
}
}
#[test]
fn layer_comp_apply_round_trips_by_name() {
for name in ["Comp 1", "合成 𠮷"] {
let descriptor = desc("null").with("null", name_ref("compsClass", name));
let data = set_of("Comps", vec![("Apply", vec![item("applyComp", "Apply Layer Comp", Some(descriptor.clone()))])]);
let mut s = Session::new();
let loaded = s.execute("actions.load", json!({"data": data})).unwrap();
assert_eq!(loaded["unsupported"], json!([]), "{loaded}");
let saved = s.execute("actions.save", json!({"set": "Comps"})).unwrap();
assert_eq!(saved["skipped"], json!([]));
let bytes = photocraft_paint::tile::b64_decode(saved["data"].as_str().unwrap()).unwrap();
let file = atn::parse(&bytes).unwrap();
assert_eq!(file.actions[0].items[0].event, AtnEvent::Name("applyComp".into()));
assert_eq!(file.actions[0].items[0].descriptor, Some(descriptor));
let mut reloaded = Session::new();
reloaded.execute("actions.load", json!({"data": saved["data"]})).unwrap();
assert_eq!(reloaded.actions.list, s.actions.list);
}
// Local comp ids and the default comp cannot be resolved to a portable name at export.
for params in [json!({}), json!({"comp": 1}), json!({"comp": null}), json!({"comp": true}), json!({"comp": []})] {
assert!(export_item("layerComp.apply", &params, "Comps").is_none(), "{params}");
}
}
#[test]
fn layer_comp_apply_unknown_references_are_kept_and_stop_playback() {
let references = [
None,
Some(D::Integer(1)),
Some(D::Reference(vec![])),
Some(name_ref("Lyr ", "Comp 1")),
Some(target_ref("compsClass")),
Some(class_ref("compsClass")),
Some(D::Reference(vec![ReferenceItem::Index { class: class("compsClass"), index: 1 }])),
Some(D::Reference(vec![ReferenceItem::Identifier { class: class("compsClass"), id: 1 }])),
Some(D::Reference(vec![
ReferenceItem::Name { class: class("compsClass"), name: UnicodeString::new_nul("Comp 1") },
ReferenceItem::Name { class: class("Dcmn"), name: UnicodeString::new_nul("Other") },
])),
];
let items: Vec<_> =
references.into_iter().map(|reference| item("applyComp", "Apply Layer Comp", reference.map(|r| desc("null").with("null", r)))).collect();
let data = set_of("Comps", vec![("Unknown", items.clone())]);
let mut s = Session::new();
let loaded = s.execute("actions.load", json!({"data": data})).unwrap();
assert_eq!(loaded["unsupported"].as_array().unwrap().len(), items.len());
assert!(s.actions.list[0].steps.iter().all(|(id, _)| id == PHOTOSHOP_STEP));
assert!(s.actions.list[0].disabled.is_empty());
s.execute("file.new", json!({"width": 8, "height": 8})).unwrap();
s.execute("layerComp.new", json!({"name": "Comp 1"})).unwrap();
let before = s.active().unwrap().doc.clone();
let played = s.execute("actions.play", json!({"action": "Unknown"})).unwrap();
assert_eq!(played["ran"], 0, "{played}");
assert!(played["failed"]["error"].as_str().unwrap().contains("Apply Layer Comp"), "{played}");
assert_eq!(*s.active().unwrap().doc, *before);
let saved = s.execute("actions.save", json!({"set": "Comps"})).unwrap();
let bytes = photocraft_paint::tile::b64_decode(saved["data"].as_str().unwrap()).unwrap();
let file = atn::parse(&bytes).unwrap();
assert_eq!(file.actions[0].items.len(), items.len());
for (saved, original) in file.actions[0].items.iter().zip(&items) {
assert_eq!(saved.event, original.event);
assert_eq!(saved.descriptor, original.descriptor);
}
}
#[test]
fn load_maps_events_keeps_flags_and_plays() {
let mut s = Session::new();
@@ -427,6 +526,7 @@ fn every_mapped_command_exports_and_imports_back() {
("image.mode.bitmap", json!({"method": "threshold"})),
("image.imageSize", json!({"resolution": 1200, "resample": "preserveDetails", "keepPrintSize": true})),
("layer.new.layerFromBackground", json!({"ifPresent": true, "select": false})),
("layerComp.apply", json!({"comp": "Comp 1"})),
];
for (id, params) in &cases {
let item = export_item(id, params, "S").unwrap_or_else(|| panic!("{id} does not export"));
+3 -2
View File
@@ -1,6 +1,6 @@
# File-format parity
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-15 · **Change:** minor (Photoshop mixed-order TIFF type geometry, #4758; DDS textures save: uncompressed or BC1–BC3, optional mip chain, DDS Options in Save As (#1327); PSD smart filters: 45 filters (31 more: Blur, Surface Blur, Smart Sharpen, Diffuse, Wind, Offset, Mezzotint, Clouds, Difference Clouds, Fibers, Lens Flare and others) plus Hue/Saturation and Perspective Warp mapped both ways, #6296, #6298, #6300, #6302, #6304, #6314, #6316, #6267, #6268, FILE-215-2; large-file fixtures: a 20 MP layered PSD, a 150-layer layout, a 2 GB PSD and a 4.4 GB PSB generated at test time and round-tripped, FILE-216-2 done, #5107; earlier: PNG `sTER` stereo pairing kept on save, #6063; PDF write one flattened page via Save As / Save a Copy / CLI, #3401; CMYK Invert, Posterize and Threshold on the inks; smart filter masks on PSD open and save; Photoshop-oracle floor 188/258; lossy DNG, compression 34892, decodes with its levels and LinearizationTable applied to the stored 8-bit samples, FILE-215-11; CMYK Hue/Saturation and Color Balance keep the black ink; CMYK Posterize on the stored values, psd-tools oracle floor 242/309; CMYK adjustment layers composite on the document's inks; Photoshop-oracle floor 165/258; CMYK Brightness/Contrast on the inks, CMYK Threshold's luma; modern Brightness/Contrast's brightness curve; Photoshop-oracle floor 151/258; Lab documents composite on their stored L, a, b; Photoshop-oracle floor 152/258; Levels on whole levels at every step with Photoshop's shadow toe; Photoshop-oracle floor 146/258, psd-tools 238/309; 32-bit adjustments unclipped; Photoshop-oracle floor 149/258; mask feathers blur with their exact variance; Threshold splits on Photoshop's luminosity; a pass-through clipping base with an adjustment renders isolated; psd-tools oracle floor 240/309; 16-bit Posterize as Photoshop bins it; psd-tools oracle floor 241/309; Gradient Map luminosity; Photoshop-oracle floor 147/258; PSD: `Mt16`/`Mt32` merged transparency read; palette PNGs open in Indexed Color mode, #4804; earlier: CMYK Invert, Posterize and Threshold on the inks; 16-bit Lab Invert; Photoshop-oracle floor 145/258; `.atn` import maps 112 events; DDS textures open, #1327; DPX read and write, #1049; #2465 fixed: CS2 placed layers with only a `PlLd` block open their contents; Photoshop Actions `.atn` read and write; `.shc` contour files read and write; earlier the same day: indexed BMP palette mode; Save keeps a JPEG's quality and a WebP's encoding) · **Target:** Adobe Photoshop 2026
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-17 · **Change:** minor (Apply Layer Comp action mapping, #7101; earlier: earlier: PDF write one flattened page via Save As / Save a Copy / CLI, #3401; Photoshop mixed-order TIFF type geometry, #4758; DDS textures save: uncompressed or BC1–BC3, optional mip chain, DDS Options in Save As (#1327); PSD smart filters: 45 filters (31 more: Blur, Surface Blur, Smart Sharpen, Diffuse, Wind, Offset, Mezzotint, Clouds, Difference Clouds, Fibers, Lens Flare and others) plus Hue/Saturation and Perspective Warp mapped both ways, #6296, #6298, #6300, #6302, #6304, #6314, #6316, #6267, #6268, FILE-215-2; large-file fixtures: a 20 MP layered PSD, a 150-layer layout, a 2 GB PSD and a 4.4 GB PSB generated at test time and round-tripped, FILE-216-2 done, #5107; earlier: PNG `sTER` stereo pairing kept on save, #6063; PDF write one flattened page via Save As / Save a Copy / CLI, #3401; CMYK Invert, Posterize and Threshold on the inks; smart filter masks on PSD open and save; Photoshop-oracle floor 188/258; lossy DNG, compression 34892, decodes with its levels and LinearizationTable applied to the stored 8-bit samples, FILE-215-11; CMYK Hue/Saturation and Color Balance keep the black ink; CMYK Posterize on the stored values, psd-tools oracle floor 242/309; CMYK adjustment layers composite on the document's inks; Photoshop-oracle floor 165/258; CMYK Brightness/Contrast on the inks, CMYK Threshold's luma; modern Brightness/Contrast's brightness curve; Photoshop-oracle floor 151/258; Lab documents composite on their stored L, a, b; Photoshop-oracle floor 152/258; Levels on whole levels at every step with Photoshop's shadow toe; Photoshop-oracle floor 146/258, psd-tools 238/309; 32-bit adjustments unclipped; Photoshop-oracle floor 149/258; mask feathers blur with their exact variance; Threshold splits on Photoshop's luminosity; a pass-through clipping base with an adjustment renders isolated; psd-tools oracle floor 240/309; 16-bit Posterize as Photoshop bins it; psd-tools oracle floor 241/309; Gradient Map luminosity; Photoshop-oracle floor 147/258; PSD: `Mt16`/`Mt32` merged transparency read; palette PNGs open in Indexed Color mode, #4804; earlier: CMYK Invert, Posterize and Threshold on the inks; 16-bit Lab Invert; Photoshop-oracle floor 145/258; `.atn` import maps 112 events; DDS textures open, #1327; DPX read and write, #1049; #2465 fixed: CS2 placed layers with only a `PlLd` block open their contents; Photoshop Actions `.atn` read and write; `.shc` contour files read and write; earlier the same day: indexed BMP palette mode; Save keeps a JPEG's quality and a WebP's encoding) · **Target:** Adobe Photoshop 2026
Every format Photoshop 2026 (27.11.0) reads or writes, with PhotoCraft's support, fidelity and
tests. Photoshop's list comes from its `Info.plist` `CFBundleDocumentTypes`, the PiPL read/write
@@ -131,7 +131,7 @@ remaining gaps in #1606 stay tracked with per-sample corpus ceilings.
| LUTs `.cube` / `.3dl` / `.look` | R/W (Export Color Lookup) | R/W / R / R | `cms/src/lutfile.rs` |
| Keyboard shortcuts `.kys` | R/W | R/W | `engine/src/kys.rs` (`write`: Edit › Keyboard Shortcuts › Export…, rows carry our id; Photoshop reading our files is unverified) |
| ICC profiles | R | R | Own CMS |
| Actions `.atn` | R/W | R/W | `psd/src/atn.rs` (version 16 sets, exact round trip), `engine/src/atn_cmds.rs` (112 Photoshop events ↔ commands; other steps kept verbatim). Panel menu, CLI `batch --actions set.atn`, `actions.load`/`actions.save`. Photoshop 5-era version 12 files unsupported (AUTO-219-2) |
| Actions `.atn` | R/W | R/W | `psd/src/atn.rs` (version 16 sets, exact round trip), `engine/src/atn_cmds.rs` (Photoshop events ↔ commands, including Apply Layer Comp by name, #7101; other steps kept verbatim). Panel menu, CLI `batch --actions set.atn`, `actions.load`/`actions.save`. Photoshop 5-era version 12 files unsupported (AUTO-219-2) |
| Styles `.asl` | R/W | no | #1693 |
| Contours `.shc` | R/W | R/W | `psd/src/shc.rs`; Contour Editor Load…/Save… (FILE-215-15). No user contour library; not yet checked against a Photoshop-written file |
| Custom shapes `.csh`, tool presets `.tpl` | R/W | no | |
@@ -142,6 +142,7 @@ remaining gaps in #1606 stay tracked with per-sample corpus ceilings.
| Date | Change | Summary |
|---|---|---|
| 2026-10-17 | minor | `.atn`: Apply Layer Comp by name maps to `layerComp.apply` and exports as `applyComp` (#7101) |
| 2026-10-15 | minor | PSD smart filters: Clouds, Difference Clouds, Fibers and Lens Flare open as editable filters with their seeds, colours and lens, and save like Photoshop (checked against a Photoshop 27 file); render filters on a smart object work within the object, so a flare sits where Photoshop puts it |
| 2026-10-15 | minor | DDS textures save (#1327): uncompressed or BC1/BC2/BC3 (DXT1/3/5) from a clean-room block encoder, optional box-filtered mip chain, legacy header; DDS Options in Save As and Save a Copy, `ddsCompression`/`ddsMipmaps` for scripts, the CLI and MCP |
| 2026-10-16 | minor | PSD smart filters: Perspective Warp (`perspectiveWarpTransform`: `vertices`, `warpedVertices`, `quads` of vertex indices, document pixels) opens as `layer.smartObjects.perspectiveWarp`, one plane per quad, and re-renders warped; save writes the original item with only the geometry rewritten, or a new one in Photoshop's layout; inconsistent descriptors stay verbatim (FILE-215-2, #6268) |
+6 -1
View File
@@ -1,6 +1,6 @@
# Where PhotoCraft falls short of Photoshop
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** minor (G9: Preferences › File Handling › Save in Background takes effect, #6207, 24 of 169 preferences do nothing; G7: Content-Aware Move previews every selection the command takes, coarse then exact, TOOL-213-4 done, #6680; G4: Pattern Stamp and Background Eraser draw live, TOOL-213-7; G9: Animated Zoom pref read: holding the Zoom tool zooms continuously, #6210, prefs that do nothing 25 of 166; G2: a cancelled 24 MP Gaussian Blur job stops in at most 44 ms, P25 under its 200 ms budget in local runs, baseline not re-run, #6205; G1: generated large-file fixtures, a 4.38 GB PSB opens on Windows through io, FILE-216-2 done, #5107; G9: fill and adjustment layers copy and paste whole, #2344; G1: smart filter masks show on PSD open, Photoshop-oracle floor 188/258; G10: DPX read and write, #1049, Cineon read; G1: CMYK Hue/Saturation and Color Balance keep the black ink; G1: CMYK Posterize on the stored values, psd-tools oracle floor 242/309; G1: CMYK adjustment layers composite on the document's inks, Photoshop-oracle floor 165/258; G1: CMYK Brightness/Contrast on the inks, CMYK Threshold's luma; G1: modern Brightness/Contrast's brightness curve matches Photoshop, Photoshop-oracle floor 151/258; G1: Lab documents composite on their stored L, a, b, Photoshop-oracle floor 152/258; G1: Levels on whole levels at every step, Photoshop-oracle floor 146/258, psd-tools 238/309; G1: 32-bit Brightness/Contrast legacy, Channel Mixer, Photo Filter and Black & White tint unclipped as in Photoshop, Photoshop-oracle floor 149/258; G1: mask feathers blur with their exact variance, psd-tools oracle floor 240/309; G1: Threshold splits on Photoshop's luminosity, psd-tools oracle floor 241/309; G1: pass-through clipping base with an adjustment renders isolated, psd-tools oracle floor 240/309; G1: 16-bit Posterize as Photoshop bins it, psd-tools oracle floor 241/309; G1: Gradient Map driven by the W3C luminosity, Photoshop-oracle floor 147/258; G7: Field Blur's on-canvas pins, #3373; G4: Hue Jitter amount track, #3825 partial; G4: minimum field interaction, #3825 partial; G1: CMYK Invert, Posterize and Threshold on the inks, 16-bit Lab Invert; Photoshop-oracle floor 145/258; G11: `.atn` import maps 112 events: Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, mask targeting, Step Backward, nudges in points, Bitmap mode; G5: Free Transform H/V skew fields; G9: toolbar tool shortcut editing; refreshed against main: G2, G5, G7, G9, G10, G13, G14, G17 items that landed; G10: DPX read and write, #1049; G4: checkbox-only Brush Settings rows; G11: Load/Save Actions `.atn` landed, AUTO-219-2 partial; G5: hold-a-key spring-loaded tools landed, #2672; G1: #2465 fixed; stale missing-tool rows corrected: Color Replacement, Red Eye and Add/Delete Anchor Point are live tools, so only Art History Brush is missing; no estimate changed) · **Target:** Adobe Photoshop 2026
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-19 · **Change:** minor (Apply Layer Comp action mapping, #7101; G9: Preferences › File Handling › Save in Background takes effect, #6207, 24 of 169 preferences do nothing; G7: Content-Aware Move previews every selection the command takes, coarse then exact, TOOL-213-4 done, #6680; G4: Pattern Stamp and Background Eraser draw live, TOOL-213-7; G9: Animated Zoom pref read: holding the Zoom tool zooms continuously, #6210, prefs that do nothing 25 of 166; G2: a cancelled 24 MP Gaussian Blur job stops in at most 44 ms, P25 under its 200 ms budget in local runs, baseline not re-run, #6205; G1: generated large-file fixtures, a 4.38 GB PSB opens on Windows through io, FILE-216-2 done, #5107; G9: fill and adjustment layers copy and paste whole, #2344; G1: smart filter masks show on PSD open, Photoshop-oracle floor 188/258; G10: DPX read and write, #1049, Cineon read; G1: CMYK Hue/Saturation and Color Balance keep the black ink; G1: CMYK Posterize on the stored values, psd-tools oracle floor 242/309; G1: CMYK adjustment layers composite on the document's inks, Photoshop-oracle floor 165/258; G1: CMYK Brightness/Contrast on the inks, CMYK Threshold's luma; G1: modern Brightness/Contrast's brightness curve matches Photoshop, Photoshop-oracle floor 151/258; G1: Lab documents composite on their stored L, a, b, Photoshop-oracle floor 152/258; G1: Levels on whole levels at every step, Photoshop-oracle floor 146/258, psd-tools 238/309; G1: 32-bit Brightness/Contrast legacy, Channel Mixer, Photo Filter and Black & White tint unclipped as in Photoshop, Photoshop-oracle floor 149/258; G1: mask feathers blur with their exact variance, psd-tools oracle floor 240/309; G1: Threshold splits on Photoshop's luminosity, psd-tools oracle floor 241/309; G1: pass-through clipping base with an adjustment renders isolated, psd-tools oracle floor 240/309; G1: 16-bit Posterize as Photoshop bins it, psd-tools oracle floor 241/309; G1: Gradient Map driven by the W3C luminosity, Photoshop-oracle floor 147/258; G7: Field Blur's on-canvas pins, #3373; G4: Hue Jitter amount track, #3825 partial; G4: minimum field interaction, #3825 partial; G1: CMYK Invert, Posterize and Threshold on the inks, 16-bit Lab Invert; Photoshop-oracle floor 145/258; G11: `.atn` import maps 112 events: Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, mask targeting, Step Backward, nudges in points, Bitmap mode; G5: Free Transform H/V skew fields; G9: toolbar tool shortcut editing; refreshed against main: G2, G5, G7, G9, G10, G13, G14, G17 items that landed; G10: DPX read and write, #1049; G4: checkbox-only Brush Settings rows; G11: Load/Save Actions `.atn` landed, AUTO-219-2 partial; G5: hold-a-key spring-loaded tools landed, #2672; G1: #2465 fixed; stale missing-tool rows corrected: Color Replacement, Red Eye and Add/Delete Anchor Point are live tools, so only Art History Brush is missing; no estimate changed) · **Target:** Adobe Photoshop 2026
Every known shortfall against Photoshop 2026 (27.11.0), one entry each, ranked by user impact
on the way to beta. This is the work list agents pick from: take the highest-ranked entry that
@@ -98,6 +98,10 @@ what an agent can't finish alone.
- **Estimate:** 120–200 h.
### G11. Actions and scripting
- **Done 2026-10-17 (#7101):** `.atn` Apply Layer Comp steps with a named `compsClass`
reference import as `layerComp.apply` and export to the same Photoshop event. Names resolve
in the document being played; missing comps stop playback. Other reference forms stay kept
Photoshop steps and are reported as unsupported. AUTO-219-2 remains partial.
- **2026-10-13 evidence:** Batch supports [output naming templates](batch-naming.md) using
`{name}`, `{serial}` and a captured UTC `{date}`, with a starting serial number. Colliding
names remain errors; custom output names cannot replace inputs. AUTO-219-5 stays partial.
@@ -292,6 +296,7 @@ what an agent can't finish alone.
|---|---|---|
| 2026-10-15 | minor | G1 / #5972 A1: render only provably constant-RGB `GdFl` noise fills; variable noise synthesis remains open |
| 2026-10-18 | minor | G9: with no selection, Copy takes fill and adjustment layers whole and Paste recreates them (#2344); UI-217-23 stays partial (a single pixel layer copies its pixels only); estimate unchanged |
| 2026-10-17 | minor | G11: `.atn` Apply Layer Comp imports, plays and exports by comp name (#7101); other reference forms remain unsupported |
| 2026-10-15 | minor | G2: a cancelled 24 MP Gaussian Blur job stops within 44 ms max (P25, budget 200 ms; was about 400 ms; local runs, baseline not re-run), the uncancelled job on a locked-transparency layer about 40% faster; #6205 |
| 2026-10-15 | minor | G9: Animated Zoom preference takes effect (hold the Zoom tool to zoom continuously, #6210); prefs that do nothing 25 of 166; no estimate changed |
| 2026-10-16 | minor | G7: Content-Aware Move's live preview covers every selection the command takes (over 256 × 256: a coarse result solved scaled down, then the exact one); TOOL-213-4 done (#6680) |