Skip to content

feat: redesign Ask as global pill with agent-driven UI actions#304

Merged
SimplicityGuy merged 33 commits intomainfrom
worktree-fix-ai-ask
Apr 15, 2026
Merged

feat: redesign Ask as global pill with agent-driven UI actions#304
SimplicityGuy merged 33 commits intomainfrom
worktree-fix-ai-ask

Conversation

@SimplicityGuy
Copy link
Copy Markdown
Owner

Summary

  • Replaces the cramped navbar Ask toggle with a global floating pill that expands into a rich card with dynamic suggestions and recent-query history
  • Adds a Tier 3 structured UI action vocabulary so the LLM agent can drive the graph, switch panes, filter trends, and open insight tiles alongside its text answer
  • Fixes the markdown-in-summary rendering bug via DOMPurify (with RETURN_DOM_FRAGMENT — never touches innerHTML) and re-injects entity links safely
  • Extracts common/agent_tools/ as a framework-free shared tool registry consumed by both the NLQ engine (api/nlq/tools.py) and the MCP server (mcp-server/mcp_server/server.py) to prevent tool drift
  • Adds a template-based /api/nlq/suggestions endpoint with Redis caching, rate limiting, and fallback-safe error paths

Architecture highlights

Backend (Phases 1-5):

  • common/agent_tools/{graph,entities,discovery,stats}.py — dependency-injected async tools
  • api/nlq/actions.py — Pydantic discriminated-union for 10 action types with server-side validation
  • Agent emits <!--actions:[...]--> marker the engine strips from the user-visible summary
  • SSE router adds an actions event between status and result

Frontend (Phases 6-10):

  • nlq-pill.js — state machine (collapsed → expanded → loading → summary) with ⌘K / ? / Esc keyboard shortcuts
  • nlq-suggestions.js — context-aware dynamic chip rows + localStorage history (capped at 5, auto-recovers from corruption)
  • nlq-action-applier.js — sanitizes, orders, dispatches, snapshots for single-action undo
  • nlq-handlers.js — thin bridge between the applier and the existing graph/insights/trends subsystems
  • nlq-markdown.js — DOMPurify-backed renderer with safe entity span injection
  • nlq-summary-strip.js — dismissable action log with undo button
  • nlq.js — thin coordinator (78 lines, down from 256)
  • GraphVisualization gains snapshot(), restore(), clearAll(), addEntity() for action-driven graph mutation

Cleanup:

  • Removes 1024 lines of legacy NLQPanel markup, wiring, and tests

Test plan

  • Python unit tests: 2024 passing in common/api/mcp-server
  • JS tests: 947 passing across 26 files (Vitest + @testing-library/dom)
  • Pre-commit hooks: all 29 clean (ruff, mypy, bandit, cargo, shellcheck, etc.)
  • Semgrep: 0 findings across 91 files / 1204 rules
  • No `innerHTML` anywhere in new NLQ frontend code (verified)
  • Action contract matches end-to-end (Pydantic aliases align with client deserialization)
  • `nlq.js` loads as `type="module"`; `app.js` safely checks for `window.NlqInit`
  • E2E Playwright: `test_ask_pill.py` and `test_ask_cross_pane.py` (run in CI with live stack)
  • Perftest: `nlq_query_simple` (target p95 < 4000ms) and `nlq_suggestions` (target p95 < 200ms) added to `config.yaml`
  • Manual browser verification with `just up` before merge

Known minor observations (follow-ups, not blocking)

  1. Client-side `filter_graph` sanitizer doesn't re-validate `by` against the dimension literal — server Pydantic validates, but a malformed stream could reach the handler. Low-severity follow-up.
  2. Client-side `find_path` sanitizer doesn't validate `from_type`/`to_type` against the entity-type set.
  3. No dedicated unit test for `parse_action_list`'s `ValidationError` drop-path — only happy paths covered.

