Skip to content

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

Open
alex-fedotyev wants to merge 11 commits into
mainfrom
alex/source-chip-ux-prototype
Open

feat(source-picker): chip + kebab menu UX#2365
alex-fedotyev wants to merge 11 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: bcee484

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 May 29, 2026 3:15pm
hyperdx-storybook Error Error May 29, 2026 3:15pm

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

Scope: 21 files, ~835 LOC changed against base cb6a74ce. TypeScript / React (Mantine 9) frontend refactor of SourceSelect into chip + kebab UX, new controlled mode for SourceSchemaPreview, drive-by Mantine 9 Slider styles override and hard-nav helpers for /team navigation.

Mode: report-only -- no edits, no commits, no artifacts.

🟡 P2 -- recommended

  • packages/app/src/components/SourceSchemaPreview.tsx:239 -- the controlled-mode Modal is rendered inside {isEnabled && (...)}, so when isSourceSchemaPreviewEnabled(source) flips to false while a parent has open=true (source cleared in DBTableSelect, table refetch yielding empty metricTables / from.tableName), the Modal unmounts without ever invoking onClose; parent state stays stuck open and the modal spontaneously reappears the next time isEnabled flips back to true.
    • Fix: Add useEffect(() => { if (controlled && open && !isEnabled) onClose?.(); }, [controlled, open, isEnabled, onClose]), or render the Modal unconditionally and gate only the inner content on isEnabled.
    • adversarial, kieran-typescript, julik-frontend-races
  • packages/app/src/components/SourceSelect.tsx:213 -- style, className, data-testid and other root-level callsite props (e.g. DBSearchPage passes style={{ minWidth: 150 }} and a data-testid) are still spread onto the inner Select via {...props}, but the visible control is now an outer Group wrapping Select + SourceManagementMenu; layout constraints and test selectors silently shift to a sub-element.
    • Fix: Pull style, className, and data-* off props before spreading and forward them to the outer Group, or expose a wrapperProps passthrough.
    • correctness, kieran-typescript
  • packages/app/src/components/SelectControlled.tsx:5 -- SelectControlledSpecialValues (the _create_new_value / _edit_value enum), the onCreate / onEdit props, and the matching onChange branches in SelectControlled are unreachable after SourceSelect stopped injecting the magic items; a future caller could re-wire action items back into the dropdown without realizing it.
    • Fix: Delete the enum, the onCreate / onEdit props on SelectControlledProps, and the two onChange branches; drop the now-dead onCreate from ServiceSelectControlled in ServicesDashboardPage.
    • maintainability, correctness
  • packages/app/src/components/Sources/SourcesList.tsx:78 -- the new ?source=<id> deep-link → auto-expand + router.replace strip effect (destination of the Edit source hard-nav) has no unit coverage, including the expandedFromQueryRef guard that prevents the effect from re-firing on subsequent renders.
    • Fix: Add a SourcesList test that mounts with router.query.source='abc', asserts setEditedSourceId('abc') is called once, that router.replace strips the param while preserving other query state, and that a repeat render does not re-expand.
    • testing, maintainability, correctness
  • packages/app/src/components/SourceSchemaPreview.tsx:210 -- isSourceSchemaPreviewEnabled(source) is a newly exported helper that gates the menu item across 8 callsites, but has no direct unit test for its branches (undefined source, log source with no from.tableName, metric source with empty metricTables, MV-only configs).
    • Fix: Add unit tests for each branch so a future schema-shape change doesn't silently disable / enable the menu everywhere.
    • testing
  • packages/app/src/DBSearchPage.tsx:1751 -- the new onEditCurrentSource / onManageSources hard-nav handlers (which compose the URL with ${router.basePath}/team?source=... and branch on IS_LOCAL_MODE / inputSource) have no unit coverage of the basePath prefix, the inputSource set vs unset branches, or the local-mode-inline-modal vs non-local-hard-nav split.
    • Fix: Add tests that stub window.location.assign, vary router.basePath and inputSource, and assert the composed URL plus the local-mode branch.
    • testing
  • packages/app/src/components/Sources/SourceForm.tsx:1525 -- the styles override on the Duration Precision Slider (trackContainer, track, bar, thumb, markWrapper, markLabel, label) pins to Mantine 9 internal CSS-variable names; a Mantine patch / minor bump that renames any of them silently regresses the slider with no test signal.
    • Fix: Add a focused render assertion (snapshot or computed-style check) for the slider inside Card so a Mantine bump that breaks the override fails CI.
    • testing, adversarial
