Version the brand images' URLs, so a new mark reaches returning visitors
ci / version (pull_request) Skipped
ci / node (pull_request) Successful in 2m25s
ci / publish (pull_request) Skipped
ci / announce (pull_request) Skipped
ci / docker-build (pull_request) Successful in 37s

The logo, favicons and app icons are served from public/img under fixed
names with a browser cache of hours, and the service worker fetches them
through that cache. After the mark changed on 2026-09-27, returning
visitors kept the old cat until their copies expired, and the favicon
and an installed app's icon hold on longer still.

Every URL that names one now carries ?v=BRAND_V (src/lib/brand.ts,
brandImage()): the header, sign-in, About, the mail empty state, the
notification icons, index.html's favicon links, the manifest's icons
and the service worker's shell and notification icons. Date-stamped,
never a counter, for the sites' ASSET_V reason; the three static files
carry the value written out, and the comment says to keep them in step.
This commit is contained in:
jcoffey-dev committed 2026-09-26 22:50:22 -07:00
1 parent 2a30a32c79
commit 0adc402629
9 files changed
+49 -25

No files matched your search

+23
View File
@@ -1,4 +1,5 @@
import { useSession } from "@/store/session";
import { withBase } from "@/lib/basePath";
/**
* What this instance calls itself, when nothing has said otherwise yet.
@@ -36,3 +37,25 @@ export function useAppName(): string {
export function currentAppName(): string {
return useSession.getState().session?.ihasmail?.appName?.trim() || DEFAULT_APP_NAME;
}
/**
* The brand images' version, carried as `?v=` on every URL that names one.
*
* The images live in `public/img` under fixed names and are served with a
* browser cache of hours, so replacing one (the mark changed on 2026-09-27)
* left returning visitors on the old picture until their copy expired -- and
* the favicon and an installed app's icon hold on longer still. A new value
* here is a new URL everywhere at once.
*
* Date-stamped with a letter for a second change the same day, never a
* counter, for the reason the sites give for ASSET_V: a value that could have
* been requested before may already be cached, with old bytes behind it.
* `index.html`, `public/manifest.webmanifest` and `public/sw.js` can't import
* this, so they carry the same value written out; keep the four in step.
*/
export const BRAND_V = "2026-09-27a";
/** A brand image's URL under the mount, versioned: `brandImage("/img/logo.png")`. */
export function brandImage(path: string): string {
return withBase(`${path}?v=${BRAND_V}`);
}
+4 -4
View File
@@ -1,4 +1,4 @@
import { withBase } from "../basePath";
import { brandImage } from "@/lib/brand";
let baseTitle = "inbuxa";
let faviconCanvas: HTMLCanvasElement | null = null;
@@ -40,13 +40,13 @@ export function setUnreadBadge(count: number): void {
if (!link) return;
if (!baseFavicon) {
baseFavicon = new Image();
baseFavicon.src = withBase("/img/favicon-64.png");
baseFavicon.src = brandImage("/img/favicon-64.png");
baseFavicon.onload = () => setUnreadBadge(count);
return;
}
if (!baseFavicon.complete) return;
if (count <= 0) {
link.href = withBase("/img/favicon-64.png");
link.href = brandImage("/img/favicon-64.png");
return;
}
faviconCanvas ??= document.createElement("canvas");
@@ -95,7 +95,7 @@ export function showNotification(title: string, opts: NotificationOptions & { on
if (!("Notification" in window) || Notification.permission !== "granted") return;
if (document.visibilityState === "visible" && document.hasFocus()) return;
const { onClick, ...options } = opts;
const full = { icon: withBase("/img/icon-192.png"), badge: withBase("/img/favicon-64.png"), ...options };
const full = { icon: brandImage("/img/icon-192.png"), badge: brandImage("/img/favicon-64.png"), ...options };
const viaWorker = navigator.serviceWorker?.controller ? navigator.serviceWorker.ready : null;
if (viaWorker) {
void viaWorker.then((reg) => reg.showNotification(title, full)).catch(() => undefined);