Skip to content

feat(ui): entities chart implementation, entities swimlanes 2.5 - #497

Merged
rapids-bot[bot] merged 3 commits into
rapidsai:mainfrom
johallar:joe/long-entities-gantt-2.5
Aug 5, 2026
Merged

feat(ui): entities chart implementation, entities swimlanes 2.5#497
rapids-bot[bot] merged 3 commits into
rapidsai:mainfrom
johallar:joe/long-entities-gantt-2.5

Conversation

@johallar

@johallar johallar commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Description

  • First draft implementation of the entities gantt chart using the new GanttChart component
  • Does NOT add it to the timeline views yet, that will be the next one

Related Issues

relates to #215

Testing

N/A

Screenshots

N/A

@johallar johallar changed the title feat(ui): entities chart implementation feat(ui): entities chart implementation, entities swimlanes 2.5 Jul 30, 2026
@johallar
johallar force-pushed the joe/long-entities-gantt-2.5 branch from 226678c to 00e8c6d Compare August 3, 2026 16:20
@johallar
johallar marked this pull request as ready for review August 4, 2026 16:20
@johallar
johallar requested a review from cmatzenbach as a code owner August 4, 2026 16:20
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 8b909cc6-c163-4c94-919c-91954be3c7d8

📥 Commits

Reviewing files that changed from the base of the PR and between 3da06d1 and f208b00.

📒 Files selected for processing (6)
  • ui/packages/@quent/components/src/gantt-chart/options.test.ts
  • ui/packages/@quent/components/src/index.ts
  • ui/packages/@quent/components/src/long-entities/LongEntitiesGantt.tsx
  • ui/packages/@quent/components/src/long-entities/types.ts
  • ui/packages/@quent/components/src/long-entities/utils.test.ts
  • ui/packages/@quent/components/src/long-entities/utils.ts
💤 Files with no reviewable changes (1)
  • ui/packages/@quent/components/src/gantt-chart/options.test.ts

📝 Walkthrough

Walkthrough

This PR adds a long-entity Gantt component, exported long-entity types, and utilities that convert FSM transitions into row-packed chart entries. It also adds tests for entry building and a chart option default.

Changes

Gantt timeline components

Layer / File(s) Summary
Long-entity Gantt rendering
ui/packages/@quent/components/src/long-entities/*, ui/packages/@quent/components/src/index.ts, ui/packages/@quent/components/src/gantt-chart/options.test.ts
Adds LongEntitySegment and LongEntityEntry types, row ID helpers, and buildLongEntityEntries to convert FSM transitions into colored millisecond segments and packed rows. Adds LongEntitiesGantt to flatten segments into custom-series data, clip and label rendered bars, configure GanttChart, and show an empty state. Exports the new component, props, types, and utility. Tests cover segment creation, filtering, labels, colors, attributes, row assignment, and the default TIMELINE_SPACING fallback in buildGanttOption.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • rapidsai/quent#496: This PR builds on the shared Gantt infrastructure from that PR and adds a long-entity chart on top of GanttChart and related utilities.

Suggested labels: feature request, non-breaking, ui

Suggested reviewers: cmatzenbach

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the UI entities chart implementation and swimlanes work.
Description check ✅ Passed The description covers the change, related issue, testing status, and screenshots, although testing and screenshots are marked N/A.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (2)
ui/packages/@quent/components/src/gantt-chart/hover.ts-26-36 (1)

26-36: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear the hover when the pixel conversion yields no finite value.

At Line 28 the handler returns without calling onChange. The consumer keeps the previous hover, so the tooltip shows a stale timestamp while the pointer continues to move. All other failure branches clear the hover.

🐛 Proposed fix
     try {
       const value = instance.convertFromPixel({ xAxisIndex: 0 }, point[0]);
-      if (value == null || !Number.isFinite(value as number)) return;
+      if (value == null || !Number.isFinite(value as number)) {
+        onChange(null);
+        return;
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/gantt-chart/hover.ts around lines 26 - 36,
Update the pixel-conversion validation in the hover handler to call
onChange(null) before returning when convertFromPixel produces null or a
non-finite value. Preserve the existing onChange timestamp path for valid finite
values and the catch-block clearing behavior.
ui/packages/@quent/components/src/gantt-chart/GanttChart.tsx-74-81 (1)

74-81: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Avoid the spread over the full datum array.

Math.max(...data.map(...)) passes one argument per datum. LongEntitiesGantt emits one datum per state segment, so the argument count grows with the total number of transitions in the trace. A large trace can throw RangeError: Maximum call stack size exceeded. Use a reduction instead.

🐛 Proposed fix
   const { yAxisCategories, rowCount } = useMemo(() => {
     if (data.length === 0) return { yAxisCategories: [] as number[], rowCount: 0 };
-    const maxRow = Math.max(...data.map(datum => datum.value[2]));
+    const maxRow = data.reduce((max, datum) => Math.max(max, datum.value[2]), 0);
     return {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/gantt-chart/GanttChart.tsx around lines 74
- 81, Replace the spread-based Math.max call in the yAxisCategories/rowCount
useMemo with a reduction over data that computes the maximum datum.value[2]
without expanding the array into function arguments. Preserve the existing
empty-data result and category/rowCount calculations.

Source: Path instructions

🧹 Nitpick comments (6)
ui/packages/@quent/components/src/index.ts (1)

263-267: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Mark the stackOperatorsIntoRows alias as deprecated.

The barrel now exposes one function under two public names. Keep the alias for compatibility, but state that stackIntervalsIntoRows is the supported name so the alias can be removed after consumers migrate.

♻️ Proposed change
 export {
   clipRectByRect,
   stackIntervalsIntoRows,
+  /** `@deprecated` Use `stackIntervalsIntoRows`. */
   stackIntervalsIntoRows as stackOperatorsIntoRows,
 } from './gantt-chart/utils';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/index.ts around lines 263 - 267, Add a
deprecation annotation for the exported stackOperatorsIntoRows alias in the
barrel export, directing consumers to use stackIntervalsIntoRows as the
supported name while preserving the alias for compatibility.
ui/packages/@quent/components/src/long-entities/utils.test.ts (1)

104-113: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Type the attribute fixture instead of casting to never.

as unknown as never removes the only check that would catch a DynamicAttribute shape change in the ts-bindings. Build the fixture from the canonical type.

