e1e89c07e4
Answers three gaps: no way to delete a single poker session (only clear_all),
no way to browse past sessions, and Lyra could only see aggregate stats.
- poker.list_sessions() (per-session summary + hand count + recap flag) and
poker.delete_session() (removes a session + its hands/reads/observations/
stacks/rituals; keeps the persistent villain file).
- /history page (date, stakes, venue, net, hours, recap link, per-row delete with
confirm) + /history/data + DELETE /history/{id}. Nav links from chat + HUD.
- recent_sessions read tool, added to the shared lookups so Lyra can answer
"how'd my last few sessions go?" in either mode.
- Delete is UI/CLI only — deliberately not a Lyra tool.
- test_modes.py +2 (list/delete, recent_sessions); 44 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
105 lines
5.6 KiB
HTML
105 lines
5.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
<meta name="theme-color" content="#070707" />
|
|
<title>Lyra — Sessions</title>
|
|
<style>
|
|
:root{--bg:#070707;--bg-elev:#0e0e0e;--bg-line:#141414;--border:#2a1d12;--text:#e8e8e8;
|
|
--fade:#8a8a8a;--accent:#ff7a00;--good:#8fd694;--low:#ff6b6b;--mid:#ffb347;}
|
|
*{box-sizing:border-box;}
|
|
html,body{margin:0;min-height:100%;background:var(--bg);color:var(--text);
|
|
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;-webkit-text-size-adjust:100%;}
|
|
header{position:sticky;top:0;z-index:10;background:var(--bg-elev);border-bottom:1px solid var(--border);
|
|
padding:env(safe-area-inset-top) 14px 0;}
|
|
.topbar{display:flex;align-items:center;gap:10px;padding:13px 0;}
|
|
.topbar h1{font-size:1.05rem;margin:0;font-weight:600;}
|
|
.topbar a.back{color:var(--accent);text-decoration:none;font-size:.92rem;}
|
|
.count{margin-left:auto;color:var(--fade);font-size:.8rem;}
|
|
main{max-width:640px;margin:0 auto;padding:12px 12px 40px;}
|
|
.summary{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;}
|
|
.pill{font-size:.8rem;color:var(--fade);background:var(--bg-elev);border:1px solid var(--border);
|
|
border-radius:999px;padding:4px 11px;} .pill b{color:var(--text);}
|
|
.row{display:flex;align-items:center;gap:12px;background:var(--bg-elev);border:1px solid var(--border);
|
|
border-radius:10px;padding:10px 12px;margin-bottom:8px;}
|
|
.row .body{flex:1;min-width:0;text-decoration:none;color:var(--text);}
|
|
.row .body:active{opacity:.7;}
|
|
.ln1{font-size:.95rem;} .ln1 .live{color:var(--accent);font-size:.7rem;border:1px solid var(--accent);
|
|
border-radius:999px;padding:0 6px;margin-left:6px;text-transform:uppercase;letter-spacing:.4px;}
|
|
.ln2{font-size:.76rem;color:var(--fade);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
|
.net{flex:none;font-variant-numeric:tabular-nums;font-weight:700;}
|
|
.net.up{color:var(--good);} .net.down{color:var(--low);} .net.flat{color:var(--fade);}
|
|
.del{flex:none;background:none;border:1px solid var(--border);color:var(--fade);border-radius:8px;
|
|
padding:6px 9px;cursor:pointer;-webkit-tap-highlight-color:transparent;font-size:.9rem;}
|
|
.del:active{background:#3a1414;color:var(--low);border-color:var(--low);}
|
|
.empty{color:var(--fade);text-align:center;padding:46px 16px;}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<div class="topbar">
|
|
<h1>📚 Sessions</h1>
|
|
<a class="back" href="/">← Chat</a>
|
|
<a class="back" href="/session">🎬 Live</a>
|
|
<span class="count" id="count"></span>
|
|
</div>
|
|
</header>
|
|
<main id="root"><p class="empty">Loading…</p></main>
|
|
|
|
<script>
|
|
function esc(s){const d=document.createElement('div');d.textContent=s==null?'':String(s);return d.innerHTML;}
|
|
function money(v){if(v==null)return '—';const n=Number(v);return (n>0?'+$':n<0?'-$':'$')+Math.abs(n).toLocaleString();}
|
|
function netClass(v){return v==null?'flat':v>0?'up':v<0?'down':'flat';}
|
|
|
|
async function del(id, label){
|
|
if(!confirm(`Delete session ${label}? This removes its hands, reads, stacks and rituals. Can't be undone.`)) return;
|
|
try{
|
|
const r=await fetch(`/history/${id}`,{method:'DELETE'});
|
|
if(!r.ok) throw new Error('HTTP '+r.status);
|
|
load();
|
|
}catch(e){alert('Delete failed: '+e.message);}
|
|
}
|
|
|
|
async function load(){
|
|
const root=document.getElementById('root');
|
|
try{
|
|
const r=await fetch('/history/data',{cache:'no-store'});
|
|
const sessions=(await r.json()).sessions||[];
|
|
document.getElementById('count').textContent=`${sessions.length} session${sessions.length===1?'':'s'}`;
|
|
if(!sessions.length){root.innerHTML='<p class="empty">No sessions yet. Start one from chat in ♠ Cash mode.</p>';return;}
|
|
|
|
const closed=sessions.filter(s=>s.net!=null);
|
|
const totNet=closed.reduce((a,s)=>a+(s.net||0),0);
|
|
const totHrs=closed.reduce((a,s)=>a+(s.hours||0),0);
|
|
const summary=`<div class="summary">
|
|
<span class="pill"><b>${sessions.length}</b> sessions</span>
|
|
<span class="pill">net <b>${money(totNet)}</b></span>
|
|
${totHrs?`<span class="pill"><b>${totHrs.toFixed(1)}h</b></span>`:''}
|
|
${totHrs?`<span class="pill">${money(Math.round(totNet/totHrs))}/hr</span>`:''}
|
|
</div>`;
|
|
|
|
root.innerHTML=summary+sessions.map(s=>{
|
|
const title=[s.stakes,s.game].filter(Boolean).join(' ')||'Session';
|
|
const live=s.status==='live'?'<span class="live">live</span>':'';
|
|
const date=(s.started_at||'').slice(0,10);
|
|
const meta=[date,s.venue,`${s.hands} hand${s.hands===1?'':'s'}`,
|
|
s.hours?`${(+s.hours).toFixed(1)}h`:''].filter(Boolean).join(' · ');
|
|
const href=s.has_recap?`/recap/${s.id}`:`/session`;
|
|
const net=s.net!=null?money(s.net):(s.status==='live'?'live':'—');
|
|
return `<div class="row">
|
|
<a class="body" href="${href}">
|
|
<div class="ln1">${esc(title)} <span style="color:var(--fade)">@ ${esc(s.venue||'?')}</span>${live}</div>
|
|
<div class="ln2">${esc(meta)}${s.has_recap?' · recap ✓':''}</div>
|
|
</a>
|
|
<span class="net ${netClass(s.net)}">${net}</span>
|
|
<button class="del" title="Delete session" onclick="del(${s.id}, '#${s.id} ${esc(title)}')">🗑</button>
|
|
</div>`;
|
|
}).join('');
|
|
}catch(e){root.innerHTML='<p class="empty">Couldn\'t load sessions.</p>';}
|
|
}
|
|
load();
|
|
</script>
|
|
</body>
|
|
</html>
|