From 885e84b215d8ba05228923e2dc7a5fd760dcd8ac Mon Sep 17 00:00:00 2001 From: mpcgird Date: Thu, 11 Dec 2025 23:21:21 +0000 Subject: [PATCH 1/3] Fix for run page highlight sync between step row and timeline --- .../route.tsx | 56 +++++++++++++------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx index 840a4376e2..95921bc525 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx @@ -644,6 +644,7 @@ function TasksTreeView({ const [showDurations, setShowDurations] = useState(true); const [showQueueTime, setShowQueueTime] = useState(false); const [scale, setScale] = useState(0); + const [hoveredNodeId, setHoveredNodeId] = useState(null); const parentRef = useRef(null); const treeScrollRef = useRef(null); const timelineScrollRef = useRef(null); @@ -769,19 +770,27 @@ function TasksTreeView({ getNodeProps={getNodeProps} getTreeProps={getTreeProps} parentClassName="pl-3" - renderNode={({ node, state, index }) => ( - <> -
{ - selectNode(node.id); - }} - > + renderNode={({ node, state, index }) => { + const isHovered = hoveredNodeId === node.id; + return ( + <> +
{ + selectNode(node.id); + }} + onMouseEnter={() => setHoveredNodeId(node.id)} + onMouseLeave={() => setHoveredNodeId(null)} + >
{Array.from({ length: node.level }).map((_, index) => (
- )} + ); + }} onScroll={(scrollTop) => { //sync the scroll to the tree if (timelineScrollRef.current) { @@ -870,6 +880,8 @@ function TasksTreeView({ treeScrollRef={treeScrollRef} virtualizer={virtualizer} toggleNodeSelection={toggleNodeSelection} + hoveredNodeId={hoveredNodeId} + setHoveredNodeId={setHoveredNodeId} /> @@ -935,6 +947,8 @@ type TimelineViewProps = Pick< toggleNodeSelection: UseTreeStateOutput["toggleNodeSelection"]; showDurations: boolean; treeScrollRef: React.RefObject; + hoveredNodeId: string | null; + setHoveredNodeId: (nodeId: string | null) => void; }; const tickCount = 5; @@ -954,6 +968,8 @@ function TimelineView({ showDurations, treeScrollRef, queuedDuration, + hoveredNodeId, + setHoveredNodeId, }: TimelineViewProps) { const timelineContainerRef = useRef(null); const initialTimelineDimensions = useInitialDimensions(timelineContainerRef); @@ -1098,6 +1114,7 @@ function TimelineView({ parentClassName="h-full scrollbar-hide" renderNode={({ node, state, index, virtualizer, virtualItem }) => { const isTopSpan = node.id === events[0]?.id; + const isHovered = hoveredNodeId === node.id; return ( console.log(`hover ${index}`)} onClick={(e) => { toggleNodeSelection(node.id); }} + onMouseEnter={() => setHoveredNodeId(node.id)} + onMouseLeave={() => setHoveredNodeId(null)} > {node.data.level === "TRACE" ? ( <> From f2112da8f243ed7d81f8f0e2e0551a6febc9c317 Mon Sep 17 00:00:00 2001 From: mpcgird Date: Mon, 15 Dec 2025 23:15:55 +0200 Subject: [PATCH 2/3] deactivated scroll while scrolling in order to hide the missynchronization between tables hover --- .../route.tsx | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx index 95921bc525..efbd5109cb 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx @@ -645,6 +645,9 @@ function TasksTreeView({ const [showQueueTime, setShowQueueTime] = useState(false); const [scale, setScale] = useState(0); const [hoveredNodeId, setHoveredNodeId] = useState(null); + const [isScrolling, setIsScrolling] = useState(false); + const scrollTimeoutRef = useRef(null); + const pendingHoverIdRef = useRef(null); const parentRef = useRef(null); const treeScrollRef = useRef(null); const timelineScrollRef = useRef(null); @@ -656,6 +659,27 @@ function TasksTreeView({ const displayEvents = events; const queuedTime = showQueueTime ? undefined : queuedDuration; + const handleHoverChange = useCallback((nodeId: string | null) => { + pendingHoverIdRef.current = nodeId; + if (!isScrolling) { + setHoveredNodeId(nodeId); + } + }, [isScrolling]); + + const handleScroll = useCallback((scrollTop: number) => { + setIsScrolling(true); + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current); + } + scrollTimeoutRef.current = setTimeout(() => { + setIsScrolling(false); + // Apply the pending hover ID when scrolling stops + if (pendingHoverIdRef.current !== null) { + setHoveredNodeId(pendingHoverIdRef.current); + } + }, 150); + }, []); + const { nodes, getTreeProps, @@ -788,8 +812,8 @@ function TasksTreeView({ onClick={() => { selectNode(node.id); }} - onMouseEnter={() => setHoveredNodeId(node.id)} - onMouseLeave={() => setHoveredNodeId(null)} + onMouseEnter={() => handleHoverChange(node.id)} + onMouseLeave={() => handleHoverChange(null)} >
{Array.from({ length: node.level }).map((_, index) => ( @@ -849,6 +873,7 @@ function TasksTreeView({ ); }} onScroll={(scrollTop) => { + handleScroll(scrollTop); //sync the scroll to the tree if (timelineScrollRef.current) { timelineScrollRef.current.scrollTop = scrollTop; @@ -882,6 +907,8 @@ function TasksTreeView({ toggleNodeSelection={toggleNodeSelection} hoveredNodeId={hoveredNodeId} setHoveredNodeId={setHoveredNodeId} + handleHoverChange={handleHoverChange} + handleScroll={handleScroll} /> @@ -949,6 +976,8 @@ type TimelineViewProps = Pick< treeScrollRef: React.RefObject; hoveredNodeId: string | null; setHoveredNodeId: (nodeId: string | null) => void; + handleHoverChange: (nodeId: string | null) => void; + handleScroll: (scrollTop: number) => void; }; const tickCount = 5; @@ -970,6 +999,8 @@ function TimelineView({ queuedDuration, hoveredNodeId, setHoveredNodeId, + handleHoverChange, + handleScroll, }: TimelineViewProps) { const timelineContainerRef = useRef(null); const initialTimelineDimensions = useInitialDimensions(timelineContainerRef); @@ -1132,8 +1163,8 @@ function TimelineView({ onClick={(e) => { toggleNodeSelection(node.id); }} - onMouseEnter={() => setHoveredNodeId(node.id)} - onMouseLeave={() => setHoveredNodeId(null)} + onMouseEnter={() => handleHoverChange(node.id)} + onMouseLeave={() => handleHoverChange(null)} > {node.data.level === "TRACE" ? ( <> @@ -1239,6 +1270,7 @@ function TimelineView({ ); }} onScroll={(scrollTop) => { + handleScroll(scrollTop); //sync the scroll to the tree if (treeScrollRef.current) { treeScrollRef.current.scrollTop = scrollTop; From 4f3575ac921a9d726e0c24c9cb6d753147f8d59d Mon Sep 17 00:00:00 2001 From: mpcgird Date: Wed, 4 Feb 2026 17:52:57 +0200 Subject: [PATCH 3/3] small review fixes --- .../route.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx index 90ee52c05f..983c20decb 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx @@ -698,6 +698,16 @@ function TasksTreeView({ const [isScrolling, setIsScrolling] = useState(false); const scrollTimeoutRef = useRef(null); const pendingHoverIdRef = useRef(null); + + useEffect(() => { + return () => { + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current); + scrollTimeoutRef.current = null; + } + }; + }, []); + const parentRef = useRef(null); const treeScrollRef = useRef(null); const timelineScrollRef = useRef(null); @@ -723,10 +733,7 @@ function TasksTreeView({ } scrollTimeoutRef.current = setTimeout(() => { setIsScrolling(false); - // Apply the pending hover ID when scrolling stops - if (pendingHoverIdRef.current !== null) { setHoveredNodeId(pendingHoverIdRef.current); - } }, 150); }, []);