Skip to content

fix(webapp): synchronize run page highlight when hovering spans#2998

Open
mpcgrid wants to merge 6 commits intomainfrom
fix/tri-6744-in-run-page-synchronize-highligh-on-hover
Open

fix(webapp): synchronize run page highlight when hovering spans#2998
mpcgrid wants to merge 6 commits intomainfrom
fix/tri-6744-in-run-page-synchronize-highligh-on-hover

Conversation

@mpcgrid
Copy link
Collaborator

@mpcgrid mpcgrid commented Feb 4, 2026

Closes #

✅ Checklist

  • I have followed every step in the contributing guide
  • The PR title follows the convention.
  • I ran and tested the code works

Testing

Checked in the interface


Changelog

Synchronized row between spans and timeline on hover


Screenshots

Screenshot 2026-02-04 at 17 36 09
Open with Devin

@changeset-bot
Copy link

changeset-bot bot commented Feb 4, 2026

⚠️ No Changeset found

Latest commit: 8b421f3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

Walkthrough

Adds hover-aware interactions and scroll-aware hover deferral to the runs route. Introduces hoveredNodeId state, an isScrolling flag with refs, and helpers handleHoverChange and handleScroll to queue and apply hover updates around scrolling. Adds onMouseEnter/onMouseLeave handlers to tree and timeline items, computes isHovered for styling, and propagates hover/scroll props through TasksTreeView into TimelineView and TreeView. TimelineViewProps and TimelineView signature are extended to accept the new fields.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: synchronizing highlight state when hovering over spans on the run page, which matches the hover interaction implementation in the changeset.
Description check ✅ Passed The description follows the template structure with checklist, testing details, changelog, and screenshots included; all required sections are present and adequately filled.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/tri-6744-in-run-page-synchronize-highligh-on-hover

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot]

This comment was marked as resolved.

@mpcgrid mpcgrid marked this pull request as ready for review February 6, 2026 07:28
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

@nicktrn nicktrn changed the title Fix/tri 6-744 In run page synchronize highlight when hovering spans fix(webapp): synchronize run page highlight when hovering spans Feb 6, 2026
Copy link
Collaborator

@nicktrn nicktrn left a comment

Choose a reason for hiding this comment

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

PR Review: Synchronize run page highlight on hover

Overall: The feature goal is good — syncing hover highlights between the tree panel and timeline panel is a clear UX improvement. However, the current implementation has a high-severity bug, significant performance concerns, and several code quality issues that should be addressed before merging.


HIGH — Stale hover after mousewheel scroll

When scrolling via mousewheel while the cursor hovers over a row, the rows move under the mouse but onMouseEnter/onMouseLeave events do not fire for the new rows appearing beneath the cursor. The pendingHoverIdRef retains the ID of the row that was under the mouse when scrolling started. When the 150ms debounce expires, that stale ID is applied — highlighting a row the user isn't pointing at.

Similarly, when virtualized rows are recycled during scroll (TanStack Virtual unmounts/mounts row components), no onMouseLeave fires for the unmounted row, leaving pendingHoverIdRef stale.

Suggestion: When scroll ends, set hoveredNodeId to null instead of applying the ref value. Let the next natural mouse event re-establish hover:

scrollTimeoutRef.current = setTimeout(() => {
  setIsScrolling(false);
  setHoveredNodeId(null);
  pendingHoverIdRef.current = null;
}, 150);

HIGH — Performance: every hover re-renders the entire tree + timeline

hoveredNodeId is React state in TasksTreeView. Every setHoveredNodeId call re-renders the parent, which re-renders both TreeView components (tree + timeline). Key factors:

  • TreeView is not wrapped in React.memo
  • renderNode callbacks are inline arrow functions, recreated every render
  • Virtualizer uses overscan: 50, so ~70-80 rows re-render per panel per hover event (~140-160 total)
  • Timeline rows are expensive (multiple Timeline.Point, motion.div, Framer Motion animations)

Suggestion: Consider a CSS/DOM-based approach that avoids React re-renders entirely. Add data-node-id attributes to rows and toggle a data-hovered attribute via refs + direct DOM manipulation. The hover highlight is purely visual — it doesn't need to participate in React's render cycle. If that feels too imperative, an intermediate option is to add pointer-events: none to the scroll container during scroll (one state toggle on the container, not per-node), and keep CSS :hover for the actual highlight.


MEDIUM — CSS hover replaced with JS hover adds latency

The PR replaces native CSS :hover pseudo-classes (hover:bg-grid-dimmed) with React state-driven styling. CSS :hover is handled on the compositor thread and is effectively instant. The JS approach adds at minimum one frame of latency (~16ms) through the React event → setState → reconciliation → paint pipeline, and more under load. Users will notice the highlight trailing the mouse when scanning quickly across rows.


