feat(lint): report a chart widget that selects no measures — or no dimensions (#15462) - #15507
Conversation
…mensions (#15462) `validateWidgetBindings` said nothing about two dataset-bound chart shapes that the `@object-ui` revision this repo pins (`.objectui-sha` = 00d3f09c500c4a45b5f27aca8af80349412faaf1) visibly degrades. Both become new warning-tier ids in the widget-binding family, suppressible per widget: - `chart-measures-missing` — `DatasetWidget.tsx:683` returns the authoring placeholder "Pick measures (values) for this dataset widget." before any query runs, above every family branch, so no chart is drawn at all. - `chart-dimensions-missing` — `DatasetWidget.tsx:423` (`const isMetric = METRIC_TYPES.has(widgetType) || dimensions.length === 0;`) routes a dimensionless chart to the single-value branch, so it renders a KPI number and the declared family is silently ignored. Warning rather than error for both: an empty selection is a work-in-progress state a build must tolerate, and erroring would gate the `sys_metadata` publish path on a half-authored widget. Neither is folded into `chart-config-missing` — neither is caused by, nor repairable with, `chartConfig` — and that rule's docblock now points at these two ids instead of recording them as deliberately unreported. "Chart family" is derived from the renderer's own routing rather than hand-listed: every declared `ChartTypeSchema` option that is neither a `METRIC_TYPES` member nor tabular. Membership of both mirrored sets is held to `ChartTypeSchema` by the tests, as #14436 did for `MARK_MIXING_CHART_TYPES`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 2 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin bf1ac0b0356b9c8a1e32f9f19fa9ec0118c4130a && git checkout bf1ac0b0356b9c8a1e32f9f19fa9ec0118c4130a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5023630b1788c497355ea6fa4194c444e35d578f fe29483503de59cf5c5e8f97abe01e3f3e3d9bb3 && git checkout -B drift-repro 5023630b1788c497355ea6fa4194c444e35d578f && git merge --no-ff fe29483503de59cf5c5e8f97abe01e3f3e3d9bb3
node scripts/docs-audit/affected-docs.mjs --json 5023630b1788c497355ea6fa4194c444e35d578f
|
|
ACCEPT — PM seat Reviewed against the three-dot diff
Generated by Claude Code |
Fixes #15462
What was unreported
validateWidgetBindingssaid nothing about two dataset-bound chart shapes that the@object-uirevision this repo PINS visibly degrades. Everything below was read at that pin —.objectui-sha=00d3f09c500c4a45b5f27aca8af80349412faaf1— inpackages/plugin-dashboard/src/DatasetWidget.tsx, not at objectui'sorigin/main::683—if (values.length === 0)returnstt('dashboard.pickMeasures', 'Pick measures (values) for this dataset widget.'). It stands above every family branch, so a widget selecting no measures never reaches a chart, a table or a KPI number: nothing is drawn.:423—const isMetric = METRIC_TYPES.has(widgetType) || dimensions.length === 0;, withMETRIC_TYPESat:343=metric,kpi,gauge,solid-gauge,bullet;:424is the tabular test (table/pivot); and the render branches (:702,:798,:854) routeisMetric ? KPI : isTable ? table : chart. A dimensionlessbartherefore draws as a single KPI number — worse than a blank tile, because the number is REAL and the missing chart reads as a design choice.The two new ids
Both are new ids in the widget-binding family, both warning tier, both suppressible per widget with
suppressWarnings:chart-measures-missing— a chart-family widget selects no measures (valuesempty or absent). Its message names the placeholder string verbatim.chart-dimensions-missing— a chart-family widget selects at least one measure but no dimensions. Its message quotes theisMetricexpression, and its hint offers the honest alternative repair: declare the tile asmetric/kpiso the type matches what renders.Tier. The card left shape 1 open (
error"looks right — the renderer draws nothing"); it lands as a warning because an empty selection is a work-in-progress state a build must tolerate, anderrorwould gate thesys_metadatapublish path on a half-authored widget. Pinned end-to-end rather than inferred from the constant: an acceptance block asserts both ids arrive on the advisory channel — never as errors — onvalidateANDbuild.Not folded into
chart-config-missing. Neither shape is caused by, nor repairable with,chartConfig(chart-field-unknownrefuses ayAxis[].fieldnaming anything the widget did not select, sochartConfigcan never supply a measurevaluesis missing). PR #15461 left them out for that reason and recorded the omission in that rule's docblock; that paragraph is now a pointer to these two ids instead."Chart family" is derived from the renderer, not hand-listed
CHART_FAMILY_WIDGET_TYPES= every declaredChartTypeSchemaoption that is neither aMETRIC_WIDGET_TYPESmember nor tabular — i.e. exactly what the renderer's ownisMetric ? KPI : isTable ? table : chartrouting leaves over. The two exception sets are this file's mirrors of the renderer's own (the same "MUST track objectui" relationshipDATE_RANGE_DEFAULT_FIELDalready carries one position up), so:ChartTypeSchemain a test, as The shippedsystem_overviewdashboard draws two chart widgets with nochartConfig—chart-config-missingfires on our own metadata #14436 did forMARK_MIXING_CHART_TYPES; and a test pins the mirrored sets verbatim against the quoted renderer lines.The two ids never both fire on one widget: with no measures the pin returns the placeholder at
:683and theisMetricbranch is never reached, so the KPI consequence is not what that widget does. A widget missing both reportschart-measures-missingalone, and its hint names the missing dimension so one fix round closes both.Tests
packages/lint/src/validate-widget-bindings.test.ts, 14 new cases: a positive case per shape (message pinned to the placeholder string / theisMetricexpression), a suppressible case per shape, cross-suppression (an unrelated id suppresses neither), the both-empty ordering case, negatives per family (metric/kpi/gauge/solid-gauge/bullet/table/pivotreport neither id, with either selection empty), a sweep over the whole taxonomy (every chart family reports, no other family does), a type outside the taxonomy reports nothing, the two membership checks, and the shippedsystem_overviewKPI tile (ametricwith a measure and no dimensions — shape 2's input, correctly silent). #15461's shippedsystem_overviewchart-tile fixture is untouched and still reports zero.Fixture pass on shipped metadata, with a control leg
validateWidgetBindingsover 53 exported@objectstack/platform-objectsobjects +SystemOverviewDatasets(5) +SystemOverviewDashboard(9 widgets: 2 chart-family, 7 single-value/tabular) — the input #15461's fixture pass used. A zero is only a measurement if the harness can report a non-zero, so the same run adds a control board in the INPUT (no source is mutated):Every in-repo example dashboard was read too (
app-showcasechart-gallery / ops / revenue-pulse,app-crmpipeline,app-todotask): every chart-family widget there declares both a dimension and a measure, so none of them moves.Verification (all at
fe2948350; every exit code captured BEFORE any pipe)pnpm --filter @objectstack/lint test—Test Files 94 passed (94)·Tests 2927 passed (2927), EXIT=0pnpm --filter @objectstack/lint typecheck— EXIT=0;check:test-typecheck: OK — @objectstack/lint's test layer compiles under packages/lint/tsconfig.test.jsonpnpm --filter @objectstack/metadata-protocol --filter @objectstack/objectql test—Test Files 161 passed | 2 skipped (163)/Tests 2375 passed, andTest Files 271 passed (271)/Tests 4649 passed (4649), EXIT=0node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack→ 45 commands, all run: 43 EXIT=0, 2 EXIT=3 (NOT MEASURED, not a red) —check:dual-build-cjs-loads("Runpnpm buildfirst. This is NOT a pass: nothing was measured.") andcheck:type-check-debt, both because this worktree has no whole-repodist. CI builds first and measures them there.pnpm -s check:pm-dispatch-gates—✓ dispatch-gates self-test: 1402 cases pass., EXIT=0pnpm check:nul-bytes—check-nul-bytes: OK (scanned 7496 text file(s) -- 7496 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes)., EXIT=0pnpm check:type-check-coverage—check-type-check-coverage: OK — 75/79 workspace packages type-checked (plus the root), 4 in the DEBT ledger, EXIT=0pnpm lint(eslint . --no-inline-config) — EXIT=0, no narrowingScope
@objectstack/lintminor — the repo's convention for a new rule id (widget-filter-field-unknown/widget-sortby-unselectedshipped under Minor Changes inpackages/lint/CHANGELOG.md).content/docsanddocs/qawere grepped for the widget-binding rule ids (chart-config-missing,table-count-only,widget-sortby-unselected,widget-measure-unknown,chart-field-unknown, ...). No page enumerates them — the only hits outside the rule file are packageCHANGELOG.mdrelease prose andcontent/docs/releases/, which a code PR never edits. Nothing to add.packages/platform-objects/**(ADR-0072 D1): the rules are added, the metadata is untouched.chart-field-unknowntier) is out of scope here and stays open.🤖 Generated with Claude Code
https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Generated by Claude Code