Make the server installable as a home-screen PWA
Build and push server image / test (push) Successful in 36s
Build and push server image / build-and-push (push) Successful in 2m37s
Build and push server image / deploy (push) Successful in 57s

Adds a web manifest, hand-drawn cup+frame icons, and a presence-only
service worker (no offline caching) so mobile browsers offer
"Add to Home Screen" for the server UI.
This commit is contained in:
2026-07-28 02:26:54 +00:00
parent 3fdda096a9
commit dd038f8e46
10 changed files with 55 additions and 1 deletions
+8 -1
View File
@@ -18,7 +18,7 @@ from __future__ import annotations
import logging
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from sqlalchemy import select
@@ -60,6 +60,13 @@ def health() -> dict:
return {"status": "ok"}
@app.get("/sw.js")
def service_worker() -> FileResponse:
# Served from / rather than /static/sw.js so its default scope is the
# whole app -- a SW can only ever control paths at or below its own URL.
return FileResponse("app/static/sw.js", media_type="application/javascript")
def _device_credential_redirect(request: Request, db, allow_legacy: bool) -> str | None:
"""The on-frame manage QR points at the server root with the device's
own credentials (new firmware: ?id=&token=; deployed firmware:
+9
View File
@@ -58,6 +58,15 @@
}
})();
// Registering this is what makes Chrome/Android offer the "Add to Home
// screen" install prompt -- a manifest link alone isn't enough. Served
// from /sw.js (not /static/sw.js) so its scope is the whole app.
if ("serviceWorker" in navigator) {
window.addEventListener("load", function () {
navigator.serviceWorker.register("/sw.js");
});
}
// Shared display names for widget_type, everywhere one shows up in the
// UI (Layout canvas, Add-a-widget buttons, button-assignment dropdowns).
const WIDGET_LABELS = {
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

+14
View File
@@ -0,0 +1,14 @@
{
"name": "ESPresso Frame",
"short_name": "ESPresso",
"description": "Manage your e-ink photo frames.",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#f5f6f8",
"theme_color": "#2563eb",
"icons": [
{ "src": "/static/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/static/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
]
}
+8
View File
@@ -0,0 +1,8 @@
// Presence-only service worker: satisfies the "installable" requirement
// (Chrome/Android in particular checks for a controlling SW with a fetch
// handler) without adding an offline cache -- every request just goes to
// the network as normal. Served from / (see app/main.py's /sw.js route)
// so its scope covers the whole app, not just /static/.
self.addEventListener("install", () => self.skipWaiting());
self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim()));
self.addEventListener("fetch", (event) => event.respondWith(fetch(event.request)));
+8
View File
@@ -17,6 +17,14 @@
})();
</script>
<link rel="stylesheet" href="/static/theme.css">
<link rel="manifest" href="/static/manifest.json">
<link rel="icon" href="/static/icons/favicon.png">
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
<meta name="theme-color" content="#2563eb">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="ESPresso">
{% block extra_head %}{% endblock %}
</head>
<body>
+8
View File
@@ -17,6 +17,14 @@
})();
</script>
<link rel="stylesheet" href="/static/theme.css">
<link rel="manifest" href="/static/manifest.json">
<link rel="icon" href="/static/icons/favicon.png">
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
<meta name="theme-color" content="#2563eb">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="ESPresso">
{% block extra_head %}{% endblock %}
</head>
<body>