Web: offer Reload to update when a new deployment is out (#231)

Every half hour the page fetches its own address past the browser's cache; when it loads
another content-hashed build of the app than the running one, the status bar offers "Reload
to update" (the page's unsaved-changes question still guards the reload). Rust only, no
service worker: the browser build has no other update path and none of the desktop's
download code.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-15 01:48:30 -04:00
1 parent 7dd50a3ca4
commit 93b2ae343c
6 files changed
+145 -3

No files matched your search

+3
View File
@@ -391,6 +391,8 @@ pub struct ModelApp {
consent_asked: bool,
/// Checking for and installing updates, when this run may ([`updates`]).
updates: Option<updates::State>,
/// The web app: a new deployment is out, and this reloads the page into it.
reload: Option<Box<dyn Fn()>>,
/// The setup progress the browser marks, and what it was worked out for.
setup: (Option<(u64, u64, bool, bool)>, std::sync::Arc<Vec<fieldcraft_engine::progress::StageState>>),
/// The file opening in the background, and files waiting to open after it.
@@ -481,6 +483,7 @@ impl ModelApp {
usage: None,
consent_asked: false,
updates: None,
reload: None,
setup: (None, std::sync::Arc::default()),
loading: None,
queued: std::collections::VecDeque::new(),
+21 -1
View File
@@ -276,6 +276,16 @@ impl ModelApp {
}
}
/// The web app: a new deployment is out; the status bar offers to reload into it.
pub fn offer_reload(&mut self, reload: Box<dyn Fn()>) {
self.reload = Some(reload);
}
/// Whether the status bar offers to reload into a new deployment.
pub fn reload_offered(&self) -> bool {
self.reload.is_some()
}
/// For the control channel's `ui.inspect`.
pub fn update_json(&self) -> serde_json::Value {
let Some(u) = &self.updates else { return serde_json::Value::Null };
@@ -291,9 +301,19 @@ impl ModelApp {
}
}
/// The status bar's notice: an update offered, or one downloading.
/// The status bar's notice: an update offered, or one downloading; in the browser, a new
/// deployment to reload into.
pub fn notice(app: &mut ModelApp, ui: &mut egui::Ui) {
let t = crate::theme::Tokens::get(app.ui.dark);
if let Some(reload) = &app.reload {
let tip = if app.has_unsaved_work() { "Reloads the page; you are asked about the unsaved changes first" } else { "Reloads the page" };
if ui.add(egui::Link::new(RichText::new("Reload to update").size(11.5))).on_hover_text(tip).clicked() {
reload();
}
ui.label(RichText::new("A new version of FieldCraft is available —").size(11.5).color(t.text));
ui.separator();
return;
}
let Some(u) = &app.updates else { return };
if let Some(busy) = &u.busy {
ui.label(RichText::new(busy).size(11.5).color(t.text_dim));