From 02409a03653603d06d44b72177913f68b0a2f38b Mon Sep 17 00:00:00 2001 From: teahubxyz Date: Wed, 21 Oct 2026 19:34:30 +0000 Subject: [PATCH 1/3] =?UTF-8?q?havre:=20update=20pill=20=E2=80=94=20notice?= =?UTF-8?q?=20a=20new=20version=20and=20offer=20a=20reload=20(as=20gandora?= =?UTF-8?q?=20does)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ventures/havre/update.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 ventures/havre/update.js diff --git a/ventures/havre/update.js b/ventures/havre/update.js new file mode 100644 index 0000000..5309d00 --- /dev/null +++ b/ventures/havre/update.js @@ -0,0 +1,37 @@ +// update.js: an installed Havre has no address bar to refresh from, so the page notices when a new version has been +// published and offers a reload, like gandora's pill. It fingerprints the files the app is made of when it opens, +// checks again whenever it comes back to the foreground (and every ten minutes), and shows the pill when they differ. +// Checking fetches with cache: "reload", which also refreshes the browser's copies, so the reload gets the new files. +(() => { + const FILES = ["./", "app.js", "style.css", "workflows.js", "abilities.js", "card.js", "venice.js", "memory.js", + "threads.js", "media.js", "backup.js", "theme.js"]; + let base = null, shown = false, busy = false; + async function print() { + const parts = await Promise.all(FILES.map((f) => fetch(f, { cache: "reload" }).then((r) => (r.ok ? r.text() : "")).catch(() => null))); + if (parts.some((p) => p === null)) return null; // offline: no verdict + const d = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(parts.join("\u0000"))); + return Array.from(new Uint8Array(d), (b) => b.toString(16).padStart(2, "0")).join(""); + } + function show() { + if (shown) return; + shown = true; + const b = document.createElement("button"); + b.id = "upd"; b.type = "button"; b.textContent = "NEW VERSION ยท RELOAD"; + b.addEventListener("click", () => location.reload()); + document.body.append(b); + } + async function check() { + if (busy || shown || !crypto.subtle) return; + busy = true; + try { + const now = await print(); + if (!now) return; + if (!base) base = now; + else if (now !== base) show(); + } finally { busy = false; } + } + if (location.protocol !== "https:") return; + setTimeout(check, 3000); + setInterval(check, 10 * 60 * 1000); + document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") check(); }); +})(); -- 2.54.0 From f1247b5afdf9dd043b6ff9830292c725ef5a4f4a Mon Sep 17 00:00:00 2001 From: teahubxyz Date: Wed, 21 Oct 2026 19:34:35 +0000 Subject: [PATCH 2/3] havre: load update.js --- ventures/havre/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/ventures/havre/index.html b/ventures/havre/index.html index 43f841d..1769d2c 100644 --- a/ventures/havre/index.html +++ b/ventures/havre/index.html @@ -23,6 +23,7 @@ + -- 2.54.0 From d4915e39b2177b45c58ca7ca48cf6ec27756393f Mon Sep 17 00:00:00 2001 From: teahubxyz Date: Wed, 21 Oct 2026 19:34:42 +0000 Subject: [PATCH 3/3] havre: the update pill, in house style --- ventures/havre/style.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ventures/havre/style.css b/ventures/havre/style.css index ca1050f..3d5c88d 100644 --- a/ventures/havre/style.css +++ b/ventures/havre/style.css @@ -551,6 +551,13 @@ p.note { padding: 8px 10px; border: 1px solid var(--ink); background: var(--hi); .lefthead [role=tab] { font-size: 19px; } .lefthead .sep::before { font-size: 19px; } } +/* a new version is out: a bar at the top to reload (update.js) */ +#upd { + position: fixed; top: calc(10px + env(safe-area-inset-top)); left: 50%; transform: translateX(-50%); z-index: 30; + padding: 9px 16px; border: 1px solid var(--paper); border-radius: 0; background: var(--ink); color: var(--paper); + font: 700 13px/1 var(--mono); letter-spacing: .12em; white-space: nowrap; cursor: pointer; +} +#upd:hover, #upd:focus-visible { background: var(--hi); color: var(--ink); border-color: var(--ink); } @media (prefers-reduced-motion: reduce) { * { scroll-behavior: auto !important; } } -- 2.54.0