Add funnel and drillable sunburst examples - #81
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughChangesChart capabilities and conformance coverage
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Input
participant drillableSunburstDefinition
participant hierarchySunburst
participant motion
participant CenterControl
Input->>drillableSunburstDefinition: Select rootId and visibleDepth
drillableSunburstDefinition->>hierarchySunburst: Build focused sector scene
hierarchySunburst->>motion: Provide sector geometry and hierarchy metadata
motion->>CenterControl: Render transition and navigation state
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
|
View your CI Pipeline Execution ↗ for commit 37a0df1
☁️ Nx Cloud last updated this comment at |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/charts-core/src/motion.ts (1)
1238-1253: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winMove the semantic path track below the stale-attribute removal loop. The loop removes
data-ts-motion-rolebecause the scene markup does not emit it. Whendis the only changed attribute, no later code restores the role.nextcontainsd, so the reorder preserveslivePathGeometryValues.🤖 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 `@packages/charts-core/src/motion.ts` around lines 1238 - 1253, Move the addSemanticPathUpdateTrack call in the surrounding motion update flow to after the stale-attribute removal loop. Keep the loop’s existing behavior, and ensure the semantic path track runs afterward so data-ts-motion-role is restored and livePathGeometryValues is preserved when d is the only changed attribute.
🧹 Nitpick comments (5)
docs/reference/marks/sunburst.md (1)
121-127: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocument the
rootIdandvisibleDepthfailure modes.
sunburstthrows aTypeErrorwhenrootIdis an empty string or does not exist in the hierarchy, and whenvisibleDepthis not a positive integer. A drill-down UI buildsrootIdfrom application state, so callers need this contract. Add one sentence to this section.📝 Proposed documentation addition
`rootId` makes an existing hierarchy node the structural root without changing its canonical ID or rebuilding source rows. Its children become depth one, and the root itself is not painted. `visibleDepth` is relative to that root; hidden descendants still contribute to aggregate values and `internal` metadata. +An empty or unknown `rootId` throws, as does a `visibleDepth` that is not a +positive integer. Validate navigation state before you rebuild the definition.🤖 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 `@docs/reference/marks/sunburst.md` around lines 121 - 127, Add one sentence to the “Drill-down and motion” section documenting that sunburst throws a TypeError when rootId is empty or absent from the hierarchy, or when visibleDepth is not a positive integer.packages/charts-core/src/hierarchy-sunburst.test.ts (1)
204-245: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider pinning the leaf-root behavior.
If
rootIdselects a leaf node,layoutRoot.heightis0, soringCountis0and the mark renders no sectors without an error. A drill-down UI can reach that state. A short test would document the empty result and prevent a future change from throwing instead.🤖 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 `@packages/charts-core/src/hierarchy-sunburst.test.ts` around lines 204 - 245, Add a test covering a leaf selected by rootId in the sunburst layout, using the existing render flow and source shape from the hierarchy-root test. Assert that the leaf-root case renders an empty scene with no sectors and does not throw, preserving the ringCount-zero behavior.packages/charts-core/src/motion.ts (1)
1733-1751: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winIndex the scene motion entries by key instead of scanning them.
sceneMotionEntryperforms a linearfindover every motion entry, andaddSemanticPathUpdateTrackcalls it twice for each updated path element.hierarchyMotionRelationadds one linearfindper ancestor ID and onequerySelectorAllscan per entering or exiting element. For a sunburst with many sectors, the reconcile pass becomes quadratic in the sector count.The entries list is already cached per scene, so a keyed
Mapcosts one extra pass and removes the repeated scans. Useroot.querySelectorwith an attribute selector for the related element.♻️ Proposed refactor
const sceneMotionEntriesCache = new WeakMap< ChartScene, - readonly SceneMotionEntry[] + ReadonlyMap<string, SceneMotionEntry> >() function sceneMotionEntries(scene: ChartScene) { const cached = sceneMotionEntriesCache.get(scene) if (cached) return cached - const entries: SceneMotionEntry[] = [] + const entries = new Map<string, SceneMotionEntry>() const visit = (nodes: readonly SceneNode[]) => { for (const node of nodes) { const metadata = (node as SceneMotionNode)[sceneMotionNode] - if (metadata) entries.push({ node, metadata }) + if (metadata) entries.set(node.key, { node, metadata }) if (node.kind === 'group') visit(node.children) } } visit(scene.nodes) sceneMotionEntriesCache.set(scene, entries) return entries } function sceneMotionEntry(scene: ChartScene, key: string) { - return sceneMotionEntries(scene).find((entry) => entry.node.key === key) + return sceneMotionEntries(scene).get(key) }The ancestor search in
hierarchyMotionRelationthen needs a second index keyed bymarkIdand hierarchyid, or it can iterateentries.values()as today. The related-element lookup simplifies to:- const relatedElement = root - ? [...root.querySelectorAll<Element>('path[data-ts-key]')].find( - (candidate) => - candidate.getAttribute('data-ts-key') === ancestor.node.key, - ) - : undefined + const relatedElement = + root?.querySelector<Element>( + `path[data-ts-key="${CSS.escape(ancestor.node.key)}"]`, + ) ?? undefined
CSS.escapeis not available in every server-side DOM shim, so keep the array scan if the motion runtime must run outside a browser.Also applies to: 1789-1791
🤖 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 `@packages/charts-core/src/motion.ts` around lines 1733 - 1751, Index the cached scene motion entries by node key in sceneMotionEntry and update callers such as addSemanticPathUpdateTrack to use O(1) lookups instead of repeated linear scans. In hierarchyMotionRelation, avoid the per-element querySelectorAll scan by using a keyed attribute lookup for ancestor.node.key, while preserving the array-scan fallback if required by server-side DOM support; retain or add the separate markId/hierarchy-id lookup needed for ancestor resolution.benchmarks/conformance/cases/126-drillable-sunburst/tanstack.test.ts (1)
244-250: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winResolve the source path from the module location, not
process.cwd().The path depends on the process working directory. If Vitest runs with a different root (for example a project root inside
benchmarks/),readFileSyncthrows and the test fails for an unrelated reason. Resolve the sibling file fromimport.meta.urlinstead.♻️ Proposed change
+import { fileURLToPath } from 'node:url' ... const source = readFileSync( - resolve( - process.cwd(), - 'benchmarks/conformance/cases/126-drillable-sunburst/tanstack.ts', - ), + fileURLToPath(new URL('./tanstack.ts', import.meta.url)), 'utf8', )🤖 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 `@benchmarks/conformance/cases/126-drillable-sunburst/tanstack.test.ts` around lines 244 - 250, Update the source-file resolution in the conformance test to derive the sibling tanstack.ts path from import.meta.url rather than process.cwd(). Preserve the existing readFileSync behavior and UTF-8 encoding while making the path independent of Vitest’s working directory.benchmarks/conformance/cases/126-drillable-sunburst/model.test.ts (1)
38-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReplace the tautological color assertion.
flareNodeColoris deterministic and holds no state. Comparing the same call to itself always passes, so this line tests nothing. Assert the returned format or compare against a computed expectation instead.💚 Proposed test change
- expect(flareNodeColor(leaf)).toBe(flareNodeColor(leaf)) + expect(flareNodeColor(leaf)).toMatch(/^hsl\(\d+ 70% \d+%\)$/)🤖 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 `@benchmarks/conformance/cases/126-drillable-sunburst/model.test.ts` at line 38, Replace the tautological self-comparison in the color assertion with a meaningful expectation: validate the format returned by flareNodeColor or compare it against a separately computed expected color value, while preserving the existing leaf fixture and flareNodeColor usage.
🤖 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 `@benchmarks/conformance/cases/125-sales-funnel/data.ts`:
- Around line 1-5: Update the FunnelStage interface so its id, label, and value
properties are readonly, ensuring shared stage objects cannot be mutated through
consumers while preserving the existing field types.
In `@packages/charts-core/src/motion.ts`:
- Around line 1734-1751: Capture the narrowed ancestor value in a const
immediately after the !ancestor guard, then use that const instead of ancestor
inside the relatedElement find callback while preserving the existing lookup
behavior.
---
Outside diff comments:
In `@packages/charts-core/src/motion.ts`:
- Around line 1238-1253: Move the addSemanticPathUpdateTrack call in the
surrounding motion update flow to after the stale-attribute removal loop. Keep
the loop’s existing behavior, and ensure the semantic path track runs afterward
so data-ts-motion-role is restored and livePathGeometryValues is preserved when
d is the only changed attribute.
---
Nitpick comments:
In `@benchmarks/conformance/cases/126-drillable-sunburst/model.test.ts`:
- Line 38: Replace the tautological self-comparison in the color assertion with
a meaningful expectation: validate the format returned by flareNodeColor or
compare it against a separately computed expected color value, while preserving
the existing leaf fixture and flareNodeColor usage.
In `@benchmarks/conformance/cases/126-drillable-sunburst/tanstack.test.ts`:
- Around line 244-250: Update the source-file resolution in the conformance test
to derive the sibling tanstack.ts path from import.meta.url rather than
process.cwd(). Preserve the existing readFileSync behavior and UTF-8 encoding
while making the path independent of Vitest’s working directory.
In `@docs/reference/marks/sunburst.md`:
- Around line 121-127: Add one sentence to the “Drill-down and motion” section
documenting that sunburst throws a TypeError when rootId is empty or absent from
the hierarchy, or when visibleDepth is not a positive integer.
In `@packages/charts-core/src/hierarchy-sunburst.test.ts`:
- Around line 204-245: Add a test covering a leaf selected by rootId in the
sunburst layout, using the existing render flow and source shape from the
hierarchy-root test. Assert that the leaf-root case renders an empty scene with
no sectors and does not throw, preserving the ringCount-zero behavior.
In `@packages/charts-core/src/motion.ts`:
- Around line 1733-1751: Index the cached scene motion entries by node key in
sceneMotionEntry and update callers such as addSemanticPathUpdateTrack to use
O(1) lookups instead of repeated linear scans. In hierarchyMotionRelation, avoid
the per-element querySelectorAll scan by using a keyed attribute lookup for
ancestor.node.key, while preserving the array-scan fallback if required by
server-side DOM support; retain or add the separate markId/hierarchy-id lookup
needed for ancestor resolution.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9269fc6f-a78e-4546-a2e9-a7162646da53
⛔ Files ignored due to path filters (2)
benchmarks/conformance/previews/125-sales-funnel.svgis excluded by!**/*.svgbenchmarks/conformance/previews/126-drillable-sunburst.svgis excluded by!**/*.svg
📒 Files selected for processing (43)
.changeset/drillable-sunburst-motion.mdAPI-FRICTION.mdbenchmarks/comparison/bundle-baseline.jsonbenchmarks/conformance/DEFINITION-COVERAGE-AUDIT.mdbenchmarks/conformance/DEFINITION-COVERAGE-OVERVIEW.mdbenchmarks/conformance/README.mdbenchmarks/conformance/cases/125-sales-funnel/case.jsonbenchmarks/conformance/cases/125-sales-funnel/data.tsbenchmarks/conformance/cases/125-sales-funnel/model.test.tsbenchmarks/conformance/cases/125-sales-funnel/model.tsbenchmarks/conformance/cases/125-sales-funnel/plot.tsbenchmarks/conformance/cases/125-sales-funnel/tanstack.tsbenchmarks/conformance/cases/126-drillable-sunburst/case.jsonbenchmarks/conformance/cases/126-drillable-sunburst/center.tsbenchmarks/conformance/cases/126-drillable-sunburst/model.test.tsbenchmarks/conformance/cases/126-drillable-sunburst/model.tsbenchmarks/conformance/cases/126-drillable-sunburst/recharts.tsbenchmarks/conformance/cases/126-drillable-sunburst/tanstack.test.tsbenchmarks/conformance/cases/126-drillable-sunburst/tanstack.tsbenchmarks/conformance/catalog-index.jsonbenchmarks/conformance/definition-coverage-roadmap.jsonbenchmarks/conformance/definition-coverage-roadmap.test.tsbenchmarks/conformance/previews/manifest.jsondocs/comparison.mddocs/config.jsondocs/examples/networks-and-hierarchies.mddocs/examples/stacked-and-composition.mddocs/reference/marks/sunburst.mdllms.txtpackages/charts-core/docs/comparison.mdpackages/charts-core/docs/config.jsonpackages/charts-core/docs/examples/networks-and-hierarchies.mdpackages/charts-core/docs/examples/stacked-and-composition.mdpackages/charts-core/docs/reference/marks/sunburst.mdpackages/charts-core/llms.txtpackages/charts-core/src/hierarchy-sunburst.test.tspackages/charts-core/src/hierarchy-sunburst.tspackages/charts-core/src/motion.tspackages/charts-core/src/scene-motion-internal.tsscripts/catalog-definition-shapes.test.mjsscripts/catalog-preview.mjsscripts/catalog-preview.test.mjsscripts/measure-bundles.mjs
| export interface FunnelStage { | ||
| id: string | ||
| label: string | ||
| value: number | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect assignments that could mutate funnel-stage objects.
ast-grep run \
--lang typescript \
--pattern '$OBJECT.$PROPERTY = $VALUE' \
benchmarks/conformance/cases/125-sales-funnel
# Inspect consumers of the revision selector before making its return value immutable.
rg -n -C 3 --type ts '\bfunnelStagesForRevision\s*\(' benchmarksRepository: TanStack/charts
Length of output: 153
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- target files ---'
git ls-files 'benchmarks/conformance/cases/125-sales-funnel/*'
target="$(git ls-files 'benchmarks/conformance/cases/125-sales-funnel/data.ts' | head -n 1)"
if [ -z "$target" ]; then
echo "target file not found"
exit 0
fi
printf '%s\n' '--- target outline ---'
ast-grep outline "$target" || true
printf '%s\n' '--- target contents ---'
cat -n "$target"
printf '%s\n' '--- selector definition and usages ---'
rg -n -C 5 --type ts 'funnelStagesForRevision|FunnelStage' benchmarks/conformance/cases/125-sales-funnel benchmarksRepository: TanStack/charts
Length of output: 20589
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- stage-field writes in the case ---'
rg -n -C 2 --type ts '\.(id|label|value)\s*=' benchmarks/conformance/cases/125-sales-funnel || true
rg -n -C 2 --type ts '\b(id|label|value)\s*:' benchmarks/conformance/cases/125-sales-funnel/model.ts benchmarks/conformance/cases/125-sales-funnel/model.test.ts || true
printf '%s\n' '--- derived interfaces and layout implementation ---'
cat -n benchmarks/conformance/cases/125-sales-funnel/model.ts | sed -n '1,75p'
printf '%s\n' '--- relevant tests ---'
cat -n benchmarks/conformance/cases/125-sales-funnel/model.test.ts | sed -n '1,90p'
printf '%s\n' '--- standalone aliasing probe ---'
node - <<'JS'
'use strict'
const revisions = [
[{ id: 'leads', label: 'Leads', value: 6800 }],
[{ id: 'leads', label: 'Leads', value: 7100 }],
]
function funnelStagesForRevision(revision) {
return revisions[revision % revisions.length] ?? revisions[0] ?? []
}
const firstCall = funnelStagesForRevision(0)
const secondCall = funnelStagesForRevision(0)
firstCall[0].value = 1
console.log(JSON.stringify({
sameArray: firstCall === secondCall,
sameStage: firstCall[0] === secondCall[0],
subsequentValue: funnelStagesForRevision(0)[0].value,
mutationObserved: funnelStagesForRevision(0)[0].value === 1,
}))
JSRepository: TanStack/charts
Length of output: 3786
Declare FunnelStage fields as readonly.
funnelStagesForRevision returns shared stage objects. readonly FunnelStage[] protects only the array, so consumers can mutate id, label, or value. Later calls then observe the mutation.
🤖 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 `@benchmarks/conformance/cases/125-sales-funnel/data.ts` around lines 1 - 5,
Update the FunnelStage interface so its id, label, and value properties are
readonly, ensuring shared stage objects cannot be mutated through consumers
while preserving the existing field types.
| let ancestor: SceneMotionEntry | undefined | ||
| for (let index = hierarchy.ancestorIds.length - 1; index >= 0; index -= 1) { | ||
| const ancestorId = hierarchy.ancestorIds[index] | ||
| ancestor = related.find( | ||
| (entry) => | ||
| entry.metadata.hierarchy?.markId === hierarchy.markId && | ||
| entry.metadata.hierarchy.id === ancestorId, | ||
| ) | ||
| if (ancestor) break | ||
| } | ||
| if (!ancestor) return undefined | ||
| const root = element.closest<SVGSVGElement>('svg') | ||
| const relatedElement = root | ||
| ? [...root.querySelectorAll<Element>('path[data-ts-key]')].find( | ||
| (candidate) => | ||
| candidate.getAttribute('data-ts-key') === ancestor.node.key, | ||
| ) | ||
| : undefined |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Does TypeScript preserve narrowing of a let variable inside an arrow function callback?
💡 Result:
TypeScript generally does not preserve type narrowing for variables when they are accessed inside a function closure (such as an arrow function callback), because the compiler cannot guarantee that the variable will not be modified or reassigned by the time the callback is eventually executed [1][2][3]. However, starting with TypeScript 5.4, the compiler includes a feature that preserves narrowing for let variables (and parameters) in closures, provided the closure is created after the "last assignment" to that variable [4][5]. Key points regarding this behavior: 1. Preserved Narrowing (TS 5.4+): If a let variable is assigned a value and is never assigned again afterward, TypeScript can safely treat the variable as narrowed within any closures created after that final assignment [4][5]. 2. Limitations: - If the variable is reassigned anywhere after the closure is created, or if it is a global variable, the narrowing will not be preserved [1][5]. - Narrowing is not preserved in functions created via declarations (e.g., function foo() {}) due to hoisting, as these functions can be called before the narrowing code has actually executed [4][6]. 3. Recommended Workaround: If you encounter issues with narrowed types being lost inside callbacks, the standard and most reliable solution is to capture the narrowed value into a new const variable before creating the callback [7][8]. Because const variables are immutable, TypeScript can safely guarantee that the captured value remains narrowed within the closure [9][8]. Example of the 5.4+ behavior: let x: string | number; x = "abc"; // Closure created after last assignment const callback = => { x }; // x is narrowed to 'string' x = 42; // Closure created after new last assignment const laterCallback = => { x }; // x is narrowed to 'number' Sources: [1][4][5]
Citations:
- 1: https://stackoverflow.com/questions/78903564/narrowing-related-behaviours-on-callbacks-in-typescript-when-using-let
- 2: Narrowed type information is **sometimes** lost in callback functions. microsoft/TypeScript#52055
- 3: https://stackoverflow.com/questions/79171452/why-does-narrowing-become-forgotten-inside-a-function-expression
- 4: Preserve type refinements in closures created past last assignment microsoft/TypeScript#56908
- 5: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html
- 6: https://stackoverflow.com/questions/79577968/why-does-a-closure-un-narrow-a-narrowed-const-type-in-typescript
- 7: 2.0: Type guards not working inside callbacks microsoft/TypeScript#7719
- 8: https://www.totaltypescript.com/workshops/typescript-pro-essentials/unions-and-narrowing/narrowing-in-different-scopes/solution
- 9: https://stackoverflow.com/questions/73052676/type-narrowed-let-variable-reverts-to-its-original-type-when-passed-to-a-closu
🏁 Script executed:
printf '%s\n' 'TypeScript configuration and compiler-version evidence:'
git ls-files '*package.json' '*tsconfig*.json' | head -80
rg -n '"typescript"|"strict"|strictNullChecks|ancestor' --glob 'package.json' --glob 'tsconfig*.json' packages/charts-core/src/motion.ts | head -120
printf '%s\n' 'Relevant source context:'
sed -n '1718,1760p' packages/charts-core/src/motion.tsRepository: TanStack/charts
Length of output: 3411
🏁 Script executed:
git ls-files '*package.json' '*tsconfig*.json' | head -80
rg -n '"typescript"|"strict"|strictNullChecks|ancestor' --glob 'package.json' --glob 'tsconfig*.json' packages/charts-core/src/motion.ts | head -120
sed -n '1718,1760p' packages/charts-core/src/motion.tsRepository: TanStack/charts
Length of output: 3330
🏁 Script executed:
printf '%s\n' 'Root package and TypeScript configuration:'
sed -n '1,220p' package.json
printf '%s\n' '--- tsconfig.json ---'
cat tsconfig.json
printf '%s\n' '--- lockfiles and TypeScript references ---'
git ls-files | rg '(^|/)(pnpm-lock\.yaml|yarn\.lock|package-lock\.json|bun\.lockb?|.*tsconfig.*\.json)$'
rg -n -i 'typescript(@|:|")|typescript:' pnpm-lock.yaml yarn.lock package-lock.json bun.lock bun.lockb 2>/dev/null | head -80Repository: TanStack/charts
Length of output: 21882
🏁 Script executed:
sed -n '1,220p' package.json
cat tsconfig.json
git ls-files | rg '(^|/)(pnpm-lock\.yaml|yarn\.lock|package-lock\.json|bun\.lockb?|.*tsconfig.*\.json)$'
rg -n -i 'typescript(@|:|")|typescript:' pnpm-lock.yaml yarn.lock package-lock.json bun.lock bun.lockb 2>/dev/null | head -80Repository: TanStack/charts
Length of output: 21773
🏁 Script executed:
set -eu
printf 'tsc: '
if command -v tsc >/dev/null 2>&1; then
tsc --version
else
printf '%s\n' 'not available'
fi
tmp=$(mktemp --suffix=.ts)
trap 'rm -f "$tmp"' EXIT
cat >"$tmp" <<'TS'
interface Entry {
node: { key: string }
metadata: { hierarchy?: { markId: string; id: string } }
}
declare const hierarchy: {
markId: string
ancestorIds: string[]
}
declare const related: Entry[]
declare const element: Element
let ancestor: Entry | undefined
for (let index = hierarchy.ancestorIds.length - 1; index >= 0; index -= 1) {
const ancestorId = hierarchy.ancestorIds[index]
ancestor = related.find(
(entry) =>
entry.metadata.hierarchy?.markId === hierarchy.markId &&
entry.metadata.hierarchy.id === ancestorId,
)
if (ancestor) break
}
if (!ancestor) throw new Error('missing')
const root = element.closest<SVGSVGElement>('svg')
const relatedElement = root
? [...root.querySelectorAll<Element>('path[data-ts-key]')].find(
(candidate) => candidate.getAttribute('data-ts-key') === ancestor.node.key,
)
: undefined
const result: Entry = ancestor
TS
if command -v tsc >/dev/null 2>&1; then
tsc --strict --noEmit --target ES2022 --lib ES2022,DOM "$tmp"
else
printf '%s\n' 'Cannot run the compiler probe in this sandbox.'
fiRepository: TanStack/charts
Length of output: 314
🏁 Script executed:
set -eu
printf 'tsc: '
if command -v tsc >/dev/null 2>&1; then
tsc --version
else
printf '%s\n' 'not available'
fi
tmp=$(mktemp --suffix=.ts)
trap 'rm -f "$tmp"' EXIT
cat >"$tmp" <<'TS'
interface Entry {
node: { key: string }
metadata: { hierarchy?: { markId: string; id: string } }
}
declare const hierarchy: { markId: string; ancestorIds: string[] }
declare const related: Entry[]
declare const element: Element
let ancestor: Entry | undefined
for (let index = hierarchy.ancestorIds.length - 1; index >= 0; index -= 1) {
const ancestorId = hierarchy.ancestorIds[index]
ancestor = related.find(
(entry) =>
entry.metadata.hierarchy?.markId === hierarchy.markId &&
entry.metadata.hierarchy.id === ancestorId,
)
if (ancestor) break
}
if (!ancestor) throw new Error('missing')
const root = element.closest<SVGSVGElement>('svg')
const relatedElement = root
? [...root.querySelectorAll<Element>('path[data-ts-key]')].find(
(candidate) => candidate.getAttribute('data-ts-key') === ancestor.node.key,
)
: undefined
const result: Entry = ancestor
TS
if command -v tsc >/dev/null 2>&1; then
tsc --strict --noEmit --target ES2022 --lib ES2022,DOM "$tmp"
else
printf '%s\n' 'Cannot run the compiler probe in this sandbox.'
fiRepository: TanStack/charts
Length of output: 314
🏁 Script executed:
set -eu
tmp=$(mktemp --suffix=.ts)
trap 'rm -f "$tmp"' EXIT
cat >"$tmp" <<'TS'
interface Entry {
node: { key: string }
metadata: { hierarchy?: { markId: string; id: string } }
}
declare const hierarchy: { markId: string; ancestorIds: string[] }
declare const related: Entry[]
declare const element: Element
let ancestor: Entry | undefined
for (let index = hierarchy.ancestorIds.length - 1; index >= 0; index -= 1) {
const ancestorId = hierarchy.ancestorIds[index]
ancestor = related.find(
(entry) =>
entry.metadata.hierarchy?.markId === hierarchy.markId &&
entry.metadata.hierarchy.id === ancestorId,
)
if (ancestor) break
}
if (!ancestor) throw new Error('missing')
const root = element.closest<SVGSVGElement>('svg')
const relatedElement = root
? [...root.querySelectorAll<Element>('path[data-ts-key]')].find(
(candidate) => candidate.getAttribute('data-ts-key') === ancestor.node.key,
)
: undefined
const result: Entry = ancestor
TS
tsc --ignoreConfig --strict --noEmit --target ES2022 --lib ES2022,DOM "$tmp"Repository: TanStack/charts
Length of output: 243
🏁 Script executed:
set -eu
tmp=$(mktemp --suffix=.ts)
trap 'rm -f "$tmp"' EXIT
cat >"$tmp" <<'TS'
interface Entry {
node: { key: string }
metadata: { hierarchy?: { markId: string; id: string } }
}
declare const hierarchy: { markId: string; ancestorIds: string[] }
declare const related: Entry[]
declare const element: Element
let ancestor: Entry | undefined
for (let index = hierarchy.ancestorIds.length - 1; index >= 0; index -= 1) {
const ancestorId = hierarchy.ancestorIds[index]
ancestor = related.find(
(entry) =>
entry.metadata.hierarchy?.markId === hierarchy.markId &&
entry.metadata.hierarchy.id === ancestorId,
)
if (ancestor) break
}
if (!ancestor) throw new Error('missing')
const relatedAncestor = ancestor
const root = element.closest<SVGSVGElement>('svg')
const relatedElement = root
? [...root.querySelectorAll<Element>('path[data-ts-key]')].find(
(candidate) =>
candidate.getAttribute('data-ts-key') === relatedAncestor.node.key,
)
: undefined
const result: Entry = relatedAncestor
TS
tsc --ignoreConfig --strict --noEmit --target ES2022 --lib ES2022,DOM "$tmp"Repository: TanStack/charts
Length of output: 153
Capture the narrowed ancestor in a const before the callback.
The loop assignment prevents TypeScript from preserving the narrowing inside the find callback. Use the captured value for ancestor.node.key.
🤖 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 `@packages/charts-core/src/motion.ts` around lines 1734 - 1751, Capture the
narrowed ancestor value in a const immediately after the !ancestor guard, then
use that const instead of ancestor inside the relatedElement find callback while
preserving the existing lookup behavior.
Summary
Release
Minor fixed-group release: 0.11.0 across all 12 published packages.
Verification
Summary by CodeRabbit
New Features
Documentation