Skip to content

feat(dashboard): AI Discuss panel + per-row Analyze button (#419) - #431

Closed
jonathaneoliver wants to merge 1 commit into
feat/llm-prompt-418from
feat/llm-dashboard-ui-419
Closed

feat(dashboard): AI Discuss panel + per-row Analyze button (#419)#431
jonathaneoliver wants to merge 1 commit into
feat/llm-prompt-418from
feat/llm-dashboard-ui-419

Conversation

@jonathaneoliver

Copy link
Copy Markdown
Owner

Summary

What's new

File Role
content/shared/llm-chat.js DOM-free SSE consumer + window.LLMChat API
content/dashboard/llm-discuss-panel.js Discuss panel for session-viewer.html
content/dashboard/llm-analyze-modal.js Analyze button + modal for sessions.html
content/dashboard/session-viewer.html +2 script tags
content/dashboard/sessions.html +2 script tags

Behaviour

Discuss panel (session-viewer.html):

  • 💬 floating toggle bottom-right; slide-out right-side panel.
  • Multi-turn chat tied to the URL's session=<id> parameter.
  • localStorage persistence (last 50 messages per session).
  • Model picker dropdown sourced from /api/llm_profiles.
  • Budget meter above history; polls /api/llm_budget every 30s while open.
  • Cmd/Ctrl+Enter sends; Escape closes (when not streaming); focus returns to toggle.
  • Cited mm:ss.ms timestamps render as click-to-seek anchors that dispatch document.dispatchEvent(new CustomEvent('llm:seek')). Wiring to the chart cursor is a follow-up.

Analyze button (sessions.html):

  • MutationObserver injects an Analyze button next to each row's star (the canonical [data-star-cell][data-session-id] hook).
  • Click opens a modal that streams a one-shot overview from the same backend.
  • z-index:3 sits above the row's stretched-link overlay so clicks don't navigate away.

Pre-commit code-review fixes (this is the meaningful section)

The Sonnet pass caught one critical bug: my initial implementation queried .session-card divs for the picker — but sessions.html actually renders rows as <tr> with the session_id on a child <span data-star-cell>. The button would never have appeared. Fixed to target the real DOM shape, with z-index above the row overlay.

Plus important polish:

  • SSE parser now tolerates spec-conformant event:foo (no space) per W3C EventSource. Original required the literal event: (with space). Forwarder happens to emit the spaced form, but a future emit site or rewriting proxy would have silently dropped events.
  • Profile-select race — Send button starts disabled ("Loading models…"); guards sendMessage until the profile fetch resolves. Stops a fast Cmd+Enter from sending with an empty profile.
  • Modal Esc handler now removes itself on every close path (backdrop click, X button, Escape). Old code only removed on Escape — opening many modals would leak listeners.
  • Discuss panel — Esc closes (when no stream is active); focus returns to the toggle button after close (WCAG 2.4.3).
  • CSS namespace tightened: .llm-msg / .llm-ts.llmchat-msg / .llmchat-ts to avoid future collision with generic class names.
  • Duplicate id="llm-profile-select" removed — label now wraps the select so no id is needed (script-double-load can't create dupes).
  • readOnly during streaming instead of disabled so users can still select / copy their own pending text.

Cross-compare per memory rule

testing-session.html (the LIVE control plane) is intentionally NOT touched. Analytical AI is for archived replay; the live-session control AI surface is sub-issues #424 (read-only inspection tools — get_session_state, get_failure_settings) and #425 (HITL mutation tools with propose/apply UX), both planned for after the analytical MVP ships.

What needs your hands-on browser verification before merge

(I'm a CLI agent — these are the gaps the reviewer + I flagged that only show up in a real browser.)

  • 💬 toggle position vs existing bottom-right chart-expand controls on session-viewer.html — visual collision check.
  • Per-row Analyze button placement in the picker's first cell — does it look right next to the star, does the click reach the modal (not the row navigation)?
  • Real session_id end-to-end: pick an archived session, click Analyze, confirm a streamed answer arrives with click-to-seek timestamps.
  • Open Discuss panel, ask a follow-up question, confirm history persists across page reload.
  • Set LLM_DAILY_BUDGET_USD=0 and confirm the budget meter renders as exhausted + the chat refuses with a 429 message inline.
  • Cancel mid-stream — does the Cancel button actually stop the upstream LLM call (server-side llm_calls.status='cancelled')?

Three files, all node --check syntax-clean. The forwarder side is fully covered by the existing 67-test suite (unchanged in this PR).

🤖 Generated with Claude Code

The user-facing surface for AI session analysis (epic #412 MVP).

  content/shared/llm-chat.js
    DOM-free SSE consumer + helpers. window.LLMChat surface:
      chat({profile, messages, sessionId, sessions, range, oneShot})
        → {on, cancel}; events: tool_call, tool_result,
        assistant_message, usage, error, done, cancelled.
      loadProfiles() / loadBudget() — wrap the read endpoints.
      formatTimestamp / parseTimestamp / linkifyTimestamps —
        used to turn cited mm:ss.ms timestamps into click-to-seek
        anchors.

  content/dashboard/llm-discuss-panel.js
    For session-viewer.html. Floating 💬 toggle (bottom-right),
    right-side slide-out panel with model picker, budget meter,
    scrollable history (per-session localStorage, last 50 msgs),
    textarea + Send + Cancel. Cmd/Ctrl+Enter sends, Escape closes
    when no stream is active. ARIA roles, focus returns to the
    toggle on close. Click on linkified timestamp dispatches a
    `llm:seek` CustomEvent — page-level glue can wire it to the
    chart cursor in a follow-up.

  content/dashboard/llm-analyze-modal.js
    For sessions.html (the picker). MutationObserver watches
    #sessionsContainer for span[data-star-cell][data-session-id]
    elements (the canonical per-row session_id hook in the table
    rendered by session-replay.js) and injects an Analyze button
    next to each row's star with z-index above the row's
    stretched-link overlay. Click opens a modal that streams a
    one-shot overview using the same shared module. Escape +
    backdrop both close; focus returns to the trigger row.

  content/dashboard/session-viewer.html, sessions.html
    +2 script tags each — load shared llm-chat.js then the page-
    specific UI module.

Pre-commit code-review fixes (Sonnet pass on the staged diff):

  - **Critical:** the picker on sessions.html renders rows as
    <tr> with the session_id on a child star span — not as
    .session-card divs. Initial selector queried the wrong shape
    and would never have found a row. Fixed: target the star
    span and inject into its cell at z-index:3 above the row's
    stretched-link overlay.
  - SSE parser now tolerates spec-conformant `event:foo` (no
    space after colon) in addition to the forwarder's `event: foo`.
    Per W3C EventSource: a single optional space after the colon.
  - Profile-select race: Send button starts disabled
    ("Loading models…"), enables only after populateProfileSelect
    resolves. Guards sendMessage so a fast Cmd+Enter no-ops
    instead of submitting with an empty profile.
  - Modal Esc handler now removes itself on every close path
    (backdrop click, X button, Escape) so opening many modals
    doesn't leak listeners. Same pattern in the Discuss panel.
  - Discuss panel: Esc closes when no stream is active; focus
    returns to the toggle button after close (WCAG 2.4.3).
  - CSS namespace tightened: .llm-msg/.llm-ts → .llmchat-msg/
    .llmchat-ts to avoid future collision with generic class
    names (e.g. "license message").
  - Removed duplicate id="llm-profile-select" — label now
    wraps the select directly so no id is needed.
  - inputEl uses readOnly during streaming so users can still
    select / copy their own pending text.

Cross-compare per memory rule: testing-session.html (LIVE
control plane) is intentionally NOT touched. Analytical AI is
for archived replay; the live-session control AI surface is
sub-issues #424 (read-only inspection tools) and #425 (HITL
mutation tools), both planned for after the analytical MVP
ships.

What still needs browser verification:
  - 💬 toggle position vs existing bottom-right chart-expand
    controls on session-viewer (visual collision check).
  - The per-row Analyze button placement in the picker's first
    cell — does it look right next to the star, does it stay
    clickable above the stretched-link overlay?
  - Click-to-seek custom event — currently fires document-level
    `llm:seek`; page-level handler that drives the chart cursor
    is a documented follow-up, not in this PR.
  - Real session_id end-to-end: pick an archived session,
    click Analyze, confirm streamed answer with timestamps.

Three files, all node --check syntax-clean. The forwarder side
is fully covered by the existing 67-test suite (unchanged here).

Part of epic #412.
Closes #419.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jonathaneoliver

Copy link
Copy Markdown
Owner Author

Closing — superseded by the v2.0.0 release.

This stale stacked-PR chain (epic #412) has no salvageable diff against current main. The feature lives on in the v3 dashboard + forwarder; further work should target those, not this stack.

@jonathaneoliver
jonathaneoliver deleted the feat/llm-dashboard-ui-419 branch May 27, 2026 22:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant