From 842a663e09638413a97d8a81c18830b533197724 Mon Sep 17 00:00:00 2001 From: marcosgilf Date: Fri, 14 Aug 2026 23:15:34 +0200 Subject: [PATCH 1/3] fix(pwa): place update marker by deployment --- README.md | 6 ++++-- docs/spec.md | 11 ++++++----- src/layouts/AppLayout.astro | 18 +++++++++++++----- src/styles/app.css | 3 ++- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 83b5332..0330a7b 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,8 @@ is a Tabata whether or not anything is labelled "Tabata". - **Offline-first** — precached service worker, no backend, no accounts, no analytics. Installable as **Timer**; waiting updates apply immediately when no Routine is active. - **Automatic updates** — an active or paused Routine defers a waiting worker until Done or Reset. The - new build reloads automatically, then the bottom version shows green **NEW vx.x.x** for that reload only. + new build reloads automatically; releases show green **NEW vx.x.x** at right, while dev previews append + green **NEW** to the PR/deployment clue at left. - **Progressive enhancement** — audio session, Wake Lock and vibration are feature-detected and no-ops where absent. The app is fully correct without any of them. - **Semantic and accessible** — [Pico CSS](https://picocss.com/) classless, WCAG 2.2 AA in every slice. @@ -88,7 +89,8 @@ published, served at [timer.marcosgilf.com](https://timer.marcosgilf.com). Release Please opens Release PRs from Conventional Commits. Merging a Release PR bumps `package.json`, updates `CHANGELOG.md`, creates a `vX.Y.Z` Git tag and a GitHub Release. Publishing -that release triggers the production deploy. The app shows that version in the bottom-right corner. +that release triggers the production deploy. The app shows that version in the bottom-right corner; +production update highlights stay there, while preview highlights use the left deployment clue. Use commit prefixes intentionally: diff --git a/docs/spec.md b/docs/spec.md index 5705bf3..8d6ba21 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -431,9 +431,10 @@ layer below is feature-detected, adds capability where present, and is a no-op w - No update button, confirmation prompt, toast dependency, popover/polyfill, polling or custom service-worker manager. Standalone Home Screen PWAs use the same service-worker lifecycle: on launch, registration checks for an update; `updateSW(true)` activates it and reloads the page. -- Before activation, store a session marker. The new build consumes the marker on first render, prefixes - the bottom version with green **NEW vX.X.X**, then clears it. Version highlighting is optional; storage - failures do not block update activation or timer behavior. +- Before activation, store a session marker. The new build consumes the marker on first render, then clears + it. Production releases prefix the bottom version with green **NEW vX.X.X**. Dev deployments append green + **NEW** to the left PR/deployment clue. Version highlighting is optional; storage failures do not block + update activation or timer behavior. - Registration errors are swallowed; offline use of the current version remains unaffected. ### Install and manifest @@ -594,8 +595,8 @@ Genuinely undecided. An implementer should raise these rather than assume. - **Default values for `Prefs`** — only `prepareMs` has a stated default (10s). Starting `muted`, `volume`, `ticks` and `vibrate` values are unspecified. - ~~**Update-prompt UI**~~ — resolved: no update control. A waiting worker activates immediately when - idle, or after an active Routine reaches Done/Reset; the new build labels its bottom version **NEW vX.X.X** - in green for that reload only. + idle, or after an active Routine reaches Done/Reset; production labels its version **NEW vX.X.X** in + green, while dev deployments append **NEW** to the left PR/deployment clue. - **Install guidance copy for iOS** — decided that it must be static copy (no `beforeinstallprompt`), but not what it says or which screen hosts it. - ~~**App name, iconography, favicon/manifest icon set, `theme_color`**~~ — resolved: app name diff --git a/src/layouts/AppLayout.astro b/src/layouts/AppLayout.astro index 8a88c0f..5687315 100644 --- a/src/layouts/AppLayout.astro +++ b/src/layouts/AppLayout.astro @@ -38,7 +38,7 @@ const deployLabel =
- {deployLabel && {deployLabel}} + {deployLabel && {deployLabel}} v{version}
@@ -47,15 +47,23 @@ const deployLabel = import { createPwaUpdateController, type RegisterSW } from "../client/pwa-update.ts"; const appVersion = document.querySelector("[data-app-version]")!; + const deployClue = document.querySelector("[data-deploy-clue]"); const updateMarker = "timer:update-activated"; try { if (sessionStorage.getItem(updateMarker) === "true") { sessionStorage.removeItem(updateMarker); - const versionLabel = appVersion.getAttribute("aria-label") ?? "Timer version"; - appVersion.textContent = `NEW ${appVersion.textContent ?? ""}`; - appVersion.dataset.updated = "true"; - appVersion.setAttribute("aria-label", `New ${versionLabel}`); + if (deployClue) { + const deployLabel = deployClue.getAttribute("aria-label") ?? "Deployment context"; + deployClue.textContent = `${deployClue.textContent ?? ""} NEW`; + deployClue.dataset.updated = "true"; + deployClue.setAttribute("aria-label", `${deployLabel} new`); + } else { + const versionLabel = appVersion.getAttribute("aria-label") ?? "Timer version"; + appVersion.textContent = `NEW ${appVersion.textContent ?? ""}`; + appVersion.dataset.updated = "true"; + appVersion.setAttribute("aria-label", `New ${versionLabel}`); + } } } catch { // Version highlighting is optional. Timer behavior stays independent. diff --git a/src/styles/app.css b/src/styles/app.css index e05cc56..6b73f19 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -250,7 +250,8 @@ body > main { user-select: none; } -.app-version[data-updated="true"] { +.app-version[data-updated="true"], +.deploy-clue[data-updated="true"] { color: #39d353; font-weight: 700; opacity: 1; From f1321d76b41670ed2a22902439768d73d53e978f Mon Sep 17 00:00:00 2001 From: marcosgilf Date: Fri, 14 Aug 2026 23:17:25 +0200 Subject: [PATCH 2/3] fix(layout): keep release version on right --- src/styles/app.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/styles/app.css b/src/styles/app.css index 6b73f19..f62ce9e 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -250,6 +250,10 @@ body > main { user-select: none; } +.app-footer .app-version { + margin-inline-start: auto; +} + .app-version[data-updated="true"], .deploy-clue[data-updated="true"] { color: #39d353; From ed04157a352953dbb272f57a4816471f91b9d286 Mon Sep 17 00:00:00 2001 From: marcosgilf Date: Fri, 14 Aug 2026 23:19:28 +0200 Subject: [PATCH 3/3] fix(layout): keep app bar fixed on scroll --- src/styles/app.css | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/styles/app.css b/src/styles/app.css index f62ce9e..7c57b01 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -2,6 +2,7 @@ :root { --pico-background-color: #0b0b0c; + scroll-padding-block-start: calc(3.75rem + env(safe-area-inset-top)); --digit-colour: #ff2b2b; --digit-glow: rgb(255 40 40 / 45%); --pico-primary: var(--digit-colour); @@ -22,19 +23,28 @@ body > main { min-height: 100dvh; width: 100%; max-width: none; - padding: 1rem max(0.5rem, env(safe-area-inset-left)) 1rem max(0.5rem, env(safe-area-inset-right)); + padding: calc(1rem + 2.75rem + env(safe-area-inset-top)) max(0.5rem, env(safe-area-inset-left)) + 1rem max(0.5rem, env(safe-area-inset-right)); padding-bottom: calc(3rem + env(safe-area-inset-bottom)); overflow-x: hidden; text-align: center; } .app-bar { + position: fixed; + z-index: 3; + inset-block-start: 0; + inset-inline-start: 50%; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; width: min(100%, 48rem); - min-height: 2.75rem; + min-height: calc(2.75rem + env(safe-area-inset-top)); margin: 0; + padding-block-start: env(safe-area-inset-top); + padding-inline: max(0.5rem, env(safe-area-inset-left)) max(0.5rem, env(safe-area-inset-right)); + transform: translateX(-50%); + background: var(--pico-background-color); } .app-bar h1 {