Part of #861: a file the user can't open is reported in a dialog

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 <[email protected]>
This commit is contained in:
bflatasticandClaude Opus 5.5 committed 2026-10-10 10:27:59 +07:00
1 parent 9a0183a02d
commit c19873e220
17 files changed
+68 -12

No files matched your search

+2 -3
View File
@@ -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<String>) {
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)));
}
}
}
+3 -4
View File
@@ -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);
}
});
}
+11
View File
@@ -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<Value, String> {
let params = d.fields.get("__params").cloned().unwrap_or_else(|| json!({}));
+1
View File
@@ -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,
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -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 Сведения о документе
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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 Відомості про документ
Can't render this file because it contains an unexpected character in line 632 and column 40.
+1
View File
@@ -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 文档信息
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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 文件資訊
Can't render this file because it contains an unexpected character in line 6 and column 47.
+36 -1
View File
@@ -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<Value, String> {
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<Vec<u8>, 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<u8> {
let v = app.session.execute(cmd, &p).unwrap();
vectorcraft_format::base64_decode(v["dataBase64"].as_str().unwrap()).unwrap()
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -884,7 +884,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
"file.revert" if p.get("confirmed").and_then(Value::as_bool) != Some(true) => 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" => {
+4 -2
View File
@@ -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(()) => {}
}
}
}