mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-22 19:08:39 +00:00
UI.7: workspaces keep their layout changes
A workspace remembers the changes made in it: switching away keeps them (UiState::workspace_changes, kept with the preferences), switching back restores them, and Reset <name> returns to the saved layout. Saving a workspace starts it with no changes; renaming keeps them, deleting drops them, and deleting the current workspace shows Essentials as it was left. Saved preferences recover a damaged remembered layout instead of losing the whole file. Split from #2965 as a reviewer asked. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01KcHisW1vt6pkVPYbzL6Cpg
This commit is contained in:
1 parent
9471f92135
commit
4308821285
7 files changed
+168
-26
No files matched your search
@@ -247,10 +247,13 @@ fn decode_saved_prefs(bytes: &[u8]) -> Option<vectorcraft_ui_egui::UiState> {
|
||||
let mut value: Value = serde_json::from_slice(bytes).ok()?;
|
||||
let object = value.as_object_mut()?;
|
||||
recover(object, "docking_hidden");
|
||||
if let Some(Value::Array(workspaces)) = object.get_mut("custom_workspaces") {
|
||||
for workspace in workspaces {
|
||||
if let Some(object) = workspace.as_object_mut() {
|
||||
recover(object, "dockingHidden");
|
||||
// Saved workspaces and the layout changes kept for each workspace carry the same layouts.
|
||||
for key in ["custom_workspaces", "workspace_changes"] {
|
||||
if let Some(Value::Array(workspaces)) = object.get_mut(key) {
|
||||
for workspace in workspaces {
|
||||
if let Some(object) = workspace.as_object_mut() {
|
||||
recover(object, "dockingHidden");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1065,6 +1068,7 @@ mod tests {
|
||||
app.ui.engine_prefs = json!({"interfaceLanguage":"fr", "uiBrightness":"light"});
|
||||
app.ui.custom_workspaces =
|
||||
vec![vectorcraft_ui_egui::workspaces::capture(&app.ui, "Future"), vectorcraft_ui_egui::workspaces::capture(&app.ui, "Valid")];
|
||||
app.ui.workspace_changes = vec![vectorcraft_ui_egui::workspaces::capture(&app.ui, "Future")];
|
||||
let original = serde_json::to_value(&app.ui).unwrap();
|
||||
let valid_location = original["docking_hidden"]["layers"].clone();
|
||||
assert!(valid_location.is_object());
|
||||
@@ -1075,6 +1079,8 @@ mod tests {
|
||||
raw["docking_hidden"] = invalid_hidden.clone();
|
||||
raw["custom_workspaces"][0]["docking"] = invalid_layout.clone();
|
||||
raw["custom_workspaces"][0]["dockingHidden"] = invalid_hidden.clone();
|
||||
raw["workspace_changes"][0]["docking"] = invalid_layout.clone();
|
||||
raw["workspace_changes"][0]["dockingHidden"] = invalid_hidden.clone();
|
||||
assert!(serde_json::from_value::<vectorcraft_ui_egui::UiState>(raw.clone()).is_err(), "runtime serde stays strict");
|
||||
let loaded = decode_saved_prefs(&serde_json::to_vec(&raw).unwrap()).unwrap();
|
||||
assert_eq!(loaded.engine_prefs, app.ui.engine_prefs);
|
||||
@@ -1091,6 +1097,8 @@ mod tests {
|
||||
assert!(loaded.custom_workspaces[0].docking.is_none());
|
||||
assert!(!loaded.custom_workspaces[0].docking_hidden.contains_key("layers"));
|
||||
assert_eq!(serde_json::to_value(&loaded.custom_workspaces[1]).unwrap(), original["custom_workspaces"][1]);
|
||||
assert_eq!(loaded.workspace_changes.len(), 1, "a remembered layout is recovered, not the whole file lost");
|
||||
assert!(loaded.workspace_changes[0].docking.is_none());
|
||||
}
|
||||
}
|
||||
let roundtrip = decode_saved_prefs(&serde_json::to_vec(&original).unwrap()).unwrap();
|
||||
|
||||
@@ -478,8 +478,18 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"{collapsed?: bool} collapse the dock's Properties | Layers | Libraries group to icons (true), expand it (false) or toggle (omitted), as the double arrow at the top of the dock does; returns the new state",
|
||||
),
|
||||
("window.brightness", "UI Brightness", "", "{brightness: dark|mediumDark|mediumLight|light}"),
|
||||
("window.workspace", "Workspace", "", "{name} switch workspace (Essentials, Essentials Classic, Painting, …)"),
|
||||
("window.workspace.reset", "Reset Essentials", "", "{} reset the current workspace"),
|
||||
(
|
||||
"window.workspace",
|
||||
"Workspace",
|
||||
"",
|
||||
"{name} switch workspace (Essentials, Essentials Classic, Painting, …): the one left keeps its layout changes, and the one switched to comes back as it was left (as saved if never changed)",
|
||||
),
|
||||
(
|
||||
"window.workspace.reset",
|
||||
"Reset Essentials",
|
||||
"",
|
||||
"{} reset the current workspace to the way it was saved, dropping the layout changes made in it",
|
||||
),
|
||||
("window.workspace.new", "New Workspace…", "", "{name?} save the current layout"),
|
||||
("window.workspace.manage", "Manage Workspaces…", "", "{}"),
|
||||
("window.workspace.delete", "Delete Workspace", "", "{name}"),
|
||||
|
||||
@@ -428,6 +428,10 @@ pub struct UiState {
|
||||
/// User-saved workspaces (New Workspace…).
|
||||
#[serde(default)]
|
||||
pub custom_workspaces: Vec<crate::workspaces::Workspace>,
|
||||
/// Layout changes made in a workspace since it was saved (or last reset), by workspace name:
|
||||
/// switching back to it brings them back; Reset <name> drops them.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub workspace_changes: Vec<crate::workspaces::Workspace>,
|
||||
/// File → Open Recent Files, most recent first.
|
||||
#[serde(default)]
|
||||
pub recent_files: Vec<String>,
|
||||
@@ -771,6 +775,11 @@ impl UiState {
|
||||
crate::edit_toolbar::sanitize(&mut self.toolbar_edits);
|
||||
self.docking = self.docking.filter(crate::panel_docking::valid);
|
||||
self.docking_icons.retain(|id| ICON_PANELS.iter().any(|entry| entry.0 == id));
|
||||
// Changes kept for workspaces that still exist, one per workspace (a hand-edited file).
|
||||
let mut seen = std::collections::BTreeSet::new();
|
||||
let custom: Vec<String> = self.custom_workspaces.iter().map(|w| w.name.clone()).collect();
|
||||
self.workspace_changes.retain(|w| (crate::workspaces::is_builtin(&w.name) || custom.contains(&w.name)) && seen.insert(w.name.clone()));
|
||||
self.workspace_changes.truncate(64);
|
||||
// Overrides that can't fire (modifier-only chords recorded by older versions, #487) give
|
||||
// the default back.
|
||||
self.shortcut_overrides.retain(|_, c| c.is_empty() || crate::shortcut_editor::normalize(c).is_some());
|
||||
@@ -829,6 +838,7 @@ impl Default for UiState {
|
||||
workspace: default_workspace(),
|
||||
touch_workspace_restore: None,
|
||||
custom_workspaces: vec![],
|
||||
workspace_changes: vec![],
|
||||
recent_files: vec![],
|
||||
recent_fonts: vec![],
|
||||
favorite_fonts: vec![],
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
//! Window → Workspace: saved panel/bar layouts (Essentials, Essentials Classic, Painting, …),
|
||||
//! Reset, New Workspace…, Manage Workspaces…. A workspace is a snapshot of `UiState` layout
|
||||
//! flags; UI brightness and everything else is untouched when switching.
|
||||
//!
|
||||
//! As in Illustrator, a workspace remembers the changes made in it: switching to another one keeps
|
||||
//! the layout as it was left (`UiState::workspace_changes`, kept with the preferences), switching
|
||||
//! back brings it back, and Reset <name> returns to the workspace as saved. New Workspace… with
|
||||
//! the name of a user workspace saves the current layout as it.
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
@@ -209,8 +214,37 @@ pub fn apply(ui: &mut UiState, w: &Workspace) {
|
||||
ui.workspace = w.name.clone();
|
||||
}
|
||||
|
||||
/// A workspace by name (user workspaces shadow nothing: names are unique across both lists).
|
||||
pub fn find(ui: &UiState, name: &str) -> Option<Workspace> {
|
||||
/// Keep the layout of the current workspace as it is now, so switching back brings it back.
|
||||
fn remember(ui: &mut UiState) {
|
||||
let current = ui.workspace.clone();
|
||||
if find_saved(ui, ¤t).is_none() {
|
||||
return;
|
||||
}
|
||||
let w = capture(ui, ¤t);
|
||||
ui.workspace_changes.retain(|x| x.name != current);
|
||||
ui.workspace_changes.push(w);
|
||||
ui.workspace_changes.truncate(64);
|
||||
}
|
||||
|
||||
/// Switch to workspace `name` as it was left (its changes since it was saved), else as saved.
|
||||
pub fn switch(ui: &mut UiState, name: &str) -> Result<String, String> {
|
||||
let saved = find_saved(ui, name).ok_or_else(|| format!("unknown workspace (one of {})", names(ui).join(", ")))?;
|
||||
remember(ui);
|
||||
let w = ui.workspace_changes.iter().find(|w| w.name == saved.name).cloned().unwrap_or(saved);
|
||||
apply(ui, &w);
|
||||
Ok(w.name)
|
||||
}
|
||||
|
||||
/// Reset the current workspace to the way it was saved, dropping its changes.
|
||||
pub fn reset(ui: &mut UiState) -> String {
|
||||
let w = find_saved(ui, &ui.workspace.clone()).unwrap_or_default();
|
||||
ui.workspace_changes.retain(|x| x.name != w.name);
|
||||
apply(ui, &w);
|
||||
w.name
|
||||
}
|
||||
|
||||
/// A workspace by name as saved (user workspaces shadow nothing: names are unique across both lists).
|
||||
pub fn find_saved(ui: &UiState, name: &str) -> Option<Workspace> {
|
||||
ui.custom_workspaces
|
||||
.iter()
|
||||
.find(|w| w.name.eq_ignore_ascii_case(name))
|
||||
@@ -218,6 +252,11 @@ pub fn find(ui: &UiState, name: &str) -> Option<Workspace> {
|
||||
.or_else(|| builtins().into_iter().find(|w| w.name.eq_ignore_ascii_case(name)))
|
||||
}
|
||||
|
||||
/// A workspace by name as saved.
|
||||
pub fn find(ui: &UiState, name: &str) -> Option<Workspace> {
|
||||
find_saved(ui, name)
|
||||
}
|
||||
|
||||
/// Every workspace name: built-ins then user workspaces.
|
||||
pub fn names(ui: &UiState) -> Vec<String> {
|
||||
builtins().into_iter().map(|w| w.name).chain(ui.custom_workspaces.iter().map(|w| w.name.clone())).collect()
|
||||
@@ -232,11 +271,14 @@ pub fn save_as(ui: &mut UiState, name: &str) -> Result<(), String> {
|
||||
if is_builtin(name) {
|
||||
return Err(format!("`{name}` is a built-in workspace; choose another name"));
|
||||
}
|
||||
// The changes go into the workspace saved, which starts with none; the one left keeps the
|
||||
// changes it had (none of these).
|
||||
let w = capture(ui, name);
|
||||
match ui.custom_workspaces.iter_mut().find(|x| x.name == name) {
|
||||
Some(x) => *x = w,
|
||||
None => ui.custom_workspaces.push(w),
|
||||
}
|
||||
ui.workspace_changes.retain(|x| x.name != name);
|
||||
ui.workspace = name.into();
|
||||
Ok(())
|
||||
}
|
||||
@@ -247,8 +289,13 @@ pub fn delete(ui: &mut UiState, name: &str) -> Result<(), String> {
|
||||
if ui.custom_workspaces.len() == before {
|
||||
return Err(if is_builtin(name) { format!("`{name}` is built in and can't be deleted") } else { format!("no workspace `{name}`") });
|
||||
}
|
||||
ui.workspace_changes.retain(|w| w.name != name);
|
||||
// The current workspace deleted: Essentials (as it was left) takes over, rather than its layout
|
||||
// staying on screen under Essentials's name, to be remembered as Essentials's changes.
|
||||
if ui.workspace == name {
|
||||
ui.workspace = ESSENTIALS.into();
|
||||
let essentials =
|
||||
ui.workspace_changes.iter().find(|w| w.name == ESSENTIALS).cloned().or_else(|| find_saved(ui, ESSENTIALS)).unwrap_or_default();
|
||||
apply(ui, &essentials);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -260,6 +307,9 @@ pub fn rename(ui: &mut UiState, from: &str, to: &str) -> Result<(), String> {
|
||||
}
|
||||
let w = ui.custom_workspaces.iter_mut().find(|w| w.name == from).ok_or_else(|| format!("no user workspace `{from}`"))?;
|
||||
w.name = to.into();
|
||||
for w in ui.workspace_changes.iter_mut().filter(|w| w.name == from) {
|
||||
w.name = to.into();
|
||||
}
|
||||
if ui.workspace == from {
|
||||
ui.workspace = to.into();
|
||||
}
|
||||
@@ -337,18 +387,8 @@ pub fn popup(app: &mut VectorcraftApp, ui: &mut egui::Ui) {
|
||||
pub fn run_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result<Value, String>> {
|
||||
let s = |k: &str| p.get(k).and_then(Value::as_str).map(str::to_string);
|
||||
let r = match id {
|
||||
"window.workspace" => match s("name").and_then(|n| find(&app.ui, &n)) {
|
||||
Some(w) => {
|
||||
apply(&mut app.ui, &w);
|
||||
Ok(json!({"workspace": w.name}))
|
||||
}
|
||||
None => Err(format!("unknown workspace (one of {})", names(&app.ui).join(", "))),
|
||||
},
|
||||
"window.workspace.reset" => {
|
||||
let w = find(&app.ui, &app.ui.workspace.clone()).unwrap_or_default();
|
||||
apply(&mut app.ui, &w);
|
||||
Ok(json!({"workspace": w.name}))
|
||||
}
|
||||
"window.workspace" => switch(&mut app.ui, &s("name").unwrap_or_default()).map(|name| json!({"workspace": name})),
|
||||
"window.workspace.reset" => Ok(json!({"workspace": reset(&mut app.ui)})),
|
||||
"window.workspace.new" => match s("name") {
|
||||
Some(n) => save_as(&mut app.ui, &n).map(|_| json!({"workspace": n})),
|
||||
None => {
|
||||
@@ -509,6 +549,77 @@ mod tests {
|
||||
assert_eq!(back.workspace, "Mine");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_workspace_keeps_its_changes_across_switches_until_reset() {
|
||||
let mut ui = UiState::default();
|
||||
save_as(&mut ui, "Görkem").unwrap();
|
||||
// Changes made in it after saving (a panel moved, the control bar shown)…
|
||||
ui.control_bar = true;
|
||||
ui.dock_tab = DockTab::Layers;
|
||||
ui.docking_icons.insert("color".into());
|
||||
// …are still there after switching away and back.
|
||||
switch(&mut ui, ESSENTIALS).unwrap();
|
||||
assert!(!ui.control_bar && ui.docking_icons.is_empty(), "Essentials comes as saved");
|
||||
switch(&mut ui, "Görkem").unwrap();
|
||||
assert!(ui.control_bar && ui.dock_tab == DockTab::Layers && ui.docking_icons.contains("color"));
|
||||
// Built-in workspaces remember theirs too.
|
||||
switch(&mut ui, ESSENTIALS).unwrap();
|
||||
ui.status_bar = false;
|
||||
switch(&mut ui, "Görkem").unwrap();
|
||||
switch(&mut ui, ESSENTIALS).unwrap();
|
||||
assert!(!ui.status_bar);
|
||||
// They survive a restart (the preferences' JSON round trip).
|
||||
let mut ui: UiState = serde_json::from_value(serde_json::to_value(&ui).unwrap()).unwrap();
|
||||
ui = ui.sanitized();
|
||||
switch(&mut ui, "Görkem").unwrap();
|
||||
assert!(ui.control_bar);
|
||||
// Reset Görkem: back to the way it was saved.
|
||||
assert_eq!(reset(&mut ui), "Görkem");
|
||||
assert!(!ui.control_bar && ui.docking_icons.is_empty());
|
||||
switch(&mut ui, ESSENTIALS).unwrap();
|
||||
switch(&mut ui, "Görkem").unwrap();
|
||||
assert!(!ui.control_bar, "a reset workspace stays reset");
|
||||
// Renamed, it keeps its changes; deleted, they go.
|
||||
ui.control_bar = true;
|
||||
switch(&mut ui, ESSENTIALS).unwrap();
|
||||
rename(&mut ui, "Görkem", "G").unwrap();
|
||||
switch(&mut ui, "G").unwrap();
|
||||
assert!(ui.control_bar);
|
||||
switch(&mut ui, ESSENTIALS).unwrap();
|
||||
delete(&mut ui, "G").unwrap();
|
||||
assert!(ui.workspace_changes.iter().all(|w| w.name != "G"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deleting_the_current_workspace_switches_to_essentials_without_leaking_its_layout() {
|
||||
let mut ui = UiState::default();
|
||||
save_as(&mut ui, "Tmp").unwrap();
|
||||
ui.control_bar = true;
|
||||
delete(&mut ui, "Tmp").unwrap();
|
||||
assert_eq!(ui.workspace, ESSENTIALS);
|
||||
assert!(!ui.control_bar, "Essentials's layout is shown, not the deleted one's");
|
||||
save_as(&mut ui, "Other").unwrap();
|
||||
switch(&mut ui, ESSENTIALS).unwrap();
|
||||
assert!(!ui.control_bar, "Essentials didn't remember the deleted workspace's layout");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saving_a_workspace_moves_the_changes_into_it() {
|
||||
let mut ui = UiState { control_bar: true, ..Default::default() };
|
||||
save_as(&mut ui, "Mine").unwrap();
|
||||
switch(&mut ui, ESSENTIALS).unwrap();
|
||||
assert!(!ui.control_bar, "Essentials stays as saved: the changes went into Mine");
|
||||
switch(&mut ui, "Mine").unwrap();
|
||||
assert!(ui.control_bar);
|
||||
// Saving again under the same name takes the current layout and starts with no changes.
|
||||
ui.status_bar = false;
|
||||
save_as(&mut ui, "Mine").unwrap();
|
||||
assert!(ui.workspace_changes.iter().all(|w| w.name != "Mine"));
|
||||
ui.status_bar = true;
|
||||
assert_eq!(reset(&mut ui), "Mine");
|
||||
assert!(!ui.status_bar);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_names_are_protected() {
|
||||
let mut ui = UiState::default();
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
# Roadmap: milestones and what's next
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (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; 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged; Polygon and Star exact-size placement remembers the chosen count for later drags and dialog openings; Cancel preserves the previous count; M12 duplicate milestone rows consolidated without losing distinct details; scores and estimates unchanged; live-trace bounds note placed under Image Trace rather than Graphs; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views; M3.57: CSS swatch libraries retain fractional gradient stop positions; scores and hours unchanged; 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; 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; DXF circular cubic paths export as native CIRCLE/ARC with R12 weighted strokes kept as POLYLINE and closing-chord safeguards (#1299); 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; 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 graph pruning and junction merging measured; M12 exact crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; 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: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; 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; correction: M12's Image Trace quick presets leave the panel as it was, the Image Trace button opens it; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 45/57, 12 remaining (8–14 h); Warp Options controls verified by focused checks and original-image review; estimates unchanged; 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: per-corner radius, #1682; Width Point Edit from the Width tool, #1814, #1630; guides, #1782, #1836; anchor X/Y/W/H, #1824; toolbar and All Tools, #1751; macOS save sheets, #1855; rendering caches, #1667–#1792; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; Dim Images, #1683; estimates unchanged; M12 row: Image Trace's enhanced presets; 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; Expand on repeats, #1756; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace #1647, #1657; estimates unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1601; Warp Arc, #1578; PathfinLine truncated
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (M14: workspaces keep their layout changes (UI.7); estimates 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; 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged; Polygon and Star exact-size placement remembers the chosen count for later drags and dialog openings; Cancel preserves the previous count; M12 duplicate milestone rows consolidated without losing distinct details; scores and estimates unchanged; live-trace bounds note placed under Image Trace rather than Graphs; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views; M3.57: CSS swatch libraries retain fractional gradient stop positions; scores and hours unchanged; 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; 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; DXF circular cubic paths export as native CIRCLE/ARC with R12 weighted strokes kept as POLYLINE and closing-chord safeguards (#1299); 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; 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 graph pruning and junction merging measured; M12 exact crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; 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: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; 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; correction: M12's Image Trace quick presets leave the panel as it was, the Image Trace button opens it; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 45/57, 12 remaining (8–14 h); Warp Options controls verified by focused checks and original-image review; estimates unchanged; 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: per-corner radius, #1682; Width Point Edit from the Width tool, #1814, #1630; guides, #1782, #1836; anchor X/Y/W/H, #1824; toolbar and All Tools, #1751; macOS save sheets, #1855; rendering caches, #1667–#1792; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; Dim Images, #1683; estimates unchanged; M12 row: Image Trace's enhanced presets; 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; Expand on repeats, #1756; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace #1647, #1657; estimates unchanged; later 2026-10-13 mLine truncated
|
||||
|
||||
Forward-looking plan. Where VectorCraft stands is in [`ROADMAP.md`](../ROADMAP.md) and
|
||||
[target-app-parity.md](target-app-parity.md); the ranked work list is [gaps.md](gaps.md). Hours are Opus 5.5
|
||||
@@ -66,7 +66,7 @@ Checked 2026-10-10 from the code, tests and open issues; no workflow fails, so V
|
||||
| M12 | Advanced color & art (CMYK/ICC, separations, Gradient Mesh, Live Paint, Image Trace, Graphs) | 🟡 Gradient Mesh, Live Paint, Image Trace (junction-returning pieces retain their positioned boundaries; a simpler contour walk with identical output; an edge's sub-pixel shift is the same from either side; notches read only their own piece; collapsed loops keep few segments; large images trace with a warning; Auto Color keeps rare distinct colours; quick presets trace at once and the Image Trace button opens the adjustment controls; Properties reopens them; tiny fitted contours retain islands and holes; Create Strokes: exact crop EDT, Guo–Hall thinning and maximal-disc graph simplification; 15 presets, the 14 documented ones each with an enhanced version; Ignore Color, Shapes, Auto Grouping and Gradients options; measured in `image-trace-quality.md`; closed outlines retain their interior after straightening; the Flat Logo mode places edges to a fraction of a pixel, restores sharp corners, snaps lines and circles, and refuses gradients; live trace result bounds and selection highlights exclude the retained bitmap except in source-image views), Recolor Artwork, colour management (ICC, soft proofing, separations preview), Graphs (all 9 tools, Graph Data/Type, group-selected series colours persist through regenerate/save/open) done; Graph Design and Marker designs, Transpose and Switch x/y, `\|` label line breaks, the pie options, drop shadows, the legend across the top, overlapping widths, First Row/Column in Front, filled lines, kept label formatting and the Graph Data worksheet done; graph Column designs pending | 6–10 |
|
||||
| M13 | Automation (Actions ✅ record/playback, persisted; variables, scripting, batch) | 🟡 MCP `add_text` exposes existing vertical point, area and path type; broader scripting and batch work remains | 18–27 |
|
||||
| M13 | Automation (Actions ✅ record/playback, persisted; variables, scripting, batch) | 🟡 | 18–27 |
|
||||
| M14 | 1.0 polish (preferences, shortcut editor, workspaces, accessibility, packaging for all OSes) (status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; sample widths cached by language/unit/DPI; regression-tested and contributor-confirmed 2026-10-19; estimates unchanged) | 🟡 Numeric-field pointer selection and focus regression-tested (#1326, in part; short movements still select all); RPM runtime capabilities and glibc floor checked (#1275; real-distro install/start-up pending), Eyedropper cursor with precise-setting checks (#1269), Preferences, shortcut editor (chords with Shift and a punctuation key fire as typed; other apps' text shortcut sets imported), workspaces, a custom title bar on Windows/Linux with a Home button, content-sized dialogs, menus that scroll when longer than the window and macOS native menu dismissal done (M14.8), and native macOS save sheets with a Format menu (#866); an experimental, unsupported Windows 7 x64 portable OpenGL build (not verified by maintainers); accessibility and broader Windows/Linux packaging/QA pending | 18–28 |
|
||||
| M14 | 1.0 polish (preferences, shortcut editor, workspaces, accessibility, packaging for all OSes) (status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; sample widths cached by language/unit/DPI; regression-tested and contributor-confirmed 2026-10-19; estimates unchanged) | 🟡 Numeric-field pointer selection and focus regression-tested (#1326, in part; short movements still select all); RPM runtime capabilities and glibc floor checked (#1275; real-distro install/start-up pending), Eyedropper cursor with precise-setting checks (#1269), Preferences, shortcut editor (chords with Shift and a punctuation key fire as typed; other apps' text shortcut sets imported), workspaces (each keeps its layout changes until Reset <name>, UI.7), a custom title bar on Windows/Linux with a Home button, content-sized dialogs, menus that scroll when longer than the window and macOS native menu dismissal done (M14.8), and native macOS save sheets with a Format menu (#866); an experimental, unsupported Windows 7 x64 portable OpenGL build (not verified by maintainers); accessibility and broader Windows/Linux packaging/QA pending | 18–28 |
|
||||
| — | Interaction fidelity pass (every tool's modifiers, Properties panel per context, isolation, nuance; width-point creation and adjoining adjustment regression-tested; dialog layout reviewed) | ⬜ (new ruler-guide Alt orientation implemented; existing-guide Escape cancellation regression-tested; broader pass remains) | 60–90 |
|
||||
| — | Hardening at scale (big-file corpus, fuzzing, cross-platform + browser QA) | 🟡 PngSuite integrity and bounded fetching verified (#1729); real-world corpus/manual QA remain | 50–80 |
|
||||
| M14 follow-up | Color Picker layout (#2008) | ✅ Compact controls, centered header, stacked actions, aligned fields and screen sampling; narrow windows scroll vertically | Included above |
|
||||
@@ -95,6 +95,7 @@ sampling coexistence. Scores and estimates unchanged. See [development.md](devel
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-20 | minor | M14: workspaces keep the layout changes made in them across switches and restarts until Reset <name> (UI.7); estimates unchanged |
|
||||
| 2026-10-21 | minor | M3: highlighted-text Eyedropper sampling preserves ranges and rejects unsupported source/target transparency atomically; regression-tested; scores and estimates unchanged. |
|
||||
| 2026-10-21 | minor | 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. Scores and estimates unchanged. |
|
||||
| 2026-10-21 | minor | Recolor model selection is available through the engine and MCP only (#2520); the dialog retains its existing controls. Scores and estimates unchanged. |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Parity with Adobe Illustrator
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (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% → Line truncated
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (UI chrome: workspaces keep their layout changes (UI.7); 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 coLine truncated
|
||||
|
||||
The authoritative parity assessment. [`ROADMAP.md`](../ROADMAP.md) summarizes it; [`gaps.md`](gaps.md) is
|
||||
the work list made from it. Deep areas have their own checklists: [UI and interaction](ui-parity.md),
|
||||
@@ -931,6 +931,7 @@ sampling coexistence. Scores and estimates unchanged. See [development.md](devel
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-20 | minor | UI chrome: workspaces keep the layout changes made in them when switching away and back, and across restarts; Reset <name> returns to the saved layout (UI.7); score and hours unchanged (estimated) |
|
||||
| 2026-10-21 | minor | M3: highlighted-text Eyedropper sampling preserves ranges and rejects unsupported source/target transparency atomically; regression-tested; scores and estimates unchanged. |
|
||||
| 2026-10-21 | minor | 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. Scores and estimates unchanged. |
|
||||
| 2026-10-21 | minor | Recolor model selection is available through the engine and MCP only (#2520); the dialog retains its existing controls. Scores and estimates unchanged. |
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
# UI and interaction parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (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; 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; Distribute Objects and Distribute Spacing space from a key guide (#3165) with the Distribute Spacing value; Distribute spaces selected ruler guides with the objects, #1597 in part; the selection tools take a clipping path on its outline, over the art it clips; 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; a ruler guide can be the key object Align aligns to, and selected guides align to a key object, #2143; Pencil: Alt key toggles to Smooth Tool, #2001; 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; on-canvas Mirror Repeat axis, #2877; Background Export progress dialog with Cancel, #2564; 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; scores unchanged; library panels: one per library, dockable and tabbable, with Persistent, #2603; the Selection tool threads text from the out port, #2572 in part; scores unchanged; 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; scores unchanged; transparency grid visibility saved in native documents, #1968; Live Corners radius up to the neighbouring cuts, #2332; Image Trace quick presets: wording corrected to the shipped behaviour, the Image Trace button opens the controls; batch exporting, #1935; document window scroll bars, #2322; Anchor Point tool: a click on a handle end retracts that handle only, #2267; scores unchanged; Smart Guides: the dragged end of a straight segment keeps to its extension at any angle, #2120; Warp Options controls verified by focused checks and original-image review; estimates unchanged; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; 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; Curvature curve geometry, #1236 in part; Width-point finite-arithmetic follow-up validated; scores and hours unchanged; Image Trace adjustment discovery; later 2026-10-14 merges: macOS save sheet Format menu back to missing, #1855, #866 reopened; anchor X/Y/W/H in the Transform panel, Properties and Control bar, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar labels, columns and the All Tools drawer, #1751; Actions panel selection, #1629 in part; scores unchanged; 2026-10-14 merges: anchor Align and Distribute, #1636; Delete on closed paths and whole objects, #1674; selection inside isolation, #1666; Escape in a Direct Selection drag and while typing, #1743, #1686; Pen clicks under 3 px, #1663; [ and ] on more tools, #1661 in part; fields commit on click away, #1787; command search keys, #1757; a guide's position in the Transform panel is a convenience with no parity claim, #1659; scores unchanged; intersect Smart Guides for drawn points and dragged anchors, #1602 in part; a ruler guide's position in the Transform panel, #1597 in part; Symbol Options before New Symbol, #1560; tool flyout widths, #1616; Pathfinder Trim, Merge and Crop strokes, #1570; scores unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; per-corner radius fields with a link toggle, split from #1594; Join merges anchors lying on one another, #1533; correction: Average and Join with a corner or smooth choice is missing, not done; guide objects take the guide colour and style and Lock Line truncated
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (workspaces keep their layout changes (UI.7); scores 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; 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; Distribute Objects and Distribute Spacing space from a key guide (#3165) with the Distribute Spacing value; Distribute spaces selected ruler guides with the objects, #1597 in part; the selection tools take a clipping path on its outline, over the art it clips; 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; a ruler guide can be the key object Align aligns to, and selected guides align to a key object, #2143; Pencil: Alt key toggles to Smooth Tool, #2001; 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; on-canvas Mirror Repeat axis, #2877; Background Export progress dialog with Cancel, #2564; 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; scores unchanged; library panels: one per library, dockable and tabbable, with Persistent, #2603; the Selection tool threads text from the out port, #2572 in part; scores unchanged; 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; scores unchanged; transparency grid visibility saved in native documents, #1968; Live Corners radius up to the neighbouring cuts, #2332; Image Trace quick presets: wording corrected to the shipped behaviour, the Image Trace button opens the controls; batch exporting, #1935; document window scroll bars, #2322; Anchor Point tool: a click on a handle end retracts that handle only, #2267; scores unchanged; Smart Guides: the dragged end of a straight segment keeps to its extension at any angle, #2120; Warp Options controls verified by focused checks and original-image review; estimates unchanged; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; 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; Curvature curve geometry, #1236 in part; Width-point finite-arithmetic follow-up validated; scores and hours unchanged; Image Trace adjustment discovery; later 2026-10-14 merges: macOS save sheet Format menu back to missing, #1855, #866 reopened; anchor X/Y/W/H in the Transform panel, Properties and Control bar, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar labels, columns and the All Tools drawer, #1751; Actions panel selection, #1629 in part; scores unchanged; 2026-10-14 merges: anchor Align and Distribute, #1636; Delete on closed paths and whole objects, #1674; selection inside isolation, #1666; Escape in a Direct Selection drag and while typing, #1743, #1686; Pen clicks under 3 px, #1663; [ and ] on more tools, #1661 in part; fields commit on click away, #1787; command search keys, #1757; a guide's position in the Transform panel is a convenience with no parity claim, #1659; scores unchanged; intersect Smart Guides for drawn points and dragged anchors, #1602 in part; a ruler guide's position in the Transform panel, #1597 in part; Symbol Options before New Symbol, #1560; tool flyout widths, #1616; Pathfinder Trim, Merge and Crop strokes, #1570; scores unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; per-corner radius fields with a link toggle, split from #1594; Join merges anchors lying on one another, #1533; correction: Average and Join with a corner or smooth choice is missing, noLine truncated
|
||||
|
||||
How VectorCraft's tools, handles, snapping and panels behave next to Illustrator's. A vector illustrator's
|
||||
trust is in the details: where a handle lands, what Shift and Alt do mid-drag, what a smart guide snaps to,
|
||||
@@ -244,7 +244,7 @@ empty), without opening the dialog.
|
||||
| Retype, Comments, Version History panels | missing | the last two are cloud features (out of scope) |
|
||||
| Blend panel (30.7) | partial | Window › Blend: Make, Spacing, Orientation, Reverse Front to Back, Reverse Spine, Replace Spine, Expand and Release, running the Object › Blend commands; the easing and color controls are missing |
|
||||
| SVG Interactivity, Retype, Comments, Version History panels | missing | the last two are cloud features (out of scope) |
|
||||
| Workspaces (save, reset, switch) | done | |
|
||||
| Workspaces (save, reset, switch); each remembers the layout changes made in it across switches and restarts until Reset <name> | done | `UiState::workspace_changes` |
|
||||
| Background Export: a progress dialog with the file name, the artboard count and a bar (busy for one artboard and for formats made in one go), Cancel (Esc) and Continue in Background; it opens after 0.4 s and never over another dialog. A cancelled export writes no file | done | #2564; on the shared `vectorcraft_doc::cancel` token (#2546), so a cancelled render keeps no half-drawn pattern tile or effect raster. The bar moves between artboards, not within one. Save As, Export for Screens and Export Selection don't show it yet |
|
||||
| Swatch and graphic style library panels: each library opens in a panel of its own, several at once, that floats, groups, docks and closes like any panel; opening an open library brings its panel forward; Previous / Next Library change the library in place; the panel menu's Persistent reopens the panel at startup with its view and find field (a library loaded from a file is read again; when its file is missing the panel reopens showing the file's path with Locate Library…, and the status bar names the file) | done | #2603; panel ids `library:<kind>:<id>`, `ui.libraryPanel {panel, library?, view?, showFind?, persistent?}`, `ui.libraryPanel.locate {panel, path?}`; the library panels have no sort option, as before |
|
||||
| DXF export/import preserves intended curve closure | done | #1299; R13–2018 closed SPLINE bit set/read; an open curve ending at its start stays open; knots and control points unchanged; native CIRCLE/ARC export and CNC preset remain pending |
|
||||
@@ -280,6 +280,7 @@ sampling coexistence. Scores and estimates unchanged. See [development.md](devel
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-20 | minor | Workspaces keep the layout changes made in them when switching away and back, and across restarts; Reset <name> returns to the saved layout; score unchanged (estimated) |
|
||||
| 2026-10-21 | minor | M3: highlighted-text Eyedropper sampling preserves ranges and rejects unsupported source/target transparency atomically; regression-tested; scores and estimates unchanged. |
|
||||
| 2026-10-21 | minor | 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. Scores and estimates unchanged. |
|
||||
| 2026-10-21 | minor | 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged |
|
||||
|
||||
Reference in new issue
Block a user