mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 03:09:57 +00:00
feat(v3): flag library songs by working-tuning match (working-tuning PR 6) (#668)
* feat(v3): flag library songs by working-tuning match (working-tuning PR 6) Each song's tuning chip in the v3 library grid is now coloured by whether your CURRENT working tuning covers it: green = play it now, amber = needs a retune (with a matching tooltip). Uses the tuner plugin's coverageReport (async), so it runs as a post-paint decoration pass — chips render instantly, then colour a tick later; a token cancels a superseded pass so scrolling stays snappy. Re-flags on working-tuning-changed (retune / instrument swap / reset), no re-fetch. Fully feature-detected: without the tuner coverage API + the host workingTuning state, the chips render exactly as before. v3-only, single file (static/v3/songs.js). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QbexxfTt8q2tAn436MqGWF * fix(tuner/library): correct bass matching + memoize player tuning (PR #668 review) Review fixes for working-tuning PR 6 (library tuning-match chips): - Bass songs were scored against the guitar tuning. The chip passed no arrangement to coverageReport, so isBassArrangement fell back to guitar — a 4-string bass drop-D read as guitar could FALSE-MATCH a drop-D guitar player (green). songCard now flags a bass-only song (every arrangement name matches /\bbass\b/) with data-tuning-bass, and decorateTuningChips passes arrangement 'Bass'/'Lead' so coverage uses the right base pitches. Mixed guitar+bass songs → guitar (the song-level tuning is the guitar one); least-wrong given one tuning per song. - Per-chip /api/settings fetch storm. coverageReport()→_playerTuning() fetched /api/settings once per visible chip per grid paint (~60). _playerTuning is now memoized (the player's tuning is song-independent) so all callers share one read; invalidated on instrument:changed / working-tuning-changed, with a 3s TTL so a settings write that doesn't emit an event still heals. A transient fetch failure is NOT cached (next read retries) — else one hiccup would freeze coverage. Tests: player tuning shared across songs (one fetch); transient-failure retry (fails without the fix). The prior #680 dedup test updated for the memoized behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: byrongamatos <xasiklas@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
byrongamatos
parent
6aed8510d7
commit
df4e17bc99
@@ -591,9 +591,11 @@ test('a configured standard-guitar player still covers a standard song', async (
|
||||
const covered = await sandbox.window._tunerAutoOpen.coveredByPlayerInstrument(E_STANDARD);
|
||||
assert.equal(covered, true, 'a known standard guitar covers a standard song (no regression)');
|
||||
});
|
||||
// ── #657 fix (#680): coverage is deduped — the auto-open gate and the badge cue both
|
||||
// call coverageReport() on the same song:ready; they must share ONE /api/settings fetch.
|
||||
test('coverage reports for the same song share one settings fetch, and a new song refetches', async () => {
|
||||
// ── #657 fix (#680) + #668 fix: coverage is deduped, and the player tuning is memoized
|
||||
// across songs (it depends on the selected instrument, not the song). Many coverage
|
||||
// calls — the auto-open gate, the badge cue, AND the library's per-song tuning-match
|
||||
// chips — share ONE /api/settings fetch until the instrument / working tuning changes.
|
||||
test('coverage reports share one /api/settings fetch across songs (player tuning memoized)', async () => {
|
||||
const sandbox = createTunerSandbox({ player: { instrument: 'guitar', string_count: 6, tuning: 'Standard' } });
|
||||
let settingsFetches = 0;
|
||||
const origFetch = sandbox.window.fetch;
|
||||
@@ -605,8 +607,27 @@ test('coverage reports for the same song share one settings fetch, and a new son
|
||||
const [a, b] = await Promise.all([api.coverageReport(DROP_D), api.coverageReport(DROP_D)]);
|
||||
assert.equal(settingsFetches, 1, 'concurrent reports for the same song share one fetch');
|
||||
assert.deepEqual(a, b);
|
||||
// A new song invalidates the cache → a fresh fetch.
|
||||
// A different song re-evaluates coverage but reuses the memoized player tuning — the
|
||||
// player didn't retune or switch instruments, so no second /api/settings read.
|
||||
api.onSongLoading();
|
||||
await api.coverageReport(E_STANDARD);
|
||||
assert.equal(settingsFetches, 2, 'a new song refetches');
|
||||
assert.equal(settingsFetches, 1, 'a different song reuses the memoized player tuning — no refetch');
|
||||
});
|
||||
|
||||
// ── #668 fix: a transient /api/settings failure must NOT be pinned by the player-tuning
|
||||
// memo — the next read retries (else one hiccup freezes coverage as "unknown" for good).
|
||||
test('a transient /api/settings failure is not cached — the next coverage read retries', async () => {
|
||||
const sandbox = createTunerSandbox({ player: { instrument: 'guitar', string_count: 6, tuning: 'Standard' } });
|
||||
let failNext = true;
|
||||
const origFetch = sandbox.window.fetch;
|
||||
sandbox.window.fetch = (url) => {
|
||||
if (String(url).includes('/api/settings') && failNext) { failNext = false; return Promise.reject(new Error('boom')); }
|
||||
return origFetch(url);
|
||||
};
|
||||
const api = sandbox.window._tunerAutoOpen;
|
||||
const r1 = await api.coverageReport(DROP_D); // settings read failed → conservative "none" report
|
||||
assert.equal(r1.retune.length, 0, 'a fetch failure yields the empty/unknown report');
|
||||
api.onSongLoading(); // clear the coverage cache to force a recompute
|
||||
const r2 = await api.coverageReport(DROP_D); // retry: settings now readable → a real report
|
||||
assert.equal(r2.retune.length, 1, 'the retry actually computes coverage (Drop-D low string vs standard)');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user