Skip to content

feat(spec): a report chart can split its bars by a second dataset dimension (chart.seriesBy) - #28191

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-1rmsao
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-1rmsao

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

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,填数据集的第二个维度名,把同一个度量按这个维度的每个取值拆成多组柱子,比如"每个负责人一根柱,按阶段分组"。
  • 为什么改:报表图表以前只有一个 X 维度和一个 Y 度量,画不出"按 X 再按 Y 拆分"的图;HotCRM 的商机漏斗按负责人×阶段只能退回成矩阵表。仪表盘早就能这样画,报表缺这一个键。
  • 风险与回滚:只加了一个可选键,不填的报表解析和绘制完全不变;拼错的维度名在 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 xAxis dimension against one yAxis measure, so a bar per owner split by stage was not expressible; HotCRM's opportunity_funnel_owner_stage fell back to an owner × stage matrix (hotcrm#2379). This is the spec half of the card: the key, its publish-time check, and its liveness grade.

  • Spec (packages/spec/src/ui/report.zod.ts). ReportChartSchema declares one optional key, seriesBy: a second dimension name of the report's bound dataset. It splits the chart's one yAxis measure into a series per value of that dimension. It is the report's spelling of a dashboard widget's two-entry dimensions selection ([xAxis, seriesBy] × [yAxis]), which the dataset path already pivots into grouped series (buildChartSeries in ui/packages/core). With seriesBy set, a chart.series[] entry's name names a VALUE of that dimension, because each derived series is one value. The docblock and the .describe() say both.
  • Lint (packages/lint/src/validate-chart-bindings.ts, validateChartBindings). This is the check that resolves chart.xAxis against the dataset's declared dimensions. chart.seriesBy now goes through the same dimensionRef, so an undeclared name is a chart-dimension-unknown error at os validate, os build and the report publish door, with the nearest declared dimension suggested. When seriesBy is set, the chart.series[].name overrides are not judged against the chart's measure, since they name dimension values that no static check can see. The os explain chart-axis-not-selected text (rule-explanations.ts) says so.
  • Liveness (packages/spec/liveness/report.json). The chart row now drills: childrenDefault: live keeps every existing chart key on the row's verdict, seriesBy is graded planned with authorWarn, so an author who writes it is told nothing draws it yet, and the tombstoned aria child, now walked, is graded dead. Drilling surfaced the three nested containers under chart (series, annotations, interaction); they are recorded in undrilled-containers.baseline.json in place of the single report/chart row they used to ride under, with no per-key evidence invented. state-counts/report.md regenerated.
  • Generated. content/docs/references/ui/report.mdx (gen:docs) and authorable-surface/ui.json (one ui/ReportChart:seriesBy line).
  • Changeset. .changeset/27974-report-chart-series-by.md: @objectstack/spec minor, @objectstack/lint minor.

The shape chosen, and the alternatives not taken

  • A new seriesBy key, not the inherited series. ChartConfigSchema.series already 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. ChartConfigSchema declares no dimension-valued series member to reuse.
  • Not groupBy. On the react ObjectChart block's aggregate groupBy is the X-axis category, and on kanban views it is the lane field, so a report author reading groupBy would expect the X axis.
  • Not widening xAxis to a list. That changes the type of a key every renderer and the Studio inspector read as one string.
  • Not reusing the report's own columns. columns is 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: seriesBy is accepted and kept as authored, absent stays undefined, and a list value is refused at seriesBy.
  • packages/spec/src/ui/chart.test.ts: the .extend() strictness pin accepts seriesBy as 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 misspelled seriesBy is one chart-dimension-unknown error at reports[0].chart.seriesBy with a did-you-mean, against a declared-dimension control; a series[].name naming a value warns without seriesBy (control) and is silent with it.

