diff --git a/static/app.js b/static/app.js
index bda30e3..6c0ad2f 100644
--- a/static/app.js
+++ b/static/app.js
@@ -127,6 +127,7 @@ import {
toggleSectionPracticePopover,
} from './js/section-practice.js';
import { configureHost } from './js/host.js';
+import { formatTime } from './js/format.js';
// The playback transport. These used to BE app.js — they are imported back now, and the
// four modules that reached for them through the host seam import them directly instead.
import {
@@ -4821,7 +4822,6 @@ if (window.feedBack) {
}
-function formatTime(s) { return `${Math.floor(s/60)}:${String(Math.floor(s%60)).padStart(2,'0')}`; }
@@ -6261,7 +6261,6 @@ async function checkScanAndLoad() {
// tests/js/host_contract.test.js fails CI if this list and the host.* uses under
// static/js/ ever drift apart.
configureHost({
- formatTime,
handleSliderInput,
playSong,
// count-in is a module now, so section-practice reaches it through the seam too —
diff --git a/static/js/format.js b/static/js/format.js
new file mode 100644
index 0000000..1a902a1
--- /dev/null
+++ b/static/js/format.js
@@ -0,0 +1,17 @@
+// Display formatters. A LEAF module: imports nothing.
+//
+// WHY THIS EXISTS FOR ONE FUNCTION. formatTime was a HOST HOOK — loops.js and
+// section-practice.js both reached back through the seam for it. It was also, by pure
+// accident of who calls it, inside the dependency closure of the library carve. Leaving
+// it there would have made loops.js and section-practice.js import the LIBRARY to format
+// a timestamp, which is nonsense, and a cycle waiting to happen.
+//
+// A hook is a cycle you agreed to live with. This one has a real owner — it just isn't
+// app.js, and it certainly isn't the library. Give it a home of its own and both
+// consumers import it directly.
+//
+// It is a leaf on purpose. Anything else that turns out to be a shared pure formatter
+// belongs here too; nothing does yet, so nothing else is here.
+
+/** Seconds -> `M:SS`. */
+export function formatTime(s) { return `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, '0')}`; }
diff --git a/static/js/loops.js b/static/js/loops.js
index 312cd09..898f484 100644
--- a/static/js/loops.js
+++ b/static/js/loops.js
@@ -20,6 +20,7 @@
// fails CI if the hooks used here and the hooks app.js wires ever drift apart.
import { esc, uiPrompt } from './dom.js';
import { _audioSeek, _audioTime } from './transport.js';
+import { formatTime } from './format.js';
import { host } from './host.js';
import {
_setSectionPracticeMode,
@@ -167,11 +168,11 @@ export function updateLoopUI() {
const label = document.getElementById('loop-label');
const hasLoop = loopA !== null && loopB !== null;
if (hasLoop) {
- label.textContent = `${host.formatTime(loopA)} → ${host.formatTime(loopB)}`;
+ label.textContent = `${formatTime(loopA)} → ${formatTime(loopB)}`;
document.getElementById('btn-loop-clear').classList.remove('hidden');
document.getElementById('btn-loop-save').classList.remove('hidden');
} else if (loopA !== null) {
- label.textContent = `${host.formatTime(loopA)} → ?`;
+ label.textContent = `${formatTime(loopA)} → ?`;
document.getElementById('btn-loop-clear').classList.add('hidden');
document.getElementById('btn-loop-save').classList.add('hidden');
} else {
@@ -190,7 +191,7 @@ export async function loadSavedLoops() {
sel.innerHTML = '';
for (const l of loops) {
- sel.innerHTML += ``;
+ sel.innerHTML += ``;
}
if (loops.length > 0) {
sel.classList.remove('hidden');
diff --git a/static/js/section-practice.js b/static/js/section-practice.js
index 741ca58..338737b 100644
--- a/static/js/section-practice.js
+++ b/static/js/section-practice.js
@@ -28,6 +28,7 @@
import { audio } from './audio-el.js';
import { esc } from './dom.js';
import { _audioDuration, _audioTime, audioSeekGen } from './transport.js';
+import { formatTime } from './format.js';
import { host } from './host.js';
export function _sectionPracticeBarContains(el) {
@@ -902,7 +903,7 @@ export function renderSectionPracticeBar() {
_showSectionPracticeBar(bar);
scroll.innerHTML = parents.map((p, i) => {
const label = _formatSectionPracticeName(p.name);
- const tip = `${label} (${host.formatTime(p.start)}–${host.formatTime(p.end)})`;
+ const tip = `${label} (${formatTime(p.start)}–${formatTime(p.end)})`;
const kindClass = _sectionPracticeChipKindClass(p.name, i);
return ``;
}).join('');