discovery: add Settings -> Discovery -> Adventurousness slider + align config key

A 0..1 range slider (Safe <-> Adventurous) in Settings -> Discovery with a live value readout. It
auto-saves via the settings page existing range-input listener, persists to config under
discover.adventurousness (matching the /api/settings "discover" section), and re-populates on load.
The listening-recs route reads that key (fixed from discovery. -> discover. to match the settings
section). Drag it up and "Based On Your Listening" leans harder toward obscure picks on the next load.

64 script-integrity tests green.
This commit is contained in:
BoulderBadgeDad 2026-06-29 16:13:51 -07:00
parent ce8faf0ecc
commit 29f84b5601
3 changed files with 39 additions and 1 deletions

View file

@ -29608,7 +29608,7 @@ def get_discover_listening_recommendations():
# don't carry it inline, so this works on existing data), then push globally-popular picks
# down per the user's dial. level 0 -> unchanged. Fail-soft: any hiccup leaves the order.
try:
level = float(config_manager.get('discovery.adventurousness', 0.3) or 0)
level = float(config_manager.get('discover.adventurousness', 0.3) or 0)
except (TypeError, ValueError):
level = 0.0
if level > 0 and stored:

View file

@ -6201,6 +6201,30 @@
</div>
</div><!-- end Listening Stats body -->
<!-- Discovery (collapsible tile) -->
<div class="settings-section-header collapsed" data-stg="library" onclick="this.classList.toggle('collapsed'); const b=this.nextElementSibling; b.classList.toggle('collapsed'); b.style.display=b.classList.contains('collapsed')?'none':''">
<span class="settings-section-arrow">&#9660;</span>
<h3>Discovery</h3>
<span class="settings-section-hint">How adventurous your recommendations are</span>
</div>
<div class="settings-section-body collapsed" data-stg="library" style="display:none">
<div class="settings-group" data-stg="library">
<div class="form-group">
<label>Adventurousness</label>
<div style="display:flex; align-items:center; gap:12px; margin-top:8px">
<span style="font-size:12px; color:#999">Safe</span>
<input type="range" id="discover-adventurousness" min="0" max="1" step="0.05" value="0.3" style="flex:1"
oninput="var v=document.getElementById('discover-adventurousness-val'); if(v) v.textContent=parseFloat(this.value).toFixed(2)">
<span style="font-size:12px; color:#999">Adventurous</span>
<span id="discover-adventurousness-val" style="min-width:36px; text-align:right; font-weight:700; color:var(--accent)">0.30</span>
</div>
<div class="help-text setting-help-body" style="margin-top:10px">
Higher pushes globally-popular artists down so more obscure, non-obvious picks surface; lower keeps recommendations safe and close to what you already know. Affects <strong>Based On Your Listening</strong>.
</div>
</div>
</div>
</div><!-- end Discovery body -->
<!-- Security & Access (collapsible tile) -->
<div class="settings-section-header collapsed" data-stg="advanced" onclick="this.classList.toggle('collapsed'); const b=this.nextElementSibling; b.classList.toggle('collapsed'); b.style.display=b.classList.contains('collapsed')?'none':''">
<span class="settings-section-arrow">&#9660;</span>

View file

@ -1480,6 +1480,13 @@ async function loadSettingsData() {
// Populate Listening Stats settings
document.getElementById('listening-stats-enabled').checked = settings.listening_stats?.enabled === true;
document.getElementById('listening-stats-interval').value = settings.listening_stats?.poll_interval || 30;
const _advEl = document.getElementById('discover-adventurousness');
if (_advEl) {
const _adv = settings.discover?.adventurousness;
_advEl.value = (typeof _adv === 'number') ? _adv : 0.3;
const _advVal = document.getElementById('discover-adventurousness-val');
if (_advVal) _advVal.textContent = parseFloat(_advEl.value).toFixed(2);
}
document.getElementById('lossy-copy-options').style.display =
settings.lossy_copy?.enabled ? 'block' : 'none';
@ -3495,6 +3502,13 @@ async function saveSettings(quiet = false) {
enabled: document.getElementById('listening-stats-enabled').checked,
poll_interval: parseInt(document.getElementById('listening-stats-interval').value) || 30,
},
discover: {
// Adventurousness dial (0 safe .. 1 obscure) — drives the Discover popularity penalty.
// Use the slider's value directly (0 is valid; don't `|| 0.3` it away).
adventurousness: document.getElementById('discover-adventurousness')
? parseFloat(document.getElementById('discover-adventurousness').value)
: 0.3,
},
m3u_export: {
enabled: document.getElementById('m3u-export-enabled').checked,
entry_base_path: document.getElementById('m3u-entry-base-path').value || ''