Skip to content

Show visualization brush filters in toolbar and chat#58

Draft
Dev-Lan wants to merge 8 commits into
mainfrom
feature/viz-filters-toolbar-and-chat
Draft

Show visualization brush filters in toolbar and chat#58
Dev-Lan wants to merge 8 commits into
mainfrom
feature/viz-filters-toolbar-and-chat

Conversation

@Dev-Lan

@Dev-Lan Dev-Lan commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Brush/click selections made directly on a visualization now behave like any other filter: they appear as removable chips in the filter toolbar and render an adjustment widget in the chat.
  • Brush selections remain a single source of truth in selectionsStore (keyed by the source viz's uuid). Nothing is mirrored into dataFiltersStore, and no synthetic messages are added to the conversation — so brush filters never leak into the LLM message history and there's no cross-store sync to drift or loop.
  • The existing LLM-originated FilterData path is untouched (the new behavior is additive), so non-visualization filters work exactly as before.

Fixes hms-dbmi/udi-yac#24.

Changes

  • src/features/dashboard/hooks/useBrushFilters.ts (new) — derives brush filters from selectionsStore + active visualizations, gated to currently-active vizzes (no stale chips from closed vizzes) and non-empty selections. Pure selectBrushFilters extracted and unit-tested.
  • src/features/dashboard/components/FilterToolbar.tsx — renders a chip per brush filter; clearing a brush chip clears its selectionsStore entry.
  • src/features/dashboard/components/DashboardCard.tsx — when a viz's own brush is cleared externally, remounts UDIVis (key bump on the true→false transition only) so the rendered brush rectangle clears too.
  • src/features/tool-calls/components/{Interval,Point}FilterComponent.tsx — add an optional onCommit override (defaults to the existing dataFiltersStore path) so the same widgets can write to selectionsStore. Exported from the tool-calls barrel.
  • src/features/chat/components/BrushFilterWidgets.tsx (new) + MessageList.tsx — render an adjustment widget per brush filter at the bottom of the chat; edits write back to selectionsStore, and emptying a widget fully clears the brush.

Spec

.claude/todo/done/show-viz-filters-in-toolbar-and-chat.md (issue hms-dbmi/udi-yac#24).

Test plan

Automated:

  • pnpm test — 151 tests pass (5 new for selectBrushFilters gating).
  • pnpm typecheck — clean.
  • pnpm lint — clean (one pre-existing DataCounts warning, unrelated).
  • pnpm build — succeeds.

Manual (needs a running app):

  • Brush a dashboard chart → a chip appears in the filter toolbar and an adjustment widget appears in the chat.
  • Click the chip's X → the chart's brush clears and both the chip and chat widget disappear.
  • Adjust the chat widget's slider/checkboxes → counts and other charts update.
  • Un-brush by clicking empty chart space → chip and widget disappear, no stale state.
  • Create an LLM-originated FilterData filter → still works as before (no regression).
  • Multiple charts brushed → each has its own chip/widget; clearing one leaves the others.

Notes / known limitation

  • Source-chart rectangle on widget edit: editing a brush filter from the chat widget propagates to the filter pipeline (counts + other charts) via selectionsStore, but the source chart excludes its own selection by design and UDIVis has no API to reposition an existing brush, so the source chart's own brush rectangle does not visually move on a widget edit (it does clear correctly on removal). This is a UDIVis constraint, not a data-correctness issue.

🤖 Generated with Claude Code

Dev-Lan and others added 8 commits June 25, 2026 16:24
Let IntervalFilterComponent and PointFilterComponent write their edits
through an injected callback instead of always targeting
dataFiltersStore.setDataSelection. Defaults to the existing LLM-filter
path, so current callers are unaffected. This lets the same widgets drive
a visualization brush (which lives in selectionsStore). Export both
components from the tool-calls barrel for reuse.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brush/click selections created by interacting with a visualization live
in selectionsStore, keyed by the source viz's uuid. This hook derives
them as first-class filters, gated to currently-active vizzes (so a
closed viz's stale selection never shows) and to non-empty selections.
The gating logic is extracted into a pure, exported selectBrushFilters
function and unit-tested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FilterToolbar now renders a chip for each active brush filter alongside
LLM-originated filters. Clearing a brush chip clears the authoritative
selectionsStore entry; DashboardCard observes its own brush being cleared
and remounts the source UDIVis (via a key bump on the true->false
transition) so the rendered brush rectangle clears too — UDIVis offers no
programmatic way to drop it otherwise.

Fixes the toolbar half of #29.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add BrushFilterWidgets, rendered at the bottom of the message list, which
shows an adjustment widget for each active visualization brush filter —
mirroring how LLM-originated FilterData filters render. Edits write back
to selectionsStore (not the conversation), so brush filters drive the
filter pipeline both ways without leaking into the LLM message history.
Emptying a widget fully clears the brush, matching the toolbar.

Fixes the chat-widget half of #29.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brush filter adjustment widgets now appear in the same muted message
bubble as LLM-originated FilterData filters, instead of a separate
"Visualization filters" section at the bottom. Addresses feedback that
they should read as chat messages like agent-generated filters.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DashboardCard previously stripped this viz's own uuid from the selections
passed to UDIVis, so an edit made in the chat adjustment widget never
reached the source chart. As a result the brush rectangle didn't move on
edit (issue 3), and entity counts only updated when a second viz happened
to bind the selection into the shared DataSourcesStore — i.e. two-way
filtering appeared to work only with more than one visualization
(issue 2). Pass the full selection set through instead; UDIVis no-ops an
external selection equal to its current one, so live brushing doesn't loop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Unchecking every value in a point/categorical brush widget used to null
out the selection, which removed the widget entirely. Now the emptied
selection is kept (present but valueless), so the chat adjustment widget
persists for re-selection. useBrushFilters gates on a present (non-null)
selection; the toolbar chip is suppressed for valueless brushes via the
new brushHasValue helper. The brush is fully removed only via the toolbar
chip's clear action.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A brush originating from a visualization can report a range slightly
outside the data extent, because some charts pad their axis with a visual
buffer. The slider thumbs were bound to that raw value and could sit past
the ends of the track. Clamp only the thumb positions to [min, max]; the
stored range value is unchanged, so the filter still reflects the brush.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

Filtering from visualizations doesn't show in filter toolbar or chat adjustment widget

1 participant