Skip to content

New hooks#6960

Merged
ckifer merged 11 commits into
mainfrom
annotations
Feb 5, 2026
Merged

New hooks#6960
ckifer merged 11 commits into
mainfrom
annotations

Conversation

@PavelVanecek

@PavelVanecek PavelVanecek commented Feb 4, 2026

Copy link
Copy Markdown
Collaborator

Description

Adds couple of new hooks with tests and documentation.

The examples are lacking, I will add more in next PR.

Related Issue

#1678

Closes #6021

Motivation and Context

We still don't get over-the-shelf annotations but with these hooks it's fairly easy to build your own.

How Has This Been Tested?

I added annotations support to the devtools package but that I can only merge after we have released 3.8 to npm

Screenshots (if appropriate):

Screen.Recording.2026-02-03.at.17.53.13.mov

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • I have added a storybook story or VR test, or extended an existing story or VR test to show my changes

Summary by CodeRabbit

  • New Features

    • Axis scale/inverse hooks for pixel↔data conversion (tick- and data-snap variants), useCartesianScale, and axis tick access.
    • New interactive "Coordinate Systems" guide with live examples (data, pixel, scale-conversion) and embedded devtools.
  • API Additions

    • New public types for scale and data-point mapping; new selectors exposing inverse/snap scales and categorical domains.
  • Documentation

    • Examples/docs updated; API links now use the dedicated API-link component and preserve original component casing.

@coderabbitai

coderabbitai Bot commented Feb 4, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds coordinate-systems features: new axis-scale/inverse hooks and types, categorical inverse utilities and combiner, selector exports, tests, snapshots/types, website guide/examples, omnidoc link handling changes, and mouse-pointer typing updates.

Changes

