Repository navigation
ui-kit(chart): ChartTooltipContent renders a zero value as a bare unstyled "0" instead of a formatted number #10051
Description
⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.
Context
ChartTooltipContent renders each series' value behind a truthiness check:
// packages/loopover-ui-kit/src/components/chart.tsx:252-256
{item.value && (
<span className="font-mono font-medium tabular-nums text-foreground">
{item.value.toLocaleString()}
</span>
)}item.value is recharts' TooltipValueType — a number, a string, or an array. When it is the number 0,
0 && <span/> evaluates to 0, and React renders the number 0 as a raw text node in place of the
element. Two things go wrong at once:
- The value loses
font-mono font-medium tabular-nums text-foreground. In a tooltip whose whole point is
tabular-numsalignment, one row renders in the surrounding body font while its siblings render monospaced. - It bypasses
toLocaleString(), so it is not the formatted value the component promises — the same code path
that turns1200into1,200is skipped.
An empty-string value ("") hits the same branch and renders nothing at all, which is at least silent; 0 is
the visible one. Zero is an ordinary data point for both live consumers —
apps/loopover-miner-ui/src/routes/ledgers.tsx and apps/loopover-miner-ui/src/routes/portfolio.tsx both
render ChartTooltipContent over count/earnings series where a zero bucket is normal.
packages/loopover-ui-kit/src/components/chart.test.tsx was added by #9937 precisely because chart.tsx
"shipped with no tests at all", but its payload fixtures use 120 and 80 only — the zero case is untested.
Requirements
- A numeric
item.valueof0must render inside the same<span className="font-mono font-medium tabular-nums text-foreground">as any other number, with its text produced bytoLocaleString()(so the
rendered text is"0", inside that span). - The value element must be omitted only when there is genuinely no value —
item.value === undefinedor
item.value === null. Every other value, including0and"", must render through the span. - The guard must be an explicit nullish check, not a truthiness check, so no falsy-but-real value can ever
again fall through as a raw React child. - The custom-
formatterbranch atpackages/loopover-ui-kit/src/components/chart.tsx:212must NOT change: it
already guards onitem?.value !== undefined && item.name, and aformattersupplied by the caller still
takes precedence over the default rendering. - No other part of the row (the indicator, the icon, the
itemConfig?.label || item.nameseries label, the
nestLabellayout) may change.
⚠️ Required pattern: replace the&&guard atpackages/loopover-ui-kit/src/components/chart.tsx:252with
a nullish-aware conditional, mirroring theitem?.value !== undefinedcheck the siblingformatterbranch
at line 212 already uses. What does NOT satisfy this issue: (a){item.value != null && (...)}written as a
loose!=without a test pinning both0andundefined, which is the same one-character-away mistake;
(b) coercing withString(item.value ?? "")and always rendering the span, which makes an
absent value render an empty monospaced box; (c) changingtoLocaleString()to a hand-rolled formatter;
(d) a repo-wide sweep of every{x && <jsx/>}in the ui-kit — this issue ischart.tsx's tooltip value
only; (e) a test-only PR.
Deliverables
- Rendering
<ChartTooltipContent active payload={[{ dataKey: "revenue", name: "revenue", value: 0, color: "#0ea5e9", payload: { month: "Jan", revenue: 0 } }]} />inside aChartContainerproduces a
<span>whoseclassNamecontainstabular-numsand whose text is"0", asserted in
packages/loopover-ui-kit/src/components/chart.test.tsx. - Rendering the same payload entry with
value: undefinedproduces no value<span>at all (the series
label still renders), asserted in the same file. - Rendering the same payload entry with
value: ""produces the value<span>with empty text, asserted
in the same file. - The existing "renders one row per payload entry, labelled from the chart config" test (values
120/
80) still passes unmodified. - A regression test at
packages/loopover-ui-kit/src/components/chart.test.tsxnamed for this bug that
asserts the zero value is NOT a direct text child of the row container — i.e. that the rendered0sits
inside the formatted span, not beside it.
All Deliverables above are required in a single PR. A PR that satisfies only some of them — for example one
that changes the guard and asserts only that the text "0" appears somewhere (which the buggy code also
satisfies) — does not resolve this issue.
Test Coverage Requirements
This repo enforces 99%+ Codecov patch coverage, branch-counted. vitest.config.ts's coverage.include
covers src/**/*.ts, packages/loopover-engine/src/**/*.ts, packages/loopover-{miner,mcp}/{lib,bin}/**/*.ts,
packages/loopover-contract/src/**/*.ts and packages/discovery-index/src/**/*.ts. It does not cover
packages/loopover-ui-kit/**, and that package's own packages/loopover-ui-kit/vitest.config.ts deliberately
declares no coverage block. Codecov does not gate the patch on this change.
The tests are still mandatory and are named above: they go in the existing
packages/loopover-ui-kit/src/components/chart.test.tsx and must be run with
npm --workspace @loopover/ui-kit run test before pushing (that suite is not currently reached by
npm run test:ci, so a local run is the only way to see it pass).
Both arms of the changed guard need a test in that file: value present (0, "", and a non-zero number all
render the span) and value absent (undefined renders nothing). The sibling formatter branch's two arms must
also stay covered — the existing "routes the value through a custom formatter" test covers the formatter arm;
add or keep an assertion for the no-formatter arm with a zero value so the two branches cannot be conflated.
Expected Outcome
A chart data point of 0 shows as a properly formatted, monospaced, tabular-aligned 0 in the tooltip like
every other value, instead of an unstyled bare digit that breaks column alignment and skips
toLocaleString().
Links & Resources
packages/loopover-ui-kit/src/components/chart.tsx:252-256— the truthiness guardpackages/loopover-ui-kit/src/components/chart.tsx:212— the siblingformatterbranch's nullish checkpackages/loopover-ui-kit/src/components/chart.test.tsx— the existing suite (fixtures120/80only)apps/loopover-miner-ui/src/routes/ledgers.tsx,apps/loopover-miner-ui/src/routes/portfolio.tsx— the
two live consumers- Ui-kit release due: 1.3.1 #9937, Evaluate recharts v3 migration for apps/loopover-ui #8610 — the test-backfill issue and the recharts v3 migration that rewrote this file
Activity
linear-code commentedon Jul 31, 2026
JSO-7026
fix(ui-kit): render chart tooltip zero inside tabular-nums span
fix(ui-kit): render chart tooltip zero inside tabular-nums span