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>
52 lines
1.8 KiB
JavaScript
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")))
|
|
);
|
|
});
|