Cohort / File(s) Summary
Hooks & public exports
src/hooks.ts, src/index.ts
Add types ScaleFunction, InverseScaleFunction, CartesianDataPoint; implement and export multiple hooks for X/Y scales, inverse/snap variants, ticks, and useCartesianScale.
Scale utilities & combiner
src/util/scale/CustomScaleDefinition.ts, src/util/scale/createCategoricalInverse.ts, src/state/selectors/combiners/combineInverseScaleFunction.ts
Add optional invert? to CustomScaleDefinition; implement createCategoricalInverse (with bisect) and combineInverseScaleFunction to derive inverse scale functions.
Axis selectors & domain logic
src/state/selectors/axisSelectors.ts
Export selectCheckedAxisDomain; add selectSortedDataPoints, combineCategoricalDomain, selectCategoricalDomain; add selectors selectAxisInverseScale, selectAxisInverseDataSnapScale, selectAxisInverseTickSnapScale.
Tests & test helpers
test/hooks/*, test/chart/BarChart.spec.tsx, test/helper/createSelectorTestCase.tsx, test/state/*
Add tests for new hooks/ticks, selector-driven tests, update BarChart tests, export ReactHook type, and update tests to use HTMLMousePointer.
Middleware typing
src/state/mouseEventsMiddleware.ts, test/state/*
Switch pointer types from MousePointer to HTMLMousePointer for mouseClick/mouseMove actions and associated tests.
omnidoс / doc tooling
omnidoc/generateApiDoc.ts, omnidoc/generateApiDoc.spec.ts
processInlineLinks now emits LinkToApi for internal Recharts references; add hasTag helper and conditional import assembly for Link/LinkToApi; update tests accordingly.
Snapshots / types / manifests
scripts/snapshots/*, scripts/snapshots/typesFiles.txt, types/...
Add combineInverseScaleFunction and createCategoricalInverse to ES6/lib/type snapshots and add corresponding .d.ts entries.
Website guide & examples
www/src/components/GuideView/CoordinateSystems/*, www/src/views/GuideView.tsx, www/src/navigation.data.ts, www/src/locale/*, www/src/docs/apiCates.ts
Add CoordinateSystems guide, three live example components (data/pixel/scale conversion), register route/locales, and update apiCates to include new hooks.
Site components & examples tweaks
www/src/components/Shared/LinkToApi.tsx, www/src/docs/exampleComponents/AreaChart/AreaChartFillByValue.tsx, www/src/docs/apiExamples/getRelativeCoordinate/CrosshairExample.tsx
LinkToApi preserves child casing; AreaChart gradient refactored to use useYAxisScale/useChartHeight; CrosshairExample adds <RechartsDevtools />.
Minor reorder / docs tag / config
src/state/selectors/polarScaleSelectors.ts, src/state/selectors/tooltipSelectors.ts, src/util/getRelativeCoordinate.ts, stryker.config.mjs, vitest.config-mutation.mts
Import reorderings, @since 3.8 JSDoc tag, Stryker target swap, add a Vitest setup file.

Sequence Diagram(s)

sequenceDiagram
    actor Component
    participant Hook as useXAxisScale
    participant State as ReduxState
    participant Selector as selectAxisScale
    participant Scale as ScaleFunction
    Component->>Hook: request scale (xAxisId)
    Hook->>State: read chart slice
    State->>Selector: selectAxisScale(state, 'x', id)
    Selector->>Scale: obtain configured scale function
    Selector-->>Hook: ScaleFunction
    Hook-->>Component: provide ScaleFunction
    Component->>Scale: scale(dataValue)
    Scale-->>Component: pixel coordinate
Loading
sequenceDiagram
    actor Component
    participant InvHook as useXAxisInverseScale
    participant State as ReduxState
    participant Combiner as combineInverseScaleFunction
    participant CatInv as createCategoricalInverse
    participant Bisect as bisect
    Component->>InvHook: request inverse scale
    InvHook->>State: read configured scale
    State->>Combiner: pass configuredScale
    alt configuredScale has invert
        Combiner-->>InvHook: bound invert function
    else
        Combiner->>CatInv: build categorical inverse (scale, dataPoints)
        CatInv->>Bisect: compute nearest index for pixel
        Bisect-->>CatInv: index
        CatInv-->>InvHook: InverseScaleFunction
    end
    InvHook-->>Component: provide InverseScaleFunction
    Component->>InvHook: inverse(pixel)
    InvHook-->>Component: data value
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

  • #6811: Related to scale abstraction and axis selector changes used by the new inverse/selector features.
  • #6808: Modifies CustomScaleDefinition interface—directly relevant to the added optional invert?.
  • #6810: Modifies omnidoc/generateApiDoc behavior—related to the LinkToApi emission changes.

Suggested labels

enhancement, feature request

Suggested reviewers

  • ckifer
🚥 Pre-merge checks | ✅ 2 | ❌ 3
❌ Failed checks (1 warning, 2 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'New hooks' is vague and generic, lacking specificity about which hooks are being added or the main feature being implemented. Provide a more descriptive title that specifies the primary hooks being added, e.g., 'Add scale and inverse scale hooks for coordinate conversion' or 'Add axis scale and coordinate conversion hooks'.
Out of Scope Changes check ❓ Inconclusive Most changes are in-scope, but several appear tangential: API documentation updates, LinkToApi component fixes, locale translations, website guide additions, and test infrastructure changes are beyond the core hook implementation objectives. Clarify scope boundaries: confirm whether documentation, website guide, and locale changes are required for this PR or should be separated into follow-ups.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The PR description covers all required sections: description, related issues, motivation, testing approach, change type, and checklist completion.
Linked Issues check ✅ Passed The PR successfully implements hooks addressing all requirements from issue #6021: useOffset, usePlotArea, useXAxisScale, useYAxisScale, and related inverse/tick snap variants for coordinate conversion and annotation support.

✏️ 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 annotations

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is AI slop, does not show the hooks at all. I will rewrite it in next PR, before publishing.

@codecov

codecov Bot commented Feb 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 42.63959% with 339 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.38%. Comparing base (f4dc8d5) to head (6d56abd).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
...c/components/GuideView/CoordinateSystems/index.tsx 4.15% 254 Missing ⚠️
...View/CoordinateSystems/PixelCoordinatesExample.tsx 25.00% 27 Missing ⚠️
...eView/CoordinateSystems/DataCoordinatesExample.tsx 37.03% 17 Missing ⚠️
src/state/selectors/axisSelectors.ts 80.00% 16 Missing ⚠️
...eView/CoordinateSystems/ScaleConversionExample.tsx 37.50% 15 Missing ⚠️
src/hooks.ts 88.33% 7 Missing ⚠️
src/util/scale/createCategoricalInverse.ts 97.67% 1 Missing ⚠️
www/src/components/Shared/LinkToApi.tsx 0.00% 1 Missing ⚠️
...xamples/getRelativeCoordinate/CrosshairExample.tsx 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6960      +/-   ##
==========================================
- Coverage   91.13%   90.38%   -0.75%     
==========================================
  Files         509      515       +6     
  Lines       37967    38497     +530     
  Branches     5287     5338      +51     
==========================================
+ Hits        34601    34797     +196     
- Misses       3357     3691     +334     
  Partials        9        9              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
omnidoc/generateApiDoc.ts (1)

598-635: ⚠️ Potential issue | 🟠 Major

Import detection misses tags in returnDesc.
returnDesc is processed through processInlineLinks, but writeApiDocFile only scans desc/prop desc. If a @return JSDoc includes component references, the generated file will use LinkToApi without importing it.

✅ Suggested fix
-function hasTag(obj: ApiDoc | ApiProps, tagName: string): boolean {
-  if (!obj.desc) return false;
-  return Object.values(obj.desc).some(desc => typeof desc === 'string' && desc.includes(`</${tagName}>`));
-}
+function hasTagInDesc(
+  desc: ApiDoc['desc'] | ApiDoc['returnDesc'] | ApiProps['desc'] | undefined,
+  tagName: string,
+): boolean {
+  if (!desc) return false;
+  return Object.values(desc).some(d => typeof d === 'string' && d.includes(`</${tagName}>`));
+}
+
+function hasTag(obj: ApiDoc | ApiProps, tagName: string): boolean {
+  return hasTagInDesc(obj.desc, tagName);
+}
@@
-  const hasLinkInDesc = hasTag(apiDoc, 'Link');
+  const hasLinkInDesc = hasTagInDesc(apiDoc.desc, 'Link') || hasTagInDesc(apiDoc.returnDesc, 'Link');
@@
-  const hasLinkToApiInDesc = hasTag(apiDoc, 'LinkToApi');
+  const hasLinkToApiInDesc =
+    hasTagInDesc(apiDoc.desc, 'LinkToApi') || hasTagInDesc(apiDoc.returnDesc, 'LinkToApi');
test/chart/BarChart.spec.tsx (1)

102-104: ⚠️ Potential issue | 🟠 Major

Enable fake timers in beforeEach and cleanup in afterEach.

The test file uses vi.runOnlyPendingTimers() and vi.advanceTimersByTime() (lines 75, 79, 86, 173, 243, 3732, 3741, 3752, 3761, 3793, 3805) but never enables fake timers, causing these calls to fail or behave unexpectedly. Add vi.useFakeTimers() in the beforeEach and cleanup with vi.useRealTimers() in afterEach.

Suggested fix
 beforeEach(() => {
+  vi.useFakeTimers();
   mockGetBoundingClientRect({ width: 100, height: 100 });
 });
+
+afterEach(() => {
+  vi.runOnlyPendingTimers();
+  vi.useRealTimers();
+});

Also applies to lines 725-953 and any other nested describe blocks.

🤖 Fix all issues with AI agents
In `@src/state/selectors/axisSelectors.ts`:
- Around line 92-94: Change the runtime import of the type to a type-only import
to avoid the circular dependency: replace the current import of
InverseScaleFunction with a type-only import (i.e., import type {
InverseScaleFunction } from '...') in axisSelectors.ts where
InverseScaleFunction is referenced; ensure all usages in return type annotations
(the selectors that return InverseScaleFunction) remain unchanged so the symbol
is only used at type level and does not introduce a runtime import that would
trigger the circular dependency with hooks.
- Around line 504-526: selectSortedDataPoints currently sorts domain values
numerically via sortBy which breaks categorical or reversed axes because bisect
later expects pixels ordered by the scale; instead compute and sort by pixel
positions using the axis scale before bisecting. Update selectSortedDataPoints
(and similarly selectAxisInverseDataSnapScale) to map each domain value to its
pixel via the scale (e.g., {value, pixel: scale(value) ?? 0}), sort that array
by pixel, then expose both sorted pixelPositions and the correspondingly-ordered
domain values (or return domain ordered by pixel) so createCategoricalInverse
can use the pixel-sorted arrays for bisecting rather than the numeric sortBy
order.

In `@src/util/scale/createCategoricalInverse.ts`:
- Around line 14-28: The bisect function can crash or behave unpredictably for
empty or single-element arrays because it dereferences haystack[0] and
haystack[haystack.length - 1]; change bisect (the function named bisect) to
first handle haystack.length === 0 by returning 0, and treat arrays with length
< 2 as trivially ascending (e.g., set const ascending = haystack.length < 2 ?
true : haystack[0] < haystack[haystack.length - 1]) and remove the non-null
assertions; keep the existing binary search logic otherwise so bisect safely
handles empty and single-element inputs called from createCategoricalInverse.
- Around line 1-71: Add comprehensive unit tests for the bisect helper and
createCategoricalInverse function: write tests that call bisect directly on
ascending and descending numeric arrays and verify insertion indices; and tests
for createCategoricalInverse covering these cases: undefined scale returns
undefined; empty domain and range length < 2 return undefined; single-element
domain returns that element for any pixel; normal categorical mapping where
scale.domain() and scale.range() produce pixelPositions (use a fake/mock
CustomScaleDefinition with scale(d), domain(), range()); verify clamping to
first/last domain when pixel is outside bounds; verify correct neighbor
selection (tie-breaking uses left when equidistant); and verify that passing
allDataPointsOnAxis overrides scale.domain(). Use assertions on returned values
to exercise every branch in createCategoricalInverse and bisect.

In `@test/hooks/useAxisTicks.spec.tsx`:
- Around line 1-18: The test suite "new axis hooks" fails because
createSelectorTestCase calls vi.runOnlyPendingTimers() but no fake timers are
enabled; enable Vitest fake timers for this suite by calling vi.useFakeTimers()
before tests (e.g., in a beforeEach or at the top of the describe) and restore
them with vi.useRealTimers() after tests (e.g., in an afterEach), so functions
like createSelectorTestCase that invoke vi.runOnlyPendingTimers() (and any
timer-based Redux autobatching) operate correctly.

In `@www/src/components/GuideView/CoordinateSystems/index.tsx`:
- Around line 189-201: Replace the non-exported hook reference useDataToPixels
with the actual exported API: update the list item to reference
useCartesianScale (or export an alias named useDataToPixels that re-exports
useCartesianScale) and ensure the LinkToApi target points to the real exported
symbol; specifically modify the docs in the CoordinateSystems component to use
useCartesianScale (or add an export alias in the module that exports
useCartesianScale) so the LinkToApi link is valid.

In `@www/src/views/GuideView.tsx`:
- Line 3: The CoordinateSystems component signature doesn't match the typed
guideMap; change the component export from "export function CoordinateSystems()"
to accept the locale prop like "export function CoordinateSystems({ locale }: {
locale: SupportedLocale })" so it conforms to Record<string, ComponentType<{
locale: SupportedLocale }>> (even if locale is unused), and update any internal
references or imports as needed to keep types consistent with SupportedLocale
and the other guide components (e.g., Installation).
🧹 Nitpick comments (4)
www/src/components/GuideView/CoordinateSystems/ScaleConversionExample.tsx (1)

12-24: JSDoc mentions hooks but example uses ReferenceDot instead.

The documentation comment mentions useXAxisScale(), useYAxisScale(), useXAxisInverseScale(), useYAxisInverseScale(), and useDataToPixels() hooks, but the example solely demonstrates ReferenceDot which handles conversion internally. Consider adding a brief example showing actual hook usage, or adjusting the comment to clarify this is a simpler approach that doesn't require hooks.

src/util/scale/createCategoricalInverse.ts (1)

30-50: Good structure with appropriate JSDoc, but consider adding return type documentation.

The function signature and behavior are clear. The @ts-expect-error on Line 43 is acceptable given the challenge of typing generic domain values.

Consider expanding the JSDoc to document the return value behavior:

📝 Enhanced JSDoc suggestion
 /**
  * Computes an inverse scale function for categorical/ordinal scales.
  * Uses bisect to find the closest domain value for a given pixel coordinate.
+ *
+ * `@param` scale - The scale definition to invert
+ * `@param` allDataPointsOnAxis - Optional override for the domain values
+ * `@returns` An inverse function, or undefined if scale is missing or domain is empty
  */
