Skip to content

feat(explore): Ask pill as conversation surface with inline answers and table rendering#306

Merged
SimplicityGuy merged 2 commits intomainfrom
feat/ask-pill-conversation
Apr 15, 2026
Merged

feat(explore): Ask pill as conversation surface with inline answers and table rendering#306
SimplicityGuy merged 2 commits intomainfrom
feat/ask-pill-conversation

Conversation

@SimplicityGuy
Copy link
Copy Markdown
Owner

Summary

Fixes two NLQ UX bugs introduced with the floating Ask pill in #304:

1. Overlapping widgets on re-expand. The Ask pill (fixed at bottom: 16px, z-index 1500) and the separate NlqSummaryStrip (fixed at bottom: 64px, z-index 1499) were two independent widgets. Submitting collapsed the pill and showed the strip; reopening the pill to ask a follow-up re-rendered the card on top of the still-visible strip, leaving fragments of the previous answer ("Unf…", "trend…", the dismiss ×) bleeding around the edges of the expanded card.

2. Markdown tables and lists rendered as raw text. nlq-markdown.js had gfm: false and a DOMPurify allowlist of only strong, em, code, p, br. Answers that contained pipe tables, lists, or headings came back to the user as literal | Label | Releases |---|---| | Anjunabeats | 3923 | text.

Solution

Merge the two widgets into one conversation surface. The pill is now a state machine:

collapsed → expanded → loading → answered → collapsed

  • expanded — input + Suggested/Recent chips
  • loading — same card, "Thinking…" spinner replaces chips, input stays focused
  • answered — same card, rendered summary + action log + Undo button replaces chips, input stays visible so follow-ups are one Enter away
  • collapsed — when an answer exists, a small "last answer: …" receipt appears above the collapsed button; clicking it reopens the full answer, × clears it

Collapse happens only via explicit user action (Esc, ×). The pill never auto-collapses after submit.

NlqSummaryStrip, #nlqStripMount, and the stripMountId parameter are deleted. The orchestrator wires the pill directly.

Fix markdown rendering. Enable GFM with breaks: true, and expand the DOMPurify allowlist to strong, em, code, p, br, ul, ol, li, h1-h6, blockquote, pre, table, thead, tbody, tr, th, td, hr. ALLOWED_ATTR stays empty — no href, on*, or style — so entity link injection remains the only source of anchors. Scoped .nlq-answer-slot styles match the dark pill aesthetic (muted borders, compact padding, horizontal scroll for wide tables). The expanded card is capped at min(70vh, 560px) with overflow-y: auto so long answers scroll inside the card.

Files changed

  • explore/static/js/nlq-pill.js — rewritten as a state machine with setLoading(), setAnswer(), clearAnswer(), reopenLastAnswer(); receipt rendering; close button
  • explore/static/js/nlq.js — orchestrator drops strip wiring, drives pill states directly
  • explore/static/js/nlq-markdown.jsgfm: true, breaks: true; expanded allowlist
  • explore/static/js/nlq-summary-strip.js — deleted
  • explore/static/css/nlq-pill.css — receipt, loading slot, answer slot, table/list/heading styles
  • explore/static/index.html#nlqStripMount removed
  • explore/__tests__/nlq-pill.test.js — new state-machine tests (loading, answered, receipt, follow-up, undo visibility, Esc from every state)
  • explore/__tests__/nlq-orchestrator.test.js — asserts the answer renders in the pill's answer slot, follow-ups work on repeat Enter, error state styling, receipt round-trip
  • explore/__tests__/nlq-markdown.test.js — GFM tables, ordered/unordered lists, headings, iframe stripping, onclick stripping, entity-link injection into table cells
  • explore/__tests__/nlq-summary-strip.test.js — deleted
  • explore/__tests__/nlq-init.test.js — asserts #nlqStripMount is fully gone

The action applier contract, SSE wire format, and backend are unchanged.