Acceptance notes

  • The renderer half is a follow-up domain:ui card (the seat files it when this lands): DatasetReportChart in ui/packages/plugin-report/src/DatasetReportRenderer.tsx queries [xAxis] × [yAxis] and passes one dimension to buildChartSeries. It needs to add seriesBy to the query dimensions and pass [xAxis, seriesBy], match chart.series[].name overrides against the pivoted series keys, and flip the chart.seriesBy liveness row to live. Until then the key is graded planned and authors are warned.
  • No rename aliases for seriesBy. The report chart's unknown-key answer is ChartConfigSchema's error map, which rides .extend(). A report-only alias such as groupBy → seriesBy would have to live on that shared base, where it is wrong for the inline-data tier.
  • seriesBy equal to xAxis parses and lints clean. It draws one series per bar, which is harmless; a refusal can follow if it turns up in practice.
  • Stacking stays a series property (series[].stack). With seriesBy set, stacking the split means naming each value with a shared stack group; a chart-level switch is not part of this card.

Verification

All readings at head 061a780d on objectstack-ai/objectstack (merge base 9f13ee81).

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 typecheck and pnpm --filter @objectstack/lint typecheck: lock VERDICT command-exit 0.
  • Ablations through scripts/ablation-replace.mjs, each restored to the HEAD blob: (1) the seriesBy dimensionRef line deleted, lint suite 1 failed / 80; (2) the seriesBy ? [] series guard reverted, 1 failed / 80.
  • Author warning probe (lintLivenessProperties on a report with and without chart.seriesBy): one liveness-planned-property finding, on the report that sets it.