💚 Proposed fix
+import type { DynamicAttribute } from '`@quent/utils`';
+
+const bytesAttribute: DynamicAttribute = { key: 'bytes', value: { Int: 42 } };
+
   it('carries transition attributes onto segments', () => {
     const fsm = makeFsm('e1', [
-      transition('a', 0, {
-        attributes: [{ key: 'bytes', value: { Int: 42 } } as unknown as never],
-      }),
+      transition('a', 0, { attributes: [bytesAttribute] }),
       transition('exit', 1),
     ]);

Adjust the literal to the exact DynamicAttribute variant shape if the binding differs.

Based on path instructions: "Build fixtures from canonical production or ts-binding types (Pick, Partial, builders/factories) instead of brittle hand-written lookalike interfaces."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/long-entities/utils.test.ts around lines
104 - 113, Update the attribute fixture in the “carries transition attributes
onto segments” test to use the canonical DynamicAttribute type or an existing
production/ts-binding builder, removing the `as unknown as never` cast. Match
the exact bound variant shape while preserving the current bytes value and
assertion.

Source: Path instructions

ui/packages/@quent/components/src/gantt-chart/utils.ts (1)

23-29: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the mutation contract in the JSDoc.

stackIntervalsIntoRows mutates rowIndex on the caller's objects and returns the same array reference in input order, while sorting only an internal copy. This function is public API through the package barrel, so state both facts in the doc comment.

♻️ Proposed doc update
-/** Greedily pack intervals into non-overlapping rows. */
+/**
+ * Greedily pack intervals into non-overlapping rows.
+ * Assigns `rowIndex` in place and returns the same array in input order.
+ */
 export function stackIntervalsIntoRows<
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/gantt-chart/utils.ts around lines 23 - 29,
Update the JSDoc for stackIntervalsIntoRows to document that it mutates each
caller-provided object's rowIndex and returns the original entries array in its
input order, while sorting only an internal copy for packing.
ui/packages/@quent/components/src/long-entities/utils.ts (1)

9-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

These row-id helpers have no consumer and are not exported.

LONG_ENTITIES_ROW_TYPE, longEntitiesRowId, and resourceIdFromLongEntitiesRowId are not re-exported from ui/packages/@quent/components/src/index.ts (lines 243-247), and no file in this PR calls them. The operator counterparts are exported at lines 253-262 of the same barrel. Either export them now for the follow-up integration or move them to that PR.

Also note the collision risk: a resource id that itself starts with __long_entities__ is misclassified by resourceIdFromLongEntitiesRowId. If the row-id space stays string based, consider a typed row variant instead of a string prefix.

Based on coding guidelines: "Avoid sentinel strings that can collide with real identifiers; use typed variants, opaque IDs, or another collision-free representation."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/long-entities/utils.ts around lines 9 -
23, Export LONG_ENTITIES_ROW_TYPE, longEntitiesRowId, and
resourceIdFromLongEntitiesRowId through the components barrel alongside the
operator helpers so follow-up consumers can use them. Also replace the
collision-prone string-prefix encoding in
longEntitiesRowId/resourceIdFromLongEntitiesRowId with the project’s typed or
otherwise collision-free row-ID representation, preserving correct
round-tripping of resource IDs that begin with __long_entities__.

Source: Coding guidelines

ui/packages/@quent/components/src/long-entities/LongEntitiesGantt.tsx (1)

66-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Narrow datum once and drop the non-null assertions.

The current guard relies on entry?. short-circuiting so that datum! at Line 68 is never evaluated when datum is undefined. Line 103 repeats the assertion. Guard datum first; both assertions then become unnecessary and the code stays correct after future edits.

♻️ Proposed refactor
       const datum = customSeriesData[params.dataIndex];
-      const entry = datum ? entries[datum.entryIndex] : undefined;
-      const segment = entry?.segments[datum!.segmentIndex];
-      if (!entry || !segment) return null;
+      if (!datum) return null;
+      const entry = entries[datum.entryIndex];
+      const segment = entry?.segments[datum.segmentIndex];
+      if (!entry || !segment) return null;

Then change Line 103 to datum.segmentIndex === 0.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/long-entities/LongEntitiesGantt.tsx around
lines 66 - 69, In the relevant render logic, guard that datum exists before
accessing its segmentIndex, then derive entry and segment only after the guard.
Remove the non-null assertions and update the later segment-index check to use
datum.segmentIndex directly, preserving the existing null return behavior when
datum, entry, or segment is absent.
ui/packages/@quent/components/src/gantt-chart/options.test.ts (1)

7-39: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the gridSpacing fallback.

buildGanttOption defaults gridSpacing to TIMELINE_SPACING (options.ts Line 36). That branch is the only fallback in the builder and no test exercises it. Add a case that omits gridSpacing and cursor.

💚 Proposed additional test
+  it('falls back to TIMELINE_SPACING when gridSpacing is omitted', () => {
+    const option = buildGanttOption({
+      data: [],
+      durationSeconds: 1,
+      yAxisCategories: [],
+      seriesName: 'test-series',
+      renderItem: vi.fn(() => null) as GanttRenderItem,
+      minZoomSpanPct: 1,
+    });
+
+    expect(option.grid).toMatchObject({ ...TIMELINE_SPACING, width: undefined, height: undefined });
+    expect((option.series as { cursor?: unknown }[])[0]?.cursor).toBeUndefined();
+  });

Import the constant:

 import { buildGanttOption, type GanttRenderItem } from './options';
+import { TIMELINE_SPACING } from '../timeline/types';

Based on path instructions: "Cover observable behavior, fallback/unknown inputs, empty/error states".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/gantt-chart/options.test.ts around lines 7
- 39, Extend the buildGanttOption tests with a case that omits gridSpacing and
cursor, then assert the resulting grid uses TIMELINE_SPACING. Import and
reference the existing TIMELINE_SPACING constant, while preserving assertions
for the other observable default behavior.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ui/packages/`@quent/components/src/gantt-chart/GanttChart.tsx:
- Around line 109-140: Update the GanttChart hover wiring around onChartReady
and useChartConnect so the chart instance is also stored in reactive state, then
manage observeGanttHover through an effect keyed by that instance,
renderTooltip, and data presence. Remove the hover subscription and clear hover
whenever any condition is false, while preserving the existing chart cleanup and
wheel-navigation behavior.

---

Other comments:
In `@ui/packages/`@quent/components/src/gantt-chart/GanttChart.tsx:
- Around line 74-81: Replace the spread-based Math.max call in the
yAxisCategories/rowCount useMemo with a reduction over data that computes the
maximum datum.value[2] without expanding the array into function arguments.
Preserve the existing empty-data result and category/rowCount calculations.

In `@ui/packages/`@quent/components/src/gantt-chart/hover.ts:
- Around line 26-36: Update the pixel-conversion validation in the hover handler
to call onChange(null) before returning when convertFromPixel produces null or a
non-finite value. Preserve the existing onChange timestamp path for valid finite
values and the catch-block clearing behavior.

---

Nitpick comments:
In `@ui/packages/`@quent/components/src/gantt-chart/options.test.ts:
- Around line 7-39: Extend the buildGanttOption tests with a case that omits
gridSpacing and cursor, then assert the resulting grid uses TIMELINE_SPACING.
Import and reference the existing TIMELINE_SPACING constant, while preserving
assertions for the other observable default behavior.

In `@ui/packages/`@quent/components/src/gantt-chart/utils.ts:
- Around line 23-29: Update the JSDoc for stackIntervalsIntoRows to document
that it mutates each caller-provided object's rowIndex and returns the original
entries array in its input order, while sorting only an internal copy for
packing.

In `@ui/packages/`@quent/components/src/index.ts:
- Around line 263-267: Add a deprecation annotation for the exported
stackOperatorsIntoRows alias in the barrel export, directing consumers to use
stackIntervalsIntoRows as the supported name while preserving the alias for
compatibility.

In `@ui/packages/`@quent/components/src/long-entities/LongEntitiesGantt.tsx:
- Around line 66-69: In the relevant render logic, guard that datum exists
before accessing its segmentIndex, then derive entry and segment only after the
guard. Remove the non-null assertions and update the later segment-index check
to use datum.segmentIndex directly, preserving the existing null return behavior
when datum, entry, or segment is absent.

In `@ui/packages/`@quent/components/src/long-entities/utils.test.ts:
- Around line 104-113: Update the attribute fixture in the “carries transition
attributes onto segments” test to use the canonical DynamicAttribute type or an
existing production/ts-binding builder, removing the `as unknown as never` cast.
Match the exact bound variant shape while preserving the current bytes value and
assertion.

In `@ui/packages/`@quent/components/src/long-entities/utils.ts:
- Around line 9-23: Export LONG_ENTITIES_ROW_TYPE, longEntitiesRowId, and
resourceIdFromLongEntitiesRowId through the components barrel alongside the
operator helpers so follow-up consumers can use them. Also replace the
collision-prone string-prefix encoding in
longEntitiesRowId/resourceIdFromLongEntitiesRowId with the project’s typed or
otherwise collision-free row-ID representation, preserving correct
round-tripping of resource IDs that begin with __long_entities__.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 5ff978b3-e856-4bfb-b53d-c8cf4c19fad9

📥 Commits

Reviewing files that changed from the base of the PR and between 4c5d674 and 00e8c6d.

📒 Files selected for processing (14)
  • ui/packages/@quent/components/src/gantt-chart/GanttChart.tsx
  • ui/packages/@quent/components/src/gantt-chart/hover.ts
  • ui/packages/@quent/components/src/gantt-chart/options.test.ts
  • ui/packages/@quent/components/src/gantt-chart/options.ts
  • ui/packages/@quent/components/src/gantt-chart/utils.test.ts
  • ui/packages/@quent/components/src/gantt-chart/utils.ts
  • ui/packages/@quent/components/src/index.ts
  • ui/packages/@quent/components/src/long-entities/LongEntitiesGantt.tsx
  • ui/packages/@quent/components/src/long-entities/types.ts
  • ui/packages/@quent/components/src/long-entities/utils.test.ts
  • ui/packages/@quent/components/src/long-entities/utils.ts
  • ui/packages/@quent/components/src/operator-timeline/OperatorGanttChart.tsx
  • ui/packages/@quent/components/src/operator-timeline/utils.test.ts
  • ui/packages/@quent/components/src/operator-timeline/utils.ts
💤 Files with no reviewable changes (1)
  • ui/packages/@quent/components/src/operator-timeline/utils.test.ts

Comment on lines +109 to +140
const onChartReady = useCallback(
(instance: EChartsInstance) => {
chartCleanupRef.current?.();
registerAxisPointerSync(instance, 0, { receiveShowTip: false });
const detachWheelNavigation = attachWheelNavigation(
instance,
wrapperRef.current ?? undefined
);
const detachHover = renderTooltip ? observeGanttHover(instance, setHover) : undefined;
const cleanup = () => {
unregisterAxisPointerSync(instance);
detachWheelNavigation();
detachHover?.();
if (chartCleanupRef.current === cleanup) chartCleanupRef.current = null;
};
chartCleanupRef.current = cleanup;
},
[attachWheelNavigation, renderTooltip]
);

const { handleChartReady, instanceRef } = useChartConnect({
durationSeconds,
chartGroup: CHART_GROUP,
onReady: onChartReady,
});

useEffect(() => {
return () => {
chartCleanupRef.current?.();
instanceRef.current = null;
};
}, [instanceRef]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect useChartConnect to confirm when onReady runs and whether the instance is exposed reactively.
fd -t f 'useChartConnect.*' ui/packages/@quent/components/src/lib --exec cat -n {}

Repository: rapidsai/quent

Length of output: 3799


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- GanttChart outline ---'
ast-grep outline ui/packages/@quent/components/src/gantt-chart/GanttChart.tsx --view compact
printf '%s\n' '--- GanttChart relevant sections ---'
cat -n ui/packages/@quent/components/src/gantt-chart/GanttChart.tsx | sed -n '1,210p'
printf '%s\n' '--- chart connection and resize usage ---'
rg -n -C 8 'useChartConnect|useChartResize|observeGanttHover|setHover|onChartReady' ui/packages/@quent/components/src

Repository: rapidsai/quent

Length of output: 330


🏁 Script executed:

#!/bin/bash
set -e
cat -n ui/packages/@quent/components/src/gantt-chart/GanttChart.tsx | sed -n '1,210p'
fd -t f 'useChartResize.*' ui/packages/@quent/components/src --exec cat -n {}

Repository: rapidsai/quent

Length of output: 8281


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- hover implementation ---'
fd -t f 'hover.*' ui/packages/@quent/components/src/gantt-chart --exec cat -n {}
printf '%s\n' '--- GanttChart consumers and conditional tooltip props ---'
rg -n -C 5 'renderTooltip=|renderTooltip:|<GanttChart|GanttChart\(' ui

Repository: rapidsai/quent

Length of output: 5451


🏁 Script executed:

#!/bin/bash
set -e
fd -t f 'hover.*' ui/packages/@quent/components/src/gantt-chart --exec cat -n {}
rg -n -C 5 'renderTooltip=|renderTooltip:|<GanttChart|GanttChart\(' ui

Repository: rapidsai/quent

Length of output: 5363


Make the hover subscription reactive.

useChartConnect invokes onReady only from handleChartReady; changing renderTooltip does not invoke it again for the current chart. Attach observeGanttHover in an effect keyed by the chart instance, renderTooltip, and data presence. Clear hover and remove the pointer listeners when any condition becomes false. Expose the chart instance through reactive state because instanceRef.current changes do not rerun effects.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/gantt-chart/GanttChart.tsx around lines
109 - 140, Update the GanttChart hover wiring around onChartReady and
useChartConnect so the chart instance is also stored in reactive state, then
manage observeGanttHover through an effect keyed by that instance,
renderTooltip, and data presence. Remove the hover subscription and clear hover
whenever any condition is false, while preserving the existing chart cleanup and
wheel-navigation behavior.

Source: Path instructions

@cmatzenbach cmatzenbach left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

New additions look good!

@johallar
johallar force-pushed the joe/long-entities-gantt-2.5 branch 2 times, most recently from 1c87da3 to 495ffd3 Compare August 4, 2026 19:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (2)
ui/packages/@quent/components/src/long-entities/utils.test.ts-104-113 (1)

104-113: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Type the attribute fixture.

as unknown as never disables validation against DynamicAttribute. A schema change can leave this test compiling and passing with a fixture that no longer represents production data. Build this fixture with DynamicAttribute or a shared canonical builder instead.

As per coding guidelines and path instructions, build fixtures from canonical production or ts-binding types instead of hand-written lookalikes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/long-entities/utils.test.ts around lines
104 - 113, Update the attribute fixture in the “carries transition attributes
onto segments” test to use the production DynamicAttribute type or the shared
canonical attribute builder. Remove the `as unknown as never` cast while
preserving the existing bytes attribute value and test behavior.

Sources: Coding guidelines, Path instructions

ui/packages/@quent/components/src/gantt-chart/hover.ts-19-28 (1)

19-28: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear hover state when timestamp conversion fails.

If the chart is disposed or convertFromPixel returns null or a non-finite value, this handler keeps the previous GanttHover. The tooltip can then remain at an obsolete position. Call onChange(null) before both returns.

Proposed fix
-    if (instance.isDisposed?.()) return;
+    if (instance.isDisposed?.()) {
+      onChange(null);
+      return;
+    }
@@
-      if (value == null || !Number.isFinite(value as number)) return;
+      if (value == null || !Number.isFinite(value as number)) {
+        onChange(null);
+        return;
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/gantt-chart/hover.ts around lines 19 - 28,
Update the hover handler around the disposed-instance check and the
convertFromPixel validation so it calls onChange(null) before returning when the
chart is disposed or timestamp conversion yields null or a non-finite value.
Preserve the existing behavior for valid conversions and out-of-grid points.
🧹 Nitpick comments (1)
ui/packages/@quent/components/src/operator-timeline/OperatorGanttChart.tsx (1)

209-221: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add focused tests for the remaining operator-timeline contracts.

Existing tests cover generic interval packing, including touching endpoints, and overlapping operator spans. Add tests for returned order, worker-filtered touching spans, and OperatorGanttChart empty-state and click behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/operator-timeline/OperatorGanttChart.tsx
around lines 209 - 221, The operator-timeline contracts lack focused test
coverage. Add tests for returned ordering and worker-filtered touching spans in
ui/packages/@quent/components/src/operator-timeline/utils.ts at lines 112 and
141, and add OperatorGanttChart tests covering the empty state and click
behavior around
ui/packages/@quent/components/src/operator-timeline/OperatorGanttChart.tsx lines
209-221; these sites require test coverage rather than production changes.

Sources: Coding guidelines, Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ui/packages/`@quent/components/src/long-entities/utils.ts:
- Around line 10-22: Replace the prefix-based identity in longEntitiesRowId and
resourceIdFromLongEntitiesRowId with collision-free metadata: store the
synthetic row kind separately from resourceId, or use an opaque tagged
representation outside the resource-ID namespace. Ensure
resourceIdFromLongEntitiesRowId only recognizes values explicitly marked as
long-entities rows and never reinterprets a real resource ID.

In `@ui/packages/`@quent/components/src/operator-timeline/OperatorGanttChart.tsx:
- Around line 219-220: Update the Gantt bar interaction around
onEvents={handleClick} to provide a semantic, focusable keyboard-operable
control for each operator bar. Ensure Enter and Space trigger the same selection
transition as the existing click handler, including selected-node detail
updates, while preserving pointer selection behavior.

---

Other comments:
In `@ui/packages/`@quent/components/src/gantt-chart/hover.ts:
- Around line 19-28: Update the hover handler around the disposed-instance check
and the convertFromPixel validation so it calls onChange(null) before returning
when the chart is disposed or timestamp conversion yields null or a non-finite
value. Preserve the existing behavior for valid conversions and out-of-grid
points.

In `@ui/packages/`@quent/components/src/long-entities/utils.test.ts:
- Around line 104-113: Update the attribute fixture in the “carries transition
attributes onto segments” test to use the production DynamicAttribute type or
the shared canonical attribute builder. Remove the `as unknown as never` cast
while preserving the existing bytes attribute value and test behavior.

---

Nitpick comments:
In `@ui/packages/`@quent/components/src/operator-timeline/OperatorGanttChart.tsx:
- Around line 209-221: The operator-timeline contracts lack focused test
coverage. Add tests for returned ordering and worker-filtered touching spans in
ui/packages/@quent/components/src/operator-timeline/utils.ts at lines 112 and
141, and add OperatorGanttChart tests covering the empty state and click
behavior around
ui/packages/@quent/components/src/operator-timeline/OperatorGanttChart.tsx lines
209-221; these sites require test coverage rather than production changes.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 45154aae-457e-4d7d-8ac5-277553d427a1

📥 Commits

Reviewing files that changed from the base of the PR and between 00e8c6d and 1c87da3.

📒 Files selected for processing (14)
  • ui/packages/@quent/components/src/gantt-chart/GanttChart.tsx
  • ui/packages/@quent/components/src/gantt-chart/hover.ts
  • ui/packages/@quent/components/src/gantt-chart/options.test.ts
  • ui/packages/@quent/components/src/gantt-chart/options.ts
  • ui/packages/@quent/components/src/gantt-chart/utils.test.ts
  • ui/packages/@quent/components/src/gantt-chart/utils.ts
  • ui/packages/@quent/components/src/index.ts
  • ui/packages/@quent/components/src/long-entities/LongEntitiesGantt.tsx
  • ui/packages/@quent/components/src/long-entities/types.ts
  • ui/packages/@quent/components/src/long-entities/utils.test.ts
  • ui/packages/@quent/components/src/long-entities/utils.ts
  • ui/packages/@quent/components/src/operator-timeline/OperatorGanttChart.tsx
  • ui/packages/@quent/components/src/operator-timeline/utils.test.ts
  • ui/packages/@quent/components/src/operator-timeline/utils.ts
💤 Files with no reviewable changes (1)
  • ui/packages/@quent/components/src/operator-timeline/utils.test.ts

Comment on lines +10 to +22
export const LONG_ENTITIES_ROW_TYPE = 'long-entities';
const LONG_ENTITIES_ROW_ID_PREFIX = '__long_entities__';

/** Id used for the synthetic long-entities row under a resource. */
export function longEntitiesRowId(resourceId: string): string {
return `${LONG_ENTITIES_ROW_ID_PREFIX}${resourceId}`;
}

/** Extract the resource id from a long-entities row id, or null if it is not one. */
export function resourceIdFromLongEntitiesRowId(id: string): string | null {
return id.startsWith(LONG_ENTITIES_ROW_ID_PREFIX)
? id.slice(LONG_ENTITIES_ROW_ID_PREFIX.length)
: null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Use a collision-free long-entities row identity.

resourceIdFromLongEntitiesRowId() classifies every ID with the __long_entities__ prefix as synthetic. If a real resource ID uses that prefix, this function strips it and returns a different resource ID. Store the row kind separately from resourceId, or use a tagged opaque value that cannot share the resource-ID namespace.

As per coding guidelines and path instructions, avoid sentinel strings that can collide with real identifiers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/long-entities/utils.ts around lines 10 -
22, Replace the prefix-based identity in longEntitiesRowId and
resourceIdFromLongEntitiesRowId with collision-free metadata: store the
synthetic row kind separately from resourceId, or use an opaque tagged
representation outside the resource-ID namespace. Ensure
resourceIdFromLongEntitiesRowId only recognizes values explicitly marked as
long-entities rows and never reinterprets a real resource ID.

Sources: Coding guidelines, Path instructions

Comment on lines +219 to +220
cursor="pointer"
onEvents={handleClick}

@coderabbitai coderabbitai Bot Aug 4, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Provide keyboard access for operator selection.

cursor="pointer" and onEvents.click expose selection only through the ECharts chart. The supplied GanttChart contract has no focusable control or keyboard event for an individual bar. Keyboard users cannot select an operator or update selected-node details. Add a semantic keyboard-operable equivalent that performs the same selection transition.

As per coding guidelines, interactive affordances must be clickable and keyboard-operable; path instructions require semantic interactive elements and keyboard behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/operator-timeline/OperatorGanttChart.tsx
around lines 219 - 220, Update the Gantt bar interaction around
onEvents={handleClick} to provide a semantic, focusable keyboard-operable
control for each operator bar. Ensure Enter and Space trigger the same selection
transition as the existing click handler, including selected-node detail
updates, while preserving pointer selection behavior.

Sources: Coding guidelines, Path instructions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressing here: #538, i think this is impacts multiple charts, we could handle better/consistently

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@johallar, agreed. Issue #538 is the correct place to implement a shared, consistent keyboard-accessibility solution for the Gantt chart consumers. This operator-specific finding can be handled there.

You are interacting with an AI system.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (3)
ui/packages/@quent/components/src/gantt-chart/options.test.ts-7-40 (1)

7-40: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Cover the default and empty-data contracts.

Lines 12-21 supply non-empty data, cursor, and gridSpacing. Add a case with data: [] and omitted optional values. Assert the default grid spacing, empty series data, and valid zoom configuration.

As per path instructions, cover fallback inputs and empty states in UI tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/gantt-chart/options.test.ts around lines 7
- 40, Extend the buildGanttOption tests with a case using data: [] and omitting
optional cursor and gridSpacing inputs. Assert the documented default grid
spacing, empty series data, and a valid dataZoom configuration, while retaining
the existing non-empty custom-series coverage.

Sources: Coding guidelines, Path instructions

ui/packages/@quent/components/src/long-entities/utils.test.ts-104-113 (1)

104-113: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use a canonical DynamicAttribute fixture.

Line 107 casts a hand-written payload through unknown to never. This removes type validation for the wire payload and can hide a binding-schema change. Construct the attribute with DynamicAttribute or the element type of FsmTransition['attributes'] instead.

As per coding guidelines, build fixtures from canonical production or generated types instead of hand-written lookalike payloads.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/long-entities/utils.test.ts around lines
104 - 113, The transition fixture in “carries transition attributes onto
segments” bypasses type validation with an unknown-to-never cast. Replace the
hand-written attribute payload with a canonical DynamicAttribute fixture or the
element type from FsmTransition['attributes'], preserving the existing bytes
value and assertion.

Sources: Coding guidelines, Path instructions

ui/packages/@quent/components/src/gantt-chart/hover.ts-26-30 (1)

26-30: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear hover state when coordinate conversion is invalid.

Line 28 returns without calling onChange(null). If containPixel succeeds but convertFromPixel returns null, NaN, or another non-finite value, the previous tooltip remains visible with stale coordinates.

Proposed fix
-      if (value == null || !Number.isFinite(value as number)) return;
+      if (value == null || !Number.isFinite(value as number)) {
+        onChange(null);
+        return;
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/gantt-chart/hover.ts around lines 26 - 30,
Update the hover handling around instance.convertFromPixel so invalid conversion
results (null, NaN, or other non-finite values) call onChange(null) before
returning, clearing any stale tooltip state; preserve the existing onChange
payload for valid timestamps.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ui/packages/`@quent/components/src/gantt-chart/GanttChart.tsx:
- Line 21: Resolve the TS2459 failure involving CHART_GROUP: ensure the symbol
is exported from its current owner in ../timeline/Timeline, or move it to an
exported leaf module and update the GanttChart import accordingly. Preserve a
single shared constant and verify pnpm --dir ui typecheck succeeds.

In `@ui/packages/`@quent/components/src/gantt-chart/options.ts:
- Around line 38-111: The Gantt chart options returned by the chart-options
factory lack an accessible summary for non-empty data. Add an explicit semantic
description or equivalent accessible table covering each interval; if using
ECharts ARIA, register AriaComponent, include its option type in EChartsOption,
and enable aria.enabled with the explicit description while preserving the
existing chart configuration.

In `@ui/packages/`@quent/components/src/gantt-chart/utils.ts:
- Around line 32-41: Replace the linear row scan in the interval-packing helper
with an active-row min-heap keyed by end time and a reusable-row-index min-heap,
preserving the lowest available row index when rows are released out of end-time
order. Update the helper used by buildLongEntityEntries() and both operator-span
builders, and add a regression test covering out-of-order row release while
verifying row assignments remain lowest-index first.

---

Other comments:
In `@ui/packages/`@quent/components/src/gantt-chart/hover.ts:
- Around line 26-30: Update the hover handling around instance.convertFromPixel
so invalid conversion results (null, NaN, or other non-finite values) call
onChange(null) before returning, clearing any stale tooltip state; preserve the
existing onChange payload for valid timestamps.

In `@ui/packages/`@quent/components/src/gantt-chart/options.test.ts:
- Around line 7-40: Extend the buildGanttOption tests with a case using data: []
and omitting optional cursor and gridSpacing inputs. Assert the documented
default grid spacing, empty series data, and a valid dataZoom configuration,
while retaining the existing non-empty custom-series coverage.

In `@ui/packages/`@quent/components/src/long-entities/utils.test.ts:
- Around line 104-113: The transition fixture in “carries transition attributes
onto segments” bypasses type validation with an unknown-to-never cast. Replace
the hand-written attribute payload with a canonical DynamicAttribute fixture or
the element type from FsmTransition['attributes'], preserving the existing bytes
value and assertion.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 4f2e12a2-6a22-44f3-85d5-6939d5d64dbc

📥 Commits

Reviewing files that changed from the base of the PR and between 1c87da3 and 495ffd3.

📒 Files selected for processing (14)
  • ui/packages/@quent/components/src/gantt-chart/GanttChart.tsx
  • ui/packages/@quent/components/src/gantt-chart/hover.ts
  • ui/packages/@quent/components/src/gantt-chart/options.test.ts
  • ui/packages/@quent/components/src/gantt-chart/options.ts
  • ui/packages/@quent/components/src/gantt-chart/utils.test.ts
  • ui/packages/@quent/components/src/gantt-chart/utils.ts
  • ui/packages/@quent/components/src/index.ts
  • ui/packages/@quent/components/src/long-entities/LongEntitiesGantt.tsx
  • ui/packages/@quent/components/src/long-entities/types.ts
  • ui/packages/@quent/components/src/long-entities/utils.test.ts
  • ui/packages/@quent/components/src/long-entities/utils.ts
  • ui/packages/@quent/components/src/operator-timeline/OperatorGanttChart.tsx
  • ui/packages/@quent/components/src/operator-timeline/utils.test.ts
  • ui/packages/@quent/components/src/operator-timeline/utils.ts
💤 Files with no reviewable changes (1)
  • ui/packages/@quent/components/src/operator-timeline/utils.test.ts

Comment thread ui/packages/@quent/components/src/gantt-chart/GanttChart.tsx Outdated
Comment thread ui/packages/@quent/components/src/gantt-chart/options.ts
Comment on lines +32 to +41
for (const entry of sorted) {
let row = 0;
while (row < rowEndMs.length && entry.startMs < rowEndMs[row]) {
row++;
}
if (row === rowEndMs.length) {
rowEndMs.push(entry.endMs);
} else {
rowEndMs[row] = Math.max(rowEndMs[row], entry.endMs);
}

@coderabbitai coderabbitai Bot Aug 4, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Prevent quadratic interval packing.

Line 34 scans every active row for each interval. For N mutually overlapping entries, this creates O(N²) work before the chart renders. buildLongEntityEntries() and both operator-span builders use this helper. Use an active-row min-heap and a reusable-row-index min-heap. Preserve lowest-index row assignment with a regression test for rows released out of end-time order. As per path instructions, “this UI renders big traces.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/gantt-chart/utils.ts around lines 32 - 41,
Replace the linear row scan in the interval-packing helper with an active-row
min-heap keyed by end time and a reusable-row-index min-heap, preserving the
lowest available row index when rows are released out of end-time order. Update
the helper used by buildLongEntityEntries() and both operator-span builders, and
add a regression test covering out-of-order row release while verifying row
assignments remain lowest-index first.

Source: Path instructions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These are limited to 100 at a time so deferring this

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@johallar, acknowledged. The 100-entry limit bounds the current cost, so deferring the heap-based optimization is reasonable for this pull request.

Would you like me to create a follow-up GitHub issue for the interval-packing optimization?
🐇

You are interacting with an AI system.

@johallar
johallar force-pushed the joe/long-entities-gantt-2.5 branch from 495ffd3 to 6cc1728 Compare August 4, 2026 21:17
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
ui/packages/@quent/components/src/gantt-chart/hover.ts-26-35 (1)

26-35: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear stale hover state when coordinate conversion fails.

When convertFromPixel returns null or a non-finite value, call onChange(null) before returning. Otherwise, GanttChart retains the previous hover until pointer leave. Add a regression test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/gantt-chart/hover.ts around lines 26 - 35,
Update the coordinate validation in the hover conversion flow to call
onChange(null) before returning when convertFromPixel produces null or a
non-finite value, matching the existing catch behavior. Add a regression test
covering both invalid conversion outcomes and verifying the stale hover state is
cleared.
🧹 Nitpick comments (4)
ui/packages/@quent/components/src/index.ts (2)

242-246: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Export the shared Gantt surface as well.

This diff adds LongEntitiesGantt and OperatorGanttChart exports, but GanttChart, GanttChartProps, GanttDatum, and GanttRenderItem stay internal. A consumer outside @quent/components cannot build a Gantt on the shared foundation or type a renderItem callback. The stack describes this layer as the shared Gantt foundation, so the primitive belongs in the barrel with its own section.

♻️ Proposed addition
+// ─── Gantt chart ──────────────────────────────────────────────────────────────
+export { GanttChart } from './gantt-chart/GanttChart';
+export type { GanttChartProps, GanttRenderItem } from './gantt-chart/GanttChart';
+
 // ─── Long-entities components ─────────────────────────────────────────────────
 export { LongEntitiesGantt } from './long-entities/LongEntitiesGantt';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/index.ts around lines 242 - 246, Update
the components barrel near the Long-entities exports to add a dedicated shared
Gantt foundation section, exporting GanttChart and its public types
GanttChartProps, GanttDatum, and GanttRenderItem from their existing module.
Keep the current LongEntitiesGantt exports unchanged.

Source: Path instructions


262-266: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move the shared Gantt utilities out of the operator-timeline section and mark the alias.

These three exports come from ./gantt-chart/utils, but they sit under the Operator-timeline components header at line 248. clipRectByRect and stackIntervalsIntoRows are shared Gantt utilities, not operator-timeline utilities. stackOperatorsIntoRows is a compatibility alias only. Group the shared exports under a Gantt section and add a deprecation note on the alias so callers migrate to the shared name.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/index.ts around lines 262 - 266,
Reorganize the exports in the index around clipRectByRect and
stackIntervalsIntoRows so they appear under a dedicated Gantt utilities section
rather than the Operator-timeline components section. Keep
stackOperatorsIntoRows as an alias for compatibility, add a deprecation
annotation directing callers to stackIntervalsIntoRows, and preserve all
existing export behavior.
ui/packages/@quent/components/src/gantt-chart/utils.test.ts (1)

48-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for row reuse behind a long interval.

The suite never exercises the case where an early long interval holds row 0 while later short intervals must reuse row 1. That path decides whether packing keeps the lowest free row index. Both operator span builders and buildLongEntityEntries() now depend on this single helper, so the boundary deserves a fixed test.

As per path instructions, retain "meaningful boundary coverage".

💚 Proposed additional test
   it('handles unsorted input and mutates the original entries', () => {
     const later = span(10, 20);
     const earlier = span(0, 5);
     const entries = [later, earlier];
     expect(stackIntervalsIntoRows(entries)).toBe(entries);
     expect(entries.map(entry => entry.rowIndex)).toEqual([0, 0]);
   });
+
+  it('reuses the lowest free row behind a long interval', () => {
+    const long = span(0, 100);
+    const first = span(10, 20);
+    const second = span(30, 40);
+    stackIntervalsIntoRows([long, first, second]);
+    expect([long.rowIndex, first.rowIndex, second.rowIndex]).toEqual([0, 1, 1]);
+  });
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/gantt-chart/utils.test.ts around lines 48
- 74, Add a focused test in the stackIntervalsIntoRows suite where an early long
interval occupies row 0 and later intervals overlap it but can reuse row 1,
asserting the helper assigns the lowest available row index. Keep the test
meaningful by verifying the resulting rowIndex values and covering the row-reuse
boundary relied on by the span builders and buildLongEntityEntries().

Source: Path instructions

ui/packages/@quent/components/src/long-entities/utils.ts (1)

74-87: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Derive entry bounds from all segments, not from positions.

buildSegments() preserves transition order, and no ascending-timestamp guarantee exists for FiniteStateMachine.transitions. Inverted entry bounds can cause stackIntervalsIntoRows() to place overlapping segments on the same row. Compute startMs with the minimum segment start and endMs with the maximum segment end.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/long-entities/utils.ts around lines 74 -
87, Update the entry-bound calculation in the loop using buildSegments so
startMs is the minimum startMs across all segments and endMs is the maximum
endMs across all segments, rather than relying on the first and last segment
positions. Keep the existing empty-segments skip and entry construction
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ui/packages/`@quent/components/src/long-entities/LongEntitiesGantt.tsx:
- Around line 84-85: Update the segment-rendering logic around clippedShape in
LongEntitiesGantt so the entity label is rendered on the first segment that
remains visible after horizontal clipping, rather than only on the original
first segment. Preserve label visibility when navigating horizontally, and add
regression coverage for an offscreen first segment followed by a visible
segment.

In `@ui/packages/`@quent/components/src/long-entities/utils.test.ts:
- Around line 104-113: Update the “carries transition attributes onto segments”
test to construct its transition metadata with the canonical production or
ts-binding DynamicAttribute fixture/builder, removing the unknown-to-never cast.
Include both regular and derived attributes in the fixture, then assert the
segment preserves both attributes and derivedAttributes outputs.
- Around line 115-120: Update the “stacks non-overlapping entities onto the same
row” test to use adjacent intervals whose end and start timestamps are equal,
such as ending the first FSM at 1 and starting the second at 1. Keep the
expected rowIndex values as [0, 0] to verify boundary-touching entities reuse
the same row.

---

Other comments:
In `@ui/packages/`@quent/components/src/gantt-chart/hover.ts:
- Around line 26-35: Update the coordinate validation in the hover conversion
flow to call onChange(null) before returning when convertFromPixel produces null
or a non-finite value, matching the existing catch behavior. Add a regression
test covering both invalid conversion outcomes and verifying the stale hover
state is cleared.

---

Nitpick comments:
In `@ui/packages/`@quent/components/src/gantt-chart/utils.test.ts:
- Around line 48-74: Add a focused test in the stackIntervalsIntoRows suite
where an early long interval occupies row 0 and later intervals overlap it but
can reuse row 1, asserting the helper assigns the lowest available row index.
Keep the test meaningful by verifying the resulting rowIndex values and covering
the row-reuse boundary relied on by the span builders and
buildLongEntityEntries().

In `@ui/packages/`@quent/components/src/index.ts:
- Around line 242-246: Update the components barrel near the Long-entities
exports to add a dedicated shared Gantt foundation section, exporting GanttChart
and its public types GanttChartProps, GanttDatum, and GanttRenderItem from their
existing module. Keep the current LongEntitiesGantt exports unchanged.
- Around line 262-266: Reorganize the exports in the index around clipRectByRect
and stackIntervalsIntoRows so they appear under a dedicated Gantt utilities
section rather than the Operator-timeline components section. Keep
stackOperatorsIntoRows as an alias for compatibility, add a deprecation
annotation directing callers to stackIntervalsIntoRows, and preserve all
existing export behavior.

In `@ui/packages/`@quent/components/src/long-entities/utils.ts:
- Around line 74-87: Update the entry-bound calculation in the loop using
buildSegments so startMs is the minimum startMs across all segments and endMs is
the maximum endMs across all segments, rather than relying on the first and last
segment positions. Keep the existing empty-segments skip and entry construction
unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 47a3be8e-6b8a-41d6-b6ab-63d6b04cdd95

📥 Commits

Reviewing files that changed from the base of the PR and between c48e7fc and 6cc1728.

📒 Files selected for processing (14)
  • ui/packages/@quent/components/src/gantt-chart/GanttChart.tsx
  • ui/packages/@quent/components/src/gantt-chart/hover.ts
  • ui/packages/@quent/components/src/gantt-chart/options.test.ts
  • ui/packages/@quent/components/src/gantt-chart/options.ts
  • ui/packages/@quent/components/src/gantt-chart/utils.test.ts
  • ui/packages/@quent/components/src/gantt-chart/utils.ts
  • ui/packages/@quent/components/src/index.ts
  • ui/packages/@quent/components/src/long-entities/LongEntitiesGantt.tsx
  • ui/packages/@quent/components/src/long-entities/types.ts
  • ui/packages/@quent/components/src/long-entities/utils.test.ts
  • ui/packages/@quent/components/src/long-entities/utils.ts
  • ui/packages/@quent/components/src/operator-timeline/OperatorGanttChart.tsx
  • ui/packages/@quent/components/src/operator-timeline/utils.test.ts
  • ui/packages/@quent/components/src/operator-timeline/utils.ts
💤 Files with no reviewable changes (1)
  • ui/packages/@quent/components/src/operator-timeline/utils.test.ts

Comment on lines +84 to +85
const clippedShape = clipBound ? clipRectByRect(rectShape, clipBound) : rectShape;
if (!clippedShape) return null;

@coderabbitai coderabbitai Bot Aug 4, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- LongEntitiesGantt outline ---'
ast-grep outline ui/packages/@quent/components/src/long-entities/LongEntitiesGantt.tsx --match 'LongEntitiesGantt' --view expanded || true

printf '%s\n' '--- LongEntitiesGantt relevant lines ---'
sed -n '1,180p' ui/packages/@quent/components/src/long-entities/LongEntitiesGantt.tsx

printf '%s\n' '--- Gantt options relevant lines ---'
sed -n '1,220p' ui/packages/@quent/components/src/gantt-chart/options.ts

printf '%s\n' '--- related tests and call sites ---'
rg -n -S 'LongEntitiesGantt|segmentIndex|clipRectByRect|entity label|long entities' ui/packages/@quent/components ui/src

Repository: rapidsai/quent

Length of output: 12302


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- clipping utility ---'
sed -n '1,100p' ui/packages/@quent/components/src/gantt-chart/utils.ts

printf '%s\n' '--- GanttChart implementation ---'
ast-grep outline ui/packages/@quent/components/src/gantt-chart/GanttChart.tsx --match 'GanttChart' --view expanded || true
sed -n '1,260p' ui/packages/@quent/components/src/gantt-chart/GanttChart.tsx

printf '%s\n' '--- LongEntityEntry types and component tests ---'
sed -n '1,180p' ui/packages/@quent/components/src/long-entities/types.ts
fd -i 'LongEntitiesGantt' ui/packages/@quent/components ui/src || true
rg -n -S 'buildGanttOption|tooltip|dataZoom|LongEntityEntry' ui/packages/@quent/components/src/long-entities ui/packages/@quent/components/src/gantt-chart ui/src

Repository: rapidsai/quent

Length of output: 9751


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path

gantt = Path("ui/packages/@quent/components/src/long-entities/LongEntitiesGantt.tsx").read_text()
options = Path("ui/packages/@quent/components/src/gantt-chart/options.ts").read_text()
chart = Path("ui/packages/@quent/components/src/gantt-chart/GanttChart.tsx").read_text()
clip_source = Path("ui/packages/@quent/components/src/gantt-chart/utils.ts").read_text()

assert "datum!.segmentIndex === 0" in gantt
assert "if (!clippedShape) return null;" in gantt
assert "axisLabel: { show: false }" in options
assert "renderTooltip" in chart
assert "renderTooltip={renderTooltip}" not in gantt
assert "return undefined;" in clip_source

# Model two segments for one entity. The first is fully outside the viewport;
# the second is visible. The render path therefore reaches the label condition
# with segmentIndex 1, which produces no label child.
bounds = {"x": 100, "y": 0, "width": 200, "height": 100}
segments = [
    {"index": 0, "x": 0, "width": 50},
    {"index": 1, "x": 120, "width": 50},
]

def clip(rect, bounds):
    x = max(rect["x"], bounds["x"])
    x2 = min(rect["x"] + rect["width"], bounds["x"] + bounds["width"])
    y = max(rect.get("y", 0), bounds["y"])
    y2 = min(rect.get("y", 0) + rect.get("height", 10), bounds["y"] + bounds["height"])
    return None if x2 < x or y2 < y else {"x": x, "width": x2 - x}

visible = []
for segment in segments:
    clipped = clip(segment, bounds)
    if clipped is not None:
        visible.append((segment["index"], segment["index"] == 0))

assert visible == [(1, False)]
print("PASS: the first segment is discarded outside the viewport, and the visible later segment has no label.")
print("PASS: the chart hides y-axis labels and LongEntitiesGantt does not provide renderTooltip.")
PY

Repository: rapidsai/quent

Length of output: 345


Keep the entity label visible after horizontal navigation.

When the first segment is outside the zoom window, later visible segments do not render entry.label. Render the label on the first visible segment or provide a persistent row or tooltip label. Add regression coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/long-entities/LongEntitiesGantt.tsx around
lines 84 - 85, Update the segment-rendering logic around clippedShape in
LongEntitiesGantt so the entity label is rendered on the first segment that
remains visible after horizontal clipping, rather than only on the original
first segment. Preserve label visibility when navigating horizontally, and add
regression coverage for an offscreen first segment followed by a visible
segment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm going to include this in part 3, other refactors happen there

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@johallar, acknowledged. Keep this finding for part 3 with the related refactors. The current PR can remain focused on the initial Gantt rendering work.

🐇

You are interacting with an AI system.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Also handle in part 3

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@johallar, acknowledged. Handle the entity-label visibility case and regression coverage in part 3.

🐇

You are interacting with an AI system.

Comment on lines +104 to +113
it('carries transition attributes onto segments', () => {
const fsm = makeFsm('e1', [
transition('a', 0, {
attributes: [{ key: 'bytes', value: { Int: 42 } } as unknown as never],
}),
transition('exit', 1),
]);
const [entry] = buildLongEntityEntries([fsm], {}, 'light');
expect(entry.segments[0].attributes).toHaveLength(1);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use a canonical metadata fixture and cover both attribute fields.

Line 107 casts a hand-written value through unknown to never. This disables the FsmTransition attribute contract. The test also omits derived_attributes, so a regression that drops derivedAttributes still passes. Build a typed DynamicAttribute fixture or use a production builder, then assert both output fields.

As per coding guidelines, fixtures must use canonical production or generated types. As per path instructions, build fixtures from canonical production or ts-binding types instead of brittle lookalike interfaces.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/long-entities/utils.test.ts around lines
104 - 113, Update the “carries transition attributes onto segments” test to
construct its transition metadata with the canonical production or ts-binding
DynamicAttribute fixture/builder, removing the unknown-to-never cast. Include
both regular and derived attributes in the fixture, then assert the segment
preserves both attributes and derivedAttributes outputs.

Sources: Coding guidelines, Path instructions

Comment on lines +115 to +120
it('stacks non-overlapping entities onto the same row', () => {
const a = makeFsm('a', [transition('s', 0), transition('exit', 1)]);
const b = makeFsm('b', [transition('s', 2), transition('exit', 3)]);
const entries = buildLongEntityEntries([a, b], {}, 'light');
expect(entries.map(e => e.rowIndex).sort()).toEqual([0, 0]);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Test adjacent intervals without a gap.

The current case leaves a one-second gap. Consecutive FSM state spans meet at the transition timestamp, so an entity ending at 1 and another starting at 1 must reuse the same row. Test that boundary to catch a < versus <= error in row packing.

Proposed test change
-    const b = makeFsm('b', [transition('s', 2), transition('exit', 3)]);
+    const b = makeFsm('b', [transition('s', 1), transition('exit', 2)]);

As per coding guidelines, tests must cover meaningful boundaries. As per path instructions, tests must cover meaningful boundary behavior.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('stacks non-overlapping entities onto the same row', () => {
const a = makeFsm('a', [transition('s', 0), transition('exit', 1)]);
const b = makeFsm('b', [transition('s', 2), transition('exit', 3)]);
const entries = buildLongEntityEntries([a, b], {}, 'light');
expect(entries.map(e => e.rowIndex).sort()).toEqual([0, 0]);
});
it('stacks non-overlapping entities onto the same row', () => {
const a = makeFsm('a', [transition('s', 0), transition('exit', 1)]);
const b = makeFsm('b', [transition('s', 1), transition('exit', 2)]);
const entries = buildLongEntityEntries([a, b], {}, 'light');
expect(entries.map(e => e.rowIndex).sort()).toEqual([0, 0]);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/packages/`@quent/components/src/long-entities/utils.test.ts around lines
115 - 120, Update the “stacks non-overlapping entities onto the same row” test
to use adjacent intervals whose end and start timestamps are equal, such as
ending the first FSM at 1 and starting the second at 1. Keep the expected
rowIndex values as [0, 0] to verify boundary-touching entities reuse the same
row.

Sources: Coding guidelines, Path instructions

Build the entity-specific visualization on the shared Gantt foundation so the common refactor remains independently reviewable.
Keep the new component files compliant with the repository copyright hook.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@johallar

johallar commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit f5dcbe6 into rapidsai:main Aug 5, 2026
15 checks passed
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.

2 participants