feat(explore): Ask pill as conversation surface with inline answers and table rendering#306
Merged
SimplicityGuy merged 2 commits intomainfrom Apr 15, 2026
Merged
Conversation
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 Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Contributor
Contributor
Contributor
Contributor
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 separateNlqSummaryStrip(fixed atbottom: 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.jshadgfm: falseand a DOMPurify allowlist of onlystrong,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 → collapsedCollapse happens only via explicit user action (Esc, ×). The pill never auto-collapses after submit.
NlqSummaryStrip,#nlqStripMount, and thestripMountIdparameter are deleted. The orchestrator wires the pill directly.Fix markdown rendering. Enable GFM with
breaks: true, and expand the DOMPurify allowlist tostrong, em, code, p, br, ul, ol, li, h1-h6, blockquote, pre, table, thead, tbody, tr, th, td, hr.ALLOWED_ATTRstays empty — nohref,on*, orstyle— so entity link injection remains the only source of anchors. Scoped.nlq-answer-slotstyles match the dark pill aesthetic (muted borders, compact padding, horizontal scroll for wide tables). The expanded card is capped atmin(70vh, 560px)withoverflow-y: autoso long answers scroll inside the card.Files changed
explore/static/js/nlq-pill.js— rewritten as a state machine withsetLoading(),setAnswer(),clearAnswer(),reopenLastAnswer(); receipt rendering; close buttonexplore/static/js/nlq.js— orchestrator drops strip wiring, drives pill states directlyexplore/static/js/nlq-markdown.js—gfm: true, breaks: true; expanded allowlistexplore/static/js/nlq-summary-strip.js— deletedexplore/static/css/nlq-pill.css— receipt, loading slot, answer slot, table/list/heading stylesexplore/static/index.html—#nlqStripMountremovedexplore/__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-tripexplore/__tests__/nlq-markdown.test.js— GFM tables, ordered/unordered lists, headings, iframe stripping, onclick stripping, entity-link injection into table cellsexplore/__tests__/nlq-summary-strip.test.js— deletedexplore/__tests__/nlq-init.test.js— asserts#nlqStripMountis fully goneThe 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% coveragejust up:<table>(not pipes and dashes)🤖 Generated with Claude Code