feat: undo / delete logged entries (fix fat-fingered live logging)

Previously the only delete was whole-session, so a mis-logged stack or a
mis-parsed hand was stuck on the HUD. Now:

- undo_last tool ("scratch that") — deletes the most recent hand/stack/read/
  scar/confidence/reset in the live session; added to the Cash toolset.
- poker.delete_hand/stack/read/ritual + delete_entry dispatch + undo_last.
- DELETE /session/entry/{kind}/{id} endpoint.
- HUD: per-row × delete buttons on hands, confidence-bank, and scar-note rows
  (stack/read deletes via the tool). Row ids now surfaced in the hud() bundle.
- test_modes.py +2 (undo_last across kinds, tool handler); 46 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 04:32:04 +00:00
parent df591e4e01
commit 67cf51a53f
6 changed files with 182 additions and 8 deletions
+86 -2
View File
@@ -242,6 +242,90 @@ def delete_session(session_id: int) -> dict:
return counts
# --- per-entry deletes / undo (fix fat-fingered live logging) ---
def delete_hand(hand_id: int) -> bool:
"""Delete one hand and any player observations derived from it."""
conn = _c()
with conn:
conn.execute("DELETE FROM player_observations WHERE hand_id = ?", (hand_id,))
cur = conn.execute("DELETE FROM poker_hands WHERE id = ?", (hand_id,))
return cur.rowcount > 0
def delete_stack(stack_id: int) -> bool:
conn = _c()
with conn:
cur = conn.execute("DELETE FROM poker_stack_log WHERE id = ?", (stack_id,))
return cur.rowcount > 0
def delete_read(read_id: int) -> bool:
conn = _c()
with conn:
cur = conn.execute("DELETE FROM player_reads WHERE id = ?", (read_id,))
return cur.rowcount > 0
def delete_ritual(ritual_id: int) -> bool:
conn = _c()
with conn:
cur = conn.execute("DELETE FROM poker_rituals WHERE id = ?", (ritual_id,))
return cur.rowcount > 0
def delete_entry(kind: str, entry_id: int) -> bool:
"""Dispatch a per-entry delete by kind — for the HUD's row delete buttons."""
return {
"hand": delete_hand, "stack": delete_stack,
"read": delete_read, "ritual": delete_ritual,
}.get(kind, lambda _id: False)(entry_id)
def undo_last(kind: str, session_id: int | None = None) -> str | None:
"""Delete the most-recent entry of `kind` in the live session and return a short
description of what was removed (None if there was nothing). For "scratch that".
kind: hand | stack | read | scar | confidence | reset | ritual.
"""
sid = _resolve(session_id)
if sid is None:
raise ValueError("no live session")
k = (kind or "").lower().strip()
if k in ("scar", "confidence", "reset", "ritual"):
sql = ("SELECT id, kind, content FROM poker_rituals WHERE session_id = ? "
+ ("AND kind = ? " if k != "ritual" else "AND kind IN ('scar','confidence','reset') ")
+ "ORDER BY id DESC LIMIT 1")
params = (sid, k) if k != "ritual" else (sid,)
r = _c().execute(sql, params).fetchone()
if not r:
return None
delete_ritual(r["id"])
label = _RITUAL_LABEL.get(r["kind"], r["kind"])
return f"{label}" + (f": {r['content']}" if r["content"] else "")
table, desc_cols = {
"hand": ("poker_hands", "position, hole_cards"),
"stack": ("poker_stack_log", "amount"),
"read": ("player_reads", "note"),
}.get(k, (None, None))
if not table:
return None
r = _c().execute(
f"SELECT id, {desc_cols} FROM {table} WHERE session_id = ? ORDER BY id DESC LIMIT 1",
(sid,),
).fetchone()
if not r:
return None
delete_entry(k, r["id"])
if k == "hand":
return f"hand ({(r['position'] or '?')} {r['hole_cards'] or ''})".strip()
if k == "stack":
return f"stack ${r['amount']:g}"
return f"read: {r['note'][:50]}"
def live_session() -> dict | None:
"""The current open session, if any."""
r = _c().execute(
@@ -308,7 +392,7 @@ def stack_log(session_id: int | None = None) -> list[dict]:
if sid is None:
return []
return [dict(r) for r in _c().execute(
"SELECT amount, created_at FROM poker_stack_log WHERE session_id = ? ORDER BY id",
"SELECT id, amount, created_at FROM poker_stack_log WHERE session_id = ? ORDER BY id",
(sid,),
).fetchall()]
@@ -996,7 +1080,7 @@ def hud(session_id: int | None = None) -> dict | None:
rituals = list_rituals(sid)
by_kind = lambda k: [ # noqa: E731
{"content": r["content"], "classification": r["classification"],
{"id": r["id"], "content": r["content"], "classification": r["classification"],
"hand_id": r["hand_id"], "at": r["created_at"]}
for r in rituals if r["kind"] == k
]