mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 11:19:24 +00:00
Tuner: passive "different tuning" badge cue naming the retune (issue E, stage 2.5/3) (#657)
* Tuner: passive "different tuning" badge cue that names the retune
Building on the coverage check: when you enter a song your current
instrument doesn't cover, the topbar tuner badge gets an amber ring + a
tooltip naming the change (e.g. "retune B->A", or "the reference pitch"
for an A440 vs A432 mismatch). Advisory only -- it never auto-opens the
panel; recomputed on song:ready, cleared on song-load / leaving the
player.
Refactors the coverage check into a structured report
(window._tunerAutoOpen.coverageReport -> { covered, retune:[{from,to}],
reference, cantCover }); the boolean gate now wraps it. The cue is
CSS-free (inline ring + native tooltip, no Tailwind rebuild) and no-ops
when the tuner plugin is absent.
Touches static/v3/badges.js (cue) + plugins/tuner/screen.js (report).
v3-only. Stacked on #656 (issue E stage 2.5/3). The splitscreen-suppress
and no-usable-input guards move to E2 (the playback gate).
Tests: tests/js/tuner_auto_open.test.js (report names the strings,
reference mismatch, badge wiring).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbexxfTt8q2tAn436MqGWF
* feat(tuner): read/write the live per-instrument working tuning — both-directions retune prompt (working-tuning PR 3) (#660)
The §4 coverage check compared each song against the player's fixed
instrument-profile tuning, so the tuner only ever prompted *away* from a "home"
tuning (E -> Drop C#) and stayed silent coming back (Drop C# -> E), even though
the player had physically retuned.
_playerTuning() now reads the host's live per-instrument working tuning
(window.feedBack.workingTuning, keyed by the selected instrument from
/api/settings) instead of re-deriving from the static settings tuning, so
coverage is measured against what the instrument is ACTUALLY in and prompts both
directions. On clearing an auto-opened tuner, _publishWorkingTuning() writes that
song's tuning as the instrument's live working tuning ('assumed' — PR 4's
explicit "I tuned / Skip" refines the write-point), so the next song is judged
against where the player now is.
Per-instrument (guitar vs bass tracked separately). Feature-detected: falls back
to the static /api/settings tuning when the working-tuning capability is absent,
so the 27 existing coverage tests are unchanged. Builds on PR 1 (host
workingTuning) + PR 2 (instrument->chart routing).
Tests: tests/js/tuner_auto_open.test.js — +2 (both-directions coverage via a live
Drop-D working tuning; publish-on-clear targets the right instrument slot); 29
pass total.
Claude-Session: https://claude.ai/code/session_01QbexxfTt8q2tAn436MqGWF
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tuner): transactional open + fail-closed auto-open config (tuner-E #655 review) (#681)
Two review fixes for the auto-open opt-in+persist stage:
- enable() wasn't transactional. The panel (with the ×/Skip buttons) is shown
before `await _tunerAudio.start()`, and `_state.enabled` was only set after it.
A ×/Skip dismiss during that await hit disable() with wasEnabled=false, then
enable() completed and flipped enabled on — an enabled-but-hidden zombie. Guard
the open with an `_openGen` token bumped on every enable()/disable(); after the
audio-start await, bail if superseded instead of enabling. Closes #675.
- Config wasn't fail-closed. routes.py normalized the opt-in with
bool(data.get("autoOpenOnTuningChange", False)), so "false"/"0"/junk coerced to
True. Accept only a real JSON boolean. Closes #676.
Tests: tuner_auto_open.test.js (dismiss-mid-open stays disabled — fails without
the token guard), test_config.py (auto-open default-false + fail-closed on
non-bool). 34 JS + 24 config tests green.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(tuner): coverage stays conservative when the instrument is unknown (tuner-E #656 review) (#682)
_playerTuning() is documented as conservative ("missing data → not covered → still
prompt"), but when /api/settings carried no instrument identity (a fresh profile:
_default_settings() omits instrument/string_count/tuning) it invented guitar/6/440/
standard, so an unconfigured player was treated as 6-string E-standard and coverage
suppressed the auto-open (and badge cue) for matching songs. The post-#660 rewrite
only returned null when the whole fetch failed (!s), not when settings existed but
lacked an instrument.
Now return null unless there's a confident identity — any of instrument/string_count/
tuning in settings, or live working-tuning offsets. A configured standard guitar still
covers a standard song (no regression). Closes #677.
Tests: tuner_auto_open.test.js — empty-settings → not covered (fails without the fix);
configured standard guitar → still covered.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(tuner): badge coverage cue staleness + unknown-as-warning + dedupe (tuner-E #657 review) (#683)
Three review fixes for the passive "different tuning" badge cue stage:
- Stale async cue (#678): _refreshCoverageCue awaited coverageReport then wrote the
DOM unconditionally, so a slow /api/settings fetch could restore the previous
song's amber ring after song:loading / leaving the player. Add a monotonic token
bumped on every refresh and both clear paths; apply the awaited report only if the
token still matches.
- "Unknown" rendered as "needs retune" (#679): the plugin returns a conservative
all-false report on a fetch hiccup; the cue painted that as an amber "retune the
reference pitch" ring. Collapse a no-signal report (not covered, no reference /
retune / cantCover) to null (no cue) via _meaningfulReport(). A genuine not-covered
report always carries reference / retune / cantCover, so real cues are preserved.
- Duplicate /api/settings fetch (#680): the auto-open gate and the badge cue both
call coverageReport() per song:ready. Cache the coverage promise per song (keyed by
session + tuning + centOffset) so they share one fetch; invalidate on song:loading,
instrument:changed, and working-tuning-changed so it can't go stale within a song.
Tests: tuner_auto_open.test.js — concurrent reports share one fetch, a new song
refetches (fails without the cache). 34 JS tests green. Codex-reviewed.
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: Byron Gamatos <xasiklas@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
Byron Gamatos
parent
6b0e37aa35
commit
6aed8510d7
@@ -25,6 +25,13 @@
|
||||
// Tuning names per instrument key (e.g. 'guitar-6', 'bass-4'), loaded from
|
||||
// GET /api/tunings. Falls back to empty arrays until the fetch resolves.
|
||||
let _tuningsByKey = {};
|
||||
// Last instrument-coverage report for the current song (from the tuner plugin) —
|
||||
// drives a passive "different tuning" cue on the tuner badge. null = covered /
|
||||
// unknown / off the player.
|
||||
let _lastCoverageReport = null;
|
||||
// Monotonic token so a slow coverage fetch can't restore a stale cue after a newer
|
||||
// song started loading / we left the player. Bumped on every refresh and clear.
|
||||
let _coverageCueToken = 0;
|
||||
function _tuningsForKey(key) { return Object.keys(_tuningsByKey[key] || {}); }
|
||||
function _tuningsForInstrument(instrument, string_count) {
|
||||
return _tuningsForKey(instrument + '-' + string_count);
|
||||
@@ -257,6 +264,52 @@
|
||||
openTuner();
|
||||
});
|
||||
_applyFrame(_lastFrame);
|
||||
_applyCoverageCue(_lastCoverageReport);
|
||||
}
|
||||
|
||||
// Passive "different tuning" cue on the tuner badge: an amber ring + a tooltip
|
||||
// naming the retune (e.g. "B→A"). The diff comes from the tuner plugin's coverage
|
||||
// report; an absent plugin or a covered song → no cue. CSS-free (inline ring +
|
||||
// native title) so it needs no Tailwind rebuild, and it never auto-opens the
|
||||
// panel — it's advisory; the user taps the badge to tune.
|
||||
function _applyCoverageCue(report) {
|
||||
const btn = document.querySelector('#v3-badge-tuner [data-open-tuner]');
|
||||
if (!btn) return;
|
||||
const needs = !!(report && !report.covered);
|
||||
btn.style.boxShadow = needs ? '0 0 0 2px #fbbf24' : '';
|
||||
if (!needs) { btn.title = 'Open tuner'; return; }
|
||||
const summary = report.cantCover ? 'a different instrument'
|
||||
: (report.retune && report.retune.length)
|
||||
? report.retune.map((d) => d.from + '→' + d.to).join(', ')
|
||||
: 'the reference pitch';
|
||||
btn.title = 'This song needs a different tuning — retune ' + summary + '. Click to tune.';
|
||||
}
|
||||
|
||||
// A coverage report only drives the cue when it carries an actual signal: covered
|
||||
// (clears the ring) or a nameable mismatch (retune / reference / cantCover). The
|
||||
// plugin returns a conservative all-false report on a fetch hiccup / missing data —
|
||||
// that's "unknown", NOT "needs retune", so collapse it to null (no cue) rather than
|
||||
// painting an amber "retune the reference pitch" ring with no evidence.
|
||||
function _meaningfulReport(report) {
|
||||
if (!report) return null;
|
||||
if (report.covered) return report;
|
||||
if (report.cantCover || report.reference || (report.retune && report.retune.length)) return report;
|
||||
return null;
|
||||
}
|
||||
|
||||
async function _refreshCoverageCue() {
|
||||
const myToken = ++_coverageCueToken;
|
||||
const songInfo = window.highway && window.highway.getSongInfo && window.highway.getSongInfo();
|
||||
const api = window._tunerAutoOpen;
|
||||
if (!songInfo || !api || typeof api.coverageReport !== 'function') {
|
||||
_lastCoverageReport = null; _applyCoverageCue(null); return;
|
||||
}
|
||||
let report = null;
|
||||
try { report = await api.coverageReport(songInfo); }
|
||||
catch (_e) { report = null; }
|
||||
if (myToken !== _coverageCueToken) return; // superseded by a newer song / a clear
|
||||
_lastCoverageReport = _meaningfulReport(report);
|
||||
_applyCoverageCue(_lastCoverageReport);
|
||||
}
|
||||
|
||||
// ── Instrument selector card (Stitch RightInstrumentSelector) ──────────--
|
||||
@@ -374,6 +427,13 @@
|
||||
await loadTunings();
|
||||
renderInstrument();
|
||||
});
|
||||
// Passive coverage cue: recompute when a song is ready; clear when a new
|
||||
// song starts loading or we leave the player screen.
|
||||
sm.on('song:ready', () => { _refreshCoverageCue(); });
|
||||
sm.on('song:loading', () => { _coverageCueToken++; _lastCoverageReport = null; _applyCoverageCue(null); });
|
||||
sm.on('screen:changed', (e) => {
|
||||
if (!e || !e.detail || e.detail.id !== 'player') { _coverageCueToken++; _lastCoverageReport = null; _applyCoverageCue(null); }
|
||||
});
|
||||
}
|
||||
}
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, { once: true });
|
||||
|
||||
Reference in New Issue
Block a user