Spec and plan

  • Spec: `docs/superpowers/specs/2026-04-14-ask-mode-integration-design.md`
  • Plan: `docs/superpowers/plans/2026-04-14-ask-mode-integration-plan.md`

🤖 Generated with Claude Code

SimplicityGuy and others added 30 commits April 15, 2026 09:32
Designs the Explore Ask feature redesign as a global floating pill with
agent-driven UI actions, dynamic suggestions, and a shared
common/agent_tools registry for the NLQ engine and MCP server.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
31 TDD tasks across 10 phases implementing the approved spec: shared
agent_tools registry, NLQ engine action contract, suggestions endpoint,
MCP server refactor, floating pill component with suggestions and action
applier, DOMPurify markdown renderer, summary strip, and E2E coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the inline name-resolution + find_shortest_path logic in
NLQToolRunner._handle_find_path with a call to the shared
common.agent_tools.find_path, injecting a resolve_name closure that
wraps EXPLORE_DISPATCH (handles numeric pass-through, missing type
pass-through, and named lookups).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nt_tools

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Defines 10 Pydantic action types with a `type` literal discriminator for strict
JSON parsing. parse_action_list drops malformed entries with a warning log.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add actions: list[Action] field to NLQResult, an _extract_actions()
helper that strips the <!--actions:[...]-->  marker from agent text,
and extend the system prompt so Claude knows to emit the marker.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add an `actions` SSE event between status events and the final `result`
event in `_stream_response`, and include `actions` in the non-SSE JSON
response branch for consistency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Delegates mcp_server.find_path to common.agent_tools.find_path via
API-backed resolver closures (_api_get for name resolution, /api/path
for path traversal), so future shared tool additions automatically
benefit MCP clients without changes to server.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add click-to-expand, ⌘K/Ctrl+K, ? (when no input focused), Esc-to-collapse,
and expanded-state render with focused input to NlqPill. Install
@testing-library/dom for fireEvent in tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ection

Adds nlq-markdown.js — a security-hardened markdown renderer that uses
DOMPurify RETURN_DOM_FRAGMENT (never innerHTML) and injects entity anchor
elements via createElement/textContent. Anchor tags from markdown source
are stripped by the ALLOWED_TAGS allowlist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add fetchNlqSuggestions to ApiClient and replace the monolithic NLQPanel
class with a thin initNlq coordinator that delegates to NlqPill,
NlqSuggestions, NlqActionApplier, NlqSummaryStrip, and nlq-handlers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove the searchAskToggle/nlqPanel/nlqExamples navbar markup, the
initNlqPanel IIFE from app.js, and all test code that depended on the
old NLQPanel class. Add a guardrail test to prevent reintroduction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds nlqStripMount div to index.html, calls window.NlqInit from app.js
DOMContentLoaded handler after exploreApp is set, and adds a static
assertion guardrail test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 15, 2026

Codecov Report

❌ Patch coverage is 99.30915% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
explore/static/js/graph.js 83.33% 2 Missing ⚠️
explore/static/js/app.js 75.00% 1 Missing ⚠️
explore/static/js/nlq-markdown.js 97.95% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

SimplicityGuy and others added 2 commits April 15, 2026 10:09
Add targeted tests covering initNlq orchestration, action applier
sanitizers, handler delegates, pill Enter-key submit, suggestions
non-array history reset, and graph addEntity app-delegate branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ions

Add targeted tests for previously uncovered branches: _extract_actions
invalid-JSON warning path, whitespace-only suggestion focus fallback,
explore-entity unknown-type guards, resolve_name None-return path,
suggestions cache hit/read-failure/write-failure, and jwt_secret=None
early-return. Add pragma: no cover to TYPE_CHECKING block and
unreachable non-list guard in _extract_actions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@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 (webkit)

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 f26e431 into main Apr 15, 2026
57 checks passed
@SimplicityGuy SimplicityGuy deleted the worktree-fix-ai-ask branch April 15, 2026 17:29
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