From a16b352f59c1193140c27455ad282c9d9683407e Mon Sep 17 00:00:00 2001 From: Reynold Hu <88558425+reynold-hu@users.noreply.github.com> Date: Wed, 14 Oct 2026 23:18:49 +0800 Subject: [PATCH 1/5] engine: Generate Image Assets needs the Generator preference enabled --- crates/engine/src/web_cmds.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/web_cmds.rs b/crates/engine/src/web_cmds.rs index 595ad21956..ac96f0a10c 100644 --- a/crates/engine/src/web_cmds.rs +++ b/crates/engine/src/web_cmds.rs @@ -1445,7 +1445,7 @@ fn image_assets(s: &mut Session, p: &Value) -> Result { /// After a save: regenerate the document's image assets when Generate › Image Assets is on. pub fn on_saved(s: &Session, index: usize) -> Option { let d = s.documents().get(index)?; - if !s.file_menu.image_assets.contains(&d.doc.id) || cfg!(target_arch = "wasm32") { + if !s.file_menu.image_assets.contains(&d.doc.id) || !s.prefs().plug_ins.generator_enabled || cfg!(target_arch = "wasm32") { return None; } let dir = assets_dir(d.path.as_deref()?); @@ -1462,6 +1462,16 @@ pub fn specs() -> Vec { fn has_doc(s: &Session) -> std::result::Result<(), String> { s.active().map(|_| ()).ok_or_else(|| "no document open".into()) } + + /// Generate ▸ Image Assets needs Preferences ▸ Plug-ins ▸ Generator enabled, as in + /// Photoshop (where "Enable Generator" is unchecked until turned on). + fn generator_enabled(s: &Session) -> std::result::Result<(), String> { + has_doc(s)?; + if !s.prefs().plug_ins.generator_enabled { + return Err("Enable Generator in Preferences ▸ Plug-ins".into()); + } + Ok(()) + } vec![ spec!( "file.export.saveForWebLegacy", @@ -1496,7 +1506,7 @@ pub fn specs() -> Vec { &["File", "Generate"], None, r##"{"on":bool? (default: toggle),"dir":folder? (default -assets next to the file)} → {enabled, files, errors}; layers named like "foo.png", "200% foo@2x.png", "48x48 icons/a.png8", "photo.jpg80%" are exported, now and after each save"##, - has_doc, + generator_enabled, image_assets ), ] From be01d7ee0e15be39e803d57d9e7a5ef74117a074 Mon Sep 17 00:00:00 2001 From: Reynold Hu <88558425+reynold-hu@users.noreply.github.com> Date: Wed, 14 Oct 2026 23:18:49 +0800 Subject: [PATCH 2/5] engine: test that Image Assets needs the Generator preference --- crates/engine/src/web_cmds/tests.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/engine/src/web_cmds/tests.rs b/crates/engine/src/web_cmds/tests.rs index c3ffe6edce..4db1912bb3 100644 --- a/crates/engine/src/web_cmds/tests.rs +++ b/crates/engine/src/web_cmds/tests.rs @@ -511,10 +511,35 @@ fn generator_naming_grammar() { assert!(parse_defaults("defaults are nice", 72.0).is_none()); } +#[test] +fn image_assets_needs_the_generator_preference() { + let dir = tmp("generator_pref"); + let mut s = session(8); + let id = s.active().unwrap().active_layer.unwrap(); + s.execute("layer.renameLayer", json!({"layer": id.0, "name": "logo.png"})).unwrap(); + assert!(!s.prefs().plug_ins.generator_enabled, "off by default, as in Photoshop"); + assert!(!s.is_enabled("file.generate.imageAssets"), "unavailable until it is enabled"); + assert!(s.execute("file.generate.imageAssets", json!({"dir": dir})).is_err()); + + s.execute("prefs.set", json!({"path": "plugIns.generatorEnabled", "value": true})).unwrap(); + assert!(s.is_enabled("file.generate.imageAssets")); + assert_eq!(s.execute("file.generate.imageAssets", json!({"dir": dir})).unwrap()["enabled"], true); + s.active_mut().unwrap().path = Some(format!("{dir}/doc.psd")); + assert!(crate::automate_cmds::document_saved(&mut s, 0).is_some(), "assets are written on save"); + + // Turning it off again takes the menu item away and stops the export, even for a document + // left switched on. + s.execute("prefs.set", json!({"path": "plugIns.generatorEnabled", "value": false})).unwrap(); + assert!(!s.is_enabled("file.generate.imageAssets")); + assert!(crate::automate_cmds::document_saved(&mut s, 0).is_none(), "no assets while Generator is off"); +} + #[test] fn image_assets_are_generated_now_and_on_save() { let dir = tmp("assets"); let mut s = session(8); + // Preferences ▸ Plug-ins ▸ Generator enabled: the feature is opt-in. + s.execute("prefs.set", json!({"path": "plugIns.generatorEnabled", "value": true})).unwrap(); let id = s.active().unwrap().active_layer.unwrap(); s.execute("layer.renameLayer", json!({"layer": id.0, "name": "logo.png, 50% small/logo.gif, logo.jpg80%"})).unwrap(); let r = s.execute("file.generate.imageAssets", json!({"dir": dir})).unwrap(); From f415b7522235ff9f0f091dd8a8bbeeebf0d61a05 Mon Sep 17 00:00:00 2001 From: Reynold Hu <88558425+reynold-hu@users.noreply.github.com> Date: Wed, 14 Oct 2026 23:18:49 +0800 Subject: [PATCH 3/5] ui: the Image Assets menu item needs Generator enabled --- crates/ui-egui/src/file_ui.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/ui-egui/src/file_ui.rs b/crates/ui-egui/src/file_ui.rs index c3b8abc4d7..0ff3e3927f 100644 --- a/crates/ui-egui/src/file_ui.rs +++ b/crates/ui-egui/src/file_ui.rs @@ -1420,6 +1420,10 @@ mod tests { assert_eq!(checked(&app, "view.lockSlices"), Some(false)); crate::menus::invoke(&mut app, &ctx, "view.lockSlices", json!({})).unwrap(); assert_eq!(checked(&app, "view.lockSlices"), Some(true)); + // Generator is opt-in (Preferences ▸ Plug-ins), as in Photoshop: the menu item is + // unavailable until it is enabled. + assert!(crate::menus::invoke(&mut app, &ctx, "file.generate.imageAssets", json!({})).is_err()); + app.run("prefs.set", json!({"path": "plugIns.generatorEnabled", "value": true})).unwrap(); crate::menus::invoke(&mut app, &ctx, "file.generate.imageAssets", json!({})).unwrap(); assert_eq!(checked(&app, "file.generate.imageAssets"), Some(true)); // Form dialogs open for the rest. From e4474298e1f7f5da035174f23218bb03a9dcd2d6 Mon Sep 17 00:00:00 2001 From: Reynold Hu <88558425+reynold-hu@users.noreply.github.com> Date: Wed, 14 Oct 2026 23:18:50 +0800 Subject: [PATCH 4/5] engine: show Generator enabled in Preferences, it is wired --- crates/engine/src/prefs.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/engine/src/prefs.rs b/crates/engine/src/prefs.rs index b163d40d88..138542a038 100644 --- a/crates/engine/src/prefs.rs +++ b/crates/engine/src/prefs.rs @@ -1066,7 +1066,6 @@ pub const HIDDEN_UNTIL_IMPLEMENTED: &[&str] = &[ "unitsAndRulers.gutter", "plugIns.showExtensionPanels", "plugIns.allowScriptsToConnect", - "plugIns.generatorEnabled", "type.textEngine", "type.recentFonts", "enhancedControls.scrubbySliderAcceleration", From 731bb2aa986f298bca7414900e6996e76ce55709 Mon Sep 17 00:00:00 2001 From: Reynold Hu <88558425+reynold-hu@users.noreply.github.com> Date: Wed, 14 Oct 2026 23:18:50 +0800 Subject: [PATCH 5/5] docs: scorecard regenerated --- docs/scorecard.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/scorecard.md b/docs/scorecard.md index 56a16654bd..f5d26ff8dd 100644 --- a/docs/scorecard.md +++ b/docs/scorecard.md @@ -9,7 +9,7 @@ Generated by `cargo xtask scorecard` from `perf/budgets.toml`, `perf/baseline.js | [Performance](#performance) | 3 | 22 | 22 | 56 scenarios: 3 within budget, 22 over budget or failing, 9 tracked only, 17 not run, 5 not measurable yet (baseline 2026-10-05) | | [File compatibility](#file-compatibility) | 7 | 13 | 7 | io corpus: oracle ≥ 146/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 236/309, round trip ≥ 308/309; photoshop oracles: oracle ≥ 145/256, round trip ≥ 258/256 | | [Tools](#tools) | 29 | 13 | 1 | | -| [Workspace and UI](#workspace-and-ui) | 13 | 6 | 4 | settings that do nothing: 26 of 157 (target 0) | +| [Workspace and UI](#workspace-and-ui) | 13 | 6 | 4 | settings that do nothing: 25 of 157 (target 0) | | [Type](#type) | 4 | 3 | 2 | | | [Automation](#automation) | 5 | 2 | 4 | 631 of 631 menu items live (100.0%) | | [Reliability](#reliability) | 5 | 2 | 0 | never-crash lints on 28 of 28 crates; panic_hunt present | @@ -176,9 +176,9 @@ Corpus floors (`crates/io/tests/corpus.rs`): the enforced minimums; the corpus t ## Workspace and UI -**Settings that do nothing: 26 of 157** (target 0, #204). Counted by the prefs audit (`xtask/src/scorecard.rs`): a `Preferences` field counts as read when code outside the prefs model (`prefs.rs` except its `impl Session` blocks; tests excluded; the schema-driven dialog never names a field) accesses `section.field`, or `.section` and `.field` in the same file, or calls a section method that reads `self.field`. It is a heuristic lower bound: a generic name can look read when it isn't. +**Settings that do nothing: 25 of 157** (target 0, #204). Counted by the prefs audit (`xtask/src/scorecard.rs`): a `Preferences` field counts as read when code outside the prefs model (`prefs.rs` except its `impl Session` blocks; tests excluded; the schema-driven dialog never names a field) accesses `section.field`, or `.section` and `.field` in the same file, or calls a section method that reads `self.field`. It is a heuristic lower bound: a generic name can look read when it isn't. -Unread: `general.color_picker`, `general.animated_zoom`, `general.zoom_resizes_windows`, `workspace.auto_show_hidden_panels`, `workspace.enable_floating_document_window_docking`, `file_handling.save_in_background`, `performance.cache_levels`, `performance.legacy_compositing`, `scratch_disks.disks`, `units_and_rulers.column_width`, `units_and_rulers.gutter`, `plug_ins.show_extension_panels`, `plug_ins.allow_scripts_to_connect`, `plug_ins.generator_enabled`, `type_.text_engine`, `type_.recent_fonts`, `enhanced_controls.scrubby_slider_acceleration`, `enhanced_controls.touch_gestures`, `raw_defaults.color_space`, `raw_defaults.bit_depth`, `raw_defaults.resolution`, `raw_defaults.sharpen_for`, `raw_defaults.open_as_smart_object`, `raw_defaults.apply_auto_tone`, `integrations.allow_agent_control`, `integrations.control_port`. +Unread: `general.color_picker`, `general.animated_zoom`, `general.zoom_resizes_windows`, `workspace.auto_show_hidden_panels`, `workspace.enable_floating_document_window_docking`, `file_handling.save_in_background`, `performance.cache_levels`, `performance.legacy_compositing`, `scratch_disks.disks`, `units_and_rulers.column_width`, `units_and_rulers.gutter`, `plug_ins.show_extension_panels`, `plug_ins.allow_scripts_to_connect`, `type_.text_engine`, `type_.recent_fonts`, `enhanced_controls.scrubby_slider_acceleration`, `enhanced_controls.touch_gestures`, `raw_defaults.color_space`, `raw_defaults.bit_depth`, `raw_defaults.resolution`, `raw_defaults.sharpen_for`, `raw_defaults.open_as_smart_object`, `raw_defaults.apply_auto_tone`, `integrations.allow_agent_control`, `integrations.control_port`. | Id | Target | Status | Issue | Note | |---|---|---|---|---|