From c19873e2204d55e620abc7bc94a72b1130ca1651 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sat, 10 Oct 2026 07:22:38 +0700 Subject: [PATCH] Part of #861: a file the user can't open is reported in a dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Opening a file that fails (File › Open, Open Recent, the Home screen, a drop, the Finder or the command line) only set the status bar's message, which is easily missed: it looked as if nothing happened. A dialog now says "Can't open “name”." with the reason, as well. An agent's file.open {path} still just answers with the error. Co-Authored-By: Claude Opus 5.5 --- apps/vectorcraft/src/main.rs | 5 ++-- crates/ui-egui/src/canvas.rs | 7 +++-- crates/ui-egui/src/dialogs/confirm.rs | 11 ++++++++ crates/ui-egui/src/dialogs/mod.rs | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + crates/ui-egui/src/io.rs | 37 ++++++++++++++++++++++++++- crates/ui-egui/src/lib.rs | 2 +- crates/ui-egui/src/menus.rs | 2 +- crates/ui-egui/src/place.rs | 6 +++-- 17 files changed, 68 insertions(+), 12 deletions(-) diff --git a/apps/vectorcraft/src/main.rs b/apps/vectorcraft/src/main.rs index f446c543..f7b6771d 100644 --- a/apps/vectorcraft/src/main.rs +++ b/apps/vectorcraft/src/main.rs @@ -118,12 +118,11 @@ fn end_before_teardown(frames: u64) { } /// Open files handed to the app (command line, macOS Finder and Dock) as documents. A file that -/// can't be opened is reported in the status bar and on stderr; the others still open. +/// can't be opened is reported to the user and on stderr; the others still open. fn open_files(app: &mut VectorcraftApp, files: Vec) { for f in files { - if let Err(e) = vectorcraft_ui_egui::io::open_path(app, &f) { + if let Err(e) = vectorcraft_ui_egui::io::open_reporting(app, &f) { eprintln!("vectorcraft: {f}: {e}"); - app.status(format!("Couldn't open {}: {e}", fileio::file_name(&f))); } } } diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 04392757..f0dccfa3 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -1850,10 +1850,9 @@ fn recent(app: &mut VectorcraftApp, ui: &mut Ui) { crate::icons::icon(ui, "history", 16.0, t.icon); let r = ui.add(egui::Button::new(egui::RichText::new(&name).color(t.accent).size(13.0)).frame(false)).on_hover_text(&path); ui.add(egui::Label::new(egui::RichText::new(&folder).size(12.0).color(t.text_dim)).truncate()); - if r.clicked() - && let Err(e) = crate::io::open_path(app, &path) - { - app.status(e); + if r.clicked() { + // A failure is reported to the user there. + let _ = crate::io::open_reporting(app, &path); } }); } diff --git a/crates/ui-egui/src/dialogs/confirm.rs b/crates/ui-egui/src/dialogs/confirm.rs index 4e02feb0..ba166874 100644 --- a/crates/ui-egui/src/dialogs/confirm.rs +++ b/crates/ui-egui/src/dialogs/confirm.rs @@ -36,6 +36,17 @@ pub fn ask(app: &mut VectorcraftApp, message: &str, detail: &str, command: &str, app.ui.dialog = Some(Dialog::new(KIND, fields)); } +/// The dialog kind of a message with a Close button only ([`tell`]). +pub const MESSAGE: &str = "message"; + +pub(super) const MESSAGE_SPEC: DialogSpec = DialogSpec { ok: None, ..SPEC }; + +/// Tell the user `message` (with an optional `detail` line), both in the UI language, in a dialog +/// they close. +pub fn tell(app: &mut VectorcraftApp, message: &str, detail: &str) { + app.ui.dialog = Some(Dialog::new(MESSAGE, json!({"message": message, "detail": detail}))); +} + /// Closes before running, so a dialog the command opens stays open. fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { let params = d.fields.get("__params").cloned().unwrap_or_else(|| json!({})); diff --git a/crates/ui-egui/src/dialogs/mod.rs b/crates/ui-egui/src/dialogs/mod.rs index c1ecc219..c0cf2d66 100644 --- a/crates/ui-egui/src/dialogs/mod.rs +++ b/crates/ui-egui/src/dialogs/mod.rs @@ -216,6 +216,7 @@ registry! { FindFont: ["findFont"] => DialogSpec::window(crate::find_font::show, |app, _| crate::find_font::confirm(app)), SwatchOptions: [swatch_options::KIND] => swatch_options::SPEC, Confirm: [confirm::KIND] => confirm::SPEC, + Message: [confirm::MESSAGE] => confirm::MESSAGE_SPEC, NewSwatch: [new_swatch::KIND] => new_swatch::SPEC, NewColorGroup: [new_color_group::KIND] => new_color_group::SPEC, GradientStop: ["gradientStop"] => gradient_stop::SPEC, diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 5e550b49..116a0d88 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -686,6 +686,7 @@ Divide Vertically Into Dividir verticalmente en Do Not Move No mover Do you want to save the changes you made to “{name}”? ¿Quieres guardar los cambios hechos en “{name}”? + Can't open “{name}”. No se puede abrir «{name}». Document Documento Document Color Mode Modo de color del documento Document Info Información del documento diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 58d52e35..138fc7e9 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -688,6 +688,7 @@ Divide Vertically Into Diviser verticalement en Do Not Move Ne pas déplacer Do you want to save the changes you made to “{name}”? Voulez-vous enregistrer les modifications apportées à « {name} » ? + Can't open “{name}”. Impossible d’ouvrir « {name} ». Document Document Document Color Mode Mode colorimétrique du document Document Info Informations sur le document diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 7ee24bd8..c6f75cd2 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -686,6 +686,7 @@ Divide Vertically Into Dividi verticalmente in Do Not Move Non spostare Do you want to save the changes you made to “{name}”? Vuoi salvare le modifiche apportate a “{name}”? + Can't open “{name}”. Impossibile aprire «{name}». Document Documento Document Color Mode Metodo colore documento Document Info Informazioni documento diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 1dc6a340..17954c92 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -1122,6 +1122,7 @@ Divide Slices スライスを分割 Divide Vertically Into 垂直方向に分割 Do you want to save the changes you made to “{name}”? 「{name}」への変更を保存しますか? + Can't open “{name}”. 「{name}」を開けません。 Document ドキュメント Document Ink Options ドキュメントのインキオプション Document JSON ドキュメント JSON diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 64c7b2af..5563671b 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -636,6 +636,7 @@ Divide Slices… Dividir Fatias… Divide Vertically Into Dividir Verticalmente Em Do you want to save the changes you made to “{name}”? Deseja salvar as alterações feitas em “{name}”? + Can't open “{name}”. Não é possível abrir “{name}”. Document Documento Document Color Mode Modo de Cor do Documento Document Info Informações do Documento diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 68dd631b..57c7a2f7 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -684,6 +684,7 @@ Divide Vertically Into Разделить по вертикали на Do Not Move Не перемещать Do you want to save the changes you made to “{name}”? Сохранить изменения, внесённые в «{name}»? + Can't open “{name}”. Не удаётся открыть «{name}». Document Документ Document Color Mode Цветовой режим документа Document Info Сведения о документе diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index de22dc22..bd73f4e2 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -680,6 +680,7 @@ Divide Vertically Into Розділити по вертикалі на Do Not Move Не переміщати Do you want to save the changes you made to “{name}”? Зберегти зміни, внесені до «{name}»? + Can't open “{name}”. Не вдається відкрити «{name}». Document Документ Document Color Mode Колірний режим документа Document Info Відомості про документ diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index b1021820..69cf9961 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -666,6 +666,7 @@ Divide Vertically Into 垂直分割为 Do Not Move 不移动 Do you want to save the changes you made to “{name}”? 要存储对“{name}”所做的更改吗? + Can't open “{name}”. 无法打开“{name}”。 Document 文档 Document Color Mode 文档颜色模式 Document Info 文档信息 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 177b1869..df3715ac 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -684,6 +684,7 @@ Divide Vertically Into 垂直分割為 Do Not Move 不移動 Do you want to save the changes you made to “{name}”? 要儲存對「{name}」所做的變更嗎? + Can't open “{name}”. 無法開啟「{name}」。 Document 文件 Document Color Mode 文件色彩模式 Document Info 文件資訊 diff --git a/crates/ui-egui/src/io.rs b/crates/ui-egui/src/io.rs index 7716e52f..5e94601f 100644 --- a/crates/ui-egui/src/io.rs +++ b/crates/ui-egui/src/io.rs @@ -97,7 +97,24 @@ pub fn open_dialog(app: &mut VectorcraftApp) -> Result<(), String> { return Ok(()); } let path = pick_open(app, &FilePick { filters: fileio::open_filters().collect(), ..Default::default() })?; - open_path(app, &path).map(|_| ()) + open_reporting(app, &path).map(|_| ()) +} + +/// A file the user asked to open (File › Open, Open Recent, the Home screen, a drop, the Finder) +/// couldn't be: say so in a dialog as well as the status bar, which is easily missed (#861). +pub fn report_open_error(app: &mut VectorcraftApp, name: &str, e: &str) { + app.status(format!("Couldn't open {name}: {e}")); + let message = crate::i18n::fmt(tl!("Can't open “{name}”."), &[("name", name)]); + dialogs::confirm::tell(app, &message, &crate::i18n::message(crate::i18n::current(), e)); +} + +/// [`open_path`] for a file the user asked for, a failure reported ([`report_open_error`]). +pub fn open_reporting(app: &mut VectorcraftApp, path: &str) -> Result { + let r = open_path(app, path); + if let Err(e) = &r { + report_open_error(app, &fileio::file_name(path), e); + } + r } fn read(app: &VectorcraftApp, path: &str) -> Result, String> { @@ -699,6 +716,24 @@ mod tests { (VectorcraftApp::new(Session::new(), services), written) } + /// #861: a file the user opens that can't be opened is reported in a dialog they close, not + /// only in the status bar; a file an agent opens by path only answers with the error. + #[test] + fn a_file_the_user_cant_open_is_reported_in_a_dialog() { + let (mut app, _) = app(); + app.services.read = Some(Box::new(|_: &str| Err("no such file".into()))); + assert!(open_reporting(&mut app, "/docs/gone.ai").is_err()); + let d = app.ui.dialog.take().expect("a dialog says so"); + assert_eq!( + (d.kind.as_str(), d.str("message"), d.str("detail")), + (dialogs::confirm::MESSAGE, "Can't open “gone.ai”.".to_string(), "no such file".to_string()) + ); + assert!(app.ui.status.contains("gone.ai")); + // An agent's `file.open {path}` gets the error back and no dialog. + assert!(app.run("file.open", json!({"path": "/docs/gone.ai"})).is_err()); + assert!(app.ui.dialog.is_none()); + } + fn bytes_of(app: &mut VectorcraftApp, cmd: &str, p: Value) -> Vec { let v = app.session.execute(cmd, &p).unwrap(); vectorcraft_format::base64_decode(v["dataBase64"].as_str().unwrap()).unwrap() diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 6c3ccdff..b5a71ba7 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -706,7 +706,7 @@ impl VectorcraftApp { self.services.inbox.as_ref().map(|q| std::mem::take(&mut *q.lock().unwrap_or_else(|e| e.into_inner()))).unwrap_or_default(); for (name, bytes) in arrived { if let Err(e) = io::open_bytes(self, &name, &bytes, None) { - self.status(format!("Couldn't open {name}: {e}")); + io::report_open_error(self, &name, &e); } } place::drain(self); diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index e53eedf6..a725fa21 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -884,7 +884,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option io::ask_revert(app), "file.reveal" => io::reveal(app), id if id.starts_with("file.openRecent") => match recent_slot(app, id).cloned() { - Some(path) => io::open_path(app, &path), + Some(path) => io::open_reporting(app, &path), None => Err("no such recent file".into()), }, "type.findFont" => { diff --git a/crates/ui-egui/src/place.rs b/crates/ui-egui/src/place.rs index 13e7d9f4..468c2dce 100644 --- a/crates/ui-egui/src/place.rs +++ b/crates/ui-egui/src/place.rs @@ -357,8 +357,10 @@ pub fn drop_files(app: &mut VectorcraftApp, mut files: Vec<(DropTarget, DropFile }), }, }; - if let Err(e) = r { - app.status(if target == DropTarget::Open { format!("Couldn't open {name}: {e}") } else { format!("Couldn't place {name}: {e}") }); + match r { + Err(e) if target == DropTarget::Open => io::report_open_error(app, &name, &e), + Err(e) => app.status(format!("Couldn't place {name}: {e}")), + Ok(()) => {} } } }