# "Chat with Claude" Floating Widget — Design Spec
_2026-05-26 · workstation-lc · status: draft for review_
Problem
The dashboard's Ask-Claude is a form/template panel on one tab. We want a
floating "chat with us"-style widget — a contrasted launcher (white circle +
chat-bubble glyph) fixed lower-right, opening a conversational popover — so
lucas, darshan, and lyreco (Pierre) can just chat with their service-Claude
to produce deliverables, from any tab (it follows them around). It must keep a
persistent, long-lived context per user that survives new threads / session
expiry / reloads, auto-compacting at ~1M tokens the way Claude's own context does.
Goals / scope
- Global, persistent widget (lives outside the tab-pages; visible on every tab) for the 3 dashboard users.
- Conversational multi-turn chat over the existing
claude -prunner. - Persistent per-user context with ~1M-token compaction → carry-over.
- v1 deliverable handling = read + copy (markdown/tables rendered, per-answer copy). No save-to-Reports/export, uploads, or voice in v1.
- Bypass per decision:
lucas/lyrecorun immediately;darshankeeps the extraction-approval gate. - Superadmin/Lucas Chat Oversight (calibration): a panel gated to {lucas, superadmin} (NOT darshan) to view + edit/clear any user's carry-over memory and see their recent chat turns. Every oversight edit/clear is audit-logged (who changed whose memory). Darshan is chat-only (no oversight).
Architecture
`
Browser (any tab)
┌ launcher (white ⬤ + bubble, fixed lower-right) ──toggles── popover
│ popover: thread (user/Claude bubbles, markdown, copy) + input
│ holds current thread token + cumulative tokens in localStorage
└────────────────────────── POST /api/chat (JSON) ───────────────────────┐
▼
dashboard/app.py POST /api/chat {message, continue_token?, carry?}
• _ac_user(request) → attribute + bypass decision
• extraction gate: lucas/lyreco bypass; darshan → DM-approval (existing flow)
• prompt = (carry-over prefix if reseeding) + message
• _ac_run_claude_with_fallback(prompt, resume_session_id, parent_token, timeout=floor)
• accumulate usage tokens on the thread; if >= COMPACT_AT → flag compaction
• returns {token, answer_markdown, session_id, status, cumulative_tokens, compacted}
│
persistent context (server): │
• per-user carry-over logs/chat_memory/`
Behaviour
- Multi-turn: widget sends the prior turn's
tokenascontinue_token→_ac_run_claude_with_fallbackresumes that Claude session (existing thread-depth + resume-fallback logic). - Persistent memory via compaction: the endpoint accumulates the thread's token usage (from the
claude -presult JSON). When cumulative ≥COMPACT_AT(default 850_000, safely under 1M; env-tunable), it runs a compaction turn — asks Claude to condense the conversation into a compact carry-over, writes it tologs/chat_memory/, and the next turn starts a fresh thread seeded with that carry-over (no.md continue_token). Cumulative resets. - Durability: the carry-over also reseeds a fresh thread when
--resumefails (session aged out) or the user returns after a reload — so context persists indefinitely; only raw transcript is shed at the 1M mark. - Bypass: reuse
_AC_EXTRACTIONdetection.user in {lucas, lyreco}→ run now (audit the bypass).darshan+ extraction-shaped → existing approval gate (DM commander,_ac_send_dm), returned to the widget asstatus="pending_approval". - Timeout: the existing per-user floor already gives Lucas 1200s; chat uses the same
effective_timeoutpath.
Components (each separable)
1.POST /api/chat (dashboard/app.py) — JSON entry point wrapping _ac_run_claude_with_fallback; bypass/gate decision; token accumulation; returns JSON. No HTML.
2. core/chat_memory.py (pure-ish) — carry_path(user), read_carry(user), write_carry(user, text), should_compact(cumulative, threshold). Unit-testable; file I/O isolated to read/write.
3. Compaction step — a function that, given a thread token, asks the runner to summarize → write_carry → returns the carry text. Triggered by the endpoint when should_compact.
4. Widget frontend — markup in dashboard/templates/home.html (launcher + popover, placed once outside the tab s, position:fixed), a block, and JS (toggle, send, render bubbles + markdown, copy button, localStorage thread token). Matches the dashboard's dark theme + existing htmx/vanilla-JS conventions.
Data flow (a turn)
1. Widget POSTs{message, continue_token} (+ a hint if it should reseed).
2. Endpoint: user → bypass/gate; assemble prompt (carry prefix only when reseeding); run; accumulate tokens; if over threshold, compact (write carry) and mark compacted.
3. Returns {token, answer_markdown, session_id, cumulative_tokens, compacted, status}.
4. Widget renders the answer bubble, stores the new token, and on compacted=true quietly drops continue_token next turn (fresh thread, carry-seeded server-side).
Error handling
- Run timeout →
{status:"timeout"}→ widget shows a retry affordance. --resumesession lost → runner already falls back to a fresh thread; here we additionally seed it with the carry-over.- Gate (darshan) →
{status:"pending_approval"}→ widget shows "sent for approval." - Compaction failure → keep the existing carry-over, don't reset the tally (degrade safely; log).
Testing
core/chat_memory.py— pure unit tests (should_compactthreshold, carry read/write round-trip in a tmp dir).- bypass decision — unit test the user→(run|gate) logic.
- endpoint — integration smoke: one real
/api/chatturn returns an answer + token; a follow-up withcontinue_tokenthreads. - widget — manual HTTP/DOM smoke (launcher renders, posts, bubble appears).
Phasing (2 plans)
- Plan A — backend + memory:
/api/chat+core/chat_memory.py+ compaction + bypass/gate + token accounting. The engine; fully testable headless. - Plan B — the widget UI: launcher + popover + thread + copy + localStorage threading, wired to
/api/chat; dashboard restart + HTTP/DOM smoke.
Open items
COMPACT_ATexact value (default 850k; tune after observing real usage).- Carry-over store: flat per-user
.md(chosen) vs a PG table —.mdkeeps it simple + inspectable; revisit if multi-instance. - Per-user carry for darshan/lyreco too (yes — one file each; same mechanism).
- Later (not v1): save-to-Reports, export, "pin" memory, file upload.
Credits / reuse
Reuses the Ask-Claude runner (_ac_run_claude_with_fallback), session-resume threading, extraction gate + _DASH_BYPASS_USERS, and Lucas's 1200s timeout floor (all shipped this session). Compaction mirrors Claude's own auto-compaction model (Pierre's framing).