metronome/mobile-sw.js
Me Here 5b11363520 pm-mobile: practice sessions, richer readout, BPM-tap, lane/feature detail
Bigger rework of the mobile player around a new "practice session" concept,
plus a second page to review sessions.

Transport / sessions:
- Practice now starts a continuous SESSION clock and begins practicing the
  current track. While practicing, the Play button becomes Stop and Practice
  becomes Pause, so Practice starts/stops individual tracks while the session
  clock keeps running. Stop (the Play button) ends the session and records it.
- Plain Play still runs the metronome with no session/recording.
- Each track-practice is one segment {name, at, sec, bpm}; sub-3s blips are
  skipped. A session = {at, endedAt, clockSec, note, segments[]} stored under
  metronome.sessions (replaces the old per-track metronome.logs sheet).
- Switching track / set list mid-session rolls the current segment over.

Display:
- Removed the Tap Tempo button; the BPM display now does it: tap = tap tempo,
  hold = type an exact value, vertical drag = scrub.
- Detail panel shows every lane (canonical share-token chips, disabled lanes
  struck through) and the active features: bar count, end behavior, ramp, and
  gaps (trainer play/mute).
- Meter line shows live bar count with total (e.g. "bar 4 / 16") and elapsed
  play time; the bottom bar shows live session time + track count while
  recording, and links to the sessions page otherwise.

New page mobile-sessions.html: lists saved sessions, each with an editable note
(autosaved) and an aggregate table of tracks practiced in that session
(track - time - plays - bpm range), with per-session delete. PWA scope widened
to /mobile so both pages stay in the installed app + offline (SW v2).

Engine untouched; conformance suite unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 09:14:41 -05:00

52 lines
1.8 KiB
JavaScript

/* Service worker for the PolyMeter mobile app (mobile.html).
*
* Deliberately minimal and non-intrusive: it only manages its OWN app-shell URLs
* (the page, manifest, icons). For every other request it does NOT call
* respondWith(), so the rest of the site behaves exactly as if no SW existed.
*
* Strategy for the shell: network-first, fall back to cache. The page is a single
* self-contained file that is version-stamped on deploy, so when the device is
* online it always gets the freshest build; offline it still launches from cache.
*/
const CACHE = "polymeter-mobile-v2";
const SHELL = [
"/mobile.html",
"/mobile-sessions.html",
"/manifest.webmanifest",
"/icon-192.png",
"/icon-512.png",
"/icon-180.png",
];
const SHELL_PATHS = new Set(SHELL);
self.addEventListener("install", (e) => {
self.skipWaiting();
e.waitUntil(caches.open(CACHE).then((c) => c.addAll(SHELL)).catch(() => {}));
});
self.addEventListener("activate", (e) => {
e.waitUntil(
caches.keys()
.then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))))
.then(() => self.clients.claim())
);
});
self.addEventListener("fetch", (e) => {
const req = e.request;
if (req.method !== "GET") return;
const url = new URL(req.url);
if (url.origin !== self.location.origin) return;
// Treat any navigation to /mobile.html (with or without ?standalone=1 etc.) as the shell.
const path = url.pathname;
if (!SHELL_PATHS.has(path)) return; // not ours — let the browser handle it
e.respondWith(
fetch(req)
.then((res) => {
if (res && res.ok) { const copy = res.clone(); caches.open(CACHE).then((c) => c.put(path, copy)); }
return res;
})
.catch(() => caches.match(path).then((hit) => hit || caches.match("/mobile.html")))
);
});