From 1b7cfc8bee65d93674d229ad7b6ec453ca59fea9 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Thu, 22 Oct 2026 21:19:01 +0700 Subject: [PATCH] Automation: --automation-read-root and --automation-write-root may each be given more than once (#3546) Squashed from #3631 onto main. --- apps/vectorcraft-cli/src/main.rs | 23 ++-- apps/vectorcraft-cli/tests/cli.rs | 54 +++++++- apps/vectorcraft/src/automation_args.rs | 59 +++++---- apps/vectorcraft/src/main.rs | 14 +- apps/vectorcraft/tests/automation_args.rs | 12 +- crates/engine/src/file_access.rs | 148 +++++++++++++++------- crates/engine/src/tests.rs | 2 +- crates/engine/src/tests_linkfiles.rs | 4 +- crates/engine/src/tests_scripts.rs | 2 +- crates/mcp/tests/automation_roots.rs | 4 +- crates/mcp/tests/protocol_props.rs | 2 +- crates/ui-egui/src/i18n/bg.tsv | 2 + crates/ui-egui/src/i18n/cs.tsv | 2 + crates/ui-egui/src/i18n/da.tsv | 2 + crates/ui-egui/src/i18n/de.tsv | 2 + crates/ui-egui/src/i18n/el.tsv | 2 + crates/ui-egui/src/i18n/es.tsv | 2 + crates/ui-egui/src/i18n/fi.tsv | 2 + crates/ui-egui/src/i18n/fr.tsv | 2 + crates/ui-egui/src/i18n/hu.tsv | 2 + crates/ui-egui/src/i18n/id.tsv | 2 + crates/ui-egui/src/i18n/it.tsv | 2 + crates/ui-egui/src/i18n/ja.tsv | 2 + crates/ui-egui/src/i18n/ka.tsv | 2 + crates/ui-egui/src/i18n/ko.tsv | 2 + crates/ui-egui/src/i18n/nl.tsv | 2 + crates/ui-egui/src/i18n/pl.tsv | 2 + crates/ui-egui/src/i18n/pt-br.tsv | 2 + crates/ui-egui/src/i18n/ru.tsv | 2 + crates/ui-egui/src/i18n/sv.tsv | 2 + crates/ui-egui/src/i18n/tr.tsv | 2 + crates/ui-egui/src/i18n/uk.tsv | 2 + crates/ui-egui/src/i18n/vi.tsv | 2 + crates/ui-egui/src/i18n/zh-hans.tsv | 2 + crates/ui-egui/src/i18n/zh-hant.tsv | 2 + crates/ui-egui/src/tests_automation.rs | 2 +- docs/control-protocol.md | 10 +- docs/mcp.md | 23 ++-- docs/target-app-parity.md | 5 +- 39 files changed, 301 insertions(+), 111 deletions(-) diff --git a/apps/vectorcraft-cli/src/main.rs b/apps/vectorcraft-cli/src/main.rs index 431492836..10a859ac5 100644 --- a/apps/vectorcraft-cli/src/main.rs +++ b/apps/vectorcraft-cli/src/main.rs @@ -1,7 +1,7 @@ //! `vectorcraft-cli`: VectorCraft from the command line. //! //! ```text -//! vectorcraft-cli mcp [--connect 127.0.0.1:7979 | --headless] [--automation-read-root DIR] [--automation-write-root DIR] +//! vectorcraft-cli mcp [--connect 127.0.0.1:7979 | --headless] [--automation-read-root DIR]... [--automation-write-root DIR]... //! vectorcraft-cli run [--in FILE] [--cmd id [--params '{json}'] | --commands FILE|-]... [--export out.svg]... [--scale 2] //! vectorcraft-cli commands //! vectorcraft-cli convert IN OUT [--scale 2] [--artboard 0 | --range 1-3,5] [--outline-text] [--decimals N] [--replace-lossy] [--editing-data] [--ai-version 7|pdf] [--hidden-objects layer|omit|visible] [--transparent-images flatten|omit] @@ -68,13 +68,14 @@ const USAGE: &str = "\ vectorcraft-cli — VectorCraft automation USAGE: - vectorcraft-cli mcp [--connect ADDR | --headless] [--automation-read-root DIR] [--automation-write-root DIR] + vectorcraft-cli mcp [--connect ADDR | --headless] [--automation-read-root DIR]... [--automation-write-root DIR]... Run the MCP server on stdio. Default: connect to a running app at 127.0.0.1:7979 (vectorcraft --control 7979), falling back to a headless in-process session. --automation-read-root / --automation-write-root confine the files the agent's commands read (open, place, relink, library loads…) and write (save, export, package, library saves…) to - DIR, links followed; a root left out grants none of its access. They imply --headless: a - running app enforces its own (vectorcraft --control PORT --automation-read-root DIR …). + DIR, links followed; each may be given more than once, a path inside any of its folders being + allowed, and a kind left out grants none of its access. They imply --headless: a running app + enforces its own (vectorcraft --control PORT --automation-read-root DIR …). vectorcraft-cli run [--in FILE] [--cmd ID [--params JSON] | --commands FILE|-]... [--export FILE]... [--scale N] Headless batch: open FILE (any readable format) or start a new document, run commands in @@ -181,7 +182,7 @@ fn with_stores(mut h: Headless) -> Headless { fn mcp(args: &[String]) -> Result<(), String> { let mut connect: Option = None; let mut headless = false; - let (mut read_root, mut write_root): (Option, Option) = (None, None); + let (mut read_roots, mut write_roots): (Vec, Vec) = (vec![], vec![]); let mut it = args.iter(); while let Some(a) = it.next() { // The roots also come as `--flag=DIR`, as the other Craft apps take them. @@ -194,10 +195,7 @@ fn mcp(args: &[String]) -> Result<(), String> { "--headless" => headless = true, "--automation-read-root" | "--automation-write-root" => { let dir = inline.or_else(|| it.next().cloned()).ok_or_else(|| format!("{flag} needs a folder"))?; - let slot = if flag == "--automation-read-root" { &mut read_root } else { &mut write_root }; - if slot.replace(dir).is_some() { - return Err(format!("{flag} is given twice: it takes one folder")); - } + if flag == "--automation-read-root" { read_roots.push(dir) } else { write_roots.push(dir) } } other => return Err(format!("unknown mcp option `{other}`")), } @@ -205,7 +203,9 @@ fn mcp(args: &[String]) -> Result<(), String> { if headless && connect.is_some() { return Err("use either --connect or --headless".into()); } - let roots = AutomationRoots::new(read_root.as_deref().map(Path::new), write_root.as_deref().map(Path::new))?; + let read: Vec<&Path> = read_roots.iter().map(Path::new).collect(); + let write: Vec<&Path> = write_roots.iter().map(Path::new).collect(); + let roots = AutomationRoots::new(&read, &write)?; if roots.is_some() && connect.is_some() { return Err("--automation-read-root and --automation-write-root confine the headless server; a running app enforces its own roots: start it with `vectorcraft --control --automation-read-root --automation-write-root `" .into()); @@ -213,8 +213,7 @@ fn mcp(args: &[String]) -> Result<(), String> { let backend: Box = if let Some(roots) = roots { // Every thread of this process, not only the server's: nothing reaches past the roots. file_access::confine_process(roots.clone()); - let show = |root: Option<&Path>| root.map_or_else(|| "none".to_string(), |r| r.display().to_string()); - eprintln!("vectorcraft-cli: files confined: read root {}, write root {}", show(roots.read_root()), show(roots.write_root())); + eprintln!("vectorcraft-cli: files confined: {}", roots.describe()); Box::new(Headless::with_document().with_automation_roots(Some(roots))) } else if headless { Box::new(with_stores(Headless::with_document())) diff --git a/apps/vectorcraft-cli/tests/cli.rs b/apps/vectorcraft-cli/tests/cli.rs index 40ed4d177..e0d035e87 100644 --- a/apps/vectorcraft-cli/tests/cli.rs +++ b/apps/vectorcraft-cli/tests/cli.rs @@ -244,6 +244,58 @@ fn mcp_automation_roots_in_a_folder_an_earlier_run_left() { automation_roots(&empty_dir(dir).join("roots")); } +/// #3546: each root flag may be given more than once: a path inside any of the read roots opens, +/// one inside any of the write roots is written, and everything else, a write root's sibling read +/// root included, is refused. +#[test] +fn mcp_automation_roots_take_several_folders() { + let base = tmp("several-roots"); + for d in ["in", "in2", "out", "out2", "outside"] { + std::fs::create_dir_all(base.join(d)).unwrap(); + } + let svg = r#""#; + for f in ["in/a.svg", "in2/b.svg", "outside/c.svg"] { + std::fs::write(base.join(f), svg).unwrap(); + } + let dir = |d: &str| base.join(d).to_string_lossy().into_owned(); + let file = |d: &str, f: &str| base.join(d).join(f).to_string_lossy().into_owned(); + let mut child = Command::new(BIN) + .args(["mcp", "--automation-read-root", &dir("in"), &format!("--automation-read-root={}", dir("in2"))]) + .args(["--automation-write-root", &dir("out"), "--automation-write-root", &dir("out2")]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let call = |id: u64, name: &str, args: Value| json!({"jsonrpc":"2.0","id":id,"method":"tools/call","params":{"name":name,"arguments":args}}); + let calls = [ + (call(1, "open_file", json!({"path": file("in", "a.svg")})), false), + (call(2, "open_file", json!({"path": file("in2", "b.svg")})), false), + (call(3, "open_file", json!({"path": file("outside", "c.svg")})), true), + (call(4, "export", json!({"path": file("out", "a.png")})), false), + (call(5, "export", json!({"path": file("out2", "b.png")})), false), + (call(6, "export", json!({"path": file("outside", "c.png")})), true), + (call(7, "export", json!({"path": file("in2", "d.png")})), true), + (call(8, "open_file", json!({"path": file("out2", "b.png")})), true), + ]; + { + let mut stdin = child.stdin.take().unwrap(); + for (m, _) in &calls { + writeln!(stdin, "{m}").unwrap(); + } + } + let out = child.wait_with_output().unwrap(); + assert!(out.status.success()); + let stderr = String::from_utf8_lossy(&out.stderr); + assert!(stderr.contains("files confined: read ") && stderr.contains("; write "), "{stderr}"); + let replies: Vec = String::from_utf8(out.stdout).unwrap().lines().map(|l| serde_json::from_str(l).unwrap()).collect(); + let errors: Vec = replies.iter().map(|r| r["result"]["isError"] == true).collect(); + assert_eq!(errors, calls.iter().map(|(_, e)| *e).collect::>(), "{replies:?}"); + assert!(replies[2]["result"]["content"][0]["text"].as_str().unwrap().contains("outside the read roots"), "{}", replies[2]); + assert!(base.join("out/a.png").is_file() && base.join("out2/b.png").is_file()); + assert!(!base.join("outside/c.png").exists() && !base.join("in2/d.png").exists()); +} + fn automation_roots(base: &std::path::Path) { for d in ["in", "out", "outside"] { std::fs::create_dir_all(base.join(d)).unwrap(); @@ -267,7 +319,7 @@ fn automation_roots(base: &std::path::Path) { assert!(!out.status.success() && err.contains(why), "{args:?}: {err}"); }; fails(&["mcp", "--automation-read-root", &dir("missing")], "cannot open the automation read root"); - fails(&["mcp", "--automation-read-root", &dir("in"), "--automation-read-root", &dir("out")], "given twice"); + fails(&["mcp", "--automation-read-root", &dir("in"), "--automation-read-root", &dir("missing")], "cannot open the automation read root"); fails(&["mcp", "--automation-write-root"], "needs a folder"); fails(&["mcp", "--connect", "127.0.0.1:1", &format!("--automation-write-root={}", dir("out"))], "a running app enforces its own roots"); diff --git a/apps/vectorcraft/src/automation_args.rs b/apps/vectorcraft/src/automation_args.rs index 26157b76d..979f42e7b 100644 --- a/apps/vectorcraft/src/automation_args.rs +++ b/apps/vectorcraft/src/automation_args.rs @@ -6,29 +6,29 @@ use std::ffi::OsString; use std::path::PathBuf; pub struct AutomationArgs { - pub read_root: Option, - pub write_root: Option, + pub read_roots: Vec, + pub write_roots: Vec, read_given: bool, write_given: bool, } impl AutomationArgs { + /// The roots the environment gives (`VECTORCRAFT_AUTOMATION_READ_ROOT` / `_WRITE_ROOT`), until + /// the command line gives its own. pub fn new(read_root: Option, write_root: Option) -> Self { - Self { read_root, write_root, read_given: false, write_given: false } + Self { read_roots: read_root.into_iter().collect(), write_roots: write_root.into_iter().collect(), read_given: false, write_given: false } } - /// Returns whether this argument is a root flag. CLI values override environment defaults - /// once; a second occurrence is an error, including when the two spellings are mixed. + /// Returns whether this argument is a root flag. Each flag may be given more than once (#3546), + /// in either spelling: the first replaces the environment's root of its kind, the others add + /// folders. pub fn consume(&mut self, arg: &str, args: &mut impl Iterator) -> Result { let (flag, inline) = arg.split_once('=').map_or((arg, None), |(flag, value)| (flag, Some(value))); - let (root, given) = match flag { - "--automation-read-root" => (&mut self.read_root, &mut self.read_given), - "--automation-write-root" => (&mut self.write_root, &mut self.write_given), + let (roots, given) = match flag { + "--automation-read-root" => (&mut self.read_roots, &mut self.read_given), + "--automation-write-root" => (&mut self.write_roots, &mut self.write_given), _ => return Ok(false), }; - if *given { - return Err(format!("{flag} given twice")); - } let value = match inline { Some(value) => value.to_owned(), None => { @@ -42,8 +42,10 @@ impl AutomationArgs { if value.is_empty() { return Err(format!("{flag} needs a non-empty folder")); } - *root = Some(value.into()); - *given = true; + if !std::mem::replace(given, true) { + roots.clear(); + } + roots.push(value.into()); Ok(true) } } @@ -97,17 +99,24 @@ mod tests { use super::*; #[test] - fn roots_override_environment_once_and_leave_other_arguments() { - let mut roots = AutomationArgs::new(Some("environment".into()), None); + fn roots_override_environment_and_add_up() { + let mut roots = AutomationArgs::new(Some("environment".into()), Some("environment out".into())); let mut args = ["a folder".to_owned(), "drawing.svg".to_owned()].into_iter(); assert!(roots.consume("--automation-read-root", &mut args).unwrap()); assert!(roots.consume("--automation-write-root=out", &mut args).unwrap()); assert!(!roots.consume("--in-window-menus", &mut args).unwrap()); - assert_eq!(roots.read_root, Some("a folder".into())); - assert_eq!(roots.write_root, Some("out".into())); + assert_eq!(roots.read_roots, [PathBuf::from("a folder")]); + assert_eq!(roots.write_roots, [PathBuf::from("out")]); assert_eq!(args.next().as_deref(), Some("drawing.svg")); - assert!(roots.consume("--automation-read-root=other", &mut args).is_err()); - assert_eq!(roots.read_root, Some("a folder".into())); + // #3546: a flag given again adds a folder, in either spelling. + assert!(roots.consume("--automation-read-root=other", &mut args).unwrap()); + assert!(roots.consume("--automation-write-root", &mut ["out2".to_owned()].into_iter()).unwrap()); + assert_eq!(roots.read_roots, [PathBuf::from("a folder"), PathBuf::from("other")]); + assert_eq!(roots.write_roots, [PathBuf::from("out"), PathBuf::from("out2")]); + // A kind the command line leaves alone keeps the environment's root. + let mut roots = AutomationArgs::new(Some("environment".into()), Some("environment out".into())); + assert!(roots.consume("--automation-read-root=a", &mut std::iter::empty()).unwrap()); + assert_eq!(roots.write_roots, [PathBuf::from("environment out")]); } #[test] @@ -117,11 +126,15 @@ mod tests { let mut roots = AutomationArgs::new(Some("read".into()), Some("write".into())); let mut args = operand.map(str::to_owned).into_iter(); assert!(roots.consume(flag, &mut args).is_err(), "{flag} {operand:?}"); - assert_eq!(roots.read_root, Some("read".into())); - assert_eq!(roots.write_root, Some("write".into())); + assert_eq!(roots.read_roots, [PathBuf::from("read")]); + assert_eq!(roots.write_roots, [PathBuf::from("write")]); } let mut roots = AutomationArgs::new(None, None); assert!(roots.consume(&format!("{flag}="), &mut std::iter::empty()).is_err()); + // A bad second folder is an error too, and keeps the first. + assert!(roots.consume(&format!("{flag}=first"), &mut std::iter::empty()).unwrap()); + assert!(roots.consume(flag, &mut std::iter::empty()).is_err()); + assert_eq!([roots.read_roots.len(), roots.write_roots.len()].iter().sum::(), 1); } } @@ -130,8 +143,8 @@ mod tests { let mut roots = AutomationArgs::new(None, None); assert!(roots.consume("--automation-read-root=-folder", &mut std::iter::empty()).unwrap()); assert!(roots.consume("--automation-write-root", &mut ["./-folder".to_owned()].into_iter()).unwrap()); - assert_eq!(roots.read_root, Some("-folder".into())); - assert_eq!(roots.write_root, Some("./-folder".into())); + assert_eq!(roots.read_roots, [PathBuf::from("-folder")]); + assert_eq!(roots.write_roots, [PathBuf::from("./-folder")]); } fn control(args: &[&str]) -> Result, String> { diff --git a/apps/vectorcraft/src/main.rs b/apps/vectorcraft/src/main.rs index 94a82abee..c857943fd 100644 --- a/apps/vectorcraft/src/main.rs +++ b/apps/vectorcraft/src/main.rs @@ -1,6 +1,6 @@ //! VectorCraft desktop app. //! -//! Usage: `vectorcraft [--control [--automation-read-root ] [--automation-write-root ]] +//! Usage: `vectorcraft [--control [--automation-read-root ]... [--automation-write-root ]...] //! [--in-window-menus] [files…]`, `vectorcraft --version`, `vectorcraft --help` (or `-h`) //! //! `--in-window-menus` (or `VECTORCRAFT_IN_WINDOW_MENUS=1`) keeps the menus inside the window on @@ -725,7 +725,9 @@ Options: --control Start the localhost JSON-lines control server on (or VECTORCRAFT_CONTROL_PORT) --automation-read-root Confine the files control requests read to + (may be given more than once) --automation-write-root Confine the files control requests write to + (may be given more than once) --in-window-menus Keep the menus inside the window on macOS (or VECTORCRAFT_IN_WINDOW_MENUS=1) --version Print the version and exit @@ -774,7 +776,7 @@ fn main() -> std::process::ExitCode { _ => files.push(a), } } - let automation_args::AutomationArgs { read_root, write_root, .. } = roots_args; + let automation_args::AutomationArgs { read_roots, write_roots, .. } = roots_args; // After the arguments: `--control` and `--version` win over a bad environment value. let control_port = match control.port { Some(port) => Some(port), @@ -813,12 +815,14 @@ fn main() -> std::process::ExitCode { // The roots confine the control channel: a bad one keeps the app from starting rather than // leaving agents unconfined. Without a control port there is nothing to confine. let roots = if control_port.is_none() { - if read_root.is_some() || write_root.is_some() { + if !read_roots.is_empty() || !write_roots.is_empty() { log::warn!("--automation-read-root and --automation-write-root confine the control channel: ignored without --control"); } None } else { - match AutomationRoots::new(read_root.as_deref(), write_root.as_deref()) { + let read: Vec<&std::path::Path> = read_roots.iter().map(std::path::PathBuf::as_path).collect(); + let write: Vec<&std::path::Path> = write_roots.iter().map(std::path::PathBuf::as_path).collect(); + match AutomationRoots::new(&read, &write) { Ok(roots) => roots, Err(e) => { log::error!("{e}"); @@ -1015,7 +1019,7 @@ fn main() -> std::process::ExitCode { // Everywhere the app doesn't say whose work it is (worker threads), the // roots hold: unconfined is only what the person at the keyboard does. file_access::confine_process(roots.clone()); - log::info!("control channel confined to the automation roots: read {:?}, write {:?}", roots.read_root(), roots.write_root()); + log::info!("control channel confined to the automation roots: {}", roots.describe()); } } #[cfg(target_os = "macos")] diff --git a/apps/vectorcraft/tests/automation_args.rs b/apps/vectorcraft/tests/automation_args.rs index 67e2ec846..8156ff678 100644 --- a/apps/vectorcraft/tests/automation_args.rs +++ b/apps/vectorcraft/tests/automation_args.rs @@ -60,11 +60,17 @@ fn missing_empty_or_option_operands_fail_before_startup() { } } +/// #3546: a root flag may be given more than once, in either spelling; a bad later operand still +/// stops the app before it starts. #[test] -fn duplicate_roots_fail_for_both_spellings() { +fn repeated_roots_are_accepted_for_both_spellings() { for flag in ["--automation-read-root", "--automation-write-root"] { - rejects(&[flag, ".", &format!("{flag}=.")], &format!("{flag} given twice")); - rejects(&[&format!("{flag}=."), flag, "."], &format!("{flag} given twice")); + let out = Command::new(BIN).args([flag, ".", format!("{flag}=.").as_str(), flag, "./", "--version"]).output().unwrap(); + assert!(out.status.success(), "{flag}: {}", String::from_utf8_lossy(&out.stderr)); + assert_eq!(String::from_utf8(out.stdout).unwrap(), format!("vectorcraft {}\n", env!("CARGO_PKG_VERSION"))); + assert!(out.stderr.is_empty()); + rejects(&[flag, ".", flag, ""], &format!("{flag} needs a non-empty folder")); + rejects(&[&format!("{flag}=."), flag], &format!("{flag} needs a folder")); } } diff --git a/crates/engine/src/file_access.rs b/crates/engine/src/file_access.rs index 0c3f44d91..bc821a357 100644 --- a/crates/engine/src/file_access.rs +++ b/crates/engine/src/file_access.rs @@ -2,15 +2,16 @@ //! //! `vectorcraft-cli mcp --automation-read-root --automation-write-root `, and //! `vectorcraft --control ` with the same flags, confine the files an agent's commands -//! reach. [`AutomationRoots`] holds the two folders. While they are in force, every path a command -//! reads goes through [`check_read`] and every path it writes through [`check_write`]: +//! reach; each flag may be given more than once (#3546). [`AutomationRoots`] holds the folders of +//! each kind. While they are in force, every path a command reads goes through [`check_read`] and +//! every path it writes through [`check_write`]: //! //! - The path is made absolute against the working directory, as the file system reads it. Its //! links are followed: for a file that doesn't exist yet, the links of its nearest existing //! folder, the rest of the path being plain names (`..` below a missing folder is refused, and so //! is a link that leads nowhere). -//! - The result must lie inside the root, compared by whole components (`/work2` isn't inside -//! `/work`), without regard to case on Windows. +//! - The result must lie inside one of the roots of its kind, compared by whole components +//! (`/work2` isn't inside `/work`), without regard to case on Windows. //! - Read and write access are separate: a root given alone grants only its own kind of access. //! //! Roots are in force on a thread inside [`confine`], and in the whole process after @@ -35,11 +36,11 @@ pub enum Access { Write, } -/// The folders automation may read and write; a root left out grants no access of its kind. +/// The folders automation may read and write; a kind with no root grants no access of its kind. #[derive(Debug)] pub struct AutomationRoots { - read: Option, - write: Option, + read: Vec, + write: Vec, } #[derive(Debug)] @@ -65,48 +66,59 @@ impl Root { } impl AutomationRoots { - /// The roots `read` and `write`, which must be existing folders; `None` when neither is given - /// (nothing is confined then). - pub fn new(read: Option<&Path>, write: Option<&Path>) -> Result>, String> { - if read.is_none() && write.is_none() { + /// The roots `read` and `write`, which must be existing folders (one given twice counts once); + /// `None` when neither kind has any (nothing is confined then). + pub fn new(read: &[&Path], write: &[&Path]) -> Result>, String> { + if read.is_empty() && write.is_empty() { return Ok(None); } - let read = read.map(|p| Root::open(p, Access::Read)).transpose()?; - let write = write.map(|p| Root::open(p, Access::Write)).transpose()?; - Ok(Some(Arc::new(Self { read, write }))) - } - - /// The read root, absolute with its links followed. - pub fn read_root(&self) -> Option<&Path> { - self.read.as_ref().map(|r| r.shown.as_path()) - } - - /// The write root, absolute with its links followed. - pub fn write_root(&self) -> Option<&Path> { - self.write.as_ref().map(|r| r.shown.as_path()) - } - - /// May the file or folder at `path` be read (or written)? `Err` says why not. - pub fn check(&self, path: &str, access: Access) -> Result<(), String> { - let root = match access { - Access::Read => self.read.as_ref(), - Access::Write => self.write.as_ref(), + let open = |paths: &[&Path], access: Access| -> Result, String> { + let mut roots: Vec = Vec::with_capacity(paths.len()); + for path in paths { + let root = Root::open(path, access)?; + if !roots.iter().any(|r| r.resolved == root.resolved) { + roots.push(root); + } + } + Ok(roots) }; - let Some(Root { resolved: folder, shown }) = root else { + Ok(Some(Arc::new(Self { read: open(read, Access::Read)?, write: open(write, Access::Write)? }))) + } + + /// May the file or folder at `path` be read (or written)? It must lie inside one of the roots + /// of that kind. `Err` says why not. + pub fn check(&self, path: &str, access: Access) -> Result<(), String> { + let roots = match access { + Access::Read => &self.read, + Access::Write => &self.write, + }; + // The messages are spelled out whole, as the translation catalogs match them. + if roots.is_empty() { return match access { Access::Read => Err(format!("automation filesystem access is not granted: read authority is absent: {path}")), Access::Write => Err(format!("automation filesystem access is not granted: write authority is absent: {path}")), }; - }; - if inside(&resolve(path)?, folder) { + } + let resolved = resolve(path)?; + if roots.iter().any(|r| inside(&resolved, &r.resolved)) { return Ok(()); } - let root = shown.display(); - match access { - Access::Read => Err(format!("automation path rejected: outside the read root {root}: {path}")), - Access::Write => Err(format!("automation path rejected: outside the write root {root}: {path}")), + let shown: Vec = roots.iter().map(|r| r.shown.display().to_string()).collect(); + match (shown.as_slice(), access) { + ([root], Access::Read) => Err(format!("automation path rejected: outside the read root {root}: {path}")), + ([root], Access::Write) => Err(format!("automation path rejected: outside the write root {root}: {path}")), + (_, Access::Read) => Err(format!("automation path rejected: outside the read roots {roots}: {path}", roots = shown.join(", "))), + (_, Access::Write) => Err(format!("automation path rejected: outside the write roots {roots}: {path}", roots = shown.join(", "))), } } + + /// The roots for people: `read A, B; write C` (`none` for a kind without any). + pub fn describe(&self) -> String { + let list = |roots: &[Root]| { + if roots.is_empty() { "none".to_string() } else { roots.iter().map(|r| r.shown.display().to_string()).collect::>().join(", ") } + }; + format!("read {}; write {}", list(&self.read), list(&self.write)) + } } /// Where the roots are in force on a thread ([`confine`], [`unconfined`]). @@ -280,7 +292,7 @@ mod tests { } std::fs::write(base.join("in/a.svg"), "").unwrap(); std::fs::write(base.join("outside/secret.svg"), "").unwrap(); - let roots = AutomationRoots::new(Some(&base.join("in")), Some(&base.join("out"))).unwrap().unwrap(); + let roots = AutomationRoots::new(&[&base.join("in")], &[&base.join("out")]).unwrap().unwrap(); (base, roots) } @@ -340,18 +352,20 @@ mod tests { #[test] fn a_missing_root_is_an_error_and_none_is_no_confinement() { let (base, _) = folders("missing"); - let e = AutomationRoots::new(Some(&base.join("nope")), None).unwrap_err(); + let e = AutomationRoots::new(&[&base.join("nope")], &[]).unwrap_err(); assert!(e.starts_with("cannot open the automation read root"), "{e}"); - let e = AutomationRoots::new(None, Some(&base.join("in/a.svg"))).unwrap_err(); + let e = AutomationRoots::new(&[], &[&base.join("in/a.svg")]).unwrap_err(); assert!( e.starts_with("cannot open the automation write root") && e.ends_with(&std::io::Error::from(std::io::ErrorKind::NotADirectory).to_string()), "{e}" ); - assert!(AutomationRoots::new(Some(Path::new("")), None).unwrap_err().starts_with("cannot open the automation read root ``")); - assert!(AutomationRoots::new(None, None).unwrap().is_none()); + assert!(AutomationRoots::new(&[Path::new("")], &[]).unwrap_err().starts_with("cannot open the automation read root ``")); + assert!(AutomationRoots::new(&[], &[]).unwrap().is_none()); + // A missing folder among several is an error too. + assert!(AutomationRoots::new(&[&base.join("in"), &base.join("nope")], &[]).unwrap_err().starts_with("cannot open the automation read root")); // One root alone grants only its own access. - let read_only = AutomationRoots::new(Some(&base.join("in")), None).unwrap().unwrap(); + let read_only = AutomationRoots::new(&[&base.join("in")], &[]).unwrap().unwrap(); let e = read_only.check(&s(base.join("in/x.svg")), Access::Write).unwrap_err(); assert!(e.contains("write authority is absent"), "{e}"); let _ = std::fs::remove_dir_all(base); @@ -388,11 +402,55 @@ mod tests { assert_eq!(roots.check(&s(base.join("in/alias/x.svg")), Access::Read), Ok(())); // A root given through a link is the folder it leads to. assert!(made(&base.join("in"), &base.join("in-link"))); - let linked = AutomationRoots::new(Some(&base.join("in-link")), None).unwrap().unwrap(); + let linked = AutomationRoots::new(&[&base.join("in-link")], &[]).unwrap().unwrap(); assert_eq!(linked.check(&s(base.join("in/a.svg")), Access::Read), Ok(())); let _ = std::fs::remove_dir_all(base); } + /// #3546: several roots of a kind: a path inside any of them passes, one inside none is + /// refused naming them all, and read and write stay separate. + #[test] + fn several_roots_of_a_kind_each_grant_their_access() { + let (base, _) = folders("several"); + std::fs::create_dir_all(base.join("out2")).unwrap(); + std::fs::write(base.join("in2/b.svg"), "").unwrap(); + let roots = AutomationRoots::new(&[&base.join("in"), &base.join("in2")], &[&base.join("out"), &base.join("out2")]).unwrap().unwrap(); + assert_eq!(roots.check(&s(base.join("in/a.svg")), Access::Read), Ok(())); + assert_eq!(roots.check(&s(base.join("in2/b.svg")), Access::Read), Ok(())); + let e = roots.check(&s(base.join("outside/secret.svg")), Access::Read).unwrap_err(); + assert!(e.starts_with("automation path rejected: outside the read roots ") && e.contains(", "), "{e}"); + assert_eq!(roots.check(&s(base.join("out/x.png")), Access::Write), Ok(())); + assert_eq!(roots.check(&s(base.join("out2/new/x.png")), Access::Write), Ok(())); + assert!(roots.check(&s(base.join("outside/x.png")), Access::Write).unwrap_err().contains("outside the write roots")); + // Read and write access stay separate with several roots. + assert!(roots.check(&s(base.join("in2/b.svg")), Access::Write).is_err()); + assert!(roots.check(&s(base.join("out2/x.png")), Access::Read).is_err()); + // `..` out of one root into another root's sibling is still outside. + let sep = std::path::MAIN_SEPARATOR; + let up = format!("{}{sep}..{sep}outside{sep}secret.svg", s(base.join("in2"))); + assert!(roots.check(&up, Access::Read).is_err()); + // A folder given twice (here once through `..`) counts once. + let twice = AutomationRoots::new(&[&base.join("in"), &base.join("in/sub/..")], &[]).unwrap().unwrap(); + assert!(!twice.describe().contains(", "), "{}", twice.describe()); + assert!(twice.describe().ends_with("; write none"), "{}", twice.describe()); + let _ = std::fs::remove_dir_all(base); + } + + /// A link inside one root that leads into another root of the same kind stays inside; one that + /// leads out of all of them is outside. + #[cfg(unix)] + #[test] + fn links_between_several_roots() { + let (base, _) = folders("several-links"); + std::fs::write(base.join("in2/b.svg"), "").unwrap(); + std::os::unix::fs::symlink(base.join("in2/b.svg"), base.join("in/to-in2.svg")).unwrap(); + std::os::unix::fs::symlink(base.join("outside/secret.svg"), base.join("in2/leak.svg")).unwrap(); + let roots = AutomationRoots::new(&[&base.join("in"), &base.join("in2")], &[]).unwrap().unwrap(); + assert_eq!(roots.check(&s(base.join("in/to-in2.svg")), Access::Read), Ok(())); + assert!(roots.check(&s(base.join("in2/leak.svg")), Access::Read).unwrap_err().contains("outside the read roots")); + let _ = std::fs::remove_dir_all(base); + } + #[cfg(windows)] #[test] fn windows_ignores_case_and_refuses_devices() { diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 41d1433bb..ed7cee619 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -3145,7 +3145,7 @@ fn file_list_lists_a_folder_within_the_read_root() { json!([{"name": "a.pdf", "kind": "file", "size": 4}, {"name": "b.svg", "kind": "file", "size": 6}, {"name": "sub", "kind": "folder"}]) ); assert_eq!(r["truncated"], false); - let roots = crate::file_access::AutomationRoots::new(Some(&base.join("in")), None).unwrap().unwrap(); + let roots = crate::file_access::AutomationRoots::new(&[&base.join("in")], &[]).unwrap().unwrap(); crate::file_access::confine(Some(&roots), || { assert!(s.execute("file.list", &json!({"path": path("in/sub")})).is_ok()); assert!(s.execute("file.list", &json!({"path": path("outside")})).is_err(), "outside the read root"); diff --git a/crates/engine/src/tests_linkfiles.rs b/crates/engine/src/tests_linkfiles.rs index 1c4cc5306..4fd26220f 100644 --- a/crates/engine/src/tests_linkfiles.rs +++ b/crates/engine/src/tests_linkfiles.rs @@ -306,7 +306,7 @@ fn a_copy_stays_inside_the_write_root() { let allowed = root.join("allowed"); write(&allowed.join("photo.png"), b"kept as it is"); std::os::unix::fs::symlink(allowed.join("photo.png"), root.join("doc/photo.png")).unwrap(); - let roots = crate::file_access::AutomationRoots::new(Some(&root), Some(&allowed)).unwrap(); + let roots = crate::file_access::AutomationRoots::new(&[&root], &[&allowed]).unwrap(); let r = crate::file_access::confine(roots.as_ref(), || s.execute("links.relink", &json!({"ids": [id], "path": found, "copy": true}))); assert!(matches!(&r, Err(EngineError::Other(m)) if m.contains("outside the write root")), "{r:?}"); assert!(!root.join("doc/photo 2.png").exists()); @@ -332,7 +332,7 @@ fn an_svg_reads_its_links_within_the_read_root() { }; let (inside, outside) = (import("in"), import("out")); assert_eq!(inside, outside, "both are read with no roots in force"); - let roots = crate::file_access::AutomationRoots::new(Some(&root.join("in")), None).unwrap(); + let roots = crate::file_access::AutomationRoots::new(&[&root.join("in")], &[]).unwrap(); crate::file_access::confine(roots.as_ref(), || { assert_eq!(import("in"), inside, "inside the read root: read as before"); assert_ne!(import("out"), outside, "outside the read root: not read"); diff --git a/crates/engine/src/tests_scripts.rs b/crates/engine/src/tests_scripts.rs index 36b163262..44341f2bc 100644 --- a/crates/engine/src/tests_scripts.rs +++ b/crates/engine/src/tests_scripts.rs @@ -113,7 +113,7 @@ fn links_out_of_the_folder_and_the_automation_read_root_keep_reads_inside() { assert_eq!(statuses(&r), ["notFollowed", "checked"]); assert_eq!(r["outside"], json!([])); // Under a read root, the script and its includes are read inside it, and a script outside isn't. - let roots = crate::file_access::AutomationRoots::new(Some(&dir.join("scripts")), None).unwrap().unwrap(); + let roots = crate::file_access::AutomationRoots::new(&[&dir.join("scripts")], &[]).unwrap().unwrap(); let mut s = Session::new(); let r = crate::file_access::confine(Some(&roots), || s.execute("script.check", &json!({"path": main, "includes": true}))).unwrap(); assert_eq!(statuses(&r), ["notFollowed", "checked"]); diff --git a/crates/mcp/tests/automation_roots.rs b/crates/mcp/tests/automation_roots.rs index fb94bd69e..2c76d658c 100644 --- a/crates/mcp/tests/automation_roots.rs +++ b/crates/mcp/tests/automation_roots.rs @@ -25,7 +25,9 @@ fn confined(tag: &str, read: bool, write: bool) -> (Server, PathBuf) { std::fs::write(base.join("in2/c.svg"), SVG).unwrap(); let in_dir = base.join("in"); let out_dir = base.join("out"); - let roots = AutomationRoots::new(read.then_some(in_dir.as_path()), write.then_some(out_dir.as_path())).unwrap(); + let (read, write): (Vec<&Path>, Vec<&Path>) = + (read.then_some(in_dir.as_path()).into_iter().collect(), write.then_some(out_dir.as_path()).into_iter().collect()); + let roots = AutomationRoots::new(&read, &write).unwrap(); (Server::new(Box::new(Headless::with_document().with_automation_roots(roots))), base) } diff --git a/crates/mcp/tests/protocol_props.rs b/crates/mcp/tests/protocol_props.rs index 9605a0ca7..2eb3db647 100644 --- a/crates/mcp/tests/protocol_props.rs +++ b/crates/mcp/tests/protocol_props.rs @@ -173,7 +173,7 @@ proptest! { let root = base.join("a/b/c/root"); std::fs::create_dir_all(&root).unwrap(); std::fs::create_dir_all(base.join("outside")).unwrap(); - let roots = vectorcraft_engine::file_access::AutomationRoots::new(Some(root.as_path()), Some(root.as_path())).unwrap(); + let roots = vectorcraft_engine::file_access::AutomationRoots::new(&[root.as_path()], &[root.as_path()]).unwrap(); let mut s = Server::new(Box::new(Headless::with_document().with_automation_roots(roots))); let path = format!("{}{}{tail}", root.display(), std::path::MAIN_SEPARATOR); let r = catch_quiet(|| call(&mut s, tool, json!({"path": path}))); diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 0212ac7e2..a2c68f1f6 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -4375,7 +4375,9 @@ axis Both И двете @msg automation filesystem access is not granted: read authority is absent: {path} достъпът на автоматизацията до файловата система не е разрешен: липсва право за четене: {path} @msg automation filesystem access is not granted: write authority is absent: {path} достъпът на автоматизацията до файловата система не е разрешен: липсва право за запис: {path} @msg automation path rejected: outside the read root {root}: {path} пътят за автоматизация е отхвърлен: извън корена за четене {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} пътят за автоматизация е отхвърлен: извън корените за четене {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} пътят за автоматизация е отхвърлен: извън корена за запис {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} пътят за автоматизация е отхвърлен: извън корените за запис {roots}: {path} @msg automation path rejected: path is empty пътят за автоматизация е отхвърлен: пътят е празен @msg automation path rejected: a link that leads nowhere: {path} пътят за автоматизация е отхвърлен: връзка, която не води доникъде: {path} @msg automation path rejected: cannot resolve {path}: {e} пътят за автоматизация е отхвърлен: не може да се разреши {path}: {e} diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 8faf1c71e..c703d3cf7 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -4737,7 +4737,9 @@ imageTrace Outline Obrys @msg automation filesystem access is not granted: read authority is absent: {path} přístup automatizace k systému souborů není udělen: chybí oprávnění ke čtení: {path} @msg automation filesystem access is not granted: write authority is absent: {path} přístup automatizace k systému souborů není udělen: chybí oprávnění k zápisu: {path} @msg automation path rejected: outside the read root {root}: {path} cesta automatizace odmítnuta: mimo kořen pro čtení {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} cesta automatizace odmítnuta: mimo kořeny pro čtení {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} cesta automatizace odmítnuta: mimo kořen pro zápis {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} cesta automatizace odmítnuta: mimo kořeny pro zápis {roots}: {path} @msg automation path rejected: path is empty cesta automatizace odmítnuta: cesta je prázdná @msg automation path rejected: a link that leads nowhere: {path} cesta automatizace odmítnuta: odkaz, který nikam nevede: {path} @msg automation path rejected: cannot resolve {path}: {e} cesta automatizace odmítnuta: {path} nelze vyhodnotit: {e} diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 314dfd2de..fe30df4b6 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -4435,7 +4435,9 @@ imageTrace Outline Kontur @msg automation filesystem access is not granted: read authority is absent: {path} automatiseringens adgang til filsystemet er ikke tildelt: læserettighed mangler: {path} @msg automation filesystem access is not granted: write authority is absent: {path} automatiseringens adgang til filsystemet er ikke tildelt: skriverettighed mangler: {path} @msg automation path rejected: outside the read root {root}: {path} automatiseringssti afvist: uden for læseroden {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} automatiseringssti afvist: uden for læserødderne {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} automatiseringssti afvist: uden for skriveroden {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} automatiseringssti afvist: uden for skriverødderne {roots}: {path} @msg automation path rejected: path is empty automatiseringssti afvist: stien er tom @msg automation path rejected: a link that leads nowhere: {path} automatiseringssti afvist: et link, der ikke fører nogen steder hen: {path} @msg automation path rejected: cannot resolve {path}: {e} automatiseringssti afvist: kan ikke finde frem til {path}: {e} diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 13e0f9771..09a1fd1cb 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4396,7 +4396,9 @@ theme Appearance Erscheinungsbild @msg automation filesystem access is not granted: read authority is absent: {path} Dateisystemzugriff für die Automatisierung ist nicht gewährt: Die Leseberechtigung fehlt: {path} @msg automation filesystem access is not granted: write authority is absent: {path} Dateisystemzugriff für die Automatisierung ist nicht gewährt: Die Schreibberechtigung fehlt: {path} @msg automation path rejected: outside the read root {root}: {path} Automatisierungspfad abgelehnt: außerhalb des Lesestammordners {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} Automatisierungspfad abgelehnt: außerhalb der Lesestammordner {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} Automatisierungspfad abgelehnt: außerhalb des Schreibstammordners {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} Automatisierungspfad abgelehnt: außerhalb der Schreibstammordner {roots}: {path} @msg automation path rejected: path is empty Automatisierungspfad abgelehnt: Der Pfad ist leer @msg automation path rejected: a link that leads nowhere: {path} Automatisierungspfad abgelehnt: eine Verknüpfung, die nirgendwohin führt: {path} @msg automation path rejected: cannot resolve {path}: {e} Automatisierungspfad abgelehnt: {path} kann nicht aufgelöst werden: {e} diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 2f64c8c73..f9aaae37a 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -4439,7 +4439,9 @@ theme Appearance Εμφάνιση @msg automation filesystem access is not granted: read authority is absent: {path} δεν έχει επιτραπεί πρόσβαση αυτοματισμού στο σύστημα αρχείων: δεν υπάρχει δικαίωμα ανάγνωσης: {path} @msg automation filesystem access is not granted: write authority is absent: {path} δεν έχει επιτραπεί πρόσβαση αυτοματισμού στο σύστημα αρχείων: δεν υπάρχει δικαίωμα εγγραφής: {path} @msg automation path rejected: outside the read root {root}: {path} απορρίφθηκε η διαδρομή αυτοματισμού: εκτός ρίζας ανάγνωσης {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} απορρίφθηκε η διαδρομή αυτοματισμού: εκτός των ριζών ανάγνωσης {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} απορρίφθηκε η διαδρομή αυτοματισμού: εκτός ρίζας εγγραφής {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} απορρίφθηκε η διαδρομή αυτοματισμού: εκτός των ριζών εγγραφής {roots}: {path} @msg automation path rejected: path is empty απορρίφθηκε η διαδρομή αυτοματισμού: η διαδρομή είναι κενή @msg automation path rejected: a link that leads nowhere: {path} απορρίφθηκε η διαδρομή αυτοματισμού: σύνδεσμος χωρίς προορισμό: {path} @msg automation path rejected: cannot resolve {path}: {e} απορρίφθηκε η διαδρομή αυτοματισμού: δεν ήταν δυνατή η επίλυση του {path}: {e} diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 3b65be408..af185d390 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -4458,7 +4458,9 @@ axis Both Ambos @msg automation filesystem access is not granted: read authority is absent: {path} la automatización no tiene acceso al sistema de archivos: falta el permiso de lectura: {path} @msg automation filesystem access is not granted: write authority is absent: {path} la automatización no tiene acceso al sistema de archivos: falta el permiso de escritura: {path} @msg automation path rejected: outside the read root {root}: {path} ruta de automatización rechazada: fuera de la carpeta raíz de lectura {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} ruta de automatización rechazada: fuera de las carpetas raíz de lectura {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} ruta de automatización rechazada: fuera de la carpeta raíz de escritura {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} ruta de automatización rechazada: fuera de las carpetas raíz de escritura {roots}: {path} @msg automation path rejected: path is empty ruta de automatización rechazada: la ruta está vacía @msg automation path rejected: a link that leads nowhere: {path} ruta de automatización rechazada: un vínculo que no lleva a ninguna parte: {path} @msg automation path rejected: cannot resolve {path}: {e} ruta de automatización rechazada: no se puede resolver {path}: {e} diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 56f1eff41..8f4d7e74d 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -4646,7 +4646,9 @@ imageTrace Outline Ääriviiva @msg automation filesystem access is not granted: read authority is absent: {path} automaation tiedostojärjestelmäkäyttöä ei ole myönnetty: lukuoikeus puuttuu: {path} @msg automation filesystem access is not granted: write authority is absent: {path} automaation tiedostojärjestelmäkäyttöä ei ole myönnetty: kirjoitusoikeus puuttuu: {path} @msg automation path rejected: outside the read root {root}: {path} automaation polku hylättiin: lukujuuren {root} ulkopuolella: {path} +@msg automation path rejected: outside the read roots {roots}: {path} automaation polku hylättiin: lukujuurten {roots} ulkopuolella: {path} @msg automation path rejected: outside the write root {root}: {path} automaation polku hylättiin: kirjoitusjuuren {root} ulkopuolella: {path} +@msg automation path rejected: outside the write roots {roots}: {path} automaation polku hylättiin: kirjoitusjuurten {roots} ulkopuolella: {path} @msg automation path rejected: path is empty automaation polku hylättiin: polku on tyhjä @msg automation path rejected: a link that leads nowhere: {path} automaation polku hylättiin: linkki, joka ei johda mihinkään: {path} @msg automation path rejected: cannot resolve {path}: {e} automaation polku hylättiin: polkua {path} ei voi selvittää: {e} diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index ad7199848..b3abd6044 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -4469,7 +4469,9 @@ axis Both Les deux @msg automation filesystem access is not granted: read authority is absent: {path} l’automatisation n’a pas accès au système de fichiers : aucune autorisation de lecture : {path} @msg automation filesystem access is not granted: write authority is absent: {path} l’automatisation n’a pas accès au système de fichiers : aucune autorisation d’écriture : {path} @msg automation path rejected: outside the read root {root}: {path} chemin d’automatisation refusé : hors du dossier racine de lecture {root} : {path} +@msg automation path rejected: outside the read roots {roots}: {path} chemin d’automatisation refusé : hors des dossiers racines de lecture {roots} : {path} @msg automation path rejected: outside the write root {root}: {path} chemin d’automatisation refusé : hors du dossier racine d’écriture {root} : {path} +@msg automation path rejected: outside the write roots {roots}: {path} chemin d’automatisation refusé : hors des dossiers racines d’écriture {roots} : {path} @msg automation path rejected: path is empty chemin d’automatisation refusé : le chemin est vide @msg automation path rejected: a link that leads nowhere: {path} chemin d’automatisation refusé : un lien qui ne mène nulle part : {path} @msg automation path rejected: cannot resolve {path}: {e} chemin d’automatisation refusé : impossible de résoudre {path} : {e} diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 69f97dcb3..ea04d18a5 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4493,7 +4493,9 @@ imageTrace Outline Körvonal @msg automation path rejected: a link that leads nowhere: {path} az automatizálás útvonala elutasítva: a hivatkozás nem vezet sehova: {path} @msg automation path rejected: cannot resolve {path}: {e} az automatizálás útvonala elutasítva: nem oldható fel: {path}: {e} @msg automation path rejected: outside the read root {root}: {path} az automatizálás útvonala elutasítva: kívül esik az olvasási gyökéren ({root}): {path} +@msg automation path rejected: outside the read roots {roots}: {path} az automatizálás útvonala elutasítva: kívül esik az olvasási gyökereken ({roots}): {path} @msg automation path rejected: outside the write root {root}: {path} az automatizálás útvonala elutasítva: kívül esik az írási gyökéren ({root}): {path} +@msg automation path rejected: outside the write roots {roots}: {path} az automatizálás útvonala elutasítva: kívül esik az írási gyökereken ({roots}): {path} @msg automation path rejected: parent traversal below a folder that doesn't exist: {path} az automatizálás útvonala elutasítva: szülőmappára lépés egy nem létező mappa alatt: {path} @msg automation path rejected: path is empty az automatizálás útvonala elutasítva: az útvonal üres @msg automation path rejected: reserved device names are not allowed: {path} az automatizálás útvonala elutasítva: fenntartott eszköznév nem használható: {path} diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 6aaaf5c22..71ec9799f 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -4435,7 +4435,9 @@ imageTrace Outline Kontur @msg automation filesystem access is not granted: read authority is absent: {path} akses sistem berkas otomatisasi tidak diberikan: otoritas baca tidak ada: {path} @msg automation filesystem access is not granted: write authority is absent: {path} akses sistem berkas otomatisasi tidak diberikan: otoritas tulis tidak ada: {path} @msg automation path rejected: outside the read root {root}: {path} jalur otomatisasi ditolak: di luar akar baca {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} jalur otomatisasi ditolak: di luar akar-akar baca {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} jalur otomatisasi ditolak: di luar akar tulis {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} jalur otomatisasi ditolak: di luar akar-akar tulis {roots}: {path} @msg automation path rejected: path is empty jalur otomatisasi ditolak: jalur kosong @msg automation path rejected: a link that leads nowhere: {path} jalur otomatisasi ditolak: tautan yang tidak mengarah ke mana pun: {path} @msg automation path rejected: cannot resolve {path}: {e} jalur otomatisasi ditolak: tidak dapat menyelesaikan {path}: {e} diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 3e7899c69..0a4cc2cb3 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -4498,7 +4498,9 @@ axis Both Entrambi @msg automation filesystem access is not granted: read authority is absent: {path} l'automazione non ha accesso al file system: manca l'autorizzazione di lettura: {path} @msg automation filesystem access is not granted: write authority is absent: {path} l'automazione non ha accesso al file system: manca l'autorizzazione di scrittura: {path} @msg automation path rejected: outside the read root {root}: {path} percorso di automazione rifiutato: fuori dalla cartella radice di lettura {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} percorso di automazione rifiutato: fuori dalle cartelle radice di lettura {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} percorso di automazione rifiutato: fuori dalla cartella radice di scrittura {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} percorso di automazione rifiutato: fuori dalle cartelle radice di scrittura {roots}: {path} @msg automation path rejected: path is empty percorso di automazione rifiutato: il percorso è vuoto @msg automation path rejected: a link that leads nowhere: {path} percorso di automazione rifiutato: un collegamento che non porta da nessuna parte: {path} @msg automation path rejected: cannot resolve {path}: {e} percorso di automazione rifiutato: impossibile risolvere {path}: {e} diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 9889b34fc..045678fc9 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4536,7 +4536,9 @@ swatch Bright Violet ビビッドバイオレット @msg automation filesystem access is not granted: read authority is absent: {path} 自動化にはファイルシステムへのアクセスが許可されていません: 読み込み権限がありません: {path} @msg automation filesystem access is not granted: write authority is absent: {path} 自動化にはファイルシステムへのアクセスが許可されていません: 書き込み権限がありません: {path} @msg automation path rejected: outside the read root {root}: {path} 自動化のパスを拒否しました: 読み込みルート {root} の外です: {path} +@msg automation path rejected: outside the read roots {roots}: {path} 自動化のパスを拒否しました: 読み込みルート {roots} のいずれの中でもありません: {path} @msg automation path rejected: outside the write root {root}: {path} 自動化のパスを拒否しました: 書き込みルート {root} の外です: {path} +@msg automation path rejected: outside the write roots {roots}: {path} 自動化のパスを拒否しました: 書き込みルート {roots} のいずれの中でもありません: {path} @msg automation path rejected: path is empty 自動化のパスを拒否しました: パスが空です @msg automation path rejected: a link that leads nowhere: {path} 自動化のパスを拒否しました: リンク先が存在しません: {path} @msg automation path rejected: cannot resolve {path}: {e} 自動化のパスを拒否しました: {path} を解決できません: {e} diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index b0ddaac3f..cbf025322 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -4187,7 +4187,9 @@ effect Regular ჩვეულებრივი @msg automation filesystem access is not granted: read authority is absent: {path} ავტომატიზაციას ფაილურ სისტემაზე წვდომა არ აქვს: წაკითხვის უფლება არ არის: {path} @msg automation filesystem access is not granted: write authority is absent: {path} ავტომატიზაციას ფაილურ სისტემაზე წვდომა არ აქვს: ჩაწერის უფლება არ არის: {path} @msg automation path rejected: outside the read root {root}: {path} ავტომატიზაციის გზა უარყოფილია: წაკითხვის ძირის {root} გარეთაა: {path} +@msg automation path rejected: outside the read roots {roots}: {path} ავტომატიზაციის გზა უარყოფილია: წაკითხვის ძირების {roots} გარეთაა: {path} @msg automation path rejected: outside the write root {root}: {path} ავტომატიზაციის გზა უარყოფილია: ჩაწერის ძირის {root} გარეთაა: {path} +@msg automation path rejected: outside the write roots {roots}: {path} ავტომატიზაციის გზა უარყოფილია: ჩაწერის ძირების {roots} გარეთაა: {path} @msg automation path rejected: path is empty ავტომატიზაციის გზა უარყოფილია: გზა ცარიელია @msg automation path rejected: a link that leads nowhere: {path} ავტომატიზაციის გზა უარყოფილია: ბმული არსად მიდის: {path} @msg automation path rejected: cannot resolve {path}: {e} ავტომატიზაციის გზა უარყოფილია: {path} ვერ გაირკვა: {e} diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index ac2b30cc4..d9a80203d 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4468,7 +4468,9 @@ theme Appearance 외관 @msg automation filesystem access is not granted: read authority is absent: {path} 자동화 파일 시스템 접근이 허용되지 않았습니다: 읽기 권한이 없습니다: {path} @msg automation filesystem access is not granted: write authority is absent: {path} 자동화 파일 시스템 접근이 허용되지 않았습니다: 쓰기 권한이 없습니다: {path} @msg automation path rejected: outside the read root {root}: {path} 자동화 경로가 거부되었습니다: 읽기 루트 {root} 밖입니다: {path} +@msg automation path rejected: outside the read roots {roots}: {path} 자동화 경로가 거부되었습니다: 읽기 루트 {roots} 밖입니다: {path} @msg automation path rejected: outside the write root {root}: {path} 자동화 경로가 거부되었습니다: 쓰기 루트 {root} 밖입니다: {path} +@msg automation path rejected: outside the write roots {roots}: {path} 자동화 경로가 거부되었습니다: 쓰기 루트 {roots} 밖입니다: {path} @msg automation path rejected: path is empty 자동화 경로가 거부되었습니다: 경로가 비어 있습니다 @msg automation path rejected: a link that leads nowhere: {path} 자동화 경로가 거부되었습니다: 어디로도 이어지지 않는 링크입니다: {path} @msg automation path rejected: cannot resolve {path}: {e} 자동화 경로가 거부되었습니다: {path}을(를) 해석할 수 없습니다: {e} diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 4d340cfa3..97c75aa52 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -4447,7 +4447,9 @@ imageTrace Outline Contour @msg automation filesystem access is not granted: read authority is absent: {path} toegang tot het bestandssysteem voor automatisering is niet verleend: de leesbevoegdheid ontbreekt: {path} @msg automation filesystem access is not granted: write authority is absent: {path} toegang tot het bestandssysteem voor automatisering is niet verleend: de schrijfbevoegdheid ontbreekt: {path} @msg automation path rejected: outside the read root {root}: {path} automatiseringspad afgewezen: buiten de leesroot {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} automatiseringspad afgewezen: buiten de leesroots {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} automatiseringspad afgewezen: buiten de schrijfroot {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} automatiseringspad afgewezen: buiten de schrijfroots {roots}: {path} @msg automation path rejected: path is empty automatiseringspad afgewezen: het pad is leeg @msg automation path rejected: a link that leads nowhere: {path} automatiseringspad afgewezen: een koppeling die nergens heen leidt: {path} @msg automation path rejected: cannot resolve {path}: {e} automatiseringspad afgewezen: kan {path} niet oplossen: {e} diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 6d942aeb6..6eeb329de 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -4562,7 +4562,9 @@ imageTrace Outline Kontur @msg automation filesystem access is not granted: read authority is absent: {path} nie przyznano dostępu automatyzacji do systemu plików: brak uprawnień do odczytu: {path} @msg automation filesystem access is not granted: write authority is absent: {path} nie przyznano dostępu automatyzacji do systemu plików: brak uprawnień do zapisu: {path} @msg automation path rejected: outside the read root {root}: {path} odrzucono ścieżkę automatyzacji: poza katalogiem głównym odczytu {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} odrzucono ścieżkę automatyzacji: poza katalogami głównymi odczytu {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} odrzucono ścieżkę automatyzacji: poza katalogiem głównym zapisu {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} odrzucono ścieżkę automatyzacji: poza katalogami głównymi zapisu {roots}: {path} @msg automation path rejected: path is empty odrzucono ścieżkę automatyzacji: ścieżka jest pusta @msg automation path rejected: a link that leads nowhere: {path} odrzucono ścieżkę automatyzacji: łącze prowadzi donikąd: {path} @msg automation path rejected: cannot resolve {path}: {e} odrzucono ścieżkę automatyzacji: nie można rozwiązać {path}: {e} diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 89d6b53d2..faf688453 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -4003,7 +4003,9 @@ axis Both Ambos @msg automation filesystem access is not granted: read authority is absent: {path} o acesso da automação ao sistema de arquivos não foi concedido: falta autorização de leitura: {path} @msg automation filesystem access is not granted: write authority is absent: {path} o acesso da automação ao sistema de arquivos não foi concedido: falta autorização de gravação: {path} @msg automation path rejected: outside the read root {root}: {path} caminho da automação rejeitado: fora da raiz de leitura {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} caminho da automação rejeitado: fora das raízes de leitura {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} caminho da automação rejeitado: fora da raiz de gravação {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} caminho da automação rejeitado: fora das raízes de gravação {roots}: {path} @msg automation path rejected: path is empty caminho da automação rejeitado: o caminho está vazio @msg automation path rejected: a link that leads nowhere: {path} caminho da automação rejeitado: um link que não leva a lugar nenhum: {path} @msg automation path rejected: cannot resolve {path}: {e} caminho da automação rejeitado: não foi possível resolver {path}: {e} diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index b6a1813f0..1a3cc47f1 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -4496,7 +4496,9 @@ axis Both Оба @msg automation filesystem access is not granted: read authority is absent: {path} у автоматизации нет доступа к файловой системе: нет права на чтение: {path} @msg automation filesystem access is not granted: write authority is absent: {path} у автоматизации нет доступа к файловой системе: нет права на запись: {path} @msg automation path rejected: outside the read root {root}: {path} путь автоматизации отклонён: вне корневой папки для чтения {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} путь автоматизации отклонён: вне корневых папок для чтения {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} путь автоматизации отклонён: вне корневой папки для записи {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} путь автоматизации отклонён: вне корневых папок для записи {roots}: {path} @msg automation path rejected: path is empty путь автоматизации отклонён: путь пуст @msg automation path rejected: a link that leads nowhere: {path} путь автоматизации отклонён: ссылка, которая никуда не ведёт: {path} @msg automation path rejected: cannot resolve {path}: {e} путь автоматизации отклонён: не удаётся определить {path}: {e} diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 422a35f64..fd478fbab 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -4381,7 +4381,9 @@ axis Both Båda @msg automation filesystem access is not granted: read authority is absent: {path} automatiseringen har inte fått åtkomst till filsystemet: läsbehörighet saknas: {path} @msg automation filesystem access is not granted: write authority is absent: {path} automatiseringen har inte fått åtkomst till filsystemet: skrivbehörighet saknas: {path} @msg automation path rejected: outside the read root {root}: {path} automatiseringssökvägen avvisades: utanför läsroten {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} automatiseringssökvägen avvisades: utanför läsrötterna {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} automatiseringssökvägen avvisades: utanför skrivroten {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} automatiseringssökvägen avvisades: utanför skrivrötterna {roots}: {path} @msg automation path rejected: path is empty automatiseringssökvägen avvisades: sökvägen är tom @msg automation path rejected: a link that leads nowhere: {path} automatiseringssökvägen avvisades: en länk som inte leder någonstans: {path} @msg automation path rejected: cannot resolve {path}: {e} automatiseringssökvägen avvisades: kan inte lösa upp {path}: {e} diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 08cba4815..adf29c2fb 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -4224,7 +4224,9 @@ axis Both Her İkisi @msg automation filesystem access is not granted: read authority is absent: {path} otomasyon dosya sistemine erişim izni verilmedi: okuma yetkisi yok: {path} @msg automation filesystem access is not granted: write authority is absent: {path} otomasyon dosya sistemine erişim izni verilmedi: yazma yetkisi yok: {path} @msg automation path rejected: outside the read root {root}: {path} otomasyon yolu reddedildi: okuma kökünün dışında {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} otomasyon yolu reddedildi: okuma köklerinin dışında {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} otomasyon yolu reddedildi: yazma kökünün dışında {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} otomasyon yolu reddedildi: yazma köklerinin dışında {roots}: {path} @msg automation path rejected: path is empty otomasyon yolu reddedildi: yol boş @msg automation path rejected: a link that leads nowhere: {path} otomasyon yolu reddedildi: hiçbir yere gitmeyen bağlantı: {path} @msg automation path rejected: cannot resolve {path}: {e} otomasyon yolu reddedildi: {path} çözülemiyor: {e} diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 5a6abd94a..9451accd5 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4482,7 +4482,9 @@ axis Both Обидві @msg automation filesystem access is not granted: read authority is absent: {path} автоматизація не має доступу до файлової системи: немає права на читання: {path} @msg automation filesystem access is not granted: write authority is absent: {path} автоматизація не має доступу до файлової системи: немає права на запис: {path} @msg automation path rejected: outside the read root {root}: {path} шлях автоматизації відхилено: поза кореневою папкою для читання {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} шлях автоматизації відхилено: поза кореневими папками для читання {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} шлях автоматизації відхилено: поза кореневою папкою для запису {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} шлях автоматизації відхилено: поза кореневими папками для запису {roots}: {path} @msg automation path rejected: path is empty шлях автоматизації відхилено: шлях порожній @msg automation path rejected: a link that leads nowhere: {path} шлях автоматизації відхилено: посилання, яке нікуди не веде: {path} @msg automation path rejected: cannot resolve {path}: {e} шлях автоматизації відхилено: не вдається визначити {path}: {e} diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 5557a1f23..5a209caa6 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4142,7 +4142,9 @@ theme Appearance Giao diện @msg automation path rejected: a link that leads nowhere: {path} đường dẫn tự động bị từ chối: liên kết không dẫn đến đâu: {path} @msg automation path rejected: cannot resolve {path}: {e} đường dẫn tự động bị từ chối: không thể phân giải {path}: {e} @msg automation path rejected: outside the read root {root}: {path} đường dẫn tự động bị từ chối: ngoài thư mục gốc đọc {root}: {path} +@msg automation path rejected: outside the read roots {roots}: {path} đường dẫn tự động bị từ chối: ngoài các thư mục gốc đọc {roots}: {path} @msg automation path rejected: outside the write root {root}: {path} đường dẫn tự động bị từ chối: ngoài thư mục gốc ghi {root}: {path} +@msg automation path rejected: outside the write roots {roots}: {path} đường dẫn tự động bị từ chối: ngoài các thư mục gốc ghi {roots}: {path} @msg automation path rejected: parent traversal below a folder that doesn't exist: {path} đường dẫn tự động bị từ chối: duyệt cha bên dưới thư mục không tồn tại: {path} @msg automation path rejected: path is empty đường dẫn tự động bị từ chối: đường dẫn trống @msg automation path rejected: reserved device names are not allowed: {path} đường dẫn tự động bị từ chối: tên thiết bị đặt trước không được phép: {path} diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 2c86910b9..4264ddf1a 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -4257,7 +4257,9 @@ imageTrace Outline 轮廓 @msg automation filesystem access is not granted: read authority is absent: {path} 未授予自动化文件系统访问权限:缺少读取权限:{path} @msg automation filesystem access is not granted: write authority is absent: {path} 未授予自动化文件系统访问权限:缺少写入权限:{path} @msg automation path rejected: outside the read root {root}: {path} 已拒绝自动化路径:位于读取根目录 {root} 之外:{path} +@msg automation path rejected: outside the read roots {roots}: {path} 已拒绝自动化路径:位于所有读取根目录 {roots} 之外:{path} @msg automation path rejected: outside the write root {root}: {path} 已拒绝自动化路径:位于写入根目录 {root} 之外:{path} +@msg automation path rejected: outside the write roots {roots}: {path} 已拒绝自动化路径:位于所有写入根目录 {roots} 之外:{path} @msg automation path rejected: path is empty 已拒绝自动化路径:路径为空 @msg automation path rejected: a link that leads nowhere: {path} 已拒绝自动化路径:链接指向不存在的位置:{path} @msg automation path rejected: cannot resolve {path}: {e} 已拒绝自动化路径:无法解析 {path}:{e} diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index fdae7ed35..ab6e577ed 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2240,7 +2240,9 @@ file-filter Templates 範本 @msg automation filesystem access is not granted: read authority is absent: {path} 未授予自動化檔案系統存取權:沒有讀取權限:{path} @msg automation filesystem access is not granted: write authority is absent: {path} 未授予自動化檔案系統存取權:沒有寫入權限:{path} @msg automation path rejected: outside the read root {root}: {path} 已拒絕自動化路徑:位於讀取根目錄 {root} 之外:{path} +@msg automation path rejected: outside the read roots {roots}: {path} 已拒絕自動化路徑:位於所有讀取根目錄 {roots} 之外:{path} @msg automation path rejected: outside the write root {root}: {path} 已拒絕自動化路徑:位於寫入根目錄 {root} 之外:{path} +@msg automation path rejected: outside the write roots {roots}: {path} 已拒絕自動化路徑:位於所有寫入根目錄 {roots} 之外:{path} @msg automation path rejected: path is empty 已拒絕自動化路徑:路徑是空的 @msg automation path rejected: a link that leads nowhere: {path} 已拒絕自動化路徑:連結指向不存在的位置:{path} @msg automation path rejected: cannot resolve {path}: {e} 已拒絕自動化路徑:無法解析 {path}:{e} diff --git a/crates/ui-egui/src/tests_automation.rs b/crates/ui-egui/src/tests_automation.rs index 2ce1cd6c7..16bda73fd 100644 --- a/crates/ui-egui/src/tests_automation.rs +++ b/crates/ui-egui/src/tests_automation.rs @@ -27,7 +27,7 @@ fn confined(tag: &str) -> (VectorcraftApp, std::sync::mpsc::Sender` and `--automation-write-root ` (or `VECTORCRAFT_AUTOMATION_READ_ROOT` / `VECTORCRAFT_AUTOMATION_WRITE_ROOT`; `--flag=` works too) confine what the control channel reads and writes, with the same flags as `vectorcraft-cli mcp` and the other Craft apps. Every path a request reads -(open, place, relink, library and profile loads, plug-ins…) must lie inside the read root, every path it -writes (save, export, package, captures, library saves…) inside the write root; anything else is an +(open, place, relink, library and profile loads, plug-ins…) must lie inside a read root, every path it +writes (save, export, package, captures, library saves…) inside a write root; anything else is an ordinary error reply. Paths are made absolute against the app's working directory, links are followed (for a file that doesn't exist yet, its folder's), and the comparison is by whole folder names (`/work2` is not inside `/work`), ignoring case on Windows; `..` below a folder that doesn't exist, a link that leads @@ -71,9 +71,9 @@ nowhere and Windows device names are refused. A root left out grants none of its existing folders, or the app doesn't start; without `--control` they are ignored. See `docs/mcp.md` › Confining file access for the details. -Each root flag may be given once; a command-line root overrides its environment default. Missing or -empty values, another option used as a value, and duplicate flags stop the desktop before it starts -any services, even without `--control`. For a folder name starting with `-`, use `--flag=-folder` +Each root flag may be given more than once, a path inside any of its folders being allowed (#3546); the +command line's folders of a kind replace its environment default. Missing or empty values and another +option used as a value stop the desktop before it starts any services, even without `--control`. For a folder name starting with `-`, use `--flag=-folder` or the relative path `./-folder`. What is confined: the requests themselves, the frames that carry input they inject (`ui.click`, `ui.key`, diff --git a/docs/mcp.md b/docs/mcp.md index f34064456..1da862e51 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -73,6 +73,8 @@ claude mcp add vectorcraft -- vectorcraft-cli mcp ```sh vectorcraft-cli mcp --automation-read-root /work/project --automation-write-root /work/project/out +vectorcraft-cli mcp --automation-read-root /work/project --automation-read-root /work/shared-assets \ + --automation-write-root /work/project/out --automation-write-root /tmp/scratch ``` ```json @@ -83,15 +85,19 @@ vectorcraft-cli mcp --automation-read-root /work/project --automation-write-root The two flags (PhotoCraft and the other Craft apps take the same ones, so one client config works across the suite) keep the agent to the folders you give it: -- Every file a command reads must lie inside the **read root**: `open_file`, `file.place`, relinking and +- Every file a command reads must lie inside a **read root**: `open_file`, `file.place`, relinking and updating links, the images an SVG links to, swatch library, graphic style library and colour-profile loads, plug-ins, `file.newFromTemplate`, preset imports, shortcut sets (`shortcutSet.read`), the folders `text.findFontFiles` and `links.findFiles` search, the scripts `script.check` reads and the files they include. -- Every file a command writes must lie inside the **write root**: `save_file`, `export`, the save and +- Every file a command writes must lie inside a **write root**: `save_file`, `export`, the save and export commands, Export for Screens, Package, Export for Web, print to file, `screenshot {path}`, library and preset saves to a path, the copies `links.relink {copy}` makes. -- `file.list {path}` (through `run_command`) lists a folder's files and folders, held to the read root like any read, so an agent can see what it was given (#1163). -- Read and write access are separate. Give the same folder twice for both; a root left out grants none +- `file.list {path}` (through `run_command`) lists a folder's files and folders, held to the read roots like any read, so an agent can see what it was given (#1163). +- Each flag may be given more than once (#3546): a path inside any of the read roots may be read, one inside any + of the write roots written, so one session can read a project folder and a shared assets folder, or write to an + output folder and a scratch folder, without widening a root to their common parent. A folder given twice + counts once. +- Read and write access are separate. Give the same folder to both flags for both; a kind left out grants none of its access (with only `--automation-read-root`, nothing can be written to disk, though `export` and the save commands without a path still return the bytes). Without either flag nothing changes. - A path is made absolute against the server's working directory, then its links are followed: a file @@ -102,15 +108,16 @@ the suite) keep the agent to the folders you give it: Windows case is ignored, UNC paths (`\\server\share\…`) are resolved like drive-letter ones, and device names (`CON`, `NUL`, `COM1`…) are refused. - A refused path is an ordinary tool error the agent can read and correct, for example - `automation path rejected: outside the write root /work/project/out: /etc/x.png` or + `automation path rejected: outside the write root /work/project/out: /etc/x.png` (with several, + `outside the write roots /work/project/out, /tmp/scratch: …`) or `automation filesystem access is not granted: write authority is absent: /work/project/out/x.png`. - Links in an opened document that point outside the read root show as missing. + Links in an opened document that point outside the read roots show as missing. - The preferences that move folders the app reads or writes on its own (`fontsFolder`, `pluginsFolder`, `recoveryFolder`, `templatesFolder`) can only be set inside the roots. The app's own folders (preferences, Data Recovery, the User Defined library folders, VectorCraft's Fonts folder) are not confined: no agent names a path there. -- The roots must be existing folders; a missing one stops the server with an error. Each flag is - given once (`--flag ` or `--flag=`). +- The roots must be existing folders; a missing one stops the server with an error. Each flag takes one + folder per use (`--flag ` or `--flag=`) and may be used again for more. The flags confine the headless server and imply `--headless`; `--connect` with them is an error, since the server would otherwise talk to an app that isn't confined. A running app confines itself: start it diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index ff3c1ba3f..9eb98623c 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # Parity with Adobe Illustrator -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (#3147: `vectorcraft-cli run --commands FILE` (or `-`) reads the steps from a JSON file or standard input (an array or JSON Lines, at most 64 MiB and 100,000 steps), among `--cmd` and `--export` in order; errors name the step. Scores and estimates unchanged; G2: Effect › Sketch › Conté Crayon (Foreground Level, Background Level, Texture, Scaling, Relief, Light Direction, Invert; a black crayon in the dark areas and a white one in the light areas on textured mid-grey paper; ranges and defaults unverified VectorCraft choices); measured Photoshop-style filters 57/57, the Effect Gallery remains (4–6 h); Affinity import AF.5: master pages, tables, paragraph formats, shadows, glows, outlines, overlays and embedded documents; Recolor Artwork Edit chips scroll within the available screen height (#3416); small palettes retain their tab height; scores and estimates unchanged; #3515: recent-font groups in font pickers; PDF/X-1a and PDF/X-3 flattening keeps a CMYK document's inks where it rasterizes, #2160; Blend panel Type and Ramp control step easing with a separate color Shift (#2539); Blend Options retains spacing and orientation; polynomial curves are original approximation; UI chrome: panels dock into any dock group or the icon column by dragging (UI.6), workspaces keep their layout changes (UI.7), dock groups collapse to their tabs by a double-click (UI.8); scores and hours unchanged; Shape Builder Gap Detection shares Live Paint’s spatial gap finder and temporary merge bridges (#2526); Custom is capped at 72 pt in the existing options dialog; Recolor model selection is available through the engine and MCP only (#2520); the dialog retains its existing controls; filter plug-ins read and set effect records and stroke joins, caps and miter limits, G17; the Print dialog's Media list has the Japanese papers: JIS B4 257 × 364 mm, JIS B5 182 × 257 mm and JIS B6 beside the ISO B4/B5, which keep their ISO 216 sizes, and はがき, 往復はがき, 封筒 長形3号/長形4号/角形2号/角形3号 and 四六判; a paper is named by its JSON id or its dialog label, any case and spacing; imported PDF type that is letter-spaced keeps its word spaces and no others; Fractional Widths: Character panel menu, per type object, on by default; System Layout remains a panel stub; scores unchanged; G4: manual Tate-chu-yoko; G4: No Break; scores unchanged; the selection tools take a clipping path on its outline, G1; Polygon and Star exact-size placement remembers the chosen count for later drags and dialog openings; Cancel preserves the previous count; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views; CSS swatch libraries retain fractional gradient stop positions; scores and hours unchanged; live-effect plug-ins given their object's fills and strokes and the item they sit on, G17; plug-in parameters: labels, units, groups, help and conditions in their dialogs, text and number-list parameters, up to 256 of them, G17; exporter plug-ins: plug-in file formats in Export As and document.export, G17; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; Libraries panel: the list scrolls, items and groups reorder by dragging, items drag into and out of groups (`library.moveItem` before/after, `library.moveGroup`); scores unchanged; PDF invisible text opens as type with no fill or stroke and is written back invisible, #2675; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Artboard Options' X and Y with a reference point, #1876; Image Trace: Auto Color keeps rare but distinct colours; Image Trace: large images trace with a warning; anchor limit raised to 8,000,000; Image Trace: Raster::new returns an error for a buffer of the wrong length; M12 maximal-disc graph pruning and junction merging measured; M12 crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; installed plug-ins stay installed after a restart, #2541; Group Selection picks and moves one path of a compound path, #2441 in part; the Move dialog opens on the last move and its last checkboxes; Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace Flat Logo traces line art on white paper instead of a blank result; score and hours unchanged; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; MCP cancellation, progress and busy answers during long exports, #2165, #2171, #2172; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; native family-name fallback from English-only typographic names, #2208; scores unchanged; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 45/57, 12 remaining (8–14 h); G5: Symbol Options Export Type (Graphic / Movie Clip); Localization: Brazilian Portuguese complete under the tests, #1914, 71% → 74%; G6 image variables: Automation ~60% → ~62% / detail ~65% → ~68%, hours lowered; Warp Options controls verified by focused checks and original-image review; estimates unchanged; G11: Asset Export Art Optimized seam (#983 / #1037) and supersampled thumbs; Export for Screens 85% → 90%; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; later 2026-10-14 merges: anchor X/Y/W/H, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar and All Tools, #1751; Actions panel selection, #1629 in part; redo during a drag, #1719; preferences written on OK, #1747; type in symbols after reopening, #1738; macOS save sheets, #1855, the Format menu withdrawn; stroke outlines shared, #1667; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; effects, #1864, Dim Images, #1683; duplicated rows of the 2026-10-05 table noted for the owner; scores unchanged; Image Trace: Shapes rework, stroke caps, enhanced presets, gradients over noise, measured in `image-trace-quality.md`, score unchanged; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Escape while typing, #1686; Pen clicks, #1663; Expand on repeats, #1756; Roughen and Scribble, #1643, #1648; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace levelling and Flat Logo on scans, #1647, #1657; scores unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1598, #1601; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment, #1584; saves, #1620 in part; Warp Arc, #1578; Zig Zag, #1558; Pathfinder strokes, #1570; OpenType features, #1607; intersect Smart Guides, #1602 in part; a guide's position in the Transform panel, #1597 in part, a convenience with no parity claim; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; Units › Identify Objects By (`identifyObjectsBy`) acts on Layers, Variables and Layer Options, #394, scores unchanged; Appearance of Black › Printing / Exporting (`blackOutput`) acts on RGB raster export and Print as Bitmap, #394, scores unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; Color panel: the mode picked last comes back after a restart, #1675; Image Trace: shared edges, memory, thin lines, strokes, corners, and the Shapes, Auto Grouping and Gradients options, measured in `image-trace-quality.md`, score unchanged; 2026-10-13 merges: Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes for `.ai`, PDF, EPS and SVG, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts lists commands outside the menus, #1539; correction: Average and Join with a corner or smooth choice is missing; scores unchanged; late 2026-10-12 merges: type that does not fit its frame is reported, #1471; Tabs panel drags, #1458; template layers in Outline view, #1486; dialog slider rails, #1483; Direct Selection anchor precedence, #1508; print-size images, #1517; 3D on outlined type, #1481; French catalog, #1511; scores unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; Localization: Vietnamese catalog added, 63% → 71%, hours 60–100 → 55–90, full readiness unchanged at ~62%; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; Image Trace: colour presets no longer shatter noisy flat art and photos trace into smooth tones, measured in `image-trace-quality.md`, score unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; MCP `add_text` exposes the existing `vertical` option for point, area and path type; horizontal remains the default; scores and hours unchanged; Vertical fullwidth question and exclamation marks keep their nominal glyph upright when the font has no vertical alternate; scores and hours unchanged.; Curvature curve geometry, #1236 in part; Width-point finite-arithmetic follow-up validated; scores and hours unchanged; Image Trace adjustment discovery; File interop: legacy `.ai` of version 7 saved with type as text, hidden objects kept and an artboard picker, #868, scores unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; later 2026-10-14 merges: anchor X/Y/W/H, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar and All Tools, #1751; Actions panel selection, #1629 in part; redo during a drag, #1719; preferences written on OK, #1747; type in symbols after reopening, #1738; macOS save sheets, #1855, the Format menu withdrawn; stroke outlines shared, #1667; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; effects, #1864, Dim Images, #1683; duplicated rows of the 2026-10-05 table noted for the owner; scores unchanged; Image Trace: Shapes rework, stroke caps, enhanced presets, gradients over noise, measured in `image-trace-quality.md`, score unchanged; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Escape while typing, #1686; Pen clicks, #1663; Expand on repeats, #1756; Roughen and Scribble, #1643, #1648; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace levelling and Flat Logo on scans, #1647, #1657; scores unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1598, #1601; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment, #1584; saves, #1620 in part; Warp Arc, #1578; Zig Zag, #1558; Pathfinder strokes, #1570; OpenType features, #1607; intersect Smart Guides, #1602 in part; a guide's position in the Transform panel, #1597 in part, a convenience with no parity claim; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; Units › Identify Objects By (`identifyObjectsBy`) acts on Layers, Variables and Layer Options, #394, scores unchanged; Appearance of Black › Printing / Exporting (`blackOutput`) acts on RGB raster export and Print as Bitmap, #394, scores unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; Color panel: the mode picked last comes back after a restart, #1675; Image Trace: shared edges, memory, thin lines, strokes, corners, and the Shapes, Auto Grouping and Gradients options, measured in `image-trace-quality.md`, score unchanged; 2026-10-13 merges: Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes for `.ai`, PDF, EPS and SVG, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts lists commands outside the menus, #1539; correction: Average and Join with a corner or smooth choice is missing; scores unchanged; late 2026-10-12 merges: type that does not fit its frame is reported, #1471; Tabs panel drags, #1458; template layers in Outline view, #1486; dialog slider rails, #1483; Direct Selection anchor precedence, #1508; print-size images, #1517; 3D on outlined type, #1481; French catalog, #1511; scores unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; Localization: Vietnamese catalog added, 63% → 71%, hours 60–100 → 55–90, full readiness unchanged at ~62%; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; Image Trace: colour presets no longer shatter noisy flat art and photos trace into smooth tones, measured in `image-trace-quality.md`, score unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; ruby (ふりがな) for live text: `type.ruby` sets or removes a reading over a byte range, above or below (right of the column in vertical type), aligned, sized and overhanging or squeezed; saved natively, laid out with line growth and drawn, with SVG/PDF outlines where ruby is present; scores and hours unchanged; previously:; 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged; one selected clip group's Properties panel has Edit Clipping Path and Edit Contents at its top, G1; the Print dialog's Media list has the Japanese papers: JIS B4 257 × 364 mm, JIS B5 182 × 257 mm and JIS B6 beside the ISO B4/B5, which keep their ISO 216 sizes, and はがき, 往復はがき, 封筒 長形3号/長形4号/角形2号/角形3号 and 四六判; a paper is named by its JSON id or its dialog label, any case and spacing; imported PDF type that is letter-spaced keeps its word spaces and no others; G4: System Layout; Fractional Widths: Character panel menu, per type object, on by default; System Layout remains a panel stub; scores unchanged; G4: No Break; scores unchanged; the selection tools take a clipping path on its outline, G1; Polygon and Star exact-size placement remembers the chosen count for later drags and dialog openings; Cancel preserves the previous count; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views; CSS swatch libraries retain fractional gradient stop positions; scores and hours unchanged; live-effect plug-ins given their object's fills and strokes and the item they sit on, G17; plug-in parameters: labels, units, groups, help and conditions in their dialogs, text and number-list parameters, up to 256 of them, G17; exporter plug-ins: plug-in file formats in Export As and document.export, G17; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; Libraries panel: the list scrolls, items and groups reorder by dragging, items drag into and out of groups (`library.moveItem` before/after, `library.moveGroup`); scores unchanged; PDF invisible text opens as type with no fill or stroke and is written back invisible, #2675; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Artboard Options' X and Y with a reference point, #1876; Image Trace: Auto Color keeps rare but distinct colours; Image Trace: large images trace with a warning; anchor limit raised to 8,000,000; Image Trace: Raster::new returns an error for a buffer of the wrong length; M12 maximal-disc graph pruning and junction merging measured; M12 crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; installed plug-ins stay installed after a restart, #2541; Group Selection picks and moves one path of a compound path, #2441 in part; the Move dialog opens on the last move and its last checkboxes; Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace Flat Logo traces line art on white paper instead of a blank result; score and hours unchanged; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; MCP cancellation, progress and busy answers during long exports, #2165, #2171, #2172; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; native family-name fallback from English-only typographic names, #2208; scores unchanged; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 45/57, 12 remaining (8–14 h); G5: Symbol Options Export Type (Graphic / Movie Clip); Localization: Brazilian Portuguese complete under the tests, #1914, 71% → 74%; G6 image variables: Automation ~60% → ~62% / detail ~65% → ~68%, hours lowered; Warp Options controls verified by focused checks and original-image review; estimates unchanged; G11: Asset Export Art Optimized seam (#983 / #1037) and supersampled thumbs; Export for Screens 85% → 90%; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; later 2026-10-14 merges: anchor X/Y/W/H, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar and All Tools, #1751; Actions panel selection, #1629 in part; redo during a drag, #1719; preferences written on OK, #1747; type in symbols after reopening, #1738; macOS save sheets, #1855, the Format menu withdrawn; stroke outlines shared, #1667; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; effects, #1864, Dim Images, #1683; duplicated rows of the 2026-10-05 table noted for the owner; scores unchanged; Image Trace: Shapes rework, stroke caps, enhanced presets, gradients over noise, measured in `image-trace-quality.md`, score unchanged; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Escape while typing, #1686; Pen clicks, #1663; Expand on repeats, #1756; Roughen and Scribble, #1643, #1648; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace levelling and Flat Logo on scans, #1647, #1657; scores unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1598, #1601; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment, #1584; saves, #1620 in part; Warp Arc, #1578; Zig Zag, #1558; Pathfinder strokes, #1570; OpenType features, #1607; intersect Smart Guides, #1602 in part; a guide's position in the Transform panel, #1597 in part, a convenience with no parity claim; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; Units › Identify Objects By (`identifyObjectsBy`) acts on Layers, Variables and Layer Options, #394, scores unchanged; Appearance of Black › Printing / Exporting (`blackOutput`) acts on RGB raster export and Print as Bitmap, #394, scores unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; Color panel: the mode picked last comes back after a restart, #1675; Image Trace: shared edges, memory, thin lines, strokes, corners, and the Shapes, Auto Grouping and Gradients options, measured in `image-trace-quality.md`, score unchanged; 2026-10-13 merges: Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes for `.ai`, PDF, EPS and SVG, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts lists commands outside the menus, #1539; correction: Average and Join with a corner or smooth choice is missing; scores unchanged; late 2026-10-12 merges: type that does not fit its frame is reported, #1471; Tabs panel drags, #1458; template layers in Outline view, #1486; dialog slider rails, #1483; Direct Selection anchor precedence, #1508; print-size images, #1517; 3D on outlined type, #1481; French catalog, #1511; scores unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; Localization: Vietnamese catalog added, 63% → 71%, hours 60–100 → 55–90, full readiness unchanged at ~62%; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; Image Trace: colour presets no longer shatter noisy flat art and photos trace into smooth tones, measured in `image-trace-quality.md`, score unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; MCP `add_text` exposes the existing `vertical` option for point, area and path type; horizontal remains the default; scores and hours unchanged; Vertical fullwidth question and exclamation marks keep their nominal glyph upright when the font has no vertical alternate; scores and hours unchanged.; Curvature curve geometry, #1236 in part; Width-point finite-arithmetic follow-up validated; scores and hours unchanged; Image Trace adjustment discovery; File interop: legacy `.ai` of version 7 saved with type as text, hidden objects kept and an artboard picker, #868, scores unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; later 2026-10-14 merges: anchor X/Y/W/H, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar and All Tools, #1751; Actions panel selection, #1629 in part; redo during a drag, #1719; preferences written on OK, #1747; type in symbols after reopening, #1738; macOS save sheets, #1855, the Format menu withdrawn; stroke outlines shared, #1667; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; effects, #1864, Dim Images, #1683; duplicated rows of the 2026-10-05 table noted for the owner; scores unchanged; Image Trace: Shapes rework, stroke caps, enhanced presets, gradients over noise, measured in `image-trace-quality.md`, score unchanged; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Escape while typing, #1686; Pen clicks, #1663; Expand on repeats, #1756; Roughen and Scribble, #1643, #1648; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace levelling and Flat Logo on scans, #1647, #1657; scores unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1598, #1601; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment, #1584; saves, #1620 in part; Warp Arc, #1578; Zig Zag, #1558; Pathfinder strokes, #1570; OpenType features, #1607; intersect Smart Guides, #1602 in part; a guide's position in the Transform panel, #1597 in part, a convenience with no parity claim; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; Units › Identify Objects By (`identifyObjectsBy`) acts on Layers, Variables and Layer Options, #394, scores unchanged; Appearance of Black › Printing / Exporting (`blackOutput`) acts on RGB raster export and Print as Bitmap, #394, scores unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; Color panel: the mode picked last comes back after a restart, #1675; Image Trace: shared edges, memory, thin lines, strokes, corners, and the Shapes, Auto Grouping and Gradients options, measured in `image-trace-quality.md`, score unchanged; 2026-10-13 merges: Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes for `.ai`, PDF, EPS and SVG, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts lists commands outside the menus, #1539; correction: Average and Join with a corner or smooth choice is missing; scores unchanged; late 2026-10-12 merges: type that does not fit its frame is reported, #1471; Tabs panel drags, #1458; template layers in Outline view, #1486; dialog slider rails, #1483; Direct Selection anchor precedence, #1508; print-size images, #1517; 3D on outlined type, #1481; French catalog, #1511; scores unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; Localization: Vietnamese catalog added, 63% → 71%, hours 60–100 → 55–90, full readiness unchanged at ~62%; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; Image Trace: colour presets no longer shatter noisy flat art and photos trace into smooth tones, measured in `image-trace-quality.md`, score unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (#3546: `--automation-read-root` and `--automation-write-root` may each be given more than once (`vectorcraft-cli mcp` and the desktop app); a path inside any root of its kind is allowed, every confinement check unchanged. Scores and estimates unchanged; #3147: `vectorcraft-cli run --commands FILE` (or `-`) reads the steps from a JSON file or standard input (an array or JSON Lines, at most 64 MiB and 100,000 steps), among `--cmd` and `--export` in order; errors name the step. Scores and estimates unchanged; G2: Effect › Sketch › Conté Crayon (Foreground Level, Background Level, Texture, Scaling, Relief, Light Direction, Invert; a black crayon in the dark areas and a white one in the light areas on textured mid-grey paper; ranges and defaults unverified VectorCraft choices); measured Photoshop-style filters 57/57, the Effect Gallery remains (4–6 h); Affinity import AF.5: master pages, tables, paragraph formats, shadows, glows, outlines, overlays and embedded documents; Recolor Artwork Edit chips scroll within the available screen height (#3416); small palettes retain their tab height; scores and estimates unchanged; #3515: recent-font groups in font pickers; PDF/X-1a and PDF/X-3 flattening keeps a CMYK document's inks where it rasterizes, #2160; Blend panel Type and Ramp control step easing with a separate color Shift (#2539); Blend Options retains spacing and orientation; polynomial curves are original approximation; UI chrome: panels dock into any dock group or the icon column by dragging (UI.6), workspaces keep their layout changes (UI.7), dock groups collapse to their tabs by a double-click (UI.8); scores and hours unchanged; Shape Builder Gap Detection shares Live Paint’s spatial gap finder and temporary merge bridges (#2526); Custom is capped at 72 pt in the existing options dialog; Recolor model selection is available through the engine and MCP only (#2520); the dialog retains its existing controls; filter plug-ins read and set effect records and stroke joins, caps and miter limits, G17; the Print dialog's Media list has the Japanese papers: JIS B4 257 × 364 mm, JIS B5 182 × 257 mm and JIS B6 beside the ISO B4/B5, which keep their ISO 216 sizes, and はがき, 往復はがき, 封筒 長形3号/長形4号/角形2号/角形3号 and 四六判; a paper is named by its JSON id or its dialog label, any case and spacing; imported PDF type that is letter-spaced keeps its word spaces and no others; Fractional Widths: Character panel menu, per type object, on by default; System Layout remains a panel stub; scores unchanged; G4: manual Tate-chu-yoko; G4: No Break; scores unchanged; the selection tools take a clipping path on its outline, G1; Polygon and Star exact-size placement remembers the chosen count for later drags and dialog openings; Cancel preserves the previous count; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views; CSS swatch libraries retain fractional gradient stop positions; scores and hours unchanged; live-effect plug-ins given their object's fills and strokes and the item they sit on, G17; plug-in parameters: labels, units, groups, help and conditions in their dialogs, text and number-list parameters, up to 256 of them, G17; exporter plug-ins: plug-in file formats in Export As and document.export, G17; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; Libraries panel: the list scrolls, items and groups reorder by dragging, items drag into and out of groups (`library.moveItem` before/after, `library.moveGroup`); scores unchanged; PDF invisible text opens as type with no fill or stroke and is written back invisible, #2675; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Artboard Options' X and Y with a reference point, #1876; Image Trace: Auto Color keeps rare but distinct colours; Image Trace: large images trace with a warning; anchor limit raised to 8,000,000; Image Trace: Raster::new returns an error for a buffer of the wrong length; M12 maximal-disc graph pruning and junction merging measured; M12 crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; installed plug-ins stay installed after a restart, #2541; Group Selection picks and moves one path of a compound path, #2441 in part; the Move dialog opens on the last move and its last checkboxes; Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace Flat Logo traces line art on white paper instead of a blank result; score and hours unchanged; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; MCP cancellation, progress and busy answers during long exports, #2165, #2171, #2172; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; native family-name fallback from English-only typographic names, #2208; scores unchanged; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 45/57, 12 remaining (8–14 h); G5: Symbol Options Export Type (Graphic / Movie Clip); Localization: Brazilian Portuguese complete under the tests, #1914, 71% → 74%; G6 image variables: Automation ~60% → ~62% / detail ~65% → ~68%, hours lowered; Warp Options controls verified by focused checks and original-image review; estimates unchanged; G11: Asset Export Art Optimized seam (#983 / #1037) and supersampled thumbs; Export for Screens 85% → 90%; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; later 2026-10-14 merges: anchor X/Y/W/H, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar and All Tools, #1751; Actions panel selection, #1629 in part; redo during a drag, #1719; preferences written on OK, #1747; type in symbols after reopening, #1738; macOS save sheets, #1855, the Format menu withdrawn; stroke outlines shared, #1667; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; effects, #1864, Dim Images, #1683; duplicated rows of the 2026-10-05 table noted for the owner; scores unchanged; Image Trace: Shapes rework, stroke caps, enhanced presets, gradients over noise, measured in `image-trace-quality.md`, score unchanged; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Escape while typing, #1686; Pen clicks, #1663; Expand on repeats, #1756; Roughen and Scribble, #1643, #1648; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace levelling and Flat Logo on scans, #1647, #1657; scores unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1598, #1601; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment, #1584; saves, #1620 in part; Warp Arc, #1578; Zig Zag, #1558; Pathfinder strokes, #1570; OpenType features, #1607; intersect Smart Guides, #1602 in part; a guide's position in the Transform panel, #1597 in part, a convenience with no parity claim; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; Units › Identify Objects By (`identifyObjectsBy`) acts on Layers, Variables and Layer Options, #394, scores unchanged; Appearance of Black › Printing / Exporting (`blackOutput`) acts on RGB raster export and Print as Bitmap, #394, scores unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; Color panel: the mode picked last comes back after a restart, #1675; Image Trace: shared edges, memory, thin lines, strokes, corners, and the Shapes, Auto Grouping and Gradients options, measured in `image-trace-quality.md`, score unchanged; 2026-10-13 merges: Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes for `.ai`, PDF, EPS and SVG, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts lists commands outside the menus, #1539; correction: Average and Join with a corner or smooth choice is missing; scores unchanged; late 2026-10-12 merges: type that does not fit its frame is reported, #1471; Tabs panel drags, #1458; template layers in Outline view, #1486; dialog slider rails, #1483; Direct Selection anchor precedence, #1508; print-size images, #1517; 3D on outlined type, #1481; French catalog, #1511; scores unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; Localization: Vietnamese catalog added, 63% → 71%, hours 60–100 → 55–90, full readiness unchanged at ~62%; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; Image Trace: colour presets no longer shatter noisy flat art and photos trace into smooth tones, measured in `image-trace-quality.md`, score unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; MCP `add_text` exposes the existing `vertical` option for point, area and path type; horizontal remains the default; scores and hours unchanged; Vertical fullwidth question and exclamation marks keep their nominal glyph upright when the font has no vertical alternate; scores and hours unchanged.; Curvature curve geometry, #1236 in part; Width-point finite-arithmetic follow-up validated; scores and hours unchanged; Image Trace adjustment discovery; File interop: legacy `.ai` of version 7 saved with type as text, hidden objects kept and an artboard picker, #868, scores unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; later 2026-10-14 merges: anchor X/Y/W/H, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar and All Tools, #1751; Actions panel selection, #1629 in part; redo during a drag, #1719; preferences written on OK, #1747; type in symbols after reopening, #1738; macOS save sheets, #1855, the Format menu withdrawn; stroke outlines shared, #1667; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; effects, #1864, Dim Images, #1683; duplicated rows of the 2026-10-05 table noted for the owner; scores unchanged; Image Trace: Shapes rework, stroke caps, enhanced presets, gradients over noise, measured in `image-trace-quality.md`, score unchanged; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Escape while typing, #1686; Pen clicks, #1663; Expand on repeats, #1756; Roughen and Scribble, #1643, #1648; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace levelling and Flat Logo on scans, #1647, #1657; scores unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1598, #1601; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment, #1584; saves, #1620 in part; Warp Arc, #1578; Zig Zag, #1558; Pathfinder strokes, #1570; OpenType features, #1607; intersect Smart Guides, #1602 in part; a guide's position in the Transform panel, #1597 in part, a convenience with no parity claim; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; Units › Identify Objects By (`identifyObjectsBy`) acts on Layers, Variables and Layer Options, #394, scores unchanged; Appearance of Black › Printing / Exporting (`blackOutput`) acts on RGB raster export and Print as Bitmap, #394, scores unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; Color panel: the mode picked last comes back after a restart, #1675; Image Trace: shared edges, memory, thin lines, strokes, corners, and the Shapes, Auto Grouping and Gradients options, measured in `image-trace-quality.md`, score unchanged; 2026-10-13 merges: Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes for `.ai`, PDF, EPS and SVG, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts lists commands outside the menus, #1539; correction: Average and Join with a corner or smooth choice is missing; scores unchanged; late 2026-10-12 merges: type that does not fit its frame is reported, #1471; Tabs panel drags, #1458; template layers in Outline view, #1486; dialog slider rails, #1483; Direct Selection anchor precedence, #1508; print-size images, #1517; 3D on outlined type, #1481; French catalog, #1511; scores unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; Localization: Vietnamese catalog added, 63% → 71%, hours 60–100 → 55–90, full readiness unchanged at ~62%; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; Image Trace: colour presets no longer shatter noisy flat art and photos trace into smooth tones, measured in `image-trace-quality.md`, score unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; ruby (ふりがな) for live text: `type.ruby` sets or removes a reading over a byte range, above or below (right of the column in vertical type), aligned, sized and overhanging or squeezed; saved natively, laid out with line growth and drawn, with SVG/PDF outlines where ruby is present; scores and hours unchanged; previously:; 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged; one selected clip group's Properties panel has Edit Clipping Path and Edit Contents at its top, G1; the Print dialog's Media list has the Japanese papers: JIS B4 257 × 364 mm, JIS B5 182 × 257 mm and JIS B6 beside the ISO B4/B5, which keep their ISO 216 sizes, and はがき, 往復はがき, 封筒 長形3号/長形4号/角形2号/角形3号 and 四六判; a paper is named by its JSON id or its dialog label, any case and spacing; imported PDF type that is letter-spaced keeps its word spaces and no others; G4: System Layout; Fractional Widths: Character panel menu, per type object, on by default; System Layout remains a panel stub; scores unchanged; G4: No Break; scores unchanged; the selection tools take a clipping path on its outline, G1; Polygon and Star exact-size placement remembers the chosen count for later drags and dialog openings; Cancel preserves the previous count; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views; CSS swatch libraries retain fractional gradient stop positions; scores and hours unchanged; live-effect plug-ins given their object's fills and strokes and the item they sit on, G17; plug-in parameters: labels, units, groups, help and conditions in their dialogs, text and number-list parameters, up to 256 of them, G17; exporter plug-ins: plug-in file formats in Export As and document.export, G17; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; Libraries panel: the list scrolls, items and groups reorder by dragging, items drag into and out of groups (`library.moveItem` before/after, `library.moveGroup`); scores unchanged; PDF invisible text opens as type with no fill or stroke and is written back invisible, #2675; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Artboard Options' X and Y with a reference point, #1876; Image Trace: Auto Color keeps rare but distinct colours; Image Trace: large images trace with a warning; anchor limit raised to 8,000,000; Image Trace: Raster::new returns an error for a buffer of the wrong length; M12 maximal-disc graph pruning and junction merging measured; M12 crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; installed plug-ins stay installed after a restart, #2541; Group Selection picks and moves one path of a compound path, #2441 in part; the Move dialog opens on the last move and its last checkboxes; Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace Flat Logo traces line art on white paper instead of a blank result; score and hours unchanged; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; MCP cancellation, progress and busy answers during long exports, #2165, #2171, #2172; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; native family-name fallback from English-only typographic names, #2208; scores unchanged; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 45/57, 12 remaining (8–14 h); G5: Symbol Options Export Type (Graphic / Movie Clip); Localization: Brazilian Portuguese complete under the tests, #1914, 71% → 74%; G6 image variables: Automation ~60% → ~62% / detail ~65% → ~68%, hours lowered; Warp Options controls verified by focused checks and original-image review; estimates unchanged; G11: Asset Export Art Optimized seam (#983 / #1037) and supersampled thumbs; Export for Screens 85% → 90%; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; later 2026-10-14 merges: anchor X/Y/W/H, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar and All Tools, #1751; Actions panel selection, #1629 in part; redo during a drag, #1719; preferences written on OK, #1747; type in symbols after reopening, #1738; macOS save sheets, #1855, the Format menu withdrawn; stroke outlines shared, #1667; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; effects, #1864, Dim Images, #1683; duplicated rows of the 2026-10-05 table noted for the owner; scores unchanged; Image Trace: Shapes rework, stroke caps, enhanced presets, gradients over noise, measured in `image-trace-quality.md`, score unchanged; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Escape while typing, #1686; Pen clicks, #1663; Expand on repeats, #1756; Roughen and Scribble, #1643, #1648; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace levelling and Flat Logo on scans, #1647, #1657; scores unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1598, #1601; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment, #1584; saves, #1620 in part; Warp Arc, #1578; Zig Zag, #1558; Pathfinder strokes, #1570; OpenType features, #1607; intersect Smart Guides, #1602 in part; a guide's position in the Transform panel, #1597 in part, a convenience with no parity claim; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; Units › Identify Objects By (`identifyObjectsBy`) acts on Layers, Variables and Layer Options, #394, scores unchanged; Appearance of Black › Printing / Exporting (`blackOutput`) acts on RGB raster export and Print as Bitmap, #394, scores unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; Color panel: the mode picked last comes back after a restart, #1675; Image Trace: shared edges, memory, thin lines, strokes, corners, and the Shapes, Auto Grouping and Gradients options, measured in `image-trace-quality.md`, score unchanged; 2026-10-13 merges: Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes for `.ai`, PDF, EPS and SVG, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts lists commands outside the menus, #1539; correction: Average and Join with a corner or smooth choice is missing; scores unchanged; late 2026-10-12 merges: type that does not fit its frame is reported, #1471; Tabs panel drags, #1458; template layers in Outline view, #1486; dialog slider rails, #1483; Direct Selection anchor precedence, #1508; print-size images, #1517; 3D on outlined type, #1481; French catalog, #1511; scores unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; Localization: Vietnamese catalog added, 63% → 71%, hours 60–100 → 55–90, full readiness unchanged at ~62%; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; Image Trace: colour presets no longer shatter noisy flat art and photos trace into smooth tones, measured in `image-trace-quality.md`, score unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; MCP `add_text` exposes the existing `vertical` option for point, area and path type; horizontal remains the default; scores and hours unchanged; Vertical fullwidth question and exclamation marks keep their nominal glyph upright when the font has no vertical alternate; scores and hours unchanged.; Curvature curve geometry, #1236 in part; Width-point finite-arithmetic follow-up validated; scores and hours unchanged; Image Trace adjustment discovery; File interop: legacy `.ai` of version 7 saved with type as text, hidden objects kept and an artboard picker, #868, scores unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; later 2026-10-14 merges: anchor X/Y/W/H, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar and All Tools, #1751; Actions panel selection, #1629 in part; redo during a drag, #1719; preferences written on OK, #1747; type in symbols after reopening, #1738; macOS save sheets, #1855, the Format menu withdrawn; stroke outlines shared, #1667; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; effects, #1864, Dim Images, #1683; duplicated rows of the 2026-10-05 table noted for the owner; scores unchanged; Image Trace: Shapes rework, stroke caps, enhanced presets, gradients over noise, measured in `image-trace-quality.md`, score unchanged; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Escape while typing, #1686; Pen clicks, #1663; Expand on repeats, #1756; Roughen and Scribble, #1643, #1648; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace levelling and Flat Logo on scans, #1647, #1657; scores unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1598, #1601; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment, #1584; saves, #1620 in part; Warp Arc, #1578; Zig Zag, #1558; Pathfinder strokes, #1570; OpenType features, #1607; intersect Smart Guides, #1602 in part; a guide's position in the Transform panel, #1597 in part, a convenience with no parity claim; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; Units › Identify Objects By (`identifyObjectsBy`) acts on Layers, Variables and Layer Options, #394, scores unchanged; Appearance of Black › Printing / Exporting (`blackOutput`) acts on RGB raster export and Print as Bitmap, #394, scores unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; Color panel: the mode picked last comes back after a restart, #1675; Image Trace: shared edges, memory, thin lines, strokes, corners, and the Shapes, Auto Grouping and Gradients options, measured in `image-trace-quality.md`, score unchanged; 2026-10-13 merges: Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes for `.ai`, PDF, EPS and SVG, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts lists commands outside the menus, #1539; correction: Average and Join with a corner or smooth choice is missing; scores unchanged; late 2026-10-12 merges: type that does not fit its frame is reported, #1471; Tabs panel drags, #1458; template layers in Outline view, #1486; dialog slider rails, #1483; Direct Selection anchor precedence, #1508; print-size images, #1517; 3D on outlined type, #1481; French catalog, #1511; scores unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; Localization: Vietnamese catalog added, 63% → 71%, hours 60–100 → 55–90, full readiness unchanged at ~62%; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; Image Trace: colour presets no longer shatter noisy flat art and photos trace into smooth tones, measured in `image-trace-quality.md`, score unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) ## Feature submission evidence @@ -495,7 +495,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - JSON-lines control channel with real egui pointer and keyboard injection. - Synthetic input validates complete gestures before queue mutation: at most 256 drag steps, 16 clicks and 1,024 pending events; up to 64 control requests per frame, with later requests retained for the next frame (#1709). - The control server writes each reply through a 64 KiB buffer. On Windows 11 it sends the 17 MB `document.inspect` reply of a document with 50,000 objects on one layer to a client over loopback in 0.09 s instead of 34 s. - - MCP server with 25 tools (drawing, text, effects, Pathfinder, transforms, graphs, text wrap, export, screenshots, any command), attached to the running app or headless. Tool descriptions and replies agree with the engine: format lists come from its format table, `save_file` says it keeps the document's own format, `inspect_document` reports the paint type's characters show, and selection exports report the encoder's warnings. `--automation-read-root` / `--automation-write-root` (on `vectorcraft-cli mcp` and on `vectorcraft --control`) confine the files commands read and write to chosen folders, links followed, with the flags PhotoCraft takes (#832). A long export can be cancelled (`notifications/cancelled` stops it at the next page or group and writes nothing), reports page progress to a client that sent a progress token, and does not make other requests wait: engine-free ones answer at once, the rest are refused as busy after two seconds (#2165, #2171, #2172). + - MCP server with 25 tools (drawing, text, effects, Pathfinder, transforms, graphs, text wrap, export, screenshots, any command), attached to the running app or headless. Tool descriptions and replies agree with the engine: format lists come from its format table, `save_file` says it keeps the document's own format, `inspect_document` reports the paint type's characters show, and selection exports report the encoder's warnings. `--automation-read-root` / `--automation-write-root` (on `vectorcraft-cli mcp` and on `vectorcraft --control`) confine the files commands read and write to chosen folders, links followed, with the flags PhotoCraft takes (#832); each flag may be given more than once, a path inside any of its folders being allowed (#3546, `file_access::tests::several_roots_of_a_kind_each_grant_their_access`, `cli::mcp_automation_roots_take_several_folders`). A long export can be cancelled (`notifications/cancelled` stops it at the next page or group and writes nothing), reports page progress to a client that sent a progress token, and does not make other requests wait: engine-free ones answer at once, the rest are refused as busy after two seconds (#2165, #2171, #2172). - MCP protocol: five prompts (`prompts/list` / `prompts/get`: poster, icon set, recolour, trace-and-style, export set), `completion/complete` for prompt arguments and template variables read from the live catalogues (formats, effect ids, command ids, trace presets, swatch and object ids), four resource templates (`vectorcraft://object/{id}`, `//command/{id}`, `//effect/{id}`, `//swatch/{name}`) so an agent reads one thing instead of the whole document, and `logging/setLevel` with `notifications/message`. `window.panel` ids are case-insensitive (display labels work too), `vectorcraft://object/{id}` reads through `document.node {summary: true}` (the same reply bytes from one lookup instead of summarizing the whole document: a leaf read on a 2,000-object document drops from ~20 ms to ~0.06 ms), large containers slice with `document.node {depth?, childLimit?}` (truncated levels report `childCount`), `document.inspect` and the `inspect_document` tool slice the same way so a depth-0 read is the layer skeleton (~9.7 KB instead of ~2 MB on a 2,000-object document), `document.find` locates nodes by name/kind/text with ancestor paths, and the CLI sweeps the protocol surface over stdio against the compiled binary. `docs/mcp.md` has a Protocol section covering the capability table and what is deliberately absent (subscriptions, progress, cancellation, sampling, elicitation, Streamable HTTP — all of which need the transport to interleave reads). - Headless CLI (`vectorcraft-cli run`, `convert`, `info`, `bench`, `perf`, `mcp`). A `run` step, like a `command_batch` step over MCP, can use an earlier step's result (`"$1.id"`, #785); `run --commands FILE` (or `-` for standard input) reads the steps from a JSON array or JSON Lines, past the Windows command-line limit (#3147, `apps/vectorcraft-cli/tests/cli.rs` `run_reads_*`, `run_refuses_malformed_commands_naming_the_step`). - File › Scripts › Check Script… reads a script written for Illustrator (`.jsx`, `.jsxinc` or a compiled `.jsxbin`) without running any of it and reports what it needs (the document model, files, dialogs, menu commands, features only Illustrator has), what it would do outside the document, the menu commands and document-model members it uses, its directives and included files, and the problems that would stop it loading (`script.check`, which also reads `.js` files); File › Open and drag-and-drop show the report for `.jsx`, `.jsxinc` and `.jsxbin` files. @@ -981,6 +981,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development | Date | Change | Summary | |---|---|---| +| 2026-10-22 | minor | #3546: `--automation-read-root` and `--automation-write-root` may each be given more than once (`vectorcraft-cli mcp` and the desktop app); a path inside any root of its kind is allowed, every confinement check unchanged. Scores and estimates unchanged. | | 2026-10-22 | minor | #3147: `vectorcraft-cli run --commands FILE` (or `-`) reads the steps from a JSON file or standard input (an array or JSON Lines, at most 64 MiB and 100,000 steps), among `--cmd` and `--export` in order; errors name the step. Scores and estimates unchanged. | | 2026-10-22 | minor | G2: Effect › Sketch › Conté Crayon (Foreground Level, Background Level, Texture, Scaling, Relief, Light Direction, Invert; a black crayon in the dark areas and a white one in the light areas on textured mid-grey paper; ranges and defaults unverified VectorCraft choices); measured Photoshop-style filters 57/57, the Effect Gallery remains (4–6 h); area score unchanged (49%, estimated). | | 2026-10-22 | minor | File formats: Affinity import gains master pages, tables, paragraph formats, text scale, shadows, glows, outlines (also on stroked shapes), overlays, underlines, strikethrough and editable embedded documents, strokes styled None left out, CMYK pixel layers through the document's CMYK profile, text placed as Affinity places it (AF.5, from storytold/designcraft#291); synthetic, corpus and local-thumbnail tests; score and hours unchanged (estimated). Hostile files stay bounded: one budget for the whole read (layers, pixels, characters, table cells, embedded documents included), a work budget for overlays, capped master page copies, objects past `MAX_COORD` left out; the import fuzzer and limit tests cover tables, paragraphs, effects, overlays, master pages, CMYK pixels and embedded documents. |