Fix stale PWA service worker after updates; collapsible task list; short screens
Installed apps checked their service worker at /sw-<old build>.js. That file no longer exists after an update and the static fallback answered with index.html, so the update check failed and the old worker kept serving the previous app shell. The old frontend crashed on the new image references (base64Size of a missing `data`), leaving a blank page on phones. Old service worker URLs now get the clean-up worker (/sw.js), which unregisters and reloads; missing scripts and styles answer 404 instead of HTML. - Task list above the composer folds to one line (progress, current task), remembered per device. - max-height: 560px layout (square phones such as the Clicks Communicator, landscape): tighter top bar, composer and new-chat wizard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b1fa54a7b6
commit
d7f89ee257
6 files changed
+232
-8
No files matched your search
+34
-2
@@ -172,8 +172,7 @@ pub fn router(app: AppState) -> Router {
|
||||
.find(|p| p.join("index.html").exists())
|
||||
.unwrap_or_else(|| PathBuf::from("./web/dist"))
|
||||
});
|
||||
let index = web_dir.join("index.html");
|
||||
let static_files = tower_http::services::ServeDir::new(&web_dir).fallback(tower_http::services::ServeFile::new(index));
|
||||
let static_files = tower_http::services::ServeDir::new(&web_dir).fallback(missing_static_file(web_dir.clone()));
|
||||
|
||||
Router::new()
|
||||
.nest("/api", api)
|
||||
@@ -186,6 +185,39 @@ pub fn router(app: AppState) -> Router {
|
||||
.with_state(app)
|
||||
}
|
||||
|
||||
/// Paths that are not a file of the web build. Usually a client-side route → index.html,
|
||||
/// except for:
|
||||
/// - `/sw-<build>.js` of an earlier build: installed apps check their service worker at
|
||||
/// that URL for updates. Answering with index.html (not a script) makes the update
|
||||
/// fail, so the old worker kept serving the old app shell forever – which broke when
|
||||
/// the API changed. They get the kill switch /sw.js instead: it removes the old worker
|
||||
/// and reloads, and the page then registers the current worker.
|
||||
/// - other missing scripts/styles (old chunks): 404 instead of HTML.
|
||||
fn missing_static_file(web_dir: PathBuf) -> axum::routing::MethodRouter {
|
||||
use axum::http::{header, StatusCode};
|
||||
use axum::response::IntoResponse;
|
||||
axum::routing::any(move |uri: axum::http::Uri| {
|
||||
let web_dir = web_dir.clone();
|
||||
async move {
|
||||
let path = uri.path();
|
||||
let name = path.trim_start_matches('/');
|
||||
if name.starts_with("sw-") && name.ends_with(".js") && !name.contains('/') {
|
||||
return match tokio::fs::read(web_dir.join("sw.js")).await {
|
||||
Ok(js) => ([(header::CONTENT_TYPE, "text/javascript"), (header::CACHE_CONTROL, "no-cache, no-store")], js).into_response(),
|
||||
Err(_) => StatusCode::NOT_FOUND.into_response(),
|
||||
};
|
||||
}
|
||||
if [".js", ".mjs", ".css", ".map"].iter().any(|ext| path.ends_with(ext)) {
|
||||
return StatusCode::NOT_FOUND.into_response();
|
||||
}
|
||||
match tokio::fs::read(web_dir.join("index.html")).await {
|
||||
Ok(html) => ([(header::CONTENT_TYPE, "text/html; charset=utf-8"), (header::CACHE_CONTROL, "no-cache")], html).into_response(),
|
||||
Err(_) => StatusCode::NOT_FOUND.into_response(),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// gzip/brotli for JSON, HTML, JS and CSS. Images and SSE streams are left alone (default
|
||||
/// predicate), and so are binary downloads (already compressed or large).
|
||||
fn compression() -> tower_http::compression::CompressionLayer<impl tower_http::compression::Predicate + Clone> {
|
||||
|
||||
Reference in new issue
Block a user