Issue #173: a failed ?file= startup fetch is reported in the app (#302)

When the browser app's `?file=` URL couldn't be fetched (an HTTP 404, a
network or CORS failure), the error went only to the developer console and
the user saw the ordinary welcome screen with no sign the PDF was tried.

The fetch now also queues `(name, error)` in a new `failed_inbox` next to
the existing `inbox`, and the app reports it on the next frame with the
notice it already uses for a delivered file that won't open: "Couldn't open
missing.pdf: HTTP 404". The console line stays for developers.

Test: a_failed_startup_url_is_reported_in_the_app (pickers.rs).


Claude-Session: https://claude.ai/code/session_01K3vVft4VXvvy8SpbbUceTW

Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
DWAA1660andClaude Opus 5.5 authored and GitHub committed 2026-10-08 12:49:25 -04:00
1 parent 5ec14155ff
commit 9fa837e452
3 files changed
+35 -4

No files matched your search

+10 -4
View File
@@ -30,19 +30,25 @@ fn main() {
// `?file=<url>` opens a PDF from a URL (same-origin or CORS-enabled).
if let Some(url) = query_param("file") {
let inbox = app.inbox.clone();
let failed = app.failed_inbox.clone();
let ctx = cc.egui_ctx.clone();
wasm_bindgen_futures::spawn_local(async move {
let name = url.rsplit('/').next().unwrap_or("document.pdf").split('?').next().unwrap_or("document.pdf").to_string();
match fetch_bytes(&url).await {
Ok(bytes) => {
let name =
url.rsplit('/').next().unwrap_or("document.pdf").split('?').next().unwrap_or("document.pdf").to_string();
if let Ok(mut q) = inbox.lock() {
q.push((name, bytes));
}
ctx.request_repaint();
}
Err(e) => eframe::web_sys::console::error_1(&format!("PdfCraft: could not fetch {url}: {e}").into()),
Err(e) => {
eframe::web_sys::console::error_1(&format!("PdfCraft: could not fetch {url}: {e}").into());
// Shown in the app too, not only in the console (#173).
if let Ok(mut q) = failed.lock() {
q.push((name, e));
}
}
}
ctx.request_repaint();
});
}
Ok(Box::new(app))
+11
View File
@@ -286,6 +286,10 @@ pub struct PageClip {
/// Files delivered asynchronously: (name, bytes).
pub type Inbox = std::sync::Arc<std::sync::Mutex<Vec<(String, Vec<u8>)>>>;
/// Files that failed to arrive asynchronously (a browser `?file=` URL that couldn't be fetched):
/// `(name, error)`, reported to the user on the next frame (#173).
pub type FailedInbox = std::sync::Arc<std::sync::Mutex<Vec<(String, String)>>>;
/// A request the operating system sends the running app, outside its window: on macOS, Finder
/// double-clicks, Open With and drops on the Dock icon arrive as Apple events, not arguments.
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -357,6 +361,8 @@ pub struct PdfCraftApp {
pub full_screen: bool,
/// Files delivered asynchronously (web drag-and-drop, web file picker).
pub inbox: Inbox,
/// Asynchronous opens that failed (web `?file=` fetches), shown as a notice.
pub failed_inbox: FailedInbox,
/// Requests from the operating system, polled every frame (macOS Apple events).
pub os_events: Option<OsEventsFn>,
/// A pending "save changes?" question (closing a dirty tab or quitting).
@@ -580,6 +586,7 @@ impl PdfCraftApp {
password_prompt: None,
full_screen: false,
inbox: Default::default(),
failed_inbox: Default::default(),
os_events: None,
close_request: None,
save_override: None,
@@ -1450,6 +1457,10 @@ impl eframe::App for PdfCraftApp {
self.notify_fmt("Couldn't open {name}: {e}", &[("name", &name), ("e", &e.to_string())]);
}
}
let failed: Vec<(String, String)> = self.failed_inbox.lock().map(|mut q| std::mem::take(&mut *q)).unwrap_or_default();
for (name, e) in failed {
self.notify_fmt("Couldn't open {name}: {e}", &[("name", &name), ("e", &e)]);
}
let os_events = self.os_events.as_mut().map(|poll| poll()).unwrap_or_default();
for e in os_events {
match e {
+14
View File
@@ -409,3 +409,17 @@ fn replace_pages_applies_only_to_the_document_its_dialog_was_opened_on() {
}
}
}
/// A file that fails to arrive asynchronously (the browser's `?file=` URL answering HTTP 404) is
/// reported in the app on the next frame, not only in the console (#173).
#[test]
fn a_failed_startup_url_is_reported_in_the_app() {
let mut h = harness(&[]);
let failed = h.state().failed_inbox.clone();
failed.lock().unwrap().push(("missing.pdf".into(), "HTTP 404".into()));
h.run_steps(2);
let toast = h.state().toast.as_ref().map(|(m, _)| m.clone()).unwrap_or_default();
assert!(toast.contains("missing.pdf") && toast.contains("HTTP 404"), "{toast:?}");
assert!(h.state().views.is_empty(), "no document was opened");
assert!(failed.lock().unwrap().is_empty(), "each failure is reported once");
}