Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-charts): a sankey that drew only some of its rows says how many - #7161
Conversation
… many The sankey arm filters to strictly positive measures, so a mixed dataset draws a normal, healthy, confident chart of a fraction of itself with nothing recording that the other rows existed. Measured in Chromium across 27 tiles at origin/main fd11e16: the mixed-sign dataset rendered svg 1 / path 3 / 18 descendants, no role, no text and — against a live console control that did fire — zero console output, and its screenshot hashed byte-identical to five other datasets including a genuinely one-row one. Six datasets, one image. The drop itself stands: a flow has no negative width. What is added is a footnote naming the ratio and the predicate the filter applies, which is true of negatives, zeros, nulls, unparseable measures and a missing key alike. A refusal is unavailable — objectui#7146 pins "one positive among zeros still draws" and that fixture is itself a thinned dataset. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-warren
commented
Sep 1, 2026
✅ Reviewed — will arm on greenReview posted now rather than at arm time because the measurement here deserves recording on its own. CI still settling; auto-merge SQUASH goes on once all 30 read green. The decisive measurement, and it is the sharpest statement of the defect anyone has made27 tiles rendered in real Chromium, each screenshotted and SHA-256'd. At base, six different datasets produced one byte-identical image
A chart showing a third of its data was pixel-for-pixel indistinguishable from a complete chart of one row. That is what "invisibly wrong" means, stated as a hash collision rather than an adjective. And the ablation breaks the collision: reverting the fix moves the five thinned tiles away while
Two design constraints discovered by measurement, not chosen by tasteA refusal was ruled OUT by a pin, not by preference. PR objectui#7146's fixture 0/7/0 is itself a thinned dataset that lands in this branch — so refusing on a thinned flow would blank the very chart that pin requires drawn. That is the kind of constraint no amount of reasoning surfaces; only running the neighbour's test does. A plain sibling paragraph is INVISIBLE in dashboards. Measured: in the dashboard shape the note landed at y=327 inside a box ending at y=316 — entirely outside the clip. And a wrapper A first attempt was measured, found clipped, and discarded before commit. It is not in the branch. That is the right disposal of a wrong approach. A2.2 falsified, and the correction is load-bearingMy wording — "the trigger is exactly: some rows survive the filter" — is necessary but not sufficient. A complete draw also has rows surviving. The real trigger is That distinction is exactly what keeps the all-positive path byte-for-byte unchanged, which the 23 identical tiles then prove. A fix built on my wording would have put a footnote on every healthy chart. The rest, checked
Correctly left aloneobjectui#7146's The treemap/funnel/pie finding was reported rather than filed, per the fence, because it lands inside already-queued objectui#7147. I have attached it there — it widens that card from all-zero datasets to mixed ones. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7148
The sankey arm keeps only strictly positive measures, so a mixed dataset drew a
normal, healthy, confident chart of a fraction of itself and nothing anywhere
recorded that the other rows existed. This adds one line of prose under the plot
saying how many rows it drew. The chart itself is unchanged.
The measurement that decided fix-over-decline
The card deliberately does not assert the drop is wrong, so this was settled the
way #7140 was: 27 tiles rendered in real Chromium (
/opt/pw-browsers/chromium)at
origin/mainfd11e16, each screenshotted and SHA-256'd.At base, six different datasets produced one byte-identical image
(
13237e6e19a7072a):sankey-card-mixed-signNew business 40 / Refunds -25 / Chargebacks -12sankey-pin-one-pos-among-zeros0 / 7 / 0sankey-pos-plus-nulls40 / null / nullsankey-pos-plus-unparseable40 / 'n/a'sankey-pos-plus-missing-key40 / (no measure key)sankey-genuine-1row-4040That last row is the finding. A chart showing a third of its dataset was
pixel-for-pixel the chart of a dataset that genuinely had one row —
svg: 1,path: 3, 18 descendants, norole, no text, and zero console output,measured against a live console control on the same instrument
(
missing-category-key, which did fire). A reader had no bit of informationseparating a complete flow from a partial one.
measured-and-declinedwasavailable and is not what the evidence supports.
What ships, and why this shape
and that fixture (
0 / 7 / 0) is itself a thinned dataset. It lands inthis branch and hashed identical to the mixed-sign tile. Refusing on a thinned
flow would blank the chart that pin requires drawn. It now draws and says so.
the only thing this arm can do with those rows. The silence was the defect.
Number(…) || 0folds negatives, zeros,null, unparseable strings and a missing key into one discard, and all fivewere measured reaching this branch beside a survivor. Naming any one of them is
a sentence that is false for the other four — the same doctrine the
no-positive-flowcopy follows.indistinguishable from a complete one; the ratio is the bit the picture cannot
carry.
No console warning, matching the sibling refusal: the sentence already names the
key, the test it failed and how many rows failed it.
The footnote could not be a plain sibling — that was measured too
A bare paragraph element rendered after
ChartContaineris invisible in dashboards. In thedashboard shape — a fixed, clipping card whose chart carries
h-full— thecontainer takes the card's full height and the note landed at
y=327in a boxending at
y=316, entirely outside the clip. A wrapper div carryingclassNameis equally unavailable: the consumer's className is the chart's height contract
and has to keep reaching the element Recharts measures (see
CHART_MIN_HEIGHTin
ChartContainerImplfor what a broken chain costs — a permanently zero boxand an invisible chart, with no refusal and no empty state).
So the plot keeps its own
classNameand the note hangs off a flex chain, inChartFootnote— deliberately the mirror image of this file's existingChartFrame, including its gate: with nothing to say, the chart is returneduntouched and gains no wrapper element.
Reverse verification (controlled)
The implementation was committed first, then the pre-fix file was restored onto
disk (mutation proven by blob hash
3105af33…matching the base blob, markercount
omitted-rows0, and the marker confirmed absent from the rebuilt bundle),the harness rebuilt with identical CSS, and all 31 tiles re-measured.
Restore proven by state:
git diff HEAD,git diff --cachedandgit status --shortall clean, on-disk blob back to5e2486b8….sankey-control-all-positive,chain-clip-all-positive,chain-auto-all-positive(the complete-flow path isunchanged in all three height chains), both
no-positive-flowrefusal tiles,the no-rows tile, and every other chart family.
sankey-genuine-1row-40is broken: that tile keeps13237e6e19a7072a(nothing was omitted) while the five thinned tiles move away.Residual collisions are correct: tiles sharing a ratio share a sentence, because
the copy deliberately does not distinguish why a row was dropped.
The seam with #7146
rows.lengthis the whole boundary, and it is now pinned from both sides:svg: 0,textContent: ''no-positive-flowrefusal (#7146)data-chart-error="no-positive-flow"svg: 1, note present0 / 7 / 0(#7146's own pin)svg: 1, note presentGates
Run at
f2df89144(the head this PR points at), repo root:pnpm vitest run packages/plugin-charts/— 38 files, 265 tests passed,including fix(plugin-charts): a sankey with no positive flow says so instead of rendering an empty div #7146's
sankeyNoPositiveFlowpin unmodified.pnpm --filter @object-ui/plugin-charts type-check— exit 0.--listFilesconfirms the new test file is inside the program (not an excluded-tests
false green).
pnpm --filter @object-ui/plugin-charts lint— 51 files judged, 0 errors,282 warnings (
--max-warningsis deliberately unset repo-wide, perlint.yml). The 6 warnings on the new test file are theas any/importActualtype-argument shape every sibling test in this package already uses.pnpm check:control-bytes—✅ OK (scanned 5931 tracked text file(s))pnpm check:vi-mock-specifiers,check:vi-mock-inherit— both✅ OKpnpm check:i18n-keys—✅(no i18n call site added; this file ishardcoded English throughout and both existing refusals are too)
pnpm changeset:check—✅ All workspace packages are in the changeset fixed group./✅ No changeset declares a major bump.pnpm check:sdui-registration-pins— first returned exit 2, "No consolebuild to weigh", which is NOT MEASURED rather than a pass. Built the console
and its closure and re-ran:
✅ All 16 registration(s) … present in the built console (517 chunks weighed).Repo-wide
pnpm lintis CI's run. The narrowing above is measured, not assumed:the universe (51 files) is eslint's own config resolution, the counts come from
--format json, andeslint.config.jsconfigures no type-aware linting(no
parserOptions.project/projectService), so a change underpackages/plugin-charts/src/cannot move the verdict on any untouched file.Out of scope, reported not fixed
Measured in the same sweep, for #7147, which owns that family:
treemapdrew 2 rects / 2 labels for the mixed-sign dataset against a control's4 / 4;
funnelwith anullmeasure drew 0 segment paths against acontrol's 2;
piedrew 1 slice against a control's 2. Those rows are neverfiltered — they stay in the data and the layout gives them no area — so this is
degenerate geometry, a different mechanism from the row drop fixed here, and
untouched by this PR. It widens #7147 beyond all-zero datasets to mixed ones.
AdvancedChartImpl.tsxline 1047 is the only row-droppingfilterin the file(the other two filter series), which is why the note cannot reach another
family — pinned in the tests.
Generated by Claude Code
Generated by Claude Code