Name each install, fix the notification badge, switch to Archivo

- Settings > General: an App name (super admin) for the installed app.
  The manifest's name/short_name, the iOS head tags and the loading
  page's title use it, so two installs on one phone can be told apart.
- Notifications get a real badge (white stone silhouette): Android masks
  the badge by alpha, so the opaque app icon showed as a square.
- The loading and offline pages load the app's font.
- The font is now Archivo (variable, one request for every weight),
  matching the marketing site.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-25 10:17:40 +01:00
co-authored by Claude Opus 5.5
parent 89c4e26d06
commit 3b7c05bfa0
23 changed files with 440 additions and 16 deletions
+15
View File
@@ -0,0 +1,15 @@
type Params = {
text: string;
};
/**
* Text made safe to drop into HTML, in an element or a quoted attribute.
*/
export default function escapeHtml({ text }: Params) {
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
+13
View File
@@ -0,0 +1,13 @@
import { SiteData } from "@/src/data/site-data";
type Params = {
install_name?: string | null;
};
/**
* What the installed app is called: the install's own name, else
* `SiteName`.
*/
export default function installDisplayName({ install_name }: Params) {
return install_name?.trim() || SiteData["SiteName"];
}
+34
View File
@@ -0,0 +1,34 @@
import { describe, expect, test } from "bun:test";
import { SiteData } from "@/src/data/site-data";
import namePwaManifest from "./name-pwa-manifest";
const manifest = {
name: SiteData["SiteName"],
short_name: SiteData["SiteName"],
start_url: "/admin/bots",
};
describe("namePwaManifest", () => {
test("leaves an unnamed install's manifest alone", () => {
expect(namePwaManifest({ manifest, install_name: "" })).toEqual(manifest);
expect(namePwaManifest({ manifest, install_name: " " })).toEqual(manifest);
expect(namePwaManifest({ manifest })).toEqual(manifest);
});
test("labels the icon with the name and adds the site name to the full name", () => {
expect(namePwaManifest({ manifest, install_name: " Acme " })).toEqual({
...manifest,
name: `Acme - ${SiteData["SiteName"]}`,
short_name: "Acme",
});
});
test("doesn't repeat the site name when the name already has it", () => {
const install_name = `Acme ${SiteData["SiteName"].toUpperCase()}`;
expect(namePwaManifest({ manifest, install_name })).toMatchObject({
name: install_name,
short_name: install_name,
});
});
});
+27
View File
@@ -0,0 +1,27 @@
import { SiteData } from "@/src/data/site-data";
type Params = {
manifest: Record<string, unknown>;
install_name?: string | null;
};
/**
* The generated manifest with the install's own name, so the launcher
* tells two installs apart. `short_name` (the label under the icon) is the
* name alone; `name` adds `SiteName` unless the name already says it.
* Unnamed, the manifest is unchanged.
*/
export default function namePwaManifest({ manifest, install_name }: Params) {
const name = install_name?.trim();
if (!name) return manifest;
const site_name = SiteData["SiteName"];
const says_site_name = name.toLowerCase().includes(site_name.toLowerCase());
return {
...manifest,
name: says_site_name ? name : `${name} - ${site_name}`,
short_name: name,
};
}