Repository navigation
feat(spec): a report chart can split its bars by a second dataset dimension (chart.seriesBy) - #28191
Conversation
…ond dataset dimension Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibxKEFRdVkHHwAWLUSE
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibxKEFRdVkHHwAWLUSE
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibxKEFRdVkHHwAWLUSE
📓 Docs Drift Check4 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 168 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 027da7046b0574c9174b9b1306ff6f3ef7f9544c && git checkout 027da7046b0574c9174b9b1306ff6f3ef7f9544c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c48cd2c103a9dfa99313a67a3ec622291d793900 061a780d00b130c4b5fcefd909c2f7f203ff4183 && git checkout -B drift-repro c48cd2c103a9dfa99313a67a3ec622291d793900 && git merge --no-ff 061a780d00b130c4b5fcefd909c2f7f203ff4183
node scripts/docs-audit/affected-docs.mjs --json c48cd2c103a9dfa99313a67a3ec622291d793900 |
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: At |
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: Inputs: card #27974 body and its two comments (claim 6526104358, os-dev-report 6527290895); PR #28191 body, 12-file list and the diff against ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Should-fix (none blocks the contract):
Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D 维护者速读
席位核对 · ACCEPT ·
|
domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D
Requested by Tesla · project thread
Fixes #27974
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up #28207 (skills/objectstack-ui/rules/dashboards.md, to land with the renderer half #28206)
维护者速读
report.chart)新增一个可选键seriesBy,填数据集的第二个维度名,把同一个度量按这个维度的每个取值拆成多组柱子,比如"每个负责人一根柱,按阶段分组"。os validate/ 发布时报错。回滚即 revert 本 PR。本 PR 只是契约这一半,图表还不会画出拆分,作者写了会收到"已声明、尚未生效"的提示,渲染器那一半是后续卡。packages/spec/src/ui/report.zod.ts的ReportChartSchema、packages/lint/src/validate-chart-bindings.ts的checkReportSurface图表分支、packages/spec/liveness/report.json的chart.children.seriesBy。pnpm --filter @objectstack/lint exec vitest run src/validate-chart-bindings.test.ts -t seriesBy,预期 2 条全绿(拼错的seriesBy报chart-dimension-unknown并提示最近的维度名)。What changes
A report chart could plot one
xAxisdimension against oneyAxismeasure, so a bar per owner split by stage was not expressible; HotCRM'sopportunity_funnel_owner_stagefell back to an owner × stagematrix(hotcrm#2379). This is the spec half of the card: the key, its publish-time check, and its liveness grade.packages/spec/src/ui/report.zod.ts).ReportChartSchemadeclares one optional key,seriesBy: a second dimension name of the report's bound dataset. It splits the chart's oneyAxismeasure into a series per value of that dimension. It is the report's spelling of a dashboard widget's two-entrydimensionsselection ([xAxis, seriesBy]×[yAxis]), which the dataset path already pivots into grouped series (buildChartSeriesinui/packages/core). WithseriesByset, achart.series[]entry'snamenames a VALUE of that dimension, because each derived series is one value. The docblock and the.describe()say both.packages/lint/src/validate-chart-bindings.ts,validateChartBindings). This is the check that resolveschart.xAxisagainst the dataset's declared dimensions.chart.seriesBynow goes through the samedimensionRef, so an undeclared name is achart-dimension-unknownerror atos validate,os buildand the report publish door, with the nearest declared dimension suggested. WhenseriesByis set, thechart.series[].nameoverrides are not judged against the chart's measure, since they name dimension values that no static check can see. Theos explain chart-axis-not-selectedtext (rule-explanations.ts) says so.packages/spec/liveness/report.json). Thechartrow now drills:childrenDefault: livekeeps every existing chart key on the row's verdict,seriesByis gradedplannedwithauthorWarn, so an author who writes it is told nothing draws it yet, and the tombstonedariachild, now walked, is gradeddead. Drilling surfaced the three nested containers underchart(series,annotations,interaction); they are recorded inundrilled-containers.baseline.jsonin place of the singlereport/chartrow they used to ride under, with no per-key evidence invented.state-counts/report.mdregenerated.content/docs/references/ui/report.mdx(gen:docs) andauthorable-surface/ui.json(oneui/ReportChart:seriesByline)..changeset/27974-report-chart-series-by.md:@objectstack/specminor,@objectstack/lintminor.The shape chosen, and the alternatives not taken
seriesBykey, not the inheritedseries.ChartConfigSchema.seriesalready exists on the report chart as the per-series presentation override array ({ name, label, color, stack, … }); reusing that name for a dimension would give one key two meanings.ChartConfigSchemadeclares no dimension-valued series member to reuse.groupBy. On the reactObjectChartblock'saggregategroupByis the X-axis category, and on kanban views it is the lane field, so a report author readinggroupBywould expect the X axis.xAxisto a list. That changes the type of a key every renderer and the Studio inspector read as one string.columns.columnsis the matrix table's across axis; tying the chart's split to the table would stop a summary report from splitting its chart.Tests
packages/spec/src/ui/report.test.ts:seriesByis accepted and kept as authored, absent staysundefined, and a list value is refused atseriesBy.packages/spec/src/ui/chart.test.ts: the.extend()strictness pin acceptsseriesByas the report chart's own key and still refuses an undeclared one with the inherited rename.packages/lint/src/validate-chart-bindings.test.ts: a misspelledseriesByis onechart-dimension-unknownerror atreports[0].chart.seriesBywith a did-you-mean, against a declared-dimension control; aseries[].namenaming a value warns withoutseriesBy(control) and is silent with it.Acceptance notes
domain:uicard (the seat files it when this lands):DatasetReportChartinui/packages/plugin-report/src/DatasetReportRenderer.tsxqueries[xAxis]×[yAxis]and passes one dimension tobuildChartSeries. It needs to addseriesByto the query dimensions and pass[xAxis, seriesBy], matchchart.series[].nameoverrides against the pivoted series keys, and flip thechart.seriesByliveness row tolive. Until then the key is gradedplannedand authors are warned.seriesBy. The report chart's unknown-key answer isChartConfigSchema's error map, which rides.extend(). A report-only alias such asgroupBy → seriesBywould have to live on that shared base, where it is wrong for the inline-data tier.seriesByequal toxAxisparses and lints clean. It draws one series per bar, which is harmless; a refusal can follow if it turns up in practice.series[].stack). WithseriesByset, stacking the split means naming each value with a sharedstackgroup; a chart-level switch is not part of this card.Verification
All readings at head
061a780don objectstack-ai/objectstack (merge base9f13ee81).Tests
pnpm --filter @objectstack/spec exec vitest run src/ui scripts/liveness: Test Files 176/176, Tests 6227/6227.pnpm --filter @objectstack/lint test: Test Files 178/178, Tests 7646/7646.pnpm --filter @objectstack/spec typecheckandpnpm --filter @objectstack/lint typecheck: lock VERDICT command-exit 0.scripts/ablation-replace.mjs, each restored to the HEAD blob: (1) theseriesBydimensionRefline deleted, lint suite 1 failed / 80; (2) theseriesBy ? []series guard reverted, 1 failed / 80.lintLivenessPropertieson a report with and withoutchart.seriesBy): oneliveness-planned-propertyfinding, on the report that sets it.Gates
dispatch-gates --commandsat061a780d: 106 families, each run with stdin from/dev/null, exit codes recorded before any pipe.--ran: 106 derived, 106 run, 0 NOT MEASURED, 0 unrun, 0 red.check-plugin-teardown-shape --self-test(after fetching the pinned control commit into the shallow clone), speccheck:skill-examples,check:dual-build-cjs-loadsandcheck:lean-entry-closure(after a fullpackages/**build).pnpm --filter @objectstack/spec check:generated: all 14 generated artifacts up to date.check:liveness: exit 0.test:repo, the UI lanes) is CI's.Generated by Claude Code