Skip to content

feat(source-picker): chip + kebab menu UX#2365

Merged
kodiakhq[bot] merged 12 commits into
mainfrom
alex/source-chip-ux-prototype
Jun 1, 2026
Merged

feat(source-picker): chip + kebab menu UX#2365
kodiakhq[bot] merged 12 commits into
mainfrom
alex/source-chip-ux-prototype

Conversation

@alex-fedotyev
Copy link
Copy Markdown
Contributor

@alex-fedotyev alex-fedotyev commented May 28, 2026

Summary

Cleans up the source picker by separating the two use cases that were tangled together inside one dropdown:

  • Primary: pick a source. The pill becomes a real chip (icon + name + chevron) with a single hover affordance on the input root, and the dropdown lists sources only.
  • Secondary: manage sources. View schema, Edit source, Manage sources, Create new source move into an adjacent kebab. The dropdown stops mixing data items with action items, and the "Schema" preview text stops outranking the source name visually.

Selector is now just a selector:
image

Actions/management moved to a separate menu:
image

What changed

  • SourceSelect.tsx: refactor to chip + kebab. Extract a reusable SourceManagementMenu so non-SourceSelectControlled callers (e.g. DBTableSelect) can hang the same actions off their own input. Drop the old sourceSchemaPreview prop in favor of onSchemaPreview + isSchemaPreviewEnabled.
  • SourceSchemaPreview.tsx: add a controlled variant that lets the parent own open state and drive the modal from outside; expose isSourceSchemaPreviewEnabled(source) for callers that need to gate a menu item.
  • 9 callsites updated to the new API: DBSearchPage, DBChartPage (via DBEditTimeChartForm/ChartEditorControls), DBTracePanel, DashboardFiltersModal, KubernetesDashboardPage, DBServiceMapPage, DBTableSelect, RawSqlChartEditor. PromqlChartEditor uses SourceSelect but not the schema preview so it's untouched.
  • Sanity fix along the way: text-sucess-hovertext-success-hover.

Drive-by fixes spotted while clicking through

  • Manage sources used to behave like Edit source. router.push('/team') from the kebab let the page's nuqs query state (source/where/select/filters/orderBy) write itself back into the new URL via history.replaceState on the way out, so SourcesList auto-expanded the leaked ?source=. Switched to a hard navigation so Manage sources lands on a clean /team?tab=data with everything collapsed.
  • Duration Precision slider on the Trace source form was unreadable. Mantine 9's Slider has :where([data-orientation="vertical"]) .<part> rules whose compiled selector matches when any ancestor is vertical. Mantine 9's Card sets data-orientation="vertical" by default and the source form renders inside a Card, so the slider's track collapsed to 8px and the four marks (Seconds / Millisecond / Microsecond / Nanosecond) stacked on top of each other. Added a styles override for every affected part (trackContainer, track, bar, thumb, markWrapper, markLabel, label) so the slider renders horizontally again with the value badge above the thumb.

Test plan

  • Lint, typecheck, prose-lint clean on changed files
  • Unit tests pass for SourceSelect, SourceSchemaPreview, DBTracePanel, DBSearchPage, SourceForm
  • Knip surfaces no new issues
  • Manage sources lands on a clean /team?tab=data with all rows collapsed
  • Edit source lands on /team?source=<id> with that source expanded
  • Duration Precision slider renders horizontally with the value badge floating above the thumb
  • Manual click-through across the 9 callsites in light + dark: chip hover, kebab open/close, view-schema modal, edit-source route, create-new modal
  • Verify the dropdown lists data items only (no Edit / Create entries leaked back in)

Tighten the source picker across the 9 callsites that surface it
(search, charts, traces, dashboards filter modal, Kubernetes,
service map, table select, raw SQL editor, secondary trace panel).

Pill becomes a real chip with a single hover affordance on the
input root; the chevron no longer steals the click target. Schema
preview, edit sources, and create new source move out of the
dropdown into an adjacent kebab menu, so the dropdown lists data
sources only and the management actions sit on a dedicated surface
with consistent ordering.

`<SourceSchemaPreview>` gains a controlled variant so the kebab
can drive the modal from outside the icon trigger, and a small
`isSourceSchemaPreviewEnabled` helper lets callers decide whether
to surface the menu item.

Also fix the `text-sucess-hover` typo flagged while in here, and
the previous draft that landed both `Edit this source` and `Manage
sources` pointing at the same handler now ships a single
`Edit sources` action.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 28, 2026

🦋 Changeset detected

Latest commit: d95b8de

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/app Patch
@hyperdx/api Patch
@hyperdx/otel-collector Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented May 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment Jun 1, 2026 12:50pm
hyperdx-storybook Ready Ready Preview, Comment Jun 1, 2026 12:50pm

Request Review

@github-actions github-actions Bot added the review/tier-3 Standard — full human review required label May 28, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

🟡 Tier 3 — Standard

Introduces new logic, modifies core functionality, or touches areas with non-trivial risk.

Why this tier:

  • Diff size: 735 production lines changed (Tier 2 max: < 250)

Review process: Full human review — logic, architecture, edge cases.
SLA: First-pass feedback within 1 business day.

Stats
  • Production files changed: 14
  • Production lines changed: 735 (+ 279 in test files, excluded from tier calculation)
  • Branch: alex/source-chip-ux-prototype
  • Author: alex-fedotyev

To override this classification, remove the review/tier-3 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

Deep Review

✅ No critical issues found. Two P2 items worth fixing before merge, plus a handful of P3 nits and testing gaps. The drive-by hard-nav rework introduces a couple of new edges (dirty-form data loss, modal state drift) that the new tests don't cover.

🟡 P2 — recommended

  • packages/app/src/components/SourceSchemaPreview.tsx:239 — In controlled mode the Modal is gated on {isEnabled && <Modal …>}, so when source becomes invalid (cleared selection, MV list shrinks, source switches to one with no preview-eligible tables) the Modal unmounts without handleClose firing, the parent's isSourceSchemaPreviewOpen stays true, and the modal silently re-pops the moment the source becomes valid again.
    • Fix: call onClose?.() from a useEffect when controlled && open && !isEnabled, or always render <Modal opened={isModalOpen && isEnabled}> so the close edge survives transitions.
    • correctness, julik-frontend-races, adversarial
  • packages/app/src/DBSearchPage.tsx:1752-1777onEditCurrentSource and onManageSources call window.location.assign unconditionally, dropping any dirty react-hook-form state (WHERE / SELECT / filters / orderBy edits) and the trailing run of the 1000 ms useDebouncedCallback(onSubmit, …) with no beforeunload or confirm. onEditCurrentSource also navigates with inputSource (the form-local value), so the destination is /team?source=Y even when the persisted search is still on source X.
    • Fix: gate the nav behind formState.isDirty + a confirm, debouncedSubmit.flush() before assigning, and resolve the target id against searchedConfig.source rather than the live form value.
    • julik-frontend-races, adversarial
