fix(plugin-dashboard): DatasetWidget localizes the null-bucket label (#4500) - #4509
Conversation
…4500) `buildChartSeries` buckets a null-keyed category under `options.nullCategoryLabel` and falls back to the English constant `NULL_CATEGORY_LABEL = '(None)'` when the caller passes none. Core is React-free and cannot read the locale bundle, so the label has to come from the renderer. ObjectChart passes its resolved label; DatasetWidget called the same helper with no options, so a dashboard widget in a zh app rendered `(None)` while the standalone chart rendered `(未指定)`. Both call sites now read ONE binding: `buildChartSeries` writes the label into the rendered rows and `findChartSeriesRow` matches a clicked category against it. Localizing only the forward call would draw a bar reading `(未指定)` while the drill matched `(None)` — the click resolves to -1 and the drawer never opens, a worse outcome than the untranslated word. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Independent re-verification (session handoff) + #4497 coordinationA host restart ended the authoring session after this PR was pushed but before its report landed. A second session inherited the branch at Red-first, re-derived. Restored: One nuance worth stating, because it is what makes the half-fix run load-bearing rather than decorative: under a full revert the PAIR case fails at its label precondition, not at the drill — it never reaches the click. So the full revert cannot by itself prove the drill half is needed. Applying only the forward argument isolates it exactly: The labels go green and only the drill dies — #4497 / PR #4506 — no collision, and a positive interaction. The two PRs are file-disjoint: #4506 touches Gates re-run locally (all green):
CI converged green: all 20 check runs completed, zero failures — Lint, Type Check, all four test shards, Control Byte Scan and all three changeset gates. Staying draft for PM review. Generated by Claude Code Generated by Claude Code |
Closes #4500
The gap
buildChartSeries(core) buckets a null-keyed category underoptions.nullCategoryLabel, falling back to the English constantNULL_CATEGORY_LABEL = '(None)'when the caller passes none.@object-ui/coreis React-free and cannot read the locale bundle, so the resolved label has to come from the renderer.ObjectChart(plugin-charts) passestt('chart.nullCategory', '(None)')and localizes; DatasetWidget called the same helper with no options at all, so a dashboard widget in a zh app labelled the bucket(None)while the standalone chart one panel over labelled it(未指定).The failure is quiet by construction: the bar draws, the count is right, only the word is wrong.
The fix
Both call sites in
DatasetWidget.tsxnow read one binding:buildChartSeries(chartRows, dimensions, values, state.fields, { nullCategoryLabel })findChartSeriesRow(chartRows, dimensions, values, ev?.category, ev?.series, { nullCategoryLabel })where
nullCategoryLabel = tt('chart.nullCategory', '(None)').ttwas already in the component; thechart.nullCategorykey already exists in all ten locale packs (PR #4498).The second call site is the sharp edge, not a symmetry nicety.
findChartSeriesRowis the inverse map behind segment-click drill-through: it compares the clicked category against its ownnullCategoryLabel, defaulting to the same English floor. Localizing only the forward call draws a bar reading(未指定)while the drill matches(None)— the click resolves to-1,handleChartDrillreturns beforeopenDrill, and the drawer never opens. That is a strictly worse outcome than the untranslated label this card is about (the dead-click rule from #4498). One binding, so the pair cannot drift.Grep confirms these are the only two call sites in the file, and the only two in plugin-dashboard.
Red-first
New pin:
packages/plugin-dashboard/src/__tests__/DatasetWidget.nullCategoryI18n.test.tsx— 7 cases, 3 red before the change, 4 green on both sides.Pre-fix, verbatim:
Post-fix:
Test Files 1 passed (1) / Tests 7 passed (7).Reverse verification of the pair property. The interesting direction is not pre/post — it is the half-fix a reviewer would most plausibly ship. Applying only the forward argument and re-running isolates the dead click exactly:
The labels go green and only the drill dies —
drillFiltersstays empty because the drawer never opened. Both call sites restored afterwards, verified byte-identical against the pre-revert patch.Must-not-change (green on both sides)
{ owner_name: 'Ada Lovelace', deals: 5 }{ owner_id: 'user-ada' }), not the display labelenstill reads(None)— through the locale pack after the change rather than core's hardcoded floor, same bytesVerification
pnpm exec vitest run packages/plugin-dashboard/— 47 files, 388 tests passed. The known flaky: DatasetWidget.dottedDimensionTable's "issues the ONE read" case asserts a fetch AFTER waiting only on the rendered cell #4487 flake (DatasetWidget.dottedDimensionTable) did not fire.pnpm --filter @object-ui/plugin-dashboard type-check— clean.eslinton both changed files — 0 errors..d.tsdiffed both ways (built from this branch and fromorigin/main):index.d.tsandDatasetWidget.d.tsidentical. No public surface movement — hencepatch.check-changeset-presenceandcheck-control-bytesgreen.Scope
DatasetWidget.tsx+ one test file + one changeset. The multi-dimension pivot arm is #4497's in-flight surface and is deliberately untouched — this card is the single-dimension label argument, and core is consumed read-only. Branched fromorigin/mainafter confirming PR #4504 had landed (it is the tip commit).Generated by Claude Code