diff --git a/crates/ui-egui/src/chrome/control_bar.rs b/crates/ui-egui/src/chrome/control_bar.rs index 2f4f8c7b9..f2c41b4df 100644 --- a/crates/ui-egui/src/chrome/control_bar.rs +++ b/crates/ui-egui/src/chrome/control_bar.rs @@ -190,6 +190,7 @@ pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) { let image = crate::place::control_bar_details(app, ui) || crate::panels::image_trace::control_bar(app, ui); crate::toolbar::control_bar_options(app, ui); crate::dialogs::envelope::control_bar(app, ui); + crate::panels::symbols::control_bar(app, ui); let opacity = if first.is_some() { crate::panels::current_transparency(app).map_or(1.0, |t| t.0) } else { 1.0 }; if !image { let shown_stroke = crate::panels::current_stroke(app); diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index acb56f53e..60522f664 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2583,6 +2583,7 @@ font-technology Variable Променливи Repeat Tab Повтаряне на табулатора Repeat {name} Повтаряне на {name} Replace Замяна + Replace: Замяна: Replace Font Замяна на шрифт Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Заменете го с друг файл или го игнорирайте: неговите изображения ще продължат да показват записания с документа преглед. Replace Spine Замяна на оста diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 08b907471..2148f134e 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -3285,6 +3285,7 @@ Repeat {name} Repeteix {name} Repeating Repetició Replace Reemplaça + Replace: Reemplaça: Replace Font Reemplaça el tipus de lletra Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Reemplaça’l per un altre fitxer o ignora’l: les seves imatges continuaran mostrant la previsualització desada amb el document. Replace Spine Reemplaça l’espina diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 91bdee122..97082fd79 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -3800,6 +3800,7 @@ axis Both Obě Set Text Nastavit text Insert Character Vložit znak Replace Nahradit + Replace: Nahradit: Type on a Path Options Možnosti textu na cestě Highlight Variable Zvýraznit proměnnou New Variable Nová proměnná diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 5c01df3c6..3ab958c4b 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -3537,6 +3537,7 @@ axis Both Begge Set Text Angiv tekst Insert Character Indsæt tegn Replace Erstat + Replace: Erstat: Type on a Path Options Indstillinger for tekst på kurve Highlight Variable Fremhæv variabel New Variable Ny variabel diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index b58127892..c3cd9d637 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2603,6 +2603,7 @@ mark-style Japanese Japanisch Repeat Tab Tabulator wiederholen Repeat {name} {name} wiederholen Replace Ersetzen + Replace: Ersetzen: Replace Font Schrift ersetzen Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Ersetzen Sie sie durch eine andere Datei oder ignorieren Sie sie: Ihre Bilder zeigen weiterhin die mit dem Dokument gespeicherte Vorschau. Replace Spine Achse ersetzen diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 0b3fbeb45..b40a460de 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1798,6 +1798,7 @@ font-technology Variable Μεταβλητές Rename Μετονομασία Repeat Επανάληψη Replace Αντικατάσταση + Replace: Αντικατάσταση: Replace… Αντικατάσταση… Reselect Επαναφορά προηγούμενης επιλογής Reset Επαναφορά diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index b47bae620..72b330091 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2641,6 +2641,7 @@ font-technology Variable Variables Repeat Tab Repetir tabulación Repeat {name} Repetir {name} Replace Reemplazar + Replace: Reemplazar: Replace Font Reemplazar fuente Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Reemplázalo con otro archivo o ignóralo: sus imágenes seguirán mostrando la vista previa guardada con el documento. Replace Spine Reemplazar eje diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 5c04f9d8d..62b0820c7 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -3720,6 +3720,7 @@ axis Both Molemmat Set Text Aseta teksti Insert Character Lisää merkki Replace Korvaa + Replace: Korvaa: Type on a Path Options Polulla olevan tekstin asetukset Highlight Variable Korosta muuttuja New Variable Uusi muuttuja diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 09381076c..0c9ddfc3d 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2638,6 +2638,7 @@ font-technology Variable Variables Repeat Tab Répéter la tabulation Repeat {name} Répéter {name} Replace Remplacer + Replace: Remplacer : Replace Font Remplacer la police Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Remplacez-le par un autre fichier ou ignorez-le : ses images continueront d’afficher l’aperçu enregistré avec le document. Replace Spine Remplacer la dorsale diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 97da7b1e7..4b6adc0ea 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -3169,6 +3169,7 @@ mark-style Japanese Japán Repeat Tab Tabulátor ismétlése Repeat {name} Ismétlés: {name} Replace Csere + Replace: Csere: Replace Font Betűtípus cseréje Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Cseréld le egy másik fájlra, vagy hagyd ki: a képei a dokumentummal mentett előnézetet mutatják tovább. Replace Spine Gerinc cseréje diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index e58b45121..752637084 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -3537,6 +3537,7 @@ axis Both Keduanya Set Text Atur teks Insert Character Sisipkan karakter Replace Ganti + Replace: Ganti: Type on a Path Options Opsi teks pada jalur Highlight Variable Sorot variabel New Variable Variabel baru diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 7ad8c5e39..4e27d545a 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2672,6 +2672,7 @@ mark-style Japanese Giapponese Repeat Tab Ripeti tabulazione Repeat {name} Ripeti {name} Replace Sostituisci + Replace: Sostituisci: Replace Font Sostituisci font Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Sostituiscilo con un altro file oppure ignoralo: le sue immagini continueranno a mostrare l'anteprima salvata con il documento. Replace Spine Sostituisci dorsale diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 2cf6eafe8..bc8c797ff 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2652,6 +2652,7 @@ mark-style Japanese 日本式 Repeat Tab タブを繰り返し Repeat {name} {name}を繰り返す Replace 置換 + Replace: 置換: Replace Font フォントを置換 Replace Symbol シンボルを置き換え Replace With Font 置換するフォント diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index d2d6669b3..20db589b7 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -3322,6 +3322,7 @@ axis Both ორივე Set Text ტექსტის დაყენება Insert Character სიმბოლოს ჩასმა Replace ჩანაცვლება + Replace: ჩანაცვლება: Type on a Path Options ტრაექტორიაზე ტექსტის პარამეტრები Highlight Variable ცვლადის გამოკვეთა New Variable ახალი ცვლადი diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 3ba2c74ea..266bbc0e7 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2639,6 +2639,7 @@ mark-style Japanese 일본식 Repeat Tab 탭 반복 Repeat {name} {name} 반복 Replace 바꾸기 + Replace: 바꾸기: Replace Font 글꼴 바꾸기 Replace Symbol 심볼 바꾸기 Replace With Font 바꿀 글꼴 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 318126cbf..7080aa48a 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -3551,6 +3551,7 @@ axis Both Beide Set Text Tekst instellen Insert Character Teken invoegen Replace Vervangen + Replace: Vervangen: Type on a Path Options Opties voor tekst op pad Highlight Variable Variabele markeren New Variable Nieuwe variabele diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 8cf23270b..56fb73b65 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -3642,6 +3642,7 @@ axis Both Obie Set Text Ustaw tekst Insert Character Wstaw znak Replace Zastąp + Replace: Zastąp: Type on a Path Options Opcje tekstu na ścieżce Highlight Variable Wyróżnij zmienną New Variable Nowa zmienna diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index c698419f1..91217099c 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2179,6 +2179,7 @@ Repeat Tab Repetir Parada de Tabulação Repeat {name} Repetir {name} Replace Substituir + Replace: Substituir: Replace Font Substituir Fonte Replace Spine Substituir Coluna Replace Symbol Substituir Símbolo diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 7db0f6a50..48d428b20 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2668,6 +2668,7 @@ mark-style Japanese Японский Repeat Tab Повторить табуляцию Repeat {name} Повторить {name} Replace Заменить + Replace: Заменить: Replace Font Заменить шрифт Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Замените его другим файлом или игнорируйте: его изображения продолжат показывать предварительный просмотр, сохранённый с документом. Replace Spine Заменить остов diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index d5e0c7c23..27bf1d75a 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -2583,6 +2583,7 @@ font-technology Variable Variabla Repeat Tab Upprepa tabb Repeat {name} Upprepa {name} Replace Ersätt + Replace: Ersätt: Replace Font Ersätt typsnitt Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Ersätt den med en annan fil eller ignorera den: dess bilder fortsätter att visa förhandsgranskningen som sparats med dokumentet. Replace Spine Ersätt ryggrad diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index d3566fbd8..7c7cc5ec8 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -2460,6 +2460,7 @@ shape Open Açık Repeat Tab Sekmeyi Tekrarla Repeat {name} {name}'yi tekrarla Replace Değiştir + Replace: Değiştir: Replace Font Yazı Tipini Değiştir Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Onu başka bir dosyayla değiştirin veya yok sayın: resimleri, belgeyle birlikte kaydedilen önizlemeyi göstermeye devam eder. Replace Spine Omurgayı Değiştir diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 1aec37d5f..217fad075 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2664,6 +2664,7 @@ mark-style Japanese Японський Repeat Tab Повторити табуляцію Repeat {name} Повторити {name} Replace Замінити + Replace: Замінити: Replace Font Замінити шрифт Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Замініть його іншим файлом або ігноруйте: зображення продовжать показувати попередній перегляд, збережений з документом. Replace Spine Замінити вісь diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 205fac49c..f30176f57 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2935,6 +2935,7 @@ mark-style Japanese Kiểu Nhật Repeat Tab Tab lặp lại Repeat {name} Lặp lại {name} Replace Thay thế + Replace: Thay thế: Replace Font Thay thế phông chữ Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Thay thế bằng tệp khác hoặc bỏ qua: ảnh của nó vẫn hiển thị bản xem trước đã lưu cùng tài liệu. Replace Spine Thay thế trục diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 49ee6d305..4b241917d 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2541,6 +2541,7 @@ font-script Japanese 汉字 Repeat Tab 重复制表位 Repeat {name} 重复 {name} Replace 替换 + Replace: 替换: Replace Font 替换字体 Replace it with another file, or ignore it: its images keep showing the preview saved with the document. 将其替换为其他文件,或忽略:其图像继续显示随文档保存的预览。 Replace Spine 替换脊线 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index c957c07ee..98026e0fb 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -3478,6 +3478,7 @@ font-script Japanese 漢字 Repeat Tab 重複定位點 Repeat {name} 重複 {name} Replace 取代 + Replace: 取代: Replace Font 取代字體 Replace it with another file, or ignore it: its images keep showing the preview saved with the document. 以其他檔案取代,或忽略:其影像會繼續顯示隨文件儲存的預覽。 Replace Spine 取代脊線 diff --git a/crates/ui-egui/src/panels/symbols.rs b/crates/ui-egui/src/panels/symbols.rs index 0bd815c0d..e99a24bf6 100644 --- a/crates/ui-egui/src/panels/symbols.rs +++ b/crates/ui-egui/src/panels/symbols.rs @@ -149,6 +149,41 @@ fn has_instances_selected(app: &VectorcraftApp) -> bool { }) } +/// The symbols of the selected objects when every one is a symbol instance; `None` otherwise. +fn selected_instance_symbols(app: &VectorcraftApp) -> Option> { + let st = app.session.active()?; + let symbols: Option> = st + .selection + .objects + .iter() + .map(|id| match st.doc.node(*id).map(|n| &n.kind) { + Some(NodeKind::SymbolInstance { symbol, .. }) => Some(symbol.clone()), + _ => None, + }) + .collect(); + symbols.filter(|s| !s.is_empty()) +} + +/// The Control bar's Replace menu while symbol instances alone are selected: the document's +/// symbols, showing the one the instances share (blank when they differ); picking another +/// replaces them all, as "Select a new symbol from the Replace dropdown in the Control panel" +/// (). +pub(crate) fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) { + let Some(symbols) = selected_instance_symbols(app) else { return }; + let Some(st) = app.session.active() else { return }; + let names: Vec = st.doc.symbols.iter().map(|s| s.name.clone()).collect(); + let shared = symbols.first().filter(|first| symbols.iter().all(|s| s == *first)).cloned().unwrap_or_default(); + ui.label(egui::RichText::new(tl!("Replace:")).size(12.0).color(Tokens::get(ui.ctx()).text)); + let options: Vec<&str> = names.iter().map(String::as_str).collect(); + if let Some(name) = widgets::dropdown_names(ui, "cb-symbol-replace", &shared, &options, 140.0).and_then(|i| names.get(i)) + && *name != shared + && let Err(e) = app.run("symbol.replace", json!({ "name": name })) + { + app.status(e); + } + ui.add_space(4.0); +} + pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { let t = Tokens::get(ui.ctx()); let Some((doc, keys)) = app.session.active().map(|d| (d.doc.clone(), document_symbols(d))) else { return }; @@ -702,4 +737,65 @@ mod tests { }; assert!(orange(3, 3) && orange(36, 36), "{:?} {:?}", thumb.pixels[3 * 40 + 3], thumb.pixels[36 * 40 + 36]); } + + /// #4121: with symbol instances selected, the Control bar's Replace menu lists the document's + /// symbols and shows the instance's own; picking another replaces it. + #[test] + fn the_control_bar_replace_menu_swaps_the_selected_instances_symbol() { + fn bar(app: &mut VectorcraftApp, ctx: &egui::Context, events: Vec) -> Vec { + let raw = egui::RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(800.0, 400.0))), events, ..Default::default() }; + let mut out = ctx.run_ui(raw, |ui| { + ui.horizontal(|ui| control_bar(app, ui)); + }); + out.textures_delta.clear(); + out.shapes.into_iter().map(|c| c.shape).collect() + } + fn text_at(shapes: &[egui::Shape], s: &str) -> Option { + shapes.iter().find_map(|sh| match sh { + egui::Shape::Text(t) if t.galley.text().trim() == s => Some(t.visual_bounding_rect().center()), + egui::Shape::Vec(v) => text_at(v, s), + _ => None, + }) + } + fn click(app: &mut VectorcraftApp, ctx: &egui::Context, at: Pos2) { + bar(app, ctx, vec![egui::Event::PointerMoved(at), button(at, true)]); + bar(app, ctx, vec![button(at, false)]); + } + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 300, "height": 200})).unwrap(); + for (name, x) in [("Leaf", 10), ("Star", 150)] { + app.run("shape.rectangle", json!({"x": x, "y": 10, "width": 30, "height": 30})).unwrap(); + app.run("symbol.new", json!({"name": name})).unwrap(); + } + let instance_of = |app: &VectorcraftApp, name: &str| { + let mut found = None; + for l in &app.session.active().unwrap().doc.layers { + l.walk(&mut |n| { + if let NodeKind::SymbolInstance { symbol, .. } = &n.kind + && symbol == name + { + found = Some(n.id); + } + }); + } + found + }; + let leaf = instance_of(&app, "Leaf").expect("a Leaf instance"); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + // Art, not an instance: no Replace menu. + let rect = app.run("shape.rectangle", json!({"x": 10, "y": 100, "width": 30, "height": 30})).unwrap()["id"].as_u64().unwrap(); + app.run("select.set", json!({"ids": [rect]})).unwrap(); + assert!(text_at(&bar(&mut app, &ctx, vec![]), "Replace:").is_none()); + app.run("select.set", json!({"ids": [leaf.0]})).unwrap(); + bar(&mut app, &ctx, vec![]); + let shapes = bar(&mut app, &ctx, vec![]); + assert!(text_at(&shapes, "Replace:").is_some()); + let shown = text_at(&shapes, "Leaf").expect("the menu shows the instance's symbol"); + click(&mut app, &ctx, shown); + let star = text_at(&bar(&mut app, &ctx, vec![]), "Star").expect("the menu lists the other symbol"); + click(&mut app, &ctx, star); + let doc = &app.session.active().unwrap().doc; + assert!(matches!(&doc.node(leaf).unwrap().kind, NodeKind::SymbolInstance { symbol, .. } if symbol == "Star"), "the instance is replaced"); + } } diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 0dafd6bab..f9d9df3a7 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI and interaction parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Color Mixer popover: the Color panel's menu with its color modes (#4249); scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Control bar: a Replace menu swaps the selected symbol instances' symbol (#4121); scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence @@ -361,6 +361,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development | Date | Change | Summary | |---|---|---| +| 2026-10-30 | minor | Control bar: a Replace menu swaps the selected symbol instances' symbol (#4121); scores and estimates unchanged | | 2026-10-30 | minor | Color Mixer popover: the Color panel's menu with its color modes (#4249); scores and estimates unchanged | | 2026-10-30 | minor | Object › Path › Average & Join… (`path.averageJoin`, Cmd+Alt+Shift+J) moves two open path ends to their midpoint and joins them there, the joined anchor a corner or a smooth point as its dialog's Points choice asks, in one undo step; one row done; scores and estimates unchanged. | | 2026-10-30 | minor | G12: Preferences audit (#394): 5 options are stored but not acted on (Open Documents As Tabs, Units › East Asian Type, Hyphenation › Default Language, Scratch Disks, Legacy vector clipboard), each waiting on a subsystem; Highlight Alternate Glyphs, Real-time Drawing and the Devices options act; scores and estimates unchanged |