1// Jev's request log (jevLog.mjs), in the pane under the editor: every 2// request this page made of Jev, newest on top, pushing the older ones down 3// out of view. A section's line says it is asking until the answer lands, 4// then what Jev decided and how long it took. 5import { useSyncExternalStore } from 'react'; 6import { jevLog } from './jevLog.mjs'; 7import { themeColor as ACCENT } from './site.mjs'; 8 9// a pick's line starts with what asked for it (⏭ or 📻), so it has no icon of its own 10const ICON = { asking: '…', answered: '🎧', fallback: '⚠', ended: '·' }; 11const time = (at) => new Date(at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); 12 13export function JevLog() { 14 const entries = useSyncExternalStore(jevLog.subscribe, jevLog.get, jevLog.get); 15 return ( 16 <div className="flex flex-col min-h-0 h-full font-mono text-xs" aria-label="Jev's requests" data-testid="jev-log"> 17 <div className="px-3 py-1 font-bold shrink-0" style={{ color: ACCENT }}> 18 requests 19 </div> 20 <ol className="overflow-y-auto grow min-h-0 px-3 pb-2 space-y-1"> 21 {!entries.length && <li className="opacity-60">none yet: Jev's requests show here as the song asks them</li>} 22 {entries.map((e) => ( 23 <li 24 key={e.id} 25 className="leading-snug" 26 style={{ color: e.status === 'fallback' || e.status === 'error' ? '#f87171' : undefined }} 27 > 28 <span className="opacity-50">{time(e.at)} </span> 29 {ICON[e.status] && <span className={e.status === 'asking' ? 'animate-pulse' : ''}>{ICON[e.status]} </span>} 30 {e.song && <span className="opacity-60">{e.song} · </span>} 31 {e.text} 32 {typeof e.ms === 'number' && <span className="opacity-50"> · {e.ms} ms</span>} 33 </li> 34 ))} 35 </ol> 36 </div> 37 ); 38}