Test plan

  • just test-js — 1003 passing (up from 985)
  • just test-api — 1538 passing with 99% coverage
  • Manual browser verification via just up:
    • Submit a query; confirm the pill stays open and shows the answer inline
    • Press Enter on a follow-up question without collapsing — confirm a new answer replaces the previous one in the same card
    • Collapse via × or Esc; confirm the "last answer: …" receipt appears above the collapsed button
    • Click the receipt; confirm it reopens in answered state
    • Click the receipt ×; confirm it clears and does not reappear
    • Ask "biggest Trance label" and confirm the answer renders as a real <table> (not pipes and dashes)
    • Resize the window; confirm the card fits and long answers scroll inside it

🤖 Generated with Claude Code

SimplicityGuy and others added 2 commits April 15, 2026 14:41
The bottom of the viewport hosted two overlapping fixed widgets: the Ask
pill and a separate summary strip. Reopening the pill after submit left
the previous answer bleeding around the expanded card, and the pill
collapsed itself on submit so follow-up questions required a full reopen.

Merge both widgets into a single conversation surface. The pill is now
a state machine (collapsed → expanded → loading → answered → collapsed)
that keeps the input visible at every stage. Submitting transitions the
same card through loading and into answered — follow-ups are one Enter
away. Collapsing when an answer exists leaves a small "last answer"
receipt above the collapsed button; clicking it reopens the pill in the
answered state, and the × clears it.

Delete NlqSummaryStrip, its #nlqStripMount element, the stripMountId
parameter, and its tests. The orchestrator wires the pill directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The NLQ markdown renderer used marked({ gfm: false }) and a DOMPurify
allowlist of only strong/em/code/p/br, so answers containing pipe
tables, lists, or headings displayed as literal pipes and dashes. The
"biggest Trance label" answer came back as raw
"| Label | Releases |---|---| | Anjunabeats | 3923 | …" text.

Enable GFM with breaks and expand the allowlist to cover tables, lists,
headings, blockquotes, preformatted blocks, and horizontal rules.
ALLOWED_ATTR stays empty — no href, no on*, no style — so the entity
link injection pass remains the only source of anchors. Add scoped
styles for the answer slot so tables and lists match the dark pill
aesthetic, and tests covering GFM tables, lists, headings, iframe
stripping, onclick stripping, and entity link injection into cells.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 15, 2026

Codecov Report

❌ Patch coverage is 96.02649% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
explore/static/js/nlq-pill.js 95.80% 6 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Copy Markdown
Contributor

E2E Coverage (webkit)

Totals Coverage
Statements: 46.5% ( 1241 / 2669 )
Lines: 46.5% ( 1241 / 2669 )

StandWithUkraine

@github-actions
Copy link
Copy Markdown
Contributor

E2E Coverage (chromium)

Totals Coverage
Statements: 46.5% ( 1241 / 2669 )
Lines: 46.5% ( 1241 / 2669 )

StandWithUkraine

@github-actions
Copy link
Copy Markdown
Contributor

E2E Coverage (firefox)

Totals Coverage
Statements: 46.5% ( 1241 / 2669 )
Lines: 46.5% ( 1241 / 2669 )

StandWithUkraine

@github-actions
Copy link
Copy Markdown
Contributor

E2E Coverage (webkit - iPhone 15)

Totals Coverage
Statements: 46.5% ( 1241 / 2669 )
Lines: 46.5% ( 1241 / 2669 )

StandWithUkraine

@github-actions
Copy link
Copy Markdown
Contributor

E2E Coverage (webkit - iPad Pro 11)

Totals Coverage
Statements: 46.5% ( 1241 / 2669 )
Lines: 46.5% ( 1241 / 2669 )

StandWithUkraine

@SimplicityGuy SimplicityGuy merged commit 94405ba into main Apr 15, 2026
57 checks passed
@SimplicityGuy SimplicityGuy deleted the feat/ask-pill-conversation branch April 15, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant