feat(source-picker): chip + kebab menu UX#2365
Conversation
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 detectedLatest commit: bcee484 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🟡 Tier 3 — StandardIntroduces new logic, modifies core functionality, or touches areas with non-trivial risk. Why this tier:
Review process: Full human review — logic, architecture, edge cases. Stats
|
Deep ReviewScope: 21 files, ~835 LOC changed against base Mode: 🟡 P2 -- recommended
🔵 P3 nitpicks (12)
Reviewers (10): correctness, testing, maintainability, project-standards, kieran-typescript, julik-frontend-races, api-contract, adversarial, agent-native, learnings-researcher. Testing gaps:
|
E2E Test Results✅ All tests passed • 190 passed • 3 skipped • 1334s
Tests ran across 4 shards in parallel. |
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.
|
Pushed 264e814 addressing the asymmetric "Edit sources" behavior across modes. The first cut had one Split by intent:
Net: in both modes, clicking Test plan additions:
|
… 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.
Fix-pack: lint, knip, e2e shard 4Pushed 5751776 addressing the three CI failures and the deep-review P2 findings on tests. Lint ( Knip ( E2E shard 4 (
P2 tests from the deep-review. Added unit coverage that was missing on the new surface:
Validation. Not in this fix-pack (deferred):
Commits since the last comment:
|
`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>
P0/P1 fix-pack: prefix router.basePath and switch onEditCurrentSource to hard-navAddressed two P0/P1 findings from the deep-review:
Validation: 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: |
elizabetdev
left a comment
There was a problem hiding this comment.
@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.
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>
Summary
Cleans up the source picker by separating the two use cases that were tangled together inside one dropdown:
Selector is now just a selector:

Actions/management moved to a separate menu:

What changed
SourceSelect.tsx: refactor to chip + kebab. Extract a reusableSourceManagementMenuso non-SourceSelectControlledcallers (e.g.DBTableSelect) can hang the same actions off their own input. Drop the oldsourceSchemaPreviewprop in favor ofonSchemaPreview+isSchemaPreviewEnabled.SourceSchemaPreview.tsx: add acontrolledvariant that lets the parent own open state and drive the modal from outside; exposeisSourceSchemaPreviewEnabled(source)for callers that need to gate a menu item.DBSearchPage,DBChartPage(viaDBEditTimeChartForm/ChartEditorControls),DBTracePanel,DashboardFiltersModal,KubernetesDashboardPage,DBServiceMapPage,DBTableSelect,RawSqlChartEditor.PromqlChartEditorusesSourceSelectbut not the schema preview so it's untouched.text-sucess-hover→text-success-hover.Drive-by fixes spotted while clicking through
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 viahistory.replaceStateon the way out, soSourcesListauto-expanded the leaked?source=. Switched to a hard navigation so Manage sources lands on a clean/team?tab=datawith everything collapsed.:where([data-orientation="vertical"]) .<part>rules whose compiled selector matches when any ancestor is vertical. Mantine 9's Card setsdata-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 astylesoverride 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
SourceSelect,SourceSchemaPreview,DBTracePanel,DBSearchPage,SourceForm/team?tab=datawith all rows collapsed/team?source=<id>with that source expanded