diff --git a/CLAUDE.md b/CLAUDE.md index 27e5297..928c971 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -367,6 +367,12 @@ v2026.09.07.003 Library repair, from an audit of a real 50-song backup. Three r confirmed corrupted flat), **bare letter only** and length-preserving. **Finding worth keeping:** ~half that library has no chords in the data at all — lyrics-only imports. No parser change fixes that. +v2026.09.18.001 Interface review, HIGH fixes: setlist/song rows and chord pills are real -
SetList69v2026.09.07.003
+
SetList69v2026.09.18.001
@@ -1070,7 +1086,10 @@

Songs

return real>0&&real*2>=musical; } function pitchClass(key){const m=(key||"").match(CHORD_RE);return m&&m[1]in IDX?IDX[m[1]]:-1;} -function keyColor(key){const pc=pitchClass(key);if(pc<0)return"#7a7160";const hue=(pc*30)%360;return"hsl("+hue+" 52% 42%)";} +/* Pill fills carry white 12.5px text, so each hue needs >= 4.5:1 against white. At 42% lightness + the yellow-green-cyan arc (hues 30-180: C# D D# E F F#) measured 2.68-4.46; at 30% it measures + 4.92-7.42. The other six roots already pass at 42% (5.57-9.80). */ +function keyColor(key){const pc=pitchClass(key);if(pc<0)return"#7a7160";const hue=(pc*30)%360;const light=(hue>=30&&hue<=180)?30:42;return"hsl("+hue+" 52% "+light+"%)";} /* Nashville Number System: chord root -> scale degree relative to the key. Computed from the RAW (un-transposed/un-capo'd) chord + raw key, so the numbers are key-invariant by construction (transpose/capo shift both together). Suffix kept. NASH is the major-scale / "flat-three" set. */ @@ -1240,7 +1259,7 @@

Songs