LOW — setHoveredNodeId prop passed but unused in TimelineView

TimelineViewProps includes setHoveredNodeId, but TimelineView only uses handleHoverChange (which wraps setHoveredNodeId with scroll-aware logic). The raw setter shouldn't be exposed — it bypasses scroll suppression and is dead code.


LOW — handleHoverChange dependency on isScrolling state destabilizes callback identity

const handleHoverChange = useCallback((nodeId) => {
  pendingHoverIdRef.current = nodeId;
  if (!isScrolling) {
    setHoveredNodeId(nodeId);
  }
}, [isScrolling]); // changes every time isScrolling toggles

When isScrolling toggles, handleHoverChange gets a new identity. This would bust any React.memo on child components receiving it. If isScrolling were tracked as a ref instead of state, the callback would be stable ([] deps).


LOW — Duplicated hover styling logic

The hover class logic is copy-pasted in both the tree renderNode and timeline renderNode:

state.selected
  ? isHovered ? "bg-grid-bright" : "bg-grid-dimmed"
  : isHovered ? "bg-grid-dimmed" : "bg-transparent"

Could be extracted into a helper like getRowBgClass(selected, hovered).


LOW — Indentation bug in handleScroll

scrollTimeoutRef.current = setTimeout(() => {
  setIsScrolling(false);
    setHoveredNodeId(pendingHoverIdRef.current); // 8 spaces, should be 6
}, 150);

LOW — Unused parameter

handleScroll takes scrollTop: number but never reads it. Should be (_scrollTop: number) => void or () => void.


LOW — NodeJS.Timeout type in browser context

useRef<NodeJS.Timeout | null> — in a browser environment this should be ReturnType<typeof setTimeout> for correctness.

Comment on lines +697 to +704
const [hoveredNodeId, setHoveredNodeId] = useState<string | null>(null);
const [isScrolling, setIsScrolling] = useState(false);
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const pendingHoverIdRef = useRef<string | null>(null);