Gates
  • dispatch-gates --commands at 061a780d: 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.
  • Four families first answered exit 3 (prerequisite not met) and were re-run after their prerequisite, all exit 0: check-plugin-teardown-shape --self-test (after fetching the pinned control commit into the shallow clone), spec check:skill-examples, check:dual-build-cjs-loads and check:lean-entry-closure (after a full packages/** build).
  • pnpm --filter @objectstack/spec check:generated: all 14 generated artifacts up to date. check:liveness: exit 0.
  • The full CI farm (test:repo, the UI lanes) is CI's.

Generated by Claude Code

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:ui tests tooling labels Oct 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

4 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. ⚠️ 3 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/liveness/report.json, packages/spec/liveness/state-counts/report.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/liveness/report.json, packages/spec/liveness/state-counts/report.md) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 58 of 210 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json c48cd2c103a9dfa99313a67a3ec622291d793900 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 027da7046b0574c9174b9b1306ff6f3ef7f9544c — the merge of head 061a780d00b130c4b5fcefd909c2f7f203ff4183 into base c48cd2c103a9dfa99313a67a3ec622291d793900, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head 061a780d00b130c4b5fcefd909c2f7f203ff4183 · 2026-10-30T16:08Z

At CONTRACT_REVIEW_TIER, by the maintainer's dedicated contract-review thread for the lanes (「开一个新的 fable thread,30分钟定时,负责车道的契约复审」, 2026-10-30T15:39Z), on spec seat 2's behalf; the reviewer is not the drafter (session_01L9qibxKEFRdVkHHwAWLUSE).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 061a780d00b130c4b5fcefd909c2f7f203ff4183
Local-runs: none
Scope: packages/spec/src/ui/report.zod.ts + packages/lint/src/validate-chart-bindings.ts (the gate that resolves the new key, read as one change with it), packages/spec/liveness/report.json (the row that grades it), and the cited code read on the head: packages/spec/src/ui/chart.zod.ts (ChartConfigSchema L601-760, ChartSeriesSchema L240-260, the groupBy notes), ui/packages/core/src/utils/chart-series.ts (buildChartSeries L567-690, the two-dimension pivot), ui/packages/plugin-report/src/DatasetReportRenderer.tsx (DatasetReportChart L1178-1230, L1421), packages/spec/scripts/liveness/check-liveness.mts (childrenDefault L1105, authorWarn L1116-1140, evidenceScope L141), packages/lint/src/rule-explanations.ts L3306.

Inputs: card #27974 body and its two comments (claim 6526104358, os-dev-report 6527290895); PR #28191 body, 12-file list and the diff against main (merge base 9f13ee81, +135/-8); the check-runs on 061a780d (read 2026-10-30T16:43Z, last completion 2026-10-30T16:24:04Z): 50 names, all completed, 39 success, 11 skipped, 0 failure. Among the successes: Governed Surface Queue Guard, Check Changeset, PR-body guards, Spec property liveness, Lint & Repo Gates, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · ESLint, Lint · CI/release/engine gates, the three scoped lints, Type Check · source/consumer/debt ledger, Test UI, Live E2E UI, Build Docs, Build UI Deps, Dogfood Regression Gate, Flag docs, Check Documentation Links; 11 skipped (opt-in harnesses, matrix placeholders, Lint UI, UI Published Artifacts, the schedule gate). Test Core 1/8-8/8, Build Core, Build & E2E UI, Type Check UI and Type Check · workspace completed success after the first reading. Author user.login is objectstack-fleet[bot], so no login note is owed. The reviewer is not the drafter (session_01L9qibxKEFRdVkHHwAWLUSE).

① Derived judgments

  1. ReportChartSchema (report.zod.ts L42-55) gains one optional key seriesBy: z.string(). .extend() keeps ChartConfigSchema's strictObject and its error map, so the accept set widens by exactly this key on the report chart and nowhere else: the inline-data tier (ChartConfigSchema itself), dashboard chartConfig and ChartSchema nodes are untouched (verified: no seriesBy anywhere else under packages/spec/src). A list value is refused at path seriesBy; the absent key stays undefined. Right. Pinned by report.test.ts (accept / absent / list refused) and chart.test.ts L370-383 (the .extend() strictness pin now asserts the own key and still refuses chartType with the inherited rename).
  2. The same schema serves report.chart and blocks[].chart (both tables in the regenerated report.mdx carry the row). No new acceptance there: ReportSchema's joined refinement already refuses chart on a joined container and no renderer reads a block's chart ([finding] a joined report's chart — container and blocks[].chart — parses and is never drawn, while the liveness ledger names the joined branch as its reader #20161), unchanged by this diff. Right.
  3. validateChartBindings (validate-chart-bindings.ts L508, L584-594): chart.seriesBy on a chart-drawing report surface goes through dimensionRef, the same resolver as xAxis, so an undeclared name is a chart-dimension-unknown error at reports[i].chart.seriesBy with the did-you-mean hint, at os validate, os build and the report publish door. Joined containers and blocks (drawsChart false) stay silent, as for xAxis. Right. Pinned by the first new lint test (control + misspelling).
  4. validate-chart-bindings.ts L596-599: with seriesBy set, chart.series[].name overrides are not passed to measureRef, so a series[].name naming a dimension value no longer raises chart-measure-unknown; without seriesBy the warning stands (control test). Right, and consistent with the pivot it anticipates: buildChartSeries with two dimensions and one measure derives one series per VALUE of dims[1] (chart-series.ts L592-600, pivotSeriesBuckets), so the measure name is no longer a series key. The os explain chart-axis-not-selected text (rule-explanations.ts L3306-3312) states the same rule. Noted, not a finding of this diff: the page-chart branch (L689-712) still judges series[].name against the measures when properties.dimensions has two entries, where the same pivot happens; a pre-existing asymmetry on another surface.
  5. Liveness (liveness/report.json): the chart row now drills with childrenDefault: live, so every inherited ChartConfig key keeps the verdict the undrilled container row already gave it (same vouching as before); aria is graded dead with its retirement pointer (the tombstone is in the walked shape, chart.zod.ts L743 retiredKey); seriesBy is planned, evidenceScope: in-repo, authorWarn: true with an authorHint. planned + authorWarn is a legal pair (check-liveness.mts L1116-1140 throws only on a warned live row), and the author-side lint turns it into one liveness-planned-property finding on a report that writes the key, which the dev probed. The three nested containers the drill exposes (series, annotations, interaction) replace the single report/chart entry in undrilled-containers.baseline.json; state-counts/report.md regenerated (27 → 39 live, 1 dead, 1 planned). Spec property liveness is success on the head. Right.
  6. Generated surfaces: authorable-surface/ui.json adds ui/ReportChart:seriesBy; content/docs/references/ui/report.mdx adds the row in both tables; Check Changeset, Lint · repo & tooling gates and Build Docs are success. Right.

② Semver level

.changeset/27974-report-chart-series-by.md: @objectstack/spec minor (one optional key on a published schema), @objectstack/lint minor (a new finding path on a new key, reachable from os validate/os build). Both match what the diff publishes; nothing is removed or tightened for existing input (a chart without seriesBy parses and lints exactly as before, incl. the series[].name warning). Clause-②: yes (widening) and Metadata-protocol: added sit line-initial on the PR body and the changeset. Check Changeset is success. Consistent.

③ Boundary flags

  • open_questions: none declared.
  • Deviation 1 (lint minor beside spec minor, the order named spec only): answered in ②, the lint diff publishes a finding, so the bump is owed.
  • Deviation 2 (liveness edits beyond the order's file surface): answered in ①.5, without the drill the chart row's blanket live would vouch for a key nothing draws, and check:liveness would have had no row to warn the author from. Accepted.
  • Deviation 3 (no assignee/reviewer written): FRAME forbids it; not a contract matter.
  • Skills-docs: follow-up owed (skills/objectstack-ui/rules/dashboards.md) and the domain:ui renderer half (DatasetReportChart queries [xAxis] × [yAxis], L1210-1230, L1421): both are the seat's follow-up cards, as the PR body and the report say; the planned grade is what makes landing the contract half alone sound.
  • out_of_scope_findings item 3 (stale groupBy sentence in the chart row's note): see S2 below.
  • Author: objectstack-fleet[bot]; no login note.

④ Design judgment

packages/spec/src/ui/report.zod.ts against the card (#27974: a declared series dimension on ReportChartSchema, validated against the report's dataset, the ui half to follow) and its first consumer:

  • The key holds against the consumer it is written for. buildChartSeries(rows, [xAxis, seriesBy], [yAxis]) is exactly the two-dimension / one-measure branch (chart-series.ts L592): dims[0] is the x bucket, dims[1] the group pivoted into one series per value, label from the value, null/empty groups kept (objectui#4673). The docblock's [xAxis, seriesBy] × [yAxis] is a true description of that path, so the renderer card is a query-widening plus a pass-through, not a new pivot.
  • Own key, not the inherited series. ChartConfigSchema.series is the per-series presentation override array (chart.zod.ts L691); one name with two meanings would have broken every reader of series[]. groupBy would collide with aggregate.groupBy on the react ObjectChart block, where it IS the category axis (chart.zod.ts L1007), and with the kanban lane field. Widening xAxis to a list would retype a key the Studio inspector and the renderer read as one string. The choice is the least-surprising of the four and the rejected shapes are named on the PR.
  • Dimension, not raw field. The .describe() and docblock say bound-dataset dimension, matching xAxis, and the lint enforces it against the dataset's declared dimensions, so the spec and its gate agree.
  • Strictness rides. Tested on the head (chart.test.ts L378): the own key is accepted and an undeclared one still gets the base error map's rename.
  • Nothing is retroactive. Existing reports carry no seriesBy; their parse, lint and drawing are byte-identical.

Should-fix (none blocks the contract):

  • S1 seriesBy equal to xAxis parses and lints clean (the PR body says so). The pivot then makes every x bucket its own series: one bar per category with a legend entry each and no grouping, which is a silent misread of the author's intent. A warning in validateChartBindings when seriesBy === xAxis (same path) would cost one line; it can ride the renderer card.
  • S2 The chart row's note still reads "Sub-key groupBy stays experimental (its own describe marker) — the dataset renderer plots a single series and never reads it": ReportChartSchema has no groupBy and the row now drills, so the sentence is stale ledger prose (the dev noted it, left it). Replace or drop it when the row is next re-verified.
  • S3 The page-chart lint branch (L689-712) judges series[].name against the measures even when properties.dimensions has two entries, where the same pivot makes the series keys dimension values. Pre-existing; a follow-up card for the lint lane if an author trips over it.

Implemented-by: claude/project-thread-1rmsao
Reviewed-by: session_01L9qibw58ehQ2gRHP2h2Puz

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D

维护者速读

  • 改了什么: 报表图表新增可选键 seriesBy(数据集的第二个维度),把同一个度量按这个维度的每个取值拆成多组柱子。发布时拼错的维度名会被 chart-dimension-unknown 拦下。这次只落契约这一半,活性台账把它标成 planned,作者写了会收到“已声明、尚未生效”的提示。
  • 为什么改: 报表图表以前只能画一个 X 维度对一个 Y 度量。HotCRM 的“按负责人、再按阶段拆分”的漏斗只能退回成矩阵表。
  • 风险与回滚: 只加了一个可选键,不写它的报表解析、校验和绘制都不变。回滚即 revert。
  • 先看: packages/spec/src/ui/report.zod.ts(ReportChartSchema.seriesBy)、packages/lint/src/validate-chart-bindings.ts(与 xAxis 同一个解析器)、packages/spec/liveness/report.json。
  • 两分钟验证: pnpm --filter @objectstack/lint exec vitest run src/validate-chart-bindings.test.ts -t seriesBy,预期 2 条全绿。
  • 需要你做的: 无。不属于受管面,契约复审已 PASS,由席位落地。渲染器那一半是 Report chart draws the seriesBy split: query the second dimension and pivot it into grouped series #28206,技能文档是 skills: teach report chart seriesBy in the objectstack-ui dashboards rule #28207。
  • 席位意见: ACCEPT,可落地。

席位核对 · ACCEPT · domain:spec seat 2 (#18549) · 2026-10-30T16:46Z

Head 061a780d00b130c4b5fcefd909c2f7f203ff4183. Card #27974, claim 6526104358.

  • Shape: draft to main, made by the relay; Fixes #27974 alone; Clause-②: yes (widening), Metadata-protocol: added at line start. Skills-docs: rewritten from follow-up owed to follow-up #28207 in this act (body read back identical). Changeset @objectstack/spec minor + @objectstack/lint minor (the lint diff publishes a finding).
  • Contract review: PASS at CONTRACT_REVIEW_TIER by the lane review thread, record 6528174510 on this head, reviewer not the drafter. No must-fix. Should-fixes: S1 (seriesBy === xAxis warning) and S2 (stale groupBy note in the liveness row) ride the renderer card Report chart draws the seriesBy split: query the second dimension and pivot it into grouped series #28206, named in its body; S3 (the page-chart lint branch judges series[].name with two dimensions) is pre-existing and not touched here → Acceptance notes.
  • Diff read by the seat: the key is the report's spelling of the dashboard path's two-dimension pivot; the dev's three deviations (lint minor, liveness drill beyond the order's surface, no assignee) are answered in the record's ③ and accepted.
  • Follow-ups filed: Report chart draws the seriesBy split: query the second dimension and pivot it into grouped series #28206 (renderer half + liveness flip to live + S1/S2), skills: teach report chart seriesBy in the objectstack-ui dashboards rule #28207 (skills/objectstack-ui/rules/dashboards.md, Tier H).
  • Gates: check-runs on this head 42 success, 13 skipped, 0 red (read at 16:25Z and again by the record at 16:43Z). mergeable_state clean. check-governed-merges --branch: NOT governed.
  • Authority: maintainer standing go-ahead 「按 pm-dispatch 审过的pr你可以自主落地」 (2026-10-30T14:20Z, project thread).
  • Landing: pr_ready + automerge_enable in this act.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Report chart: split a series by a second dataset dimension

1 participant