feat(lineage): float kebab on hover, keep status icons visible, enrich name tooltip#1383
Conversation
…h name tooltip Previously, hovering a lineage node swapped the descriptor + change-status icons out for an impact-radius + kebab cluster. The motion was distracting and hid info the user wanted to keep reading. Now the in-card icons stay put and a small floating toolbar (xyflow NodeToolbar) appears above the card's top-right corner on hover with just the kebab. A short hide-delay plus mouse handlers on the toolbar itself bridge the portal gap so the cursor can reach the kebab without losing hover. The model-name tooltip is upgraded from "stg_customers" to "stg_customers - view" / "raw_customers - seed" / "int_orders - table" (materialization for models, resource type otherwise) via a shared formatNodeTooltip helper in lineage/styles.tsx, reused by NodeView's sidebar header so the same label appears wherever the model name is shown. LineageCanvas now forwards onNodeContextMenu and onShowImpactRadius through a memoized wrapper nodeTypes, so Storybook stories (and any future caller) can populate the toolbar without going through GraphNodeOss. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Danyel Fisher <danyel@gmail.com>
The hover kebab and the model-name tooltip both rendered above the node card, so they could overlap on shorter names. The kebab now floats to the right side of the card (vertically centered) with a left-side hover bridge, freeing the entire top of the card for the tooltip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Danyel Fisher <danyel@gmail.com>
- Drop dead onShowImpactRadius prop + its wiring through LineageCanvas and GraphNodeOss (the icon was removed earlier; the callback no longer had a consumer). - Stabilize the LineageCanvas nodeTypes wrapper with a ref-backed callback so an unstable caller-side handler can no longer trigger full node remounts when ReactFlow re-registers node types. - Toolbar now uses MUI theme tokens (background.paper / divider) instead of hardcoded hex; MUI 7's CSS-variables mode flips them on .dark. - formatNodeTooltip drops the trailing "- unknown" suffix when resource type is missing. - NodeView Typography uses component="span" rather than a display:inline-block hack to attach the tooltip. - Add a hover -> kebab -> onContextMenu test for the floating toolbar (tagged data-testid="lineage-node-kebab"). Remove obsolete GraphNode impact-radius assertions and rewrite "hides resource icon on hover" to verify the icon now stays put after hover. - Extend the hover-hide grace period from 150ms to 500ms so the cursor has more leniency to travel from the card to the kebab. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Danyel Fisher <danyel@gmail.com>
… into worktree-remove-hide-icon-hover Signed-off-by: Danyel Fisher <danyel@gmail.com>
…ge comment - LineageCanvas: move onNodeContextMenu ref sync into useEffect (avoid mutating ref during render); give the conditional wrapper component a displayName so it shows up in React DevTools. - LineageNode: expand the hover-bridge comment to explain the asymmetric 12px-vs-6px sizing — the extra buffer is intentional and gives the user a generous target between the card edge and the floating kebab. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Danyel Fisher <danyel@gmail.com>
gcko
left a comment
There was a problem hiding this comment.
Code Review: PR #1383
SHA 3db06643 · Verdict GO
UX polish on the lineage node card: status icons stay visible on hover, a NodeToolbar-portaled kebab floats to the right with a 500ms grace + 12px bridge, and the title tooltip now appends materialization / resource type via a shared formatNodeTooltip helper. The LineageCanvas onNodeContextMenu plumbing is gated behind a ref-backed wrapper that is only consumed by Storybook today — the production OSS path still routes the kebab through GraphNodeOss.handleContextMenu. Tests (3707 passing), tsc --noEmit, and biome are clean on 3db06643. Verified the security surface (no dangerouslySetInnerHTML, no new I/O, model names rendered as text into MUI Tooltip), correctness on the tooltip fallback chain (materialized || resourceType for models, plain resourceType otherwise, name alone if no resourceType), and that the hover timer cancels on hover-begin and on unmount.
Notes
-
js/src/components/lineage/__tests__/GraphNode.test.tsx:504-513—"shows impact radius icon on hover for modified nodes"survives but no longer asserts anything (the only "assertion" is the trailing comment). The behavior it documents was removed by this PR. Same shape at:491-502("shows kebab menu on hover") and:638-654("calls showColumnLevelLineage on impact radius click for modified nodes"): they hover, then assert that a mock variable isdefined. They pass trivially. Worth deleting in a follow-up so the file stops describing removed behavior.
Evidence: lines 504-513 containfireEvent.mouseEnter(...)and a comment, noexpect(...)calls. The PR's diff already pruned four such tests aroundonShowImpactRadius; these two slipped through.
Pass C. -
LineageCanvas.tsx:114-131— thenodeTypeswrapper pattern is good (useMemogated onhasContextMenu, ref-backed indirection so callback updates don't re-mount the node-type registry). The inline arrow(event, nodeId) => onNodeContextMenuRef.current?.(event, nodeId)is created on every render ofLineageNodeWithContextMenu, which defeatsReact.memoonLineageNodefor the Storybook path. Not an issue in production (LineageView.tsx:406doesn't passonNodeContextMenu, sohasContextMenu=falseanddefaultNodeTypesis used). Mentioning it only so the constraint is on the record if this prop ever ships to production consumers.
Pass H.
PR checklist
What type of PR is this?
feat— UX polish on the lineage viewWhat this PR does / why we need it:
Three related improvements to the lineage view's model nodes:
Stop swapping icons on hover. Previously, hovering a node hid the descriptor + change-status icons and replaced them with an impact-radius + kebab cluster. The motion was distracting and removed information the viewer wanted to keep reading. Now the in-card icons stay put; a small floating toolbar (xyflow
NodeToolbar) appears on hover, containing just the kebab. The impact-radius button was redundant with the node's color/border treatment, so it's been dropped from the hover entirely (callback still exists onLineageNodefor future menu items). A 500ms hide grace period plus mouse handlers + an invisible bridge on the toolbar bridge the portal gap so the cursor can reach the kebab without losing hover.Move the hover kebab to the right of the card. The kebab originally appeared above the top-right corner, where it could overlap the model-name tooltip on shorter names. It now floats to the right side of the card, vertically centered, with a left-side hover bridge — leaving the entire top of the card free for the tooltip.
Enrich the model-name tooltip. Was:
stg_customers. Now:stg_customers - view/raw_customers - seed/int_orders - table(materialization for models, resource type otherwise). The sharedformatNodeTooltip(name, resourceType, materialized)helper lives inlineage/styles.tsxand is reused byNodeView's right-sidebar header so the same label appears anywhere the model name is shown.LineageCanvasalso gained anonNodeContextMenuprop that flows through a memoizednodeTypeswrapper to every node. Note for reviewers: in production the OSS app routes the context menu internally viaGraphNodeOss, so this prop is currently only consumed by Storybook stories — the wrapper exists so the kebab can be exercised in dev. It's intentional plumbing for forward use, not dead code, but worth knowing during review.Which issue(s) this PR fixes:
None linked.
Special notes for your reviewer:
recce serveron the jaffle-shop-duckdb project (snowflake_dev target), not just Storybook.LineageNode.test.tsxmock for@xyflow/reacthad to be extended withNodeToolbar. Two tooltip-format assertions updated from the oldname (kind)to the newname - kindshape.Does this PR introduce a user-facing change?:
Generated with Claude Code