useEffect(() => {
return () => {
if (scrollTimeoutRef.current) {
Copy link
Member

Choose a reason for hiding this comment

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

I am pretty sure this is going to cause a re-render of the entire tree every time a hover changes. Which will crush the rendering performance in the browser for complex trees.

We have to very careful to avoid the inputs to the tree view changing rapidly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:
- Around line 1034-1060: TimelineView declares and destructures setHoveredNodeId
in TimelineViewProps and the TimelineView parameter list but never uses it;
remove this unused prop to simplify the API by deleting setHoveredNodeId from
TimelineViewProps, removing it from the TimelineView function parameter
destructuring, and removing the argument passed from the parent component that
supplies TimelineView (where setHoveredNodeId is currently forwarded), ensuring
only handleHoverChange remains for hover handling.
- Around line 729-738: The handleScroll function currently takes an unused
parameter scrollTop; update the signature to either remove the parameter
entirely or rename it to _scrollTop to indicate it's intentionally unused
(change in the handleScroll definition where scrollTop is declared). No other
logic changes needed — keep the setIsScrolling, clearTimeout/scrollTimeoutRef,
pendingHoverIdRef, and setHoveredNodeId behavior intact.
🧹 Nitpick comments (2)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:
- Around line 1034-1060: TimelineView declares and destructures setHoveredNodeId
in TimelineViewProps and the TimelineView parameter list but never uses it;
remove this unused prop to simplify the API by deleting setHoveredNodeId from
TimelineViewProps, removing it from the TimelineView function parameter
destructuring, and removing the argument passed from the parent component that
supplies TimelineView (where setHoveredNodeId is currently forwarded), ensuring
only handleHoverChange remains for hover handling.
- Around line 729-738: The handleScroll function currently takes an unused
parameter scrollTop; update the signature to either remove the parameter
entirely or rename it to _scrollTop to indicate it's intentionally unused
(change in the handleScroll definition where scrollTop is declared). No other
logic changes needed — keep the setIsScrolling, clearTimeout/scrollTimeoutRef,
pendingHoverIdRef, and setHoveredNodeId behavior intact.
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx (2)

1034-1060: setHoveredNodeId is passed but unused in TimelineView.

setHoveredNodeId is declared in TimelineViewProps (line 1035), destructured (line 1058), and passed from the parent (line 966), but it is never referenced inside TimelineView. Only handleHoverChange is used for hover state transitions. Consider removing it to keep the component API minimal.

Suggested fix
   treeScrollRef: React.RefObject<HTMLDivElement>;
   hoveredNodeId: string | null;
-  setHoveredNodeId: (nodeId: string | null) => void;
   handleHoverChange: (nodeId: string | null) => void;
   handleScroll: (scrollTop: number) => void;

And in the parent where props are passed:

             hoveredNodeId={hoveredNodeId}
-            setHoveredNodeId={setHoveredNodeId}
             handleHoverChange={handleHoverChange}
             handleScroll={handleScroll}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
around lines 1034 - 1060, TimelineView declares and destructures
setHoveredNodeId in TimelineViewProps and the TimelineView parameter list but
never uses it; remove this unused prop to simplify the API by deleting
setHoveredNodeId from TimelineViewProps, removing it from the TimelineView
function parameter destructuring, and removing the argument passed from the
parent component that supplies TimelineView (where setHoveredNodeId is currently
forwarded), ensuring only handleHoverChange remains for hover handling.

729-738: Unused scrollTop parameter.

scrollTop is accepted but never referenced in the function body. Remove it or prefix with _ to signal intent.

Suggested fix
-  const handleScroll = useCallback((scrollTop: number) => {
+  const handleScroll = useCallback((_scrollTop: number) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
around lines 729 - 738, The handleScroll function currently takes an unused
parameter scrollTop; update the signature to either remove the parameter
entirely or rename it to _scrollTop to indicate it's intentionally unused
(change in the handleScroll definition where scrollTop is declared). No other
logic changes needed — keep the setIsScrolling, clearTimeout/scrollTimeoutRef,
pendingHoverIdRef, and setHoveredNodeId behavior intact.
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f3575a and 8b421f3.

📒 Files selected for processing (1)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
🧠 Learnings (7)
📚 Learning: 2026-02-11T16:50:07.201Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3019
File: apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardId.widgets.tsx:126-131
Timestamp: 2026-02-11T16:50:07.201Z
Learning: In apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardId.widgets.tsx, MetricsDashboard entities are intentionally scoped to the organization level, not the project level. The dashboard lookup should filter by organizationId only (not projectId), allowing dashboards to be accessed across projects within the same organization. The optional projectId field on MetricsDashboard serves other purposes and should not be used as an authorization constraint.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (e.g., Edit with PencilSquareIcon) in the TableCellMenu are intentionally icon-only with no text labels as a compact UI pattern. This is a deliberate design choice for this route; preserve the icon-only behavior for consistency in this file.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Attach metadata to task runs using the metadata option when triggering, and access/update it inside runs using metadata functions

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Export tasks with unique IDs within the project to enable proper task discovery and execution

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `metadata.parent` and `metadata.root` to update parent and root task metadata from child tasks

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
📚 Learning: 2026-02-11T16:37:32.429Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3019
File: apps/webapp/app/components/primitives/charts/Card.tsx:26-30
Timestamp: 2026-02-11T16:37:32.429Z
Learning: In projects using react-grid-layout, avoid relying on drag-handle class to imply draggability. Ensure drag-handle elements only affect dragging when the parent grid item is configured draggable in the layout; conditionally apply cursor styles based on the draggable prop. This improves correctness and accessibility.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
🧬 Code graph analysis (1)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx (1)
apps/webapp/app/utils/cn.ts (1)
  • cn (77-79)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (26)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: typecheck / typecheck
🔇 Additional comments (5)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx (5)

697-709: LGTM! State declarations and cleanup effect are well-structured. The cleanup properly clears any pending timeout on unmount to prevent state updates on an unmounted component.


722-727: LGTM! The callback correctly defers hover updates while scrolling and stores the pending value for later application.


854-873: LGTM! Hover detection and styling logic are correctly applied, and onMouseEnter/onMouseLeave handlers are properly wired. The styling matches the corresponding timeline view logic for visual consistency.


1206-1225: LGTM! Hover logic in the timeline view mirrors the tree view, ensuring synchronized highlighting across both panels.


932-938: LGTM! Both scroll handlers properly integrate handleScroll alongside the existing scroll synchronization logic.

Also applies to: 1335-1341

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines +722 to +727
const handleHoverChange = useCallback((nodeId: string | null) => {
pendingHoverIdRef.current = nodeId;
if (!isScrolling) {
setHoveredNodeId(nodeId);
}
}, [isScrolling]);

Choose a reason for hiding this comment

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

🚩 handleHoverChange recreated on every isScrolling toggle

handleHoverChange has [isScrolling] as its useCallback dependency (route.tsx:727). Every time scrolling starts or stops, isScrolling toggles, creating a new function reference. This new reference propagates to TimelineView via the handleHoverChange prop, causing TimelineView to re-render (and all its renderNode callbacks to receive the new function). For traces with many spans, this means two extra full re-renders per scroll gesture (one when isScrolling flips to true, one when it flips back). Consider using a ref for isScrolling instead of state to avoid this, since the scroll-suppression logic doesn't actually need to trigger re-renders — only the timeout callback's setHoveredNodeId(pendingHoverIdRef.current) needs to cause a render.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants