Quick-logged hands (log_hand) store flat fields with no structured JSON, so the
hand viewer dead-ended with "no structured data to replay" — even when the full
street-by-street action was captured (e.g. the KhQh-vs-Louis hand). Now:
- hand.html renders a readable STATIC view of any flat hand (hero cards, board,
street narratives, result, lesson) instead of erroring; also handles empty/garbage
structured rows by falling back to the flat view.
- "▶ Build replay" button + poker.reconstruct_hand + POST /hand/{id}/reconstruct:
parse a flat hand's narrative into structured form on demand, making any
quick-logged hand replayable without an LLM call per log during live play.
- test_modes.py +1 (reconstruct wiring).
(Also reconstructed the two live Meadows hands and removed one empty hand in the DB.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Desktop nav was scattered and inconsistent — the chat header was crammed with
cross-page links and each standalone page had its own ad-hoc, incomplete back-links
(e.g. /hands could only reach Chat). Now a single nav.js (one source of truth, no
build step) injects a left sidebar on desktop (>=769px) with active-page
highlighting across Chat/Session/History/Hands/Mind/Journal/Logs + Settings.
- nav.js: injects sidebar + its own CSS; body gets padding-left on desktop; hidden
on mobile (each page keeps its bottom bar / back-links there).
- Included on every page (index, session, history, hands, self, journal, logs,
recap, hand).
- Decluttered the chat header: removed the now-redundant cross-page links (kept the
chat-specific session selector + inline Live Log toggle).
- Sidebar Settings opens the chat modal, or navigates to /?settings=1 from elsewhere.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- View any past session as a read-only HUD: /session?id=N (hud(session_id) +
/session/data?id=); /history rows now link there. Closed sessions show played
duration + final net; recap link when one exists.
- Edit session details during or after play: poker.update_session (recomputes net
when buy-in/cash-out change), PATCH /session/{id}, an update_session tool ("venue
was actually Bellagio", "I bought in for 600"), and an inline ✎ Edit form on the HUD.
- Rituals attach to the most-recent session post-close (poker.review_session_id),
so scar/confidence/reset work while reviewing after you rack up.
- Edit form is poll-safe (won't clobber mid-edit); past-session view doesn't poll.
- test_modes.py +3 (edit, review rituals, past-session HUD); 49 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lyra.db is on disk-backed ext4, where each WAL commit fsync'd (~0.15s here). Every
chat turn does several writes (remember user+assistant, summaries, poker logging),
so this was adding real per-turn latency, and made the dream loop + tests crawl.
synchronous=NORMAL is WAL's recommended companion: durable across app crashes, only
a power/OS crash can drop the last txn (never corrupts). Per-write dropped from
~0.4s to ~0.001s; test suite 72s -> 24s.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Embeddings shared LOCAL_BASE_URL with the local chat backend (the 3090's Ollama),
so the 3090 being powered off killed all chat (every turn embeds to recall + to
store). Add a separate EMBED_BASE_URL (defaults to LOCAL_BASE_URL, so existing
setups are unchanged) and use it in llm.embed.
Deployed: a user-level Ollama (CPU) now runs nomic-embed-text on lyra-cortex
itself; EMBED_BASE_URL points at 127.0.0.1:11434 while LOCAL_BASE_URL still points
the local chat backend at the 3090. Local embeddings verified identical to the
3090's (cosine 0.999994, 768-dim) so existing vectors stay valid — no re-embed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two bugs surfacing in the log during live play:
- SUMMARY_BACKEND=mi50 (llama.cpp, 32B) was fed 24k-char chunks → "Context size
has been exceeded". Chunk budget is now backend-aware: cloud 24k, local/mi50 8k,
and the merge step recurses so merged partials never overflow either.
- maybe_summarize ran inline in the chat turn and retried 4× with backoff (~30s),
stalling the reply and surfacing the error. It now runs in a background daemon
thread, swallows errors (consolidation is best-effort maintenance), and dedupes
so at most one summary per session runs at a time.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The execCommand fallback returned true but copied nothing because the textarea
was readOnly=false. iOS only copies from a readOnly + contentEditable field with
a real Range selection + setSelectionRange — fixed that. Also skip the async
Clipboard API unless window.isSecureContext (on the plain-HTTP LAN PWA it could
resolve without copying, showing a false checkmark). If programmatic copy still
fails, fall back to a prompt() with the text so it can be copied by hand, and only
show the ✓ on real success.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stops fat-fingered early sends. The single-line input is now an auto-growing
textarea (Claude-app style): Enter inserts a newline and expands the box (up to a
cap, then it scrolls); you tap the ↑ arrow to send. ⌘/Ctrl+Enter still sends from
a hardware keyboard. Send button is now a round arrow; box resets to one line
after sending. Mobile button is a 42-44px touch target.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each user and assistant message gets a copy button (⧉ → ✓) that puts the whole
message on the clipboard. Assistant copies the raw markdown (its dataset.raw);
user copies its text.
- copyToClipboard uses the async Clipboard API when available and falls back to a
hidden-textarea + execCommand with an explicit iOS selection range, so it works
on the iPhone PWA over plain-HTTP LAN (no secure context).
- Copy sits in the assistant rate-bar and in a right-aligned bar on user bubbles;
tools stay visible on touch (@media hover:none) since there's no hover on iOS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
She could write everything the HUD shows but not read most of it back (stack,
live net, alligator state, scar/confidence entries) — so "what's my live net?"
or "what's in my confidence bank?" was a memory guess.
- session_state tool returns the same bundle the HUD renders, as a readable
summary; added to the Cash toolset.
- Cash card tells her the HUD exists, that she and it share the same data, and to
answer where-am-I questions from session_state, never memory.
- test_modes.py +1; 42 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brian's own rituals (mined from his logs) become first-class, live tools instead
of post-hoc recap sections:
- Scar Note — instructive mistakes with the punt/cooler/standard distinction.
- Confidence Bank — good process, banked regardless of result.
- Alligator Blood — invokable adversity state; she suggests it when he's
card-dead/short/stuck, and her coaching register shifts while it's on (live
state injected into context per-turn via chat._mode_state_note).
- Reset — tilt circuit-breaker; mental marker only, stats stay continuous.
poker_rituals table + log_ritual/list_rituals/set_alligator/alligator_active;
4 tools added to the Cash toolset and taught in the mode card; HUD gains a 🐊
banner + Confidence Bank + Scar Notes panels; recap grounded via _rituals_block.
tests/test_modes.py +5 ritual tests; 41 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lockfile caught up to the pyproject version bump from 1f5a321 (it wasn't
regenerated at the time). No dependency changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Mobile bottom tab bar: Chat · Hands · Mind · More. "More" opens the drawer
for the long tail (Journal, Log, Settings, sessions); hamburger retired.
- Auto-hides while the keyboard is open (body.kb) so the input pins to the
keyboard; mobile-only (desktop keeps its header nav).
- Removed now-redundant Mind/Hands from the drawer + their listeners.
- Bottom-fill fix: #chat uses 100dvh (the visible viewport) — 100vh/inset:0
reach into the home-indicator zone iOS won't comfortably show, clipping the
bar; dvh/svh exclude it. Tab bar is flex:none with a small fixed bottom
padding (safe-area padding double-counts at dvh height), and the body bg
matches the bar so any strip below #chat is seamless.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- llm.chat_call_stream: streaming generator for all 3 backends (Ollama NDJSON,
OpenAI/MI50 SSE), accumulating tool-call fragments by index.
- chat.respond_stream: mirrors respond()'s tool loop and persistence/compaction,
yielding ("delta", text) / ("tool", name) / ("done", reply).
- POST /v1/chat/stream: SSE endpoint; blocking generator bridged to async via a
worker thread + asyncio.Queue. Old completions endpoint kept as fallback.
- Client streams into a live bubble with a blinking caret; rAF-throttled render
(no full re-parse per token) and instant scroll during stream — fixes iOS
Safari ghosting from per-token smooth-scroll. Falls back to the blocking
endpoint only if nothing streamed (no double-persist).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
M1 — PWA mechanics:
- Generate real app icons (apple-touch-icon + manifest 192/512/maskable)
via pure-stdlib gen_icons.py; iOS uses apple-touch-icon, not manifest icons.
- viewport-fit=cover + env(safe-area-inset-*) on header/input/menu so content
clears the notch and home indicator.
- Dynamic height pinned to the VisualViewport (height + offsetTop, re-measured
across the keyboard animation) so the input stays above the iOS keyboard;
100dvh fallback. Kills the squish/gap bugs in standalone mode.
- overscroll containment; flesh out manifest (scope, portrait, maskable).
M2 — visual redesign:
- Realign style.css to the warm low-glow RTO palette already used by the
standalone pages (#0e0e0e panels, #2a1d12 borders); remove the neon
saturated-orange borders and ~15 glow shadows.
- Reserve filled accent for one element (Send); glow only on status pulse +
input focus. Flat warm message bubbles with tail corners.
- Reclaim the mobile header into [≡] Lyra · [status dot]; drop the redundant
status bar (relay status now the header dot, updated in checkHealth).
- prefers-reduced-motion support; fix undefined var(--text); real light-mode tokens.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>