🔵 P3 nitpicks (13)
  • packages/app/src/DBSearchPage.tsx:1758window.location.assign(\${router.basePath}/team?source=${inputSource}`)interpolates the id withoutencodeURIComponent; server-generated UUIDs are safe today but any future id containing &, #, space, or +` corrupts the deep link.
    • Fix: wrap the id with encodeURIComponent(inputSource).
    • correctness, kieran-typescript, julik-frontend-races
  • packages/app/src/DBSearchPage.tsx:1772-1777onManageSources navigates to ${router.basePath}/team without ?tab=data, contradicting the PR description's stated destination; it works only because tabs[0].value === 'data'. Reordering tabs would silently send users to a different tab.
    • Fix: include ?tab=data (and &source=<id> only where intended) in the constructed URL.
    • correctness
  • packages/app/styles/SourceSelectControlled.module.scss:1-15 — The .sourceSchemaPreviewButton class is documented as "Still used by DBTableSelect for its inline schema-preview text button," but DBTableSelect now uses SourceManagementMenu; a repo-wide grep for sourceSchemaPreviewButton returns only this declaration.
    • Fix: delete the rule and its comment.
    • maintainability, adversarial
  • packages/app/src/components/SourceSelect.tsx:138data-testid="source-actions-menu" is hardcoded inside SourceManagementMenu with no override prop, and KubernetesDashboardPage renders two kebabs (log + metric) carrying identical test ids.
    • Fix: accept a data-testid override on SourceManagementMenu and pass distinct ids per kebab in KubernetesDashboardPage.
    • adversarial
  • packages/app/src/components/Sources/SourceForm.tsx:1524-1576 — The Slider styles override pins to Mantine 9 internal CSS custom properties (--slider-size, --slider-bar-offset, --slider-bar-width, --slider-thumb-offset, --mark-offset) without fallbacks; a Mantine patch that renames any of these silently collapses the track to 0 height with no test failure.
    • Fix: wrap the Slider in <div data-orientation="horizontal"> to neutralize the inherited Card attribute at the boundary, or add safe fallbacks to each var(--…) call.
    • julik-frontend-races, adversarial
  • packages/app/src/components/SourceSchemaPreview.tsx:246<Tabs defaultValue={…tables[0]…}> is uncontrolled, so when source mutates while the modal is mounted (background useSources refetch, saved-search URL reset) the captured tab key can reference a table that no longer exists and the panel renders blank.
    • Fix: switch to controlled <Tabs value/onChange> driven by the current tables[0] identity.
    • adversarial
  • packages/app/src/components/SourceSchemaPreview.tsx:127-223SourceSchemaPreviewProps declares controlled, open, and onClose as independent optionals; in controlled mode, omitting onClose leaves the modal uncloseable with no TypeScript error.
    • Fix: model the prop as a discriminated union so controlled: true requires both open and onClose.
    • kieran-typescript, maintainability
  • packages/app/src/components/SourceSelect.tsx:64-85isSchemaPreviewEnabled defaults to true in SourceManagementMenu; a caller that wires onSchemaPreview but forgets the gate gets an always-enabled item that opens a modal whose internal isEnabled then suppresses the body.
    • Fix: default to false, or require the gate whenever onSchemaPreview is provided.
    • kieran-typescript
  • packages/app/src/components/SourceSchemaPreview.tsx:15-57 — After every callsite migrated to controlled mode, SourceSchemaInfoIcon, the variant/iconStyles props, and the internalOpen state are unreachable from production code.
    • Fix: delete the uncontrolled path and tighten the prop shape, or add a comment naming the consumer that justifies keeping it.
    • maintainability
  • packages/app/src/components/__tests__/DBTracePanel.test.tsx:39, packages/app/src/__tests__/DBSearchPage.directTrace.test.tsx:211 — Both mock factories declare getSourceSchemaTables: () => [], but SourceSchemaPreview does not export that symbol; the key is a no-op that misleads readers about the module's public surface.
    • Fix: drop the getSourceSchemaTables key from both mock factories.
    • maintainability, project-standards
  • packages/app/src/components/__tests__/SourceSchemaPreview.test.tsx:71 — Selects the modal close button via document.querySelector('.mantine-Modal-close'), coupling the test to a Mantine internal class name.
    • Fix: query by role and name (getByRole('button', { name: /close/i })), and prefer aria-disabled over data-disabled for menu-item disabled assertions.
    • testing
  • packages/app/src/DBSearchPage.tsx:1908-1923style={{ minWidth: 150 }} and data-testid now flow into the inner SelectControlled only; the visible chip surface is the wrapping Group (Select + 30 px kebab), so size and test-id hooks no longer target what looks like "the chip."
    • Fix: forward style/className (and consider data-testid) to the outer Group in SourceSelectControlled.
    • kieran-typescript
  • packages/app/src/components/Sources/SourcesList.tsx:82-98expandedFromQueryRef is keyed by the component lifetime, so an in-page back-button visit that re-presents the same ?source=<id> value short-circuits the auto-expand; the deep-link "fights the user" path is also re-litigated on every router-state tick because the effect deps are [router, sources].
    • Fix: narrow the deps to [router.isReady, router.query.source, router.pathname, sources] and reset the ref on router.query.source transitions so repeat visits still expand.
    • correctness, julik-frontend-races, adversarial

Reviewers (9): correctness, testing, maintainability, project-standards, kieran-typescript, julik-frontend-races, adversarial, agent-native, learnings.

Testing gaps:

  • No test exercises SourceSelectControlled end-to-end with the new chip + kebab integration (hasSelection, hasMenu, the onSchemaPreview-only configuration that DBTableSelect relies on).
  • No coverage for DBSearchPage.tsx:1752-1777 hard-nav branches: dirty-form behavior, IS_LOCAL_MODE toggle, or the router.basePath prefix that the /clickstack build depends on.
  • No coverage for SourcesList.tsx:82-98 auto-expand effect (happy path, missing source, router.replace strip, hash preservation, repeat-visit de-dup).
  • No test for the controlled-mode SourceSchemaPreview lifecycle when isEnabled flips false while open=true, nor for isSourceSchemaPreviewEnabled across log/metric/MV branches.
  • Slider override at SourceForm.tsx:1524-1576 is not exercised — JSDOM doesn't compute CSS, so a Mantine bump that renames any of the cited internal vars would not fail CI; a Playwright snapshot would close the gap.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

E2E Test Results

All tests passed • 189 passed • 3 skipped • 1275s

Status Count
✅ Passed 189
❌ Failed 0
⚠️ Flaky 6
⏭️ Skipped 3

Tests ran across 4 shards in parallel.

View full report →

The first cut had one `Edit sources` item that behaved differently
across modes: local opened the form for the current source, docker
dumped the user on /team with nothing pre-expanded. Same label,
wildly different journey.

Split the action by intent:

- `Edit source` (singular) operates on the current selection.
  Local mode keeps the inline modal. Docker / managed navigates
  to `/team?source=<id>` and `<SourcesList>` reads the param to
  auto-expand that source on mount.
- `Manage sources` opens the all-sources list. Wired only in
  non-local mode; local has no list-view surface so the menu
  item hides via the optional handler.

`SourcesList` strips the `source` param from the URL after
expanding so subsequent in-page interactions don't fight the
user. A `useRef` guard prevents the effect from re-firing on
later route ticks.
@alex-fedotyev
Copy link
Copy Markdown
Contributor Author

Pushed 264e814 addressing the asymmetric "Edit sources" behavior across modes.

The first cut had one Edit source item wired to a handler that branched on IS_LOCAL_MODE: local mode opened the inline SourceEditModal with the current source, docker / managed dropped the user on /team with nothing pre-expanded. Same label, very different journey.

Split by intent:

  • Edit source (singular) operates on the current selection. Local mode keeps the inline modal. Docker / managed navigates to /team?source=<id>; SourcesList reads the param and auto-expands that source's accordion on mount, then strips the param from the URL so subsequent interactions don't fight the user. A useRef guard prevents the effect from re-firing on later route ticks.
  • Manage sources opens the all-sources list. Wired only in non-local mode; local mode leaves the handler undefined and the menu item hides itself.

Net: in both modes, clicking Edit source lands you on the form for the source you were just looking at, one click. Manage sources is the explicit "show me everything" exit.

Test plan additions:

  • Local mode: Edit source opens the modal pre-filled with the chip's source.
  • Local mode: Manage sources is absent from the kebab.
  • Docker: Edit source lands on /team with that source's accordion expanded.
  • Docker: Manage sources lands on /team#sources with nothing pre-expanded.
  • Docker: refreshing /team?source=<id> once expanded clears the param from the URL.

… controlled-mode tests

- Remove `export` from `SelectControlledSpecialValues` enum; it is only used within SelectControlled.tsx and was flagged by knip after SourceSelect.tsx stopped importing it.
- Remove the unrelated prose-lint JSX annotation appended after `</title>` in KubernetesDashboardPage.tsx; prettier was complaining about the missing newline, and the annotation was outside the scope of this PR.
- Drop the dead `SourceManagementMenu: () => null` mock from DBEditTimeChartForm.test.tsx; ChartEditorControls does not import SourceManagementMenu, so the entry was never exercised.
- Add SourceSelect.test.tsx: eight unit tests covering SourceManagementMenu renders-null-when-empty, trigger-visible-when-any-action-wired, View-schema disabled/enabled matrix, and callback dispatch for each menu item.
- Add SourceSchemaPreview.test.tsx: five unit tests covering controlled mode — no trigger rendered, modal open/closed by the open prop, onClose called via the close button, and onClose called on Escape.
…rtions for Mantine 9

- Replace `container.firstChild` with `screen.queryByTestId` for the "returns null" check; MantineProvider injects a style tag as the first child.
- Use `screen.findByText()` for menu item queries after clicking the trigger; Mantine 9 Menu renders items asynchronously via floating-ui positioning.
- Use `.mantine-Modal-close` selector for the modal close button; Mantine 9 CloseButton renders without aria-label by default.
@alex-fedotyev
Copy link
Copy Markdown
Contributor Author

Fix-pack: lint, knip, e2e shard 4

Pushed 5751776 addressing the three CI failures and the deep-review P2 findings on tests.

Lint (KubernetesDashboardPage.tsx:1345). Removed the inline {/* [prose-lint: allow] ... */} annotation appended to </title>; prettier was rejecting the inline comment and the change was outside this PR's scope. Lint is clean locally on the changed files.

Knip (SelectControlledSpecialValues). Dropped the export keyword; the enum is only used inside SelectControlled.tsx and was flagged after SourceSelect.tsx removed its import.

E2E shard 4 (sources.spec.ts:105 should show source actions in dropdown). The test was asserting that Edit Sources and Create New Source were getByRole('option') items inside the source dropdown. The PR intentionally moves those actions out of the dropdown and into the adjacent kebab menu. Updated the page object + spec:

  • SearchPage page object now exposes sourceActionsMenu, createNewSourceItem, editSourceItem, manageSourcesItem, viewSchemaItem (all getByRole('menuitem') after the kebab opens).
  • Renamed the test to should show source actions in kebab menu and asserted what's actually rendered: Edit source + Create new source in both modes, Manage sources only when E2E_FULLSTACK=true (local mode wires onManageSources={undefined}).
  • openEditSourceModal() helper now opens the kebab and clicks Edit source (previously opened the dropdown and clicked Edit Sources).

P2 tests from the deep-review. Added unit coverage that was missing on the new surface:

  • SourceSelect.test.tsx (new, 21 tests): SourceManagementMenu null-render with no actions, kebab trigger present per handler combination, View-schema disabled/enabled matrix, dispatch for each menu item, Edit-source rename, onManageSources wired.
  • SourceSchemaPreview.test.tsx (new, 5 tests): controlled mode suppresses the inline trigger, open prop drives the modal, onClose fires via close button and Escape.

Validation. eslint clean on changed files, yarn ci:unit --testPathPatterns='SourceSelect|SourceSchemaPreview|DBTracePanel|DBSearchPage' is 143/143 green, knip no longer flags SelectControlledSpecialValues. The pre-existing knip noise (HeatmapSettingsSchema, SelectControlledProps, PageLayoutProps, etc.) also exists on main and is outside this PR.

Not in this fix-pack (deferred):

  • P3.1 (isEnabled effect for the controlled-mode edge case in SourceSchemaPreview) and P3.2 (dead-code removal of SourceSchemaInfoIcon).
  • P3.5 (useSourceSchemaPreview hook to converge the 9 callsite boilerplate). Worth a follow-up after this lands.

Commits since the last comment:

  • 039f72f6 fix(source-picker): knip/lint clean-up and add SourceManagementMenu + controlled-mode tests
  • cc1b9aca test(source-picker): fix SourceSelect + SourceSchemaPreview test assertions for Mantine 9
  • 252ba5b0 test(source-picker): cover onManageSources and Edit-source rename
  • 5751776c test(e2e): move source-action assertions from dropdown to kebab menu

`router.push('/team')` from the kebab let DBSearchPage's
`useQueryStates` (source/where/select/whereLanguage/filters/orderBy)
restore its state into the new URL during the client-side transition,
so the team page loaded with `?source=<id>` and `SourcesList`
auto-expanded that row. Manage sources ended up acting like Edit
source. Explicit `{ pathname: '/team', query: {} }` didn't help, since
nuqs still wrote its tracked state back via `history.replaceState` on
the way out.

Switch to `window.location.assign('/team')` so the navigation drops
all client state and lands on a clean list view at `/team?tab=data`.
… Card

Mantine 9's Slider styles use the pattern
`:where([data-orientation="vertical"]) .<part>` for vertical-mode
overrides. The intent was to scope those rules to a vertical Slider,
but the compiled selector matches when ANY ancestor has
`data-orientation="vertical"`. Mantine 9's Card sets
`data-orientation="vertical"` by default, and SourceForm renders
inside SourcesList's Card, so the Duration Precision slider's
trackContainer, track, bar, thumb, markWrapper and markLabel all
picked up the vertical-orientation styling: the track collapsed to
8px wide and the four marks (Seconds / Millisecond / Microsecond /
Nanosecond) stacked on top of each other.

Pass `styles` for every affected part to restore the
horizontal-orientation values from Mantine's base CSS. Inline styles
beat the `:where()` rules regardless of specificity.

Other Sliders in the app live outside any Card, so no global fix is
needed.

Also fix a pre-existing em-dash in a nearby PromQL comment per the
team's no-em-dash rule.
Same Mantine 9 cascade as 09274a9: the Slider's label part has
`:where([data-orientation="vertical"]) .label { top: auto;
inset-inline-start: calc(100% + 8px); }`, so the value badge moved
from "above the thumb" (base CSS sets `top: -36px`) to inline with
the thumb but offset to the right. Override the label part too so
the badge floats above the thumb as designed.
…itch onEditCurrentSource to hard-nav

onManageSources was already using window.location.assign for hard
navigation, but the path lacked router.basePath, breaking the
/clickstack build where basePath=/clickstack.

onEditCurrentSource was using router.push, which lets the page's
useQueryStates merge stale /search state into the destination URL,
causing SourcesList to auto-expand the leaked ?source= param.
Switch it to the same window.location.assign pattern used by
onManageSources, also adding the basePath prefix.

Co-Authored-By: Claude Opus <noreply@anthropic.com>
@alex-fedotyev
Copy link
Copy Markdown
Contributor Author

P0/P1 fix-pack: prefix router.basePath and switch onEditCurrentSource to hard-nav

Addressed two P0/P1 findings from the deep-review:

  • DBSearchPage.tsx:1770-1775 (onManageSources). Switched window.location.assign('/team') to prepend router.basePath, so the Manage sources item works on the /clickstack build where Next.js basePath=/clickstack.
  • DBSearchPage.tsx:1750-1760 (onEditCurrentSource). Replaced router.push({pathname:'/team', ...}) with the same window.location.assign hard-nav pattern. The client-side push let useQueryStates merge stale /search state into the destination URL; the hard-nav drops it cleanly and also picks up the basePath prefix.

Validation: yarn workspace @hyperdx/app lint clean, tsc --noEmit no errors in DBSearchPage.tsx, jest --testPathPatterns='SourceSelect|DBSearchPage' 8 suites 137 passing.

Not in this fix-pack (deferred per scope): the P3 nitpicks and testing gaps listed in the review (SourcesList ?source= auto-expand unit test, nav unit tests for these helpers).

Commit: 4af69980.

Copy link
Copy Markdown
Contributor

@elizabetdev elizabetdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alex-fedotyev I opened a PR on top of yours to tweak the kebab placement a bit.

Right now it sits a bit detached from the source selector, which makes it unclear what it actually applies to. It can feel like it might be a global “more” menu for the page instead of actions for the selected source.

What I’m trying to fix is that ambiguity. By visually grouping the Select + kebab into one unit (more like a split-button / chip pattern), it becomes much clearer at a glance that the actions belong to that specific source.

Image

Here's the PR:
#2369

…utton chip

The kebab `SourceManagementMenu` was rendered as a separate sibling of the
Select with a 4px gap, so it read as "Select plus an unrelated icon button"
rather than "actions on this source." Restyle the pair as a single
split-button chip: the input's right corners are squared, the kebab's left
corners are squared, the kebab stretches to match the input's height, and
its left border overlaps the input's right border by 1px so the two share a
single seam. Override Mantine v7 corners via `--input-radius` /
`--ai-radius` (the canonical override path) instead of fighting class-level
`border-radius` against `@layer mantine`.

Also normalize the kebab tooltip to the app-wide convention (`withArrow`,
default color) so it matches `Filter Settings` and other gray-tinted tooltips
across the app.

When `hasMenu` is false (no handlers wired, e.g. `DBTableSelect` callers),
the `[data-with-menu]` attribute is omitted and the original side-by-side
layout is preserved.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kodiakhq kodiakhq Bot merged commit 2a68145 into main Jun 1, 2026
20 checks passed
@kodiakhq kodiakhq Bot deleted the alex/source-chip-ux-prototype branch June 1, 2026 12:55
brandon-pereira pushed a commit that referenced this pull request Jun 2, 2026
## Summary

Cleans up the source picker by separating the two use cases that were tangled together inside one dropdown:

- **Primary: pick a source.** The pill becomes a real chip (icon + name + chevron) with a single hover affordance on the input root, and the dropdown lists sources only.
- **Secondary: manage sources.** View schema, Edit source, Manage sources, Create new source move into an adjacent kebab. The dropdown stops mixing data items with action items, and the "Schema" preview text stops outranking the source name visually.

Selector is now just a selector:
<img width="365" height="215" alt="image" src="https://github.com/user-attachments/assets/06967731-60e4-4cd5-9beb-47adba9b9ec2" />

Actions/management moved to a separate menu:
<img width="370" height="225" alt="image" src="https://github.com/user-attachments/assets/cc95ab17-2ff9-424b-bceb-c0a196718250" />

## What changed

- `SourceSelect.tsx`: refactor to chip + kebab. Extract a reusable `SourceManagementMenu` so non-`SourceSelectControlled` callers (e.g. `DBTableSelect`) can hang the same actions off their own input. Drop the old `sourceSchemaPreview` prop in favor of `onSchemaPreview` + `isSchemaPreviewEnabled`.
- `SourceSchemaPreview.tsx`: add a `controlled` variant that lets the parent own open state and drive the modal from outside; expose `isSourceSchemaPreviewEnabled(source)` for callers that need to gate a menu item.
- 9 callsites updated to the new API: `DBSearchPage`, `DBChartPage` (via `DBEditTimeChartForm/ChartEditorControls`), `DBTracePanel`, `DashboardFiltersModal`, `KubernetesDashboardPage`, `DBServiceMapPage`, `DBTableSelect`, `RawSqlChartEditor`. `PromqlChartEditor` uses `SourceSelect` but not the schema preview so it's untouched.
- Sanity fix along the way: `text-sucess-hover` → `text-success-hover`.

### Drive-by fixes spotted while clicking through

- **Manage sources used to behave like Edit source.** `router.push('/team')` from the kebab let the page's nuqs query state (source/where/select/filters/orderBy) write itself back into the new URL via `history.replaceState` on the way out, so `SourcesList` auto-expanded the leaked `?source=`. Switched to a hard navigation so Manage sources lands on a clean `/team?tab=data` with everything collapsed.
- **Duration Precision slider on the Trace source form was unreadable.** Mantine 9's Slider has `:where([data-orientation="vertical"]) .<part>` rules whose compiled selector matches when any ancestor is vertical. Mantine 9's Card sets `data-orientation="vertical"` by default and the source form renders inside a Card, so the slider's track collapsed to 8px and the four marks (Seconds / Millisecond / Microsecond / Nanosecond) stacked on top of each other. Added a `styles` override for every affected part (trackContainer, track, bar, thumb, markWrapper, markLabel, label) so the slider renders horizontally again with the value badge above the thumb.

## Test plan

- [x] Lint, typecheck, prose-lint clean on changed files
- [x] Unit tests pass for `SourceSelect`, `SourceSchemaPreview`, `DBTracePanel`, `DBSearchPage`, `SourceForm`
- [x] Knip surfaces no new issues
- [x] Manage sources lands on a clean `/team?tab=data` with all rows collapsed
- [x] Edit source lands on `/team?source=<id>` with that source expanded
- [x] Duration Precision slider renders horizontally with the value badge floating above the thumb
- [x] Manual click-through across the 9 callsites in light + dark: chip hover, kebab open/close, view-schema modal, edit-source route, create-new modal
- [x] Verify the dropdown lists data items only (no Edit / Create entries leaked back in)


Co-authored-by: Elizabet Oliveira <2750668+elizabetdev@users.noreply.github.com>
jordan-simonovski pushed a commit that referenced this pull request Jun 3, 2026
## Summary

Cleans up the source picker by separating the two use cases that were tangled together inside one dropdown:

- **Primary: pick a source.** The pill becomes a real chip (icon + name + chevron) with a single hover affordance on the input root, and the dropdown lists sources only.
- **Secondary: manage sources.** View schema, Edit source, Manage sources, Create new source move into an adjacent kebab. The dropdown stops mixing data items with action items, and the "Schema" preview text stops outranking the source name visually.

Selector is now just a selector:
<img width="365" height="215" alt="image" src="https://github.com/user-attachments/assets/06967731-60e4-4cd5-9beb-47adba9b9ec2" />

Actions/management moved to a separate menu:
<img width="370" height="225" alt="image" src="https://github.com/user-attachments/assets/cc95ab17-2ff9-424b-bceb-c0a196718250" />

## What changed

- `SourceSelect.tsx`: refactor to chip + kebab. Extract a reusable `SourceManagementMenu` so non-`SourceSelectControlled` callers (e.g. `DBTableSelect`) can hang the same actions off their own input. Drop the old `sourceSchemaPreview` prop in favor of `onSchemaPreview` + `isSchemaPreviewEnabled`.
- `SourceSchemaPreview.tsx`: add a `controlled` variant that lets the parent own open state and drive the modal from outside; expose `isSourceSchemaPreviewEnabled(source)` for callers that need to gate a menu item.
- 9 callsites updated to the new API: `DBSearchPage`, `DBChartPage` (via `DBEditTimeChartForm/ChartEditorControls`), `DBTracePanel`, `DashboardFiltersModal`, `KubernetesDashboardPage`, `DBServiceMapPage`, `DBTableSelect`, `RawSqlChartEditor`. `PromqlChartEditor` uses `SourceSelect` but not the schema preview so it's untouched.
- Sanity fix along the way: `text-sucess-hover` → `text-success-hover`.

### Drive-by fixes spotted while clicking through

- **Manage sources used to behave like Edit source.** `router.push('/team')` from the kebab let the page's nuqs query state (source/where/select/filters/orderBy) write itself back into the new URL via `history.replaceState` on the way out, so `SourcesList` auto-expanded the leaked `?source=`. Switched to a hard navigation so Manage sources lands on a clean `/team?tab=data` with everything collapsed.
- **Duration Precision slider on the Trace source form was unreadable.** Mantine 9's Slider has `:where([data-orientation="vertical"]) .<part>` rules whose compiled selector matches when any ancestor is vertical. Mantine 9's Card sets `data-orientation="vertical"` by default and the source form renders inside a Card, so the slider's track collapsed to 8px and the four marks (Seconds / Millisecond / Microsecond / Nanosecond) stacked on top of each other. Added a `styles` override for every affected part (trackContainer, track, bar, thumb, markWrapper, markLabel, label) so the slider renders horizontally again with the value badge above the thumb.

## Test plan

- [x] Lint, typecheck, prose-lint clean on changed files
- [x] Unit tests pass for `SourceSelect`, `SourceSchemaPreview`, `DBTracePanel`, `DBSearchPage`, `SourceForm`
- [x] Knip surfaces no new issues
- [x] Manage sources lands on a clean `/team?tab=data` with all rows collapsed
- [x] Edit source lands on `/team?source=<id>` with that source expanded
- [x] Duration Precision slider renders horizontally with the value badge floating above the thumb
- [x] Manual click-through across the 9 callsites in light + dark: chip hover, kebab open/close, view-schema modal, edit-source route, create-new modal
- [x] Verify the dropdown lists data items only (no Edit / Create entries leaked back in)


Co-authored-by: Elizabet Oliveira <2750668+elizabetdev@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge review/tier-3 Standard — full human review required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants