Merge pull request #5457 from Taha-Mahmoodi/fix/ruler-double-click-guide

Fix #5239: double-clicking a ruler adds a guide there
This commit is contained in:
BFlat
2026-11-11 18:24:56 +07:00
committed by GitHub
6 changed files with 203 additions and 7 deletions
+9 -3
View File
@@ -708,6 +708,14 @@ impl Session {
Ok(())
}
/// The artboard a guide made from a ruler belongs to: with the Artboard tool, its active
/// artboard (an artboard guide); with any other tool, none (a canvas guide).
pub fn ruler_guide_artboard(&self) -> Option<usize> {
let boards = self.active().map_or(0, |d| d.doc.artboards.len());
let active = (self.tool.id() == "artboard").then(|| self.tool.options()["active"].as_u64()).flatten();
active.and_then(|i| usize::try_from(i).ok()).filter(|i| *i < boards)
}
/// A guide dragged out of a ruler, whatever the tool: a vertical one out of the left ruler, a
/// horizontal one out of the top ruler. `ev` is the pointer in document space (a drag, then the
/// release), over the canvas or not (`on_canvas`). The guide shows where the pointer is over
@@ -722,11 +730,9 @@ impl Session {
let g = match self.ruler_guide.take() {
Some(g) => Some(g),
None => {
let boards = self.active().map_or(0, |d| d.doc.artboards.len());
let artboard_tool = self.tool.id() == "artboard";
let swap = self.prefs.cmd_ruler_drag_artboard_guides && ev.mods.cmd;
let active = artboard_tool.then(|| self.tool.options()["active"].as_u64()).flatten();
let artboard = active.and_then(|i| usize::try_from(i).ok()).filter(|i| *i < boards);
let artboard = self.ruler_guide_artboard();
let view_board = view.artboard;
self.with_tool_cx(view, |_, cx| {
Some(match (swap, artboard_tool) {
+39 -1
View File
@@ -178,7 +178,11 @@ pub(super) fn ruler_rects(full: egui::Rect) -> [egui::Rect; 3] {
/// A drag from a ruler onto the canvas makes a guide where the button is released (the engine's
/// `Session::ruler_guide`): a horizontal one from the top ruler, a vertical one from the left
/// ruler, snapped as a moved guide is (with Shift to the ruler's ticks). Released anywhere else,
/// it makes none. The corner drags the rulers' origin or resets it on a double-click.
/// it makes none. A double-click on a ruler (both clicks on that ruler) makes a guide at the
/// pointer, across the ruler's axis (a vertical one on the top ruler, a horizontal one on the
/// left; swapped in a view turned 90°, where the rulers read the other document axis, and the
/// nearer axis in between), unsnapped, as one `guide.add` and so one undo step. The corner drags the rulers' origin or resets it on a
/// double-click.
pub(super) fn ruler_input(app: &mut VectorcraftApp, ui: &Ui, full: egui::Rect, canvas: egui::Rect, xf: &Xf) -> Option<Point> {
let [top, left, corner] = ruler_rects(full);
// Right-click on a ruler or the origin box: the document units, to swap between them as
@@ -189,6 +193,40 @@ pub(super) fn ruler_input(app: &mut VectorcraftApp, ui: &Ui, full: egui::Rect, c
// (right). Not focusable (`click_and_drag` is): no key acts on a ruler, so Tab skips it.
let resp = ui.interact(r, egui::Id::unique(id), Sense::CLICK | Sense::DRAG);
resp.context_menu(|ui| crate::menus::ruler_menu_body(app, ui, &mut unit_clicked));
// egui counts a double-click by position and time alone, so a click on the canvas right
// at the ruler and one on the ruler would pass for one: the pair counts only when the
// ruler's own click before it came within the double-click delay.
let last_click = egui::Id::unique(if vertical { "ruler-left-last-click" } else { "ruler-top-last-click" });
let now = ui.input(|i| i.time);
let first_here =
ui.data(|d| d.get_temp::<f64>(last_click)).is_some_and(|t| now - t <= ui.ctx().options(|o| o.input_options.max_double_click_delay));
if resp.clicked_by(egui::PointerButton::Primary) {
ui.data_mut(|d| d.insert_temp(last_click, now));
}
if resp.double_clicked_by(egui::PointerButton::Primary)
&& first_here
&& let Some(p) = resp.interact_pointer_pos().or_else(|| ui.input(|i| i.pointer.latest_pos()))
{
// The same document point the drag path reads. A ruler reads the canvas's coordinates
// turned with the view, so the guide is the one whose line crosses the ruler's axis:
// the vertical one when that axis (the screen's x for the top ruler, y for the left,
// in document directions) is nearer the document's x, else the horizontal one. At 0°
// that is a vertical guide from the top ruler; at 90° a horizontal one; between, the
// nearer axis, whose line still runs through the point and never along the ruler.
let at = xf.to_doc(p);
let (sn, cs) = xf.rot.sin_cos();
let axis = if vertical { (sn, cs) } else { (cs, -sn) };
let across = axis.0.abs() >= axis.1.abs();
let mut params = json!({"vertical": across, "pos": if across { at.x } else { at.y }});
if let Some(ab) = app.session.ruler_guide_artboard() {
params["artboard"] = json!(ab);
}
// The new guide shows even if guides were hidden.
app.ui.view.guides = true;
if let Err(e) = app.run("guide.add", params) {
app.status(e.to_string());
}
}
let kind = if resp.drag_stopped() {
PointerKind::Up
} else if resp.dragged() {
+145
View File
@@ -11365,6 +11365,151 @@ fn direct_selection_shows_the_bounding_box_while_cmd_is_held() {
assert!(app.ui.view.bounding_box, "View › Show Bounding Box is unchanged");
}
/// #5239: a double-click on a ruler adds a guide at the pointer: a vertical one on the top ruler
/// at its x, a horizontal one on the left ruler at its y, as one undo step, shown even when
/// guides were hidden. The first click of the pair adds nothing.
#[test]
fn double_clicking_a_ruler_adds_a_guide_at_that_position() {
// The ruler to click, the guide's orientation and its position, the pointer at that position.
for (top, vertical, pos) in [(true, true, 120.0), (false, false, 80.0)] {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
app.ui.view.rulers = true;
app.ui.view.guides = false;
let ctx = egui::Context::default();
frame(&mut app, &ctx, vec![]);
let view = app.view_mut().unwrap();
view.zoom = 2.0;
view.rotation = 0.0;
view.center = Point::new(200.0, 150.0);
frame(&mut app, &ctx, vec![]);
let rect = app.canvas_rect.unwrap();
let xf = Xf::new(rect, app.view().unwrap());
let at = if top {
pos2(xf.to_screen(Point::new(pos, 0.0)).x, rect.top() - RULER / 2.0)
} else {
pos2(rect.left() - RULER / 2.0, xf.to_screen(Point::new(0.0, pos)).y)
};
let guides = |app: &mut VectorcraftApp| {
let list = app.session.execute("guide.list", &json!({})).unwrap();
list.as_array().unwrap().iter().map(|g| (g["vertical"].as_bool().unwrap(), g["pos"].as_f64().unwrap())).collect::<Vec<_>>()
};
let steps = |app: &VectorcraftApp| app.session.active().unwrap().history.undo.len();
let before = steps(&app);
let click = |app: &mut VectorcraftApp| {
let button =
|pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
frame(app, &ctx, vec![egui::Event::PointerMoved(at)]);
frame(app, &ctx, vec![button(true)]);
frame(app, &ctx, vec![button(false)]);
};
click(&mut app);
assert_eq!(guides(&mut app), vec![], "a single click adds none");
click(&mut app);
let got = guides(&mut app);
assert_eq!(got.len(), 1, "top ruler: {top}, {got:?}");
assert!(got[0].0 == vertical && (got[0].1 - pos).abs() < 0.01, "top ruler: {top}, want vertical {vertical} at {pos}: {got:?}");
assert!(app.ui.view.guides, "the new guide is shown");
assert_eq!(steps(&app), before + 1, "one undo step");
app.run("edit.undo", json!({})).unwrap();
assert_eq!(guides(&mut app), vec![]);
}
}
/// A fresh app with a 400 x 300 document, rulers on, the view at zoom 2 turned `rotation` degrees,
/// and its first frame drawn.
fn ruler_guide_app(rotation: f64) -> (VectorcraftApp, egui::Context) {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
app.ui.view.rulers = true;
app.ui.view.guides = false;
let ctx = egui::Context::default();
frame(&mut app, &ctx, vec![]);
let view = app.view_mut().unwrap();
view.zoom = 2.0;
view.rotation = rotation;
view.center = Point::new(200.0, 150.0);
frame(&mut app, &ctx, vec![]);
(app, ctx)
}
/// One primary click at `at`, as three frames (move, press, release).
fn click_at(app: &mut VectorcraftApp, ctx: &egui::Context, at: Pos2) {
let button = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
frame(app, ctx, vec![egui::Event::PointerMoved(at)]);
frame(app, ctx, vec![button(true)]);
frame(app, ctx, vec![button(false)]);
}
/// The ruler guides as (vertical, position).
fn guide_list(app: &mut VectorcraftApp) -> Vec<(bool, f64)> {
let list = app.session.execute("guide.list", &json!({})).unwrap();
list.as_array().unwrap().iter().map(|g| (g["vertical"].as_bool().unwrap(), g["pos"].as_f64().unwrap())).collect()
}
/// #5239 review: egui counts a double-click by position and time, not by widget, so a click on
/// the canvas right at a ruler followed at once by a click on the ruler is not a double-click on
/// the ruler. No guide; two clicks on the ruler still make one.
#[test]
fn a_canvas_click_then_a_ruler_click_adds_no_guide() {
for (top, canvas_first) in [(true, true), (false, true), (true, false), (false, false)] {
let (mut app, ctx) = ruler_guide_app(0.0);
let rect = app.canvas_rect.unwrap();
// Two points less than 6 pt apart, either side of the ruler's inner edge.
let (on_canvas, on_ruler) = if top {
(pos2(rect.center().x, rect.top() + 1.0), pos2(rect.center().x, rect.top() - 1.0))
} else {
(pos2(rect.left() + 1.0, rect.center().y), pos2(rect.left() - 1.0, rect.center().y))
};
click_at(&mut app, &ctx, if canvas_first { on_canvas } else { on_ruler });
click_at(&mut app, &ctx, on_ruler);
let want = usize::from(!canvas_first);
assert_eq!(guide_list(&mut app).len(), want, "top ruler: {top}, first click on the canvas: {canvas_first}");
}
}
/// #5239 review: the rulers read the canvas's coordinates turned with the view, so a guide made
/// by a double-click runs across the ruler's axis in any view: it passes through the clicked
/// point and crosses the ruler's edge at it, never along it. At 90° the top ruler reads the
/// document's y, so its guide is horizontal; the left ruler reads x, so its guide is vertical.
/// A view turned part of the way takes the document axis nearer to the ruler's.
#[test]
fn a_ruler_double_click_follows_a_rotated_view() {
// Rotation (degrees), top ruler's guide is vertical, left ruler's guide is vertical.
for (rotation, top_vertical, left_vertical) in [
(0.0, true, false),
(90.0, false, true),
(-90.0, false, true),
(180.0, true, false),
(270.0, false, true),
(30.0, true, false),
(60.0, false, true),
] {
for top in [true, false] {
let (mut app, ctx) = ruler_guide_app(rotation);
let rect = app.canvas_rect.unwrap();
let xf = Xf::new(rect, app.view().unwrap());
let at =
if top { pos2(rect.center().x + 37.0, rect.top() - RULER / 2.0) } else { pos2(rect.left() - RULER / 2.0, rect.center().y + 23.0) };
click_at(&mut app, &ctx, at);
click_at(&mut app, &ctx, at);
let got = guide_list(&mut app);
let label = format!("rotation {rotation}, top ruler: {top}: {got:?}");
assert_eq!(got.len(), 1, "{label}");
let (vertical, pos) = got[0];
assert_eq!(vertical, if top { top_vertical } else { left_vertical }, "{label}");
// Through the clicked point.
let doc = xf.to_doc(at);
assert!((pos - if vertical { doc.x } else { doc.y }).abs() < 0.01, "{label}: through {doc:?}");
// Its direction on screen is not along the ruler: the line crosses the ruler's edge.
let along = if vertical { Point::new(doc.x, doc.y + 1.0) } else { Point::new(doc.x + 1.0, doc.y) };
let dir = (xf.to_screen(along) - xf.to_screen(doc)).normalized();
let across = if top { dir.y.abs() } else { dir.x.abs() };
assert!(across > 0.5, "{label}: the guide runs along the ruler ({dir:?})");
}
}
}
/// An edit to a 3D effect renders the part it changed on this thread, measured by the renderer
/// that drew the frame shown ([`vectorcraft_render::Renderer::changed`]): it draws the part with
/// the 3D art it made to measure it.
+3 -1
View File
@@ -1,6 +1,6 @@
# Gaps
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-10 · **Change:** minor (G5: built-in Status Effects graphic style library; estimate 7–17 → 6–16 h; MCP docs catch-up for UI Frames and Map Markers; previously minor: G5: built-in Inventory Icons symbol library; estimate 8–18 → 7–17 h; previously minor: G9: `.ai` save names a variable font instance's design-space setting, #6388; previously minor: G5: built-in Game Trails brush library; estimate 9–19 → 8–18 h; previously minor: Edit › Edit Custom Dictionary…: its dialog adds, deletes and changes the custom dictionary's words, #4936; previously minor: docs tidy-up, no content change: stray notes at the top moved into the revision table; scores and estimates unchanged; previously minor: G5: built-in Game HUD graphic style library; estimate 10–20 → 9–19 h; previously minor: G5: built-in Map Markers symbol library; estimate 11–21 → 10–20 h; previously minor: right-click on type: Spelling › Auto Spell Check, with its check mark, #4936; previously minor: right-click on a word Auto Spell Check underlined: its suggestions, Ignore All and Add to Dictionary, #4936; previously minor: G2: Effect Gallery thumbnails and in-dialog preview; estimate 1–2 h → 0; Auto Spell Check: `edit.autoSpellCheck` underlines the unknown words, #4936; previously minor: PDF editable import preserves authored ActualText once, including empty/nested spans and authored ordering (#6165); cross-line/style layout remains approximate; scores and estimates unchanged; PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; G5: built-in UI Frames symbol library with default 9-slice; estimate 11–21 h; previously minor: G5: dynamic symbol part on-canvas takes opacity, blend, gradient and pattern; docs Overlap/auto-corners sync; estimate 12–22 h; previously minor: interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; previously minor: Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; previously minor: G5 docs: Art brush Overlap, Pattern brush auto corners and Global Edit Shift-click leave-out moved from Missing to Done (code already shipped); previously minor: custom dictionary and Ignore All: `text.spelling.addWord`, `removeWord`, `changeWord`, `dictionary` and `ignoreAll`, #4936; previously minor: EPS import: embedded fonts (composite, Type 42, CID-keyed, Type 1, CFF FontSet) draw their glyphs; spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; G9: EPS gradients drawn as abutting strips import as one gradient, without seams; Tools stays beside the canvas with left-side panels; new gradients follow document color mode; Selection QoL: marquee modes and visible feedback, #5313; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Indian-language hyphenation review and license allowlist, #4947; opening a slow file shows a progress window, part of #5930; Hindi catalog and contributor loanword preferences; agent wording pass complete; wider native proofreading welcome; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); scores and estimates unchanged; a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window; scores and estimates unchanged; large images in CMYK documents are shrunk before they are separated into ink planes (#4155); scores and estimates unchanged; `.ai` paths with a Calligraphic brush open brushed, with the 6D Art Pen's channels, and Windows reads the pen's tilt and barrel rotation, #372; scores and estimates unchanged; Compound Shapes add-on, beyond Illustrator; scores and estimates unchanged; Contour add-on, beyond Illustrator; scores and estimates unchanged; a slow first frame is waited for instead of restarting without the graphics adapter, and the menu bar no longer waits for the installed fonts' scan; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-10 · **Change:** minor (ruler double-click adds a guide at the pointer, #5239; G5: built-in Status Effects graphic style library; estimate 7–17 → 6–16 h; MCP docs catch-up for UI Frames and Map Markers; previously minor: G5: built-in Inventory Icons symbol library; estimate 8–18 → 7–17 h; previously minor: G9: `.ai` save names a variable font instance's design-space setting, #6388; previously minor: G5: built-in Game Trails brush library; estimate 9–19 → 8–18 h; previously minor: Edit › Edit Custom Dictionary…: its dialog adds, deletes and changes the custom dictionary's words, #4936; previously minor: docs tidy-up, no content change: stray notes at the top moved into the revision table; scores and estimates unchanged; previously minor: G5: built-in Game HUD graphic style library; estimate 10–20 → 9–19 h; previously minor: G5: built-in Map Markers symbol library; estimate 11–21 → 10–20 h; previously minor: right-click on type: Spelling › Auto Spell Check, with its check mark, #4936; previously minor: right-click on a word Auto Spell Check underlined: its suggestions, Ignore All and Add to Dictionary, #4936; previously minor: G2: Effect Gallery thumbnails and in-dialog preview; estimate 1–2 h → 0; Auto Spell Check: `edit.autoSpellCheck` underlines the unknown words, #4936; previously minor: PDF editable import preserves authored ActualText once, including empty/nested spans and authored ordering (#6165); cross-line/style layout remains approximate; scores and estimates unchanged; PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; G5: built-in UI Frames symbol library with default 9-slice; estimate 11–21 h; previously minor: G5: dynamic symbol part on-canvas takes opacity, blend, gradient and pattern; docs Overlap/auto-corners sync; estimate 12–22 h; previously minor: interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; previously minor: Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; previously minor: G5 docs: Art brush Overlap, Pattern brush auto corners and Global Edit Shift-click leave-out moved from Missing to Done (code already shipped); previously minor: custom dictionary and Ignore All: `text.spelling.addWord`, `removeWord`, `changeWord`, `dictionary` and `ignoreAll`, #4936; previously minor: EPS import: embedded fonts (composite, Type 42, CID-keyed, Type 1, CFF FontSet) draw their glyphs; spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; G9: EPS gradients drawn as abutting strips import as one gradient, without seams; Tools stays beside the canvas with left-side panels; new gradients follow document color mode; Selection QoL: marquee modes and visible feedback, #5313; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Indian-language hyphenation review and license allowlist, #4947; opening a slow file shows a progress window, part of #5930; Hindi catalog and contributor loanword preferences; agent wording pass complete; wider native proofreading welcome; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); scores and estimates unchanged; a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window; scores and estimates unchanged; large images in CMYK documents are shrunk before they are separated into ink planes (#4155); scores and estimates unchanged; `.ai` paths with a Calligraphic brush open brushed, with the 6D Art Pen's channels, and Windows reads the pen's tilt and barrel rotation, #372; scores and estimates unchanged; Compound Shapes add-on, beyond Illustrator; scores and estimates unchanged; Contour add-on, beyond Illustrator; scores and estimates unchanged; a slow first frame is waited for instead of restarting without the graphics adapter, and the menu bar no longer waits for the installed fonts' scan; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
## Feature submission evidence
@@ -34,6 +34,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
| Date | Change | Summary |
|---|---|---|
| 2026-11-03 | minor | A double-click on a ruler adds a guide at the pointer (#5239): a vertical one on the top ruler, a horizontal one on the left ruler; scores and estimates unchanged. |
| 2026-11-08 | minor | #3380, deletion only: preserve gradient placement and exact surviving geometry; consume owned subpaths with a shared original-node snapshot; consolidate status/evidence and document command/helper contracts. Compound behavior and the existing M2 status entry are retained. Endpoint highlighting remains open; scores and estimates unchanged. |
| 2026-11-07 | minor | #3380 (deletion only): independent surviving path objects; transformed appearance, stacking, selection, cubic handles and Undo/Redo covered by `delete_path_pieces`; compound semantics retained. Endpoint highlighting remains open; scores and estimates unchanged. |
| 2026-11-06 | minor | G9: EPS import reads gradients a page paints as abutting strips as one linear gradient each (no seams); an Illustrator EPS whose page drew them that way keeps its layers. Estimate unchanged. |
@@ -667,6 +668,7 @@ macOS cursor delivery (#2292) is narrowed to a native host adapter using the exi
- **Done:** Selection artwork drag across visible document tabs (400 ms hover); lossless target copy, source/clipboard preservation and cancellation regressions. Cross-window dragging remains separate.
- **Done (#5239):** a double-click on a ruler adds a guide at the pointer (vertical on the top ruler, horizontal on the left one, swapped in a view turned 90°; both clicks on that ruler) through `guide.add`, unsnapped and in one undo step; no scored gap or estimate reassessed.
- **Done:** custom document ruler origin (#1212): corner drag with preview, double-click reset to the active artboard, relative X/Y inputs and native save/reopen.
- **Done:** View › Rulers › Change to Global / Artboard Rulers (`view.globalRulers`, Cmd+Alt+R): artboard rulers, a new document's, count from the active artboard, each artboard with its own zero point; rulers, X/Y fields and guide readouts follow the mode, which is saved with the document. Estimate unchanged.
- **Done (#2849):** View › Rulers › Show Video Rulers (`view.videoRulers`): the rulers show and count the active artboard in video pixels from its top-left corner, a pixel as many points wide as the artboard's Video Ruler Pixel Aspect Ratio and one point tall ([public source](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/set-video-display-options-for-artboards.html)); Hide Video Rulers returns them to the General unit and the ruler origin. Shift-snapping a new guide to ruler ticks still uses the General unit's ticks. Estimate unchanged.
File diff suppressed because one or more lines are too long
+4 -1
View File
@@ -1,6 +1,6 @@
# UI and interaction parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-11 · **Change:** minor (G5: Inventory Icons built-in symbol library (four built-ins); previously minor: files that record no units open in Preferences › Units › General, #5820; previously minor: interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; numeric fields step on wheel hover without taking keyboard focus, #5949; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen; Selection QoL: marquee modes and visible feedback, #5313; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; shape tools: a press on the selection's centre point moves it and one in the rotate zone outside a box corner turns it, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-11 · **Change:** minor (ruler double-click adds a guide at the pointer, #5239; G5: Inventory Icons built-in symbol library (four built-ins); previously minor: files that record no units open in Preferences › Units › General, #5820; previously minor: interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; numeric fields step on wheel hover without taking keyboard focus, #5949; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen; Selection QoL: marquee modes and visible feedback, #5313; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; shape tools: a press on the selection's centre point moves it and one in the rotate zone outside a box corner turns it, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
>
> Vector Halftone preview update (2026-11-10): temporary dot selection marks are hidden, and the bounding box stays available (#6369).
>
@@ -51,6 +51,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
| Date | Change | Summary |
|---|---|---|
| 2026-11-03 | minor | A double-click on a ruler adds a guide at the pointer (#5239): a vertical one on the top ruler, a horizontal one on the left ruler; scores and estimates unchanged. |
| 2026-11-08 | minor | Style panels: a double-click on a user character style's row opens its Character Style Options (`ui.charStyleOptions`), and on a paragraph style's row its Paragraph Style Options (rename); the Character Style Options dialog keeps a fixed width and scrolls its sections in a shorter window, so OK and Cancel stay on screen. Tests `panels::text_styles::tests::double_clicking_a_character_style_row_opens_its_options` and `dialogs::char_style_options::tests::the_sections_scroll_in_a_window_shorter_than_the_general_list`. Scores and estimates unchanged. |
| 2026-11-08 | minor | #3380, deletion only: preserve gradient placement and exact surviving geometry; consume owned subpaths with a shared original-node snapshot; consolidate status/evidence and document command/helper contracts. Compound behavior and the existing M2 status entry are retained. Endpoint highlighting remains open; scores and estimates unchanged. |
| 2026-11-07 | minor | #3380 (deletion only): independent surviving path objects; transformed appearance, stacking, selection, cubic handles and Undo/Redo covered by `delete_path_pieces`; compound semantics retained. Endpoint highlighting remains open; scores and estimates unchanged. |
@@ -237,6 +238,7 @@ tools, never checked side by side) isn't captured row by row.
| Snapping panel/popover consolidating the snap options (30.0) | partial | Control bar › Snapping provides quick access through existing view commands and prefs.set, including Snap to Glyph and Alignment Guides (#6164); no View › Snapping submenu. Floating-panel parity remains unverified. |
| Customisable rotation snap angle (#973) | missing | Illustrator: Constrain Angle and construction angles only |
| Ruler guides: drag out, select, move, delete, lock, Make/Release Guides | done | #414, #451; marquee selects unlocked ruler guides |
| A double-click on a ruler adds a guide at the pointer: a vertical one on the top ruler, a horizontal one on the left ruler (swapped in a view turned 90°, where the rulers read the other document axis; the nearer axis in between), exactly where the pointer is (no snapping), shown even with guides hidden, one undo step (`guide.add`); both clicks of the pair must be on that ruler, and the first adds nothing. The [public page on rulers](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/use-rulers.html) doesn't describe this gesture, so it follows the request in #5239 | done | #5239; an artboard guide with the Artboard tool, as a drag from a ruler makes one; `canvas/tests.rs` `double_clicking_a_ruler_adds_a_guide_at_that_position`, `a_canvas_click_then_a_ruler_click_adds_no_guide`, `a_ruler_double_click_follows_a_rotated_view` |
| Escape cancels an existing ruler-guide press, move or copy | done | Selection and Direct/Group Selection; app-input regressions cover preselected guides, document/interaction-start selection, Undo/Redo and saved state, later on-canvas motion/release and temporary borrowing from idle Pen. All eight checks and the Direct Selection Escape control passed in current full CI. [Validation evidence](target-app-parity.md#evidence-what-ships-today-by-area) |
| Ruler origin: corner drag with preview, double-click reset to the active artboard, relative X/Y fields, native save/reopen | done | #1212; `document.setRulerOrigin`, `document.resetRulerOrigin`; geometry and command coordinates stay absolute |
| Ruler guides as Layers rows: shown, hidden, locked and deleted with their layer; rows dragged to another layer | done | `guide.setLayer`; guides from older files go on a layer |
@@ -507,6 +509,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development
| Date | Change | Summary |
|---|---|---|
| 2026-11-03 | minor | A double-click on a ruler adds a guide at the pointer (#5239): a vertical one on the top ruler, a horizontal one on the left ruler; scores and estimates unchanged. |
| 2026-11-10 | minor | Symbol libraries: Inventory Icons joins Basic Shapes, UI Frames and Map Markers as a fourth built-in library (Sword, Shield, Potion, Key, Gem; no default 9-slice). |
| 2026-11-11 | minor | Files that record no units (PDF, EPS or `.ai` without ruler units, raster images, metafiles, Affinity files, importer plug-ins) open and revert in Preferences › Units › General instead of points; native, SVG and DXF files and `.ai`/EPS ruler units are kept (#5820). Scores and estimates unchanged. |
| 2026-11-10 | minor | Held numeric spinner arrows keep stepping past the click timeout and stop on release (#6307). Scores and estimates unchanged. |