test/state/mouseEventsMiddleware.spec.ts (1)

1-25: Ensure fake timers are enabled for the timer-count assertions.
If not already set in global test setup, add vi.useFakeTimers() here to keep vi.getTimerCount()/runOnlyPendingTimers() reliable.

Suggested setup (if not already global)
 beforeEach(() => {
+  vi.useFakeTimers();
   store = createRechartsStore();
 });
+
+afterEach(() => {
+  vi.useRealTimers();
+});
As per coding guidelines: Use `vi.useFakeTimers()` in all tests due to Redux autoBatchEnhancer dependency on timers and `requestAnimationFrame`.
www/src/docs/exampleComponents/AreaChart/AreaChartFillByValue.tsx (1)

52-66: Guard against zero/invalid height when computing gradient offset.
Clamping avoids invalid offsets when height is 0 or scale output falls outside the expected range.

Suggested tweak
   const scale = useYAxisScale();
   const height = useChartHeight();
   const scaledZero = scale?.(0);
-  if (scaledZero == null || height == null) {
+  if (scaledZero == null || height == null || height <= 0) {
     return null;
   }
-  const ratio = scaledZero / height;
+  const ratio = Math.min(1, Math.max(0, scaledZero / height));

Comment thread src/state/selectors/axisSelectors.ts
Comment on lines +504 to +526
function sortBy(a: unknown, b: unknown): number {
const aNum = makeNumber(a);
const bNum = makeNumber(b);
if (aNum == null && bNum == null) {
return 0;
}
if (aNum == null) {
return -1;
}
if (bNum == null) {
return 1;
}
return aNum - bNum;
}

export const selectSortedDataPoints: (
state: RechartsRootState,
axisType: AllAxisTypes,
axisId: AxisId,
isPanorama: boolean,
) => ReadonlyArray<unknown> | undefined = createSelector([selectAllAppliedValues], appliedData => {
return appliedData?.map(item => item.value).sort(sortBy);
});

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check the file structure and read the relevant sections
wc -l src/state/selectors/axisSelectors.ts

Repository: recharts/recharts

Length of output: 102


🏁 Script executed:

# Read lines 504-526 and surrounding context
sed -n '490,530p' src/state/selectors/axisSelectors.ts | cat -n

Repository: recharts/recharts

Length of output: 1326


🏁 Script executed:

# Check lines 1495-1510 (mentioned as "Also applies to")
sed -n '1485,1520p' src/state/selectors/axisSelectors.ts | cat -n

Repository: recharts/recharts

Length of output: 1394


🏁 Script executed:

# Search for createCategoricalInverse definition
rg -A 30 "function createCategoricalInverse|const createCategoricalInverse" src/state/selectors/axisSelectors.ts

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

# Search for bisect usage in the file
rg -B 3 -A 3 "bisect" src/state/selectors/axisSelectors.ts

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

# Search for createCategoricalInverse across all files
rg "createCategoricalInverse" --type ts --type tsx

Repository: recharts/recharts

Length of output: 88


🏁 Script executed:

# Search for bisect function
rg "bisect" --type ts --type tsx

Repository: recharts/recharts

Length of output: 88


🏁 Script executed:

# Look at imports in axisSelectors.ts
head -50 src/state/selectors/axisSelectors.ts

Repository: recharts/recharts

Length of output: 1440


🏁 Script executed:

# Continue reading imports to find createCategoricalInverse
head -100 src/state/selectors/axisSelectors.ts | tail -50

Repository: recharts/recharts

Length of output: 3018


🏁 Script executed:

# Search for createCategoricalInverse in all typescript files
rg "createCategoricalInverse"

Repository: recharts/recharts

Length of output: 850


🏁 Script executed:

# Search for bisect in all files
rg "bisect"

Repository: recharts/recharts

Length of output: 619


🏁 Script executed:

# Read the createCategoricalInverse implementation
cat -n src/util/scale/createCategoricalInverse.ts

Repository: recharts/recharts

Length of output: 2823


🏁 Script executed:

# Now let me check how selectSortedDataPoints is actually used
# Look at the selector definition again and trace the issue
sed -n '1495,1520p' src/state/selectors/axisSelectors.ts

Repository: recharts/recharts

Length of output: 880


🏁 Script executed:

# Let's also check if there are any tests for this behavior
fd "axisSelectors" -e test -e spec

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

# Check what makeNumber does to categorical values
rg -A 10 "function makeNumber|export.*makeNumber" src/util/DataUtils.ts

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

# Let me check what the scale.domain() returns and understand better
# Search for scale usage in categorical contexts
rg -B 5 -A 5 "isCategoricalAxis" src/state/selectors/axisSelectors.ts | head -50

Repository: recharts/recharts

Length of output: 1568


🏁 Script executed:

# Check for categorical scale types and how they work
rg "ordinal|categorical" src/util/scale/RechartsScale.ts | head -20

Repository: recharts/recharts

Length of output: 124


🏁 Script executed:

# Let's trace what happens with the sorted data points
# by looking at the actual scale implementation
fd "RechartsScale" -e ts

Repository: recharts/recharts

Length of output: 125


🏁 Script executed:

# Find and read the makeNumber function
rg -B 3 -A 15 "export.*function makeNumber|function makeNumber" --type ts

Repository: recharts/recharts

Length of output: 1230


🏁 Script executed:

# Let's also check what selectAllAppliedValues returns
rg -B 5 -A 10 "selectAllAppliedValues" src/state/selectors/axisSelectors.ts | head -40

Repository: recharts/recharts

Length of output: 1394


🏁 Script executed:

# Let's understand how categorical scales and domains work
# Check how the domain is built for categorical axes
rg -B 5 -A 15 "function combineCheckedDomain" src/state/selectors/combiners/combineCheckedDomain.ts

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

# Also check if there are any tests showing the issue
fd "test" -type f -name "*axisSelectors*" -o -name "*categorical*"

Repository: recharts/recharts

Length of output: 231


🏁 Script executed:

# Check how selectConfiguredScale determines the domain
rg -B 5 -A 15 "selectConfiguredScale\b" src/state/selectors/axisSelectors.ts | head -40

Repository: recharts/recharts

Length of output: 1345


🏁 Script executed:

# Let me check what selectAllAppliedValues actually contains
# and when selectSortedDataPoints is actually used
rg -B 10 -A 5 "selectAxisInverseDataSnapScale" src/state/selectors/axisSelectors.ts

Repository: recharts/recharts

Length of output: 635


🏁 Script executed:

# Check when selectSortedDataPoints is used
rg "selectSortedDataPoints" src/

Repository: recharts/recharts

Length of output: 224


🏁 Script executed:

# Let me trace through how the domain is actually used
# Check if there's any special handling in combineConfiguredScale
rg -B 5 -A 20 "combineConfiguredScale\(" src/state/selectors/combiners/combineConfiguredScale.ts

Repository: recharts/recharts

Length of output: 672


🏁 Script executed:

# Look for ordinal or band scale usage
rg "band|ordinal" src/util/scale/

Repository: recharts/recharts

Length of output: 1586


🏁 Script executed:

# Let me understand when selectSortedDataPoints is actually passed
# Check combineAppliedValues to see what values are in selectAllAppliedValues
rg -B 5 -A 30 "export.*combineAppliedValues" src/state/selectors/combiners/combineAppliedValues.ts

Repository: recharts/recharts

Length of output: 154


🏁 Script executed:

# Check if there's special handling for categorical vs numeric axes
rg -B 10 -A 10 "selectSortedDataPoints\|selectAllAppliedValues" src/state/selectors/axisSelectors.ts | grep -A 40 "selectAxisInverseDataSnapScale"

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

# Find where combineAppliedValues is defined
rg "export.*combineAppliedValues|function combineAppliedValues"

Repository: recharts/recharts

Length of output: 135


🏁 Script executed:

# Let me understand the actual problem better by creating a concrete example
# First, let's see if there are any tests that would show the issue
find . -name "*.test.ts" -o -name "*.spec.ts" | grep -i "categorical\|axis" | head -10

Repository: recharts/recharts

Length of output: 148


🏁 Script executed:

# Check the usage pattern - is selectSortedDataPoints actually used for categorical axes?
rg -B 20 "createCategoricalInverse" src/state/selectors/axisSelectors.ts | grep -A 20 "selectAxisInverseDataSnapScale"

Repository: recharts/recharts

Length of output: 343


🏁 Script executed:

# Let me check how the domain is actually set on the scale
# Look at the scale function more carefully
rg -B 5 -A 30 "export const selectAxisInverseDataSnapScale" src/state/selectors/axisSelectors.ts

Repository: recharts/recharts

Length of output: 1076


🏁 Script executed:

# Check if selectSortedDataPoints could have non-monotonic pixel positions
# by understanding what makeNumber does with categorical values
rg -B 10 -A 20 "export const combineAppliedValues" src/state/selectors/axisSelectors.ts

Repository: recharts/recharts

Length of output: 1134


🏁 Script executed:

# Let me create a test scenario to understand the issue better
# Check if there are any categorical axis tests
cat test/state/selectors/cartesianAxisSlice.spec.ts | head -100

Repository: recharts/recharts

Length of output: 3202


🏁 Script executed:

# Let me verify the actual issue by checking when selectSortedDataPoints is used
# and what happens with categorical numeric values
rg -B 30 "selectAxisInverseDataSnapScale" src/state/selectors/axisSelectors.ts | grep -A 30 "selectSortedDataPoints"

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

# Check if there's a case where the domain order differs from numeric sort order
# Let's look at what happens with reversed or categorical numeric axes
rg -B 5 -A 15 "reversed\|type.*category" src/util/scale/combineConfiguredScale.ts

Repository: recharts/recharts

Length of output: 141


🏁 Script executed:

# The key issue: does sortBy produce a different order than scale.map?
# Let me check a concrete example by looking for reversed axis handling
rg -B 5 -A 15 "reversed" src/state/selectors/combiners/combineAxisRangeWithReverse.ts | head -50

Repository: recharts/recharts

Length of output: 289


🏁 Script executed:

# Let me trace through the actual flow more carefully
# The key issue is: does selectSortedDataPoints override the domain order?
# Let's check what domain() returns on a categorical scale
rg -B 10 -A 10 "function domain\|\.domain\(" src/util/scale/RechartsScale.ts | head -60

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

# More importantly - when allDataPointsOnAxis is provided to createCategoricalInverse,
# it replaces scale.domain(). Let's verify this logic:
sed -n '30,50p' src/util/scale/createCategoricalInverse.ts

Repository: recharts/recharts

Length of output: 828


🏁 Script executed:

# The critical question: can the sorted data points be in a different order than scale.domain()?
# This would happen if:
# 1. Scale domain is categorical with custom order (e.g., ["C", "A", "B"])
# 2. selectSortedDataPoints sorts them numerically (e.g., ["A", "B", "C"])
# Let me check if there are categorical axes with numeric/string values that could be sorted differently
rg -B 5 -A 15 "isCategorical.*type.*=.*category" src/state/selectors/axisSelectors.ts | head -50

Repository: recharts/recharts

Length of output: 719


🏁 Script executed:

# Let me trace the actual issue step by step
# Check what type of scale is used and when selectSortedDataPoints would be problematic
# First, understand: is selectSortedDataPoints only for specific axis types?
rg -B 15 "selectAxisInverseDataSnapScale" src/state/selectors/axisSelectors.ts | grep -B 15 "createCategoricalInverse"

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

# Check if there are any guards or conditions on when this selector is used
rg "selectAxisInverseDataSnapScale" src/

Repository: recharts/recharts

Length of output: 423


🏁 Script executed:

# The critical test: can a categorical numeric axis have values that sort differently than the domain?
# Check how categorical numeric axes work in tests
find . -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "category\|categorical" | head -5

Repository: recharts/recharts

Length of output: 291


Data-snap ordering can be wrong for categorical or reversed axes.

selectSortedDataPoints sorts values numerically via sortBy, but when passed to createCategoricalInverse as allDataPointsOnAxis, it overrides the scale's domain order. For categorical axes with numeric categories, custom ordering, or reversed ranges, this causes pixelPositions to be mapped in the wrong order. Since bisect then searches based on this incorrectly-ordered pixel array, it can return the wrong nearest value.

The scale itself was constructed with the original categorical domain order, but bisect operates on pixel positions calculated from the numerically-sorted domain—creating a mismatch.

Consider sorting by scale.map output (pixel positions) instead, or reordering the domain inside createCategoricalInverse before bisecting:

Example fix
const domainWithPixels = domain
  .map(value => ({ value, pixel: scale(value) ?? 0 }))
  .sort((a, b) => a.pixel - b.pixel);

const pixelPositions = domainWithPixels.map(d => d.pixel);
const sortedDomain = domainWithPixels.map(d => d.value);

// Use sortedDomain + pixelPositions in bisect

Also applies to: selectAxisInverseDataSnapScale (lines 1495–1510)

🤖 Prompt for AI Agents
In `@src/state/selectors/axisSelectors.ts` around lines 504 - 526,
selectSortedDataPoints currently sorts domain values numerically via sortBy
which breaks categorical or reversed axes because bisect later expects pixels
ordered by the scale; instead compute and sort by pixel positions using the axis
scale before bisecting. Update selectSortedDataPoints (and similarly
selectAxisInverseDataSnapScale) to map each domain value to its pixel via the
scale (e.g., {value, pixel: scale(value) ?? 0}), sort that array by pixel, then
expose both sorted pixelPositions and the correspondingly-ordered domain values
(or return domain ordered by pixel) so createCategoricalInverse can use the
pixel-sorted arrays for bisecting rather than the numeric sortBy order.

Comment on lines +1 to +71
import { InverseScaleFunction } from '../../hooks';
import { CustomScaleDefinition } from './CustomScaleDefinition';

/**
* Binary search to find the index where x would fit in array a.
* Works for arrays that are sorted both ascending and descending.
*
* Unlike d3.bisect, this implementation handles both ascending and descending arrays.
*
* @param haystack Sorted array of numbers
* @param needle Number to find the insertion index for
* @returns Index where x would fit in array a
*/
function bisect(haystack: ReadonlyArray<number>, needle: number): number {
let lo = 0;
let hi = haystack.length;
const ascending = haystack[0]! < haystack[haystack.length - 1]!;

while (lo < hi) {
const mid = Math.floor((lo + hi) / 2);
if (ascending ? haystack[mid]! < needle : haystack[mid]! > needle) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}

/**
* Computes an inverse scale function for categorical/ordinal scales.
* Uses bisect to find the closest domain value for a given pixel coordinate.
*/
export function createCategoricalInverse(
scale: CustomScaleDefinition | undefined,
allDataPointsOnAxis?: ReadonlyArray<unknown>,
): InverseScaleFunction | undefined {
if (!scale) {
return undefined;
}
const domain = allDataPointsOnAxis ?? scale.domain();
// Build an array of pixel positions for each domain value
// @ts-expect-error we're attempting to scale unknown without having guarantee that it is a Domain type
const pixelPositions: number[] = domain.map(d => scale(d) ?? 0);
const range = scale.range();

if (domain.length === 0 || range.length < 2) {
return undefined;
}

return (pixelValue: number): unknown => {
// Find the closest domain value using bisect
const index = bisect(pixelPositions, pixelValue);

// Clamp to valid range
if (index <= 0) {
return domain[0];
}
if (index >= domain.length) {
return domain[domain.length - 1];
}

// Check which neighbor is closer
const leftPixel = pixelPositions[index - 1] ?? 0;
const rightPixel = pixelPositions[index] ?? 0;
if (Math.abs(pixelValue - leftPixel) <= Math.abs(pixelValue - rightPixel)) {
return domain[index - 1];
}
return domain[index];
};
}

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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for test files covering createCategoricalInverse
rg 'createCategoricalInverse' --type ts --type tsx --type js --type jsx | grep -E '\.(test|spec)\.(ts|tsx|js|jsx):'

Repository: recharts/recharts

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Also search more broadly for test files
find . -type f \( -name "*.test.ts" -o -name "*.test.tsx" -o -name "*.spec.ts" -o -name "*.spec.tsx" \) | xargs rg -l 'createCategoricalInverse' 2>/dev/null

Repository: recharts/recharts

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check if there's a test directory and what test files exist related to scale
fd -e test.ts -e spec.ts 'scale' | head -20

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Search for any test file that mentions createCategoricalInverse
find . -name "*.test.ts" -o -name "*.test.js" -o -name "*.spec.ts" -o -name "*.spec.js" | xargs grep -l "createCategoricalInverse" 2>/dev/null

Repository: recharts/recharts

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Look at the directory structure around the source file
ls -la src/util/scale/

Repository: recharts/recharts

Length of output: 644


🏁 Script executed:

#!/bin/bash
# Search more broadly for createCategoricalInverse mentions anywhere in the repo
grep -r "createCategoricalInverse" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" . 2>/dev/null | head -30

Repository: recharts/recharts

Length of output: 617


🏁 Script executed:

#!/bin/bash
# Search for test files in the entire src directory
find src -type f \( -name "*.test.ts" -o -name "*.test.js" \) | head -20

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Check if there's a __tests__ or test directory structure
find . -type d -name "__tests__" -o -type d -name "tests" -o -type d -name "test" | grep -v node_modules | head -20

Repository: recharts/recharts

Length of output: 124


🏁 Script executed:

#!/bin/bash
# Look for any test files related to scale utilities
find . -path ./node_modules -prune -o -type f \( -name "*scale*.test.ts" -o -name "*scale*.spec.ts" \) -print

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Check what's in the test directory
ls -la test/ | head -30

Repository: recharts/recharts

Length of output: 1315


🏁 Script executed:

#!/bin/bash
# Search for createCategoricalInverse in test directory
grep -r "createCategoricalInverse" test/ 2>/dev/null

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Look for scale-related tests
find test -type f -name "*scale*" 2>/dev/null

Repository: recharts/recharts

Length of output: 106


🏁 Script executed:

#!/bin/bash
# Search for any tests that might indirectly test this utility
find test -type f -name "*axis*" -o -name "*inverse*" 2>/dev/null

Repository: recharts/recharts

Length of output: 186


🏁 Script executed:

#!/bin/bash
# Check the axisSelectors test file
wc -l test/state/selectors/axisSelectors.spec.tsx

Repository: recharts/recharts

Length of output: 109


🏁 Script executed:

#!/bin/bash
# Look at the axisSelectors test file
cat test/state/selectors/axisSelectors.spec.tsx

Repository: recharts/recharts

Length of output: 50373


🏁 Script executed:

#!/bin/bash
# Search for createCategoricalInverse in the test file
grep -n "createCategoricalInverse" test/state/selectors/axisSelectors.spec.tsx

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Check if there's a test file for combineInverseScaleFunction
find test -name "*combineInverseScaleFunction*" -o -name "*inverse*"

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Search for any test that mentions the inverse scale function
find test -type f -name "*.tsx" -o -name "*.ts" | xargs grep -l "InverseScaleFunction" 2>/dev/null

Repository: recharts/recharts

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check if there's a dedicated test directory for scale utilities
find test -type d -name "*scale*" 2>/dev/null

Repository: recharts/recharts

Length of output: 76


🏁 Script executed:

#!/bin/bash
# Check what test files exist in test/util/scale directory
ls -la test/util/scale/

Repository: recharts/recharts

Length of output: 402


🏁 Script executed:

#!/bin/bash
# Search for any test file that might test createCategoricalInverse
find test/util/scale -type f -name "*.test.*" -o -name "*.spec.*"

Repository: recharts/recharts

Length of output: 186


🏁 Script executed:

#!/bin/bash
# List all files in test/util/scale with details
find test/util/scale -type f

Repository: recharts/recharts

Length of output: 186


Add comprehensive unit tests for createCategoricalInverse utility.

This utility exports a public function with complex logic including binary search and multiple edge cases (empty domain, single element, ascending vs descending ranges) but currently has no test coverage. Per coding guidelines, source files in src/ must aim for 100% unit test coverage. Add unit tests covering the bisect helper function and all branches of createCategoricalInverse, including the edge cases mentioned.

🤖 Prompt for AI Agents
In `@src/util/scale/createCategoricalInverse.ts` around lines 1 - 71, Add
comprehensive unit tests for the bisect helper and createCategoricalInverse
function: write tests that call bisect directly on ascending and descending
numeric arrays and verify insertion indices; and tests for
createCategoricalInverse covering these cases: undefined scale returns
undefined; empty domain and range length < 2 return undefined; single-element
domain returns that element for any pixel; normal categorical mapping where
scale.domain() and scale.range() produce pixelPositions (use a fake/mock
CustomScaleDefinition with scale(d), domain(), range()); verify clamping to
first/last domain when pixel is outside bounds; verify correct neighbor
selection (tie-breaking uses left when equidistant); and verify that passing
allDataPointsOnAxis overrides scale.domain(). Use assertions on returned values
to exercise every branch in createCategoricalInverse and bisect.

Comment on lines +14 to +28
function bisect(haystack: ReadonlyArray<number>, needle: number): number {
let lo = 0;
let hi = haystack.length;
const ascending = haystack[0]! < haystack[haystack.length - 1]!;

while (lo < hi) {
const mid = Math.floor((lo + hi) / 2);
if (ascending ? haystack[mid]! < needle : haystack[mid]! > needle) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}

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.

⚠️ Potential issue | 🟡 Minor

Potential crash on empty or single-element arrays in bisect.

The ascending calculation on Line 17 accesses haystack[0] and haystack[haystack.length - 1] with non-null assertions. If haystack is empty, this will throw. Additionally, for a single-element array, both indices reference the same element, making the ascending check unreliable.

The function is called from createCategoricalInverse which does check for empty domain on Line 47, but pixelPositions is built before that check, so bisect could theoretically be called with an empty array if the flow changes in the future.

🛡️ Proposed defensive fix
 function bisect(haystack: ReadonlyArray<number>, needle: number): number {
+  if (haystack.length === 0) {
+    return 0;
+  }
   let lo = 0;
   let hi = haystack.length;
-  const ascending = haystack[0]! < haystack[haystack.length - 1]!;
+  const ascending = haystack.length < 2 || haystack[0]! < haystack[haystack.length - 1]!;
🤖 Prompt for AI Agents
In `@src/util/scale/createCategoricalInverse.ts` around lines 14 - 28, The bisect
function can crash or behave unpredictably for empty or single-element arrays
because it dereferences haystack[0] and haystack[haystack.length - 1]; change
bisect (the function named bisect) to first handle haystack.length === 0 by
returning 0, and treat arrays with length < 2 as trivially ascending (e.g., set
const ascending = haystack.length < 2 ? true : haystack[0] <
haystack[haystack.length - 1]) and remove the non-null assertions; keep the
existing binary search logic otherwise so bisect safely handles empty and
single-element inputs called from createCategoricalInverse.

Comment thread test/hooks/useAxisTicks.spec.tsx
Comment thread www/src/components/GuideView/CoordinateSystems/index.tsx
Comment thread www/src/views/GuideView.tsx
@github-actions

github-actions Bot commented Feb 4, 2026

Copy link
Copy Markdown
Contributor

Staging Deployment Details

These deployments will remain available for 30 days.

To update snapshots: Comment /update-snapshots on this PR to automatically update the baseline screenshots.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@test/hooks/useAxisScale.spec.tsx`:
- Around line 1-13: This test file renders charts with timers and layout
measurements; add local fake-timer setup and bounding-rect mocks to prevent
flakiness by calling vi.useFakeTimers() at test start and vi.useRealTimers() in
teardown, and apply the provided MockGetBoundingClientRect helper to mock
element.getBoundingClientRect during the test run; update useAxisScale.spec.tsx
(around the top-level describe or beforeEach/afterEach) to import and invoke
MockGetBoundingClientRect and ensure timers are restored after tests so symbols
like vi.useFakeTimers, vi.useRealTimers, and MockGetBoundingClientRect are used
to wrap the existing createSelectorTestCase/chart-rendering tests.
🧹 Nitpick comments (1)
test/hooks/useAxisScale.spec.tsx (1)

24-268: Add selector call-count assertions for createSelectorTestCase spies.

This keeps selector re-renders in check and aligns with existing test conventions.

As per coding guidelines, Verify the number of selector calls using the spy object from createSelectorTestCase to spot unnecessary re-renders and improve performance.

Comment on lines +1 to +13
import React from 'react';
import { describe, it, expect } from 'vitest';
import { createSelectorTestCase } from '../helper/createSelectorTestCase';
import { Line, LineChart, XAxis, YAxis, Brush } from '../../src';
import {
useXAxisScale,
useYAxisScale,
useXAxisInverseScale,
useYAxisInverseScale,
useCartesianScale,
} from '../../src/hooks';
import { PageData } from '../_data';

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.

⚠️ Potential issue | 🟡 Minor

Add test setup for fake timers and bounding-rect mocks.

These tests render charts and rely on timers; add local setup (or ensure a shared setup does it) to avoid flakiness.

🧪 Suggested setup
-import { describe, it, expect } from 'vitest';
+import { beforeEach, afterEach, describe, it, expect, vi } from 'vitest';
+import { mockGetBoundingClientRect } from '../helper/mockGetBoundingClientRect';
+
+beforeEach(() => {
+  vi.useFakeTimers();
+  mockGetBoundingClientRect({ width: 500, height: 300 });
+});
+
+afterEach(() => {
+  vi.useRealTimers();
+});

As per coding guidelines, Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame; Mock getBoundingClientRect in tests using the helper function provided in test/helper/MockGetBoundingClientRect.ts.

🤖 Prompt for AI Agents
In `@test/hooks/useAxisScale.spec.tsx` around lines 1 - 13, This test file renders
charts with timers and layout measurements; add local fake-timer setup and
bounding-rect mocks to prevent flakiness by calling vi.useFakeTimers() at test
start and vi.useRealTimers() in teardown, and apply the provided
MockGetBoundingClientRect helper to mock element.getBoundingClientRect during
the test run; update useAxisScale.spec.tsx (around the top-level describe or
beforeEach/afterEach) to import and invoke MockGetBoundingClientRect and ensure
timers are restored after tests so symbols like vi.useFakeTimers,
vi.useRealTimers, and MockGetBoundingClientRect are used to wrap the existing
createSelectorTestCase/chart-rendering tests.

@codecov

codecov Bot commented Feb 5, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 42.74kB (1.51%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
recharts/bundle-cjs 1.25MB 21.72kB (1.77%) ⬆️
recharts/bundle-es6 1.09MB 18.29kB (1.71%) ⬆️
recharts/bundle-umd 538.88kB 2.72kB (0.51%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: recharts/bundle-cjs

Assets Changed:

Asset Name Size Change Total Size Change (%)
state/selectors/axisSelectors.js 1.97kB 64.52kB 3.14%
hooks.js 15.03kB 23.45kB 178.33% ⚠️
index.js 1.64kB 14.06kB 13.22% ⚠️
util/getRelativeCoordinate.js 14 bytes 4.42kB 0.32%
util/scale/createCategoricalInverse.js (New) 2.48kB 2.48kB 100.0% 🚀
state/selectors/combiners/combineInverseScaleFunction.js (New) 592 bytes 592 bytes 100.0% 🚀
view changes for bundle: recharts/bundle-umd

Assets Changed:

Asset Name Size Change Total Size Change (%)
Recharts.js 2.72kB 538.88kB 0.51%
view changes for bundle: recharts/bundle-es6

Assets Changed:

Asset Name Size Change Total Size Change (%)
state/selectors/axisSelectors.js 1.52kB 54.78kB 2.86%
hooks.js 13.77kB 21.18kB 185.7% ⚠️
util/getRelativeCoordinate.js 14 bytes 4.29kB 0.33%
index.js 243 bytes 3.69kB 7.04% ⚠️
util/scale/createCategoricalInverse.js (New) 2.33kB 2.33kB 100.0% 🚀
state/selectors/combiners/combineInverseScaleFunction.js (New) 421 bytes 421 bytes 100.0% 🚀

@github-actions

github-actions Bot commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

Staging Deployment Details

These deployments will remain available for 30 days.

To update snapshots: Comment /update-snapshots on this PR to automatically update the baseline screenshots.

@github-actions

github-actions Bot commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

Staging Deployment Details

These deployments will remain available for 30 days.

To update snapshots: Comment /update-snapshots on this PR to automatically update the baseline screenshots.

@ckifer ckifer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🚀

@ckifer
ckifer merged commit 108be69 into main Feb 5, 2026
46 of 48 checks passed
@ckifer
ckifer deleted the annotations branch February 5, 2026 23:56
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.

v3 hook requests for Customized component prop replacements

2 participants