const known=cell.raw?looksChord(cell.raw):false; const crow=lineHasChord ? ''+(cell.chord?(known - ? ''+((nashville&&nashKeyPc>=0)?nashPill(toNashville(cell.raw,nashKeyPc)):esc(cell.chord))+'' + ? '' : (CHORD_CUE.test(cell.raw) ? ''+esc(cell.raw)+'' : ''+esc(cell.raw)+'')) @@ -1355,12 +1374,12 @@

Songs

const n=sl.songIds.length; const keys=[...new Set(sl.songIds.map(id=>(state.songs.find(s=>s.id===id)||{}).key).filter(Boolean))]; const dots=keys.slice(0,6).map(k=>'').join(""); - const notesEl=sl.notes?'
'+esc(sl.notes)+'
':""; + const notesEl=sl.notes?''+esc(sl.notes)+'':""; const eid=esc(sl.id); /* Row carries name + count only. Reorder/settings/delete moved to the context menu (long-press, or the ⋯ button for pointer devices) — three always-visible icons per row was chrome, and it squeezed the name. i is kept on the ⋯ so the menu can offer move up/down. */ - return '
'+esc(sl.name)+'
'+n+' song'+(n!==1?"s":"")+'  '+dots+'
'+notesEl+'
'; + return '
'; }).join(""); } function renderSetSongs(){ @@ -1378,7 +1397,7 @@

Songs

/* Played shows as a ✓ in the number slot plus a dimmed row — one signal, not three. Remove-from-set lives in the context menu; the row keeps the drag handle, which the reorder code keys on. */ const numCell=isPlayed?'':''+(i+1)+''; - return '
'+numCell+'
'+esc(s.title)+'
'+esc(s.sub||"")+'
'+(s.key?''+esc(s.key)+'':"")+'
'; + return '
'+numCell+''+(s.key?''+esc(s.key)+'':"")+'
'; }).join(""); document.getElementById("resetPlayed").style.display=anyPlayed?"":"none"; } @@ -1390,7 +1409,7 @@

Songs

const songs=q?all.filter(s=>s.title.toLowerCase().includes(q)||(s.sub||"").toLowerCase().includes(q)):all; if(!all.length){wrap.innerHTML='
♪
No songs yet.
';return;} if(!songs.length){wrap.innerHTML='
No matches.
';return;} - wrap.innerHTML=songs.map(s=>{const eid=esc(s.id);return '
'+esc(s.title)+'
'+esc(s.sub||"")+'
'+(s.key?''+esc(s.key)+'':"")+'
';}).join(""); + wrap.innerHTML=songs.map(s=>{const eid=esc(s.id);return '
'+(s.key?''+esc(s.key)+'':"")+'
';}).join(""); } /* ===== open song ===== */ @@ -1456,7 +1475,9 @@

Songs

let dockTimer=null; function summonDock(){const d=document.querySelector(".dock");if(!d)return;d.classList.remove("away"); clearTimeout(dockTimer);dockTimer=setTimeout(dismissDock,4000);} -function dismissDock(){clearTimeout(dockTimer);const d=document.querySelector(".dock");if(d)d.classList.add("away");} +function dismissDock(){clearTimeout(dockTimer);const d=document.querySelector(".dock");if(!d)return; + if(d.querySelector(":focus-visible")){dockTimer=setTimeout(dismissDock,4000);return;} // keyboard is in the dock: keep it, re-check later + d.classList.add("away");} /* scroll progress bar along the top edge of the dock (auto-scroll AND manual) */ const updateScrollProg=(function(){ @@ -1677,7 +1698,7 @@

Songs

const raw=await linkToSong(location.hash); // Strip the fragment either way, so a reload doesn't re-prompt for a song already added. try{history.replaceState(null,"",location.href.split("#")[0]);}catch(_){} - if(!raw){toast("That song link couldn't be read");return;} + if(!raw){toast("That song link couldn't be read",{sticky:true});return;} offerSharedSong(raw); } async function shareSong(id){ @@ -1948,7 +1969,12 @@

Songs

} /* ===== misc ===== */ -let toastT;function toast(m){const t=document.getElementById("toast");t.textContent=m;t.classList.add("show");clearTimeout(toastT);toastT=setTimeout(()=>t.classList.remove("show"),1900);} +/* toast(msg) auto-clears in 1.9s. toast(msg,{sticky:true}) — or any message starting with ⚠ — + stays until tapped: an error the player has to act on must not vanish before it can be read. */ +let toastT;function toast(m,opts){const t=document.getElementById("toast");const sticky=!!(opts&&opts.sticky)||/^⚠/.test(m); + t.textContent=m;t.classList.toggle("sticky",sticky);t.classList.add("show");clearTimeout(toastT); + if(!sticky)toastT=setTimeout(()=>t.classList.remove("show"),1900);} +document.getElementById("toast").addEventListener("click",()=>document.getElementById("toast").classList.remove("show")); /* Update banner: set by the service-worker block. Flashes once on the home screen (never mid-song — an unexpected reload during a set would be worse than a stale version), auto-hides after 6s; the header ⟳ stays visible so the update can still be applied later. */ @@ -2126,7 +2152,7 @@

Songs

showPrompt("Add a song from a link","Paste the song link here","",async v=>{ if(!v||!v.trim())return; const raw=await linkToSong(v); - if(!raw){toast("That song link couldn't be read");return;} + if(!raw){toast("That song link couldn't be read",{sticky:true});return;} offerSharedSong(raw); }); }); @@ -2263,7 +2289,7 @@

Songs

const q=document.getElementById("pasteSearch").value.trim(); if(!text){toast("Paste some lyrics first");return;} const parsed=parseImport(text,q||"New Song"); - if(!parsed){toast("Couldn't parse — try pasting in ChordPro format");return;} + if(!parsed){toast("Couldn't parse — try pasting in ChordPro format",{sticky:true});return;} document.getElementById("pasteModal").classList.remove("show"); openEditor(null); document.getElementById("fTitle").value=parsed.title||q||""; @@ -2384,7 +2410,7 @@

Songs

function install(containerId,getCtx,open){ const el=document.getElementById(containerId); el.addEventListener("touchstart",e=>{ - if(e.target.closest("button")||e.target.closest(".drag-handle"))return; + if(e.target.closest("button:not(.meta)")||e.target.closest(".drag-handle"))return; // .meta IS the row (a button since v2026.09.18.001) const card=e.target.closest(".card");if(!card)return; const ctx=getCtx(card);if(!ctx)return; clearTimeout(lpTimer); diff --git a/sw.js b/sw.js index 337fcdb..5e520b4 100644 --- a/sw.js +++ b/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'setlist69-v2026.09.07.003'; +const CACHE = 'setlist69-v2026.09.18.001'; const PRECACHE = [ './', './index.html',