Install as a desktop or phone app
Every Mateu UI is an installable Progressive Web App (PWA). There is nothing to write:
- On the desktop, Chrome and Edge show an Install app button in the address bar (and in the browser menu). The installed UI opens in its own window, with its own icon in the dock, the taskbar or the Start menu, and without the browser’s tabs and toolbars.
- On a phone, Android browsers offer Install app / Add to home screen, and Safari on iOS has Share → Add to Home Screen. The UI then opens full screen from its icon.
- The installed app opens offline: the shell starts from a cache, so the user sees your app (and its “No connection” notice) instead of the browser’s offline page.
It is the same project and the same deployment as the web UI, with the same URL: the user installs it from the page. It works for every way a Mateu UI is served: the Java adapters (Spring MVC, WebFlux, Quarkus, Micronaut, Helidon MP), .NET, Python, YAML mounts and static bundles.
What Mateu adds
Section titled “What Mateu adds”Every page Mateu serves carries, in its <head>:
<link rel="manifest" href="/crm/mateu/v3/pwa/manifest.webmanifest" crossorigin="use-credentials"><meta name="theme-color" content="#1f6feb"><link rel="apple-touch-icon" href="/crm/mateu/v3/pwa/icon-192.png"><script src="/crm/mateu/v3/pwa/register.js" data-sw="/crm/mateu/v3/pwa/sw.js?v=…" data-scope="/crm" defer></script>and serves, under each UI’s base URL:
| File | What it is |
|---|---|
<baseUrl>/mateu/v3/pwa/manifest.webmanifest | The web app manifest: name, icons, colours, display: standalone, start URL and scope. |
<baseUrl>/mateu/v3/pwa/sw.js | The service worker. Served with Service-Worker-Allowed, so it controls the whole UI. |
<baseUrl>/mateu/v3/pwa/register.js | The script that registers it: an external file, so a script-src 'self' policy needs no hash. |
<baseUrl>/mateu/v3/pwa/icon-192.png, icon-512.png, icon-maskable-512.png | Mateu’s default icons. |
Each UI is its own app: a UI at /crm and another at /admin install separately, each with its
own scope, cache and icon.
A static bundle carries the same files: pwa/manifest.webmanifest, pwa/register.js and the
icons, and the worker at the bundle’s root, mateu-sw.js. A static host cannot send the header that
widens a worker’s scope, so the worker sits at the root.
Name, colour and icon
Section titled “Name, colour and icon”The manifest is derived from your app exactly like the mobile app’s manifest. It is the same derivation, so a rename reaches the installed app, the mobile app and the browser tab alike:
| Member | From (highest precedence first) |
|---|---|
name | mateu.mobile.name › @MobileApp(name) › @Title / the app shell’s title |
short_name | mateu.mobile.short-name › @MobileApp(shortName) › the name |
theme_color | mateu.mobile.theme-color › @MobileApp(themeColor) › @App(accentColor) / the shell’s accentColor. Only a hex colour is kept. |
| icon | mateu.mobile.icon › @MobileApp(icon) › @Logo / the shell’s logo, followed by Mateu’s default icons |
@UI("/crm")@Title("Acme CRM")@App(accentColor = "#1f6feb")@MobileApp(shortName = "CRM", icon = "/images/acme-512.png")public class Crm { /* … */ }type: AppShelltitle: Acme CRMaccentColor: "#1f6feb"logo: /images/acme.svgIn .NET and Python, [MobileApp(...)] / @mobile_app(...) and the MATEU_MOBILE_* environment
variables set the same members (those ports have no logo or accent colour of their own).
Icons. Your icon comes first, declared with sizes: any; Mateu’s own icons (192 and 512 px,
and a maskable one) always follow, so the app is installable even when you declare none, and the
browser falls back to them if it cannot use yours. For a crisp installed icon, declare a square
PNG of at least 512 px, or an SVG. The rules:
- accepted: an
https://URL, a path on the UI’s own origin (a relative one is taken from the origin’s root), adata:image/png|jpeg|webp|gif|svg+xmlURI; - refused (the default icons are used): plain
http://, other schemes,.icofiles; - the favicon is never the app icon: a 16–32 px image makes a blurred app icon. Declare the
icon with
@MobileApp(icon)(ormateu.mobile.icon) if your logo is not suitable.
iOS ignores the manifest’s icons and SVGs: its home-screen icon is your PNG icon when you declare one, else Mateu’s.
Offline and caching
Section titled “Offline and caching”The service worker is deliberately minimal, and network first, always:
- Online, nothing changes. Every request it handles goes to the network first, exactly as without it; the cache only answers when the network fails. A new deploy is never hidden behind a cached copy.
- One cache per build. The worker’s URL carries a version (a hash of the page and the build), so the next deploy installs a new worker, which takes over at once and deletes the previous cache. There is no “close every tab to update” trap.
- What it caches: the shell page (served for every page of the UI when offline), the folders
the page loads its own scripts and stylesheets from (the renderer’s
/assets/, Redwood’s/_redwood/, Oracle’s CDN), scripts, stylesheets and fonts, same-origin image files, and a static bundle’smanifest.json. - What it never touches: anything but
GET; the Mateu API (/mateu/…), so no screen data is ever stored; and every otherfetch/XHR, so the data of your named REST sources — which may be personal or need a token — is never cached; a response markedCache-Control: no-storeis never stored either.
What the user sees offline depends on where the screens come from:
- A static bundle opens fully offline, screens included: the definitions travel in
manifest.json. Data from REST sources still needs the network (the screen says so). - A served app opens its shell offline and shows its “No connection” notice: its screens come from the server, which is not reachable.
Turning it off
Section titled “Turning it off”It is on by default. To turn it off:
| Where | How |
|---|---|
| Java (any adapter) | mateu.pwa.enabled=false — a JVM system property, application.properties / MicroProfile Config, or MATEU_PWA_ENABLED=false |
| .NET | MateuOptions.Pwa = false, or MATEU_PWA_ENABLED=false |
| Python | MATEU_PWA_ENABLED=false |
| Static bundle | -Dmateu.bundle.pwa=false, or <pwa>false</pwa> in the plugin’s configuration |
Off, the page carries none of the above, and the worker’s URL answers a worker that unregisters
itself and deletes its cache, so the browsers that installed it before let it go on their next
visit. It is also off in development mode (mateu.dev=true, and the bundle plugin’s
mateu-bundle:dev goal), where a cached shell would
only get in the way of live reload.
The worker is not registered inside a frame (the IDE editors’ previews, a page that embeds a Mateu
UI) nor on an insecure origin (browsers only allow service workers on https:// and localhost).
Content Security Policy
Section titled “Content Security Policy”Under a strict policy, also allow manifest-src 'self' and worker-src 'self'. The registration
script is a file on your own origin, so script-src 'self' covers it. See the
production checklist.
Check it
Section titled “Check it”In Chrome or Edge: DevTools → Application → Manifest (the installability section should list no errors) and Service workers (the worker active and controlling the page). Then use Install app in the address bar, and tick Offline in DevTools → Network and reload.