🔵 P3 nitpicks (12)
  • packages/app/src/DBSearchPage.tsx:1757 -- inputSource is interpolated into the URL raw; today source IDs are UUID-shaped so the URL is mechanically safe, but a future ID containing &, #, ?, or whitespace silently corrupts the ?source= param SourcesList reads back.
    • Fix: Wrap with encodeURIComponent(inputSource).
    • kieran-typescript, julik-frontend-races
  • packages/app/src/components/SourceSchemaPreview.tsx:225 -- getSourceSchemaTables(source) is invoked once directly and again inside isSourceSchemaPreviewEnabled(source) on every render of the controlled callers, walking the source twice for the same result.
    • Fix: Memoize tables = useMemo(() => getSourceSchemaTables(source), [source]) and derive isEnabled from tables.length.
    • correctness, maintainability, kieran-typescript
  • packages/app/src/components/__tests__/DBTracePanel.test.tsx:43 -- the jest.mock factory for SourceSchemaPreview declares getSourceSchemaTables: () => [], but getSourceSchemaTables is a private helper with no export keyword; the mock entry is a no-op that misleads readers into thinking an export contract exists. Same pattern in DBSearchPage.directTrace.test.tsx.
    • Fix: Drop the getSourceSchemaTables key from both jest.mock factories, or actually export the helper if a caller needs it.
    • maintainability, project-standards
  • packages/app/src/DBSearchPage.tsx:1761 -- onEditCurrentSource (useCallback) and onManageSources (useMemo) read router.basePath inside their closures but list neither router nor router.basePath in their dep arrays; values are static at runtime, but react-hooks/exhaustive-deps flags this and yarn lint:fix per the project workflow will surface it.
    • Fix: Add router to the dep arrays or hoist const basePath = router.basePath above the closures.
    • project-standards, correctness, kieran-typescript
  • packages/app/src/components/DBTableSelect.tsx:72 -- SourceManagementMenu is rendered unconditionally with only onSchemaPreview wired, so the kebab is always visible even before a table is selected; in that state the only menu item (View schema) is permanently disabled, surfacing a non-functional dot-menu next to every table picker.
    • Fix: Render the menu conditionally on previewSource truthy, or make SourceManagementMenu return null when every item it would render is disabled.
    • adversarial
  • packages/app/src/components/Sources/SourcesList.tsx:84 -- the deep-link router.replace appends hash: 'sources', but no DOM element has id="sources" (actual id is team-data-sources); the resulting URL gets a dead fragment that races with TeamPage's tab-init router.replace in the same tick.
    • Fix: Drop the hash: 'sources' from the replace; tighten effect deps to [router.isReady, router.query.source, sources].
    • adversarial
  • packages/app/src/DBSearchPage.tsx:1757 -- window.location.assign(${router.basePath}/team) does not normalize trailing slashes; a misconfigured NEXT_PUBLIC_HYPERDX_BASE_PATH='/' or '/app/' produces '//team' or '/app//team', which browsers can interpret as protocol-relative or as a malformed path.
    • Fix: Extract a small hardNavTo(path) helper that strips a trailing / from basePath before concatenation.
    • adversarial
  • packages/app/src/components/SourceSelect.tsx:250 -- SourceSelectControlled is wrapped in React.memo, but every callsite passes inline onSchemaPreview, onEdit, and onManageSources handlers, so the shallow-compare always misses; the memo is now a misleading no-op that suggests the component is cheap to re-render.
    • Fix: Drop the memo wrapper, or stabilize handlers at every callsite with useCallback.
    • adversarial, kieran-typescript
  • packages/app/src/components/SourceSelect.tsx:131 -- Menu.Target wraps a Tooltip wrapping the ActionIcon; other Menu targets in this codebase always wrap a bare ActionIcon, and the Tooltip + Menu pair fight over ref forwarding and over staying-visible-on-click behavior.
    • Fix: Drop the Tooltip (the aria-label covers a11y), or move the tooltip outside Menu.Target so it does not nest into the trigger.
    • kieran-typescript, julik-frontend-races
  • packages/app/styles/SourceSelectControlled.module.scss:1 -- .sourceSchemaPreviewButton is orphaned; its comment claims it is "still used by DBTableSelect", but DBTableSelect no longer imports SourceSelectRightSection and a repo-wide grep returns only this declaration.
    • Fix: Delete the class and its comment.
    • maintainability
  • packages/app/src/components/SourceSchemaPreview.tsx:137 -- the controlled: boolean + optional open + optional onClose shape lets invalid states (e.g. controlled without open) compile and silently render a permanently closed modal.
    • Fix: Replace with a discriminated union where presence of open/onClose is the controlled signal, or split into ControlledProps / UncontrolledProps.
    • maintainability, kieran-typescript
  • packages/app/tests/e2e/features/sources.spec.ts:105 -- the new e2e moves action assertions onto the kebab but does not exercise the View schema item (the viewSchemaItem getter was added to SearchPage and is referenced nowhere), nor the disabled state of Edit source / View schema when no source is selected.
    • Fix: Add an e2e that asserts viewSchemaItem is visible when a source is selected, and that editSourceItem / viewSchemaItem show data-disabled="true" when the selection is cleared.
    • testing

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

Testing gaps:

  • No regression test for the controlled SourceSchemaPreview lifecycle when source becomes invalid mid-open.
  • No coverage of the SourcesList ?source=<id> deep-link → expand → strip cycle.
  • No coverage of the DBSearchPage hard-nav handlers (onEditCurrentSource, onManageSources) under varying router.basePath and IS_LOCAL_MODE.
  • No direct unit tests for the new exported isSourceSchemaPreviewEnabled helper.
  • No visual / computed-style regression for the Duration Precision slider styles override inside Card.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

E2E Test Results

All tests passed • 190 passed • 3 skipped • 1334s

Status Count
✅ Passed 190
❌ Failed 0
⚠️ Flaky 5
⏭️ 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>
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