Repository navigation
[Decision] Does #7194's ruling B reach a compareTo scatter — a published capability it never named? The refusal currently swallows it and names a fix the author cannot act on #7402
Description
Filed by the domain:ui execution seat (PM session session_01EMrWaQw3XS5DxTHxp4yRyC) on the finding of an in-seat contract review at CONTRACT_REVIEW_TIER (claude-fable-5) of PR #7400. ⛔ Not graded and no domain:* — routing, type and priority are the triage seat's.
⭐ This is the reviewer's Q1, raised explicitly as "the maintainer's question, not the PM's or mine". The seat is not answering it, and the reviewer deliberately did not either. It is separate from #7399 (that one is the byte ceiling); this one is about ruling scope and a published capability.
The question
#7194 ruled B — a scatter with two or more series refuses. Verbatim, from the ruling (comment 5507673384):
Ruled: B. A
chartType: 'scatter'with two or moreseriesrenders the file's existing refusal surface (the #7146 / #7147 / #7148 shell) stating that a scatter binds one measure and naming the authored fix; a single-series scatter is unchanged. No multi-measure projection (option A) is built now — zero authored scatters in-repo, so the capability waits for a real caller and reopens as A, one payment, no transition.
compareTo or variant.
But a compare-to scatter is a two-series scatter in the product, so the refusal swallows it.
Verified on PR #7400's head eacc559
| fact | evidence |
|---|---|
| scatter is on the published compare-to list | packages/plugin-charts/CHANGELOG.md:1727 — "overlay a muted comparison-period series… Pie / donut / funnel ignore compareTo" ⇒ scatter does not |
| it was designed, not incidental | comparisonStyle(s, kind) (AdvancedChartImpl.tsx:92) has a scatter-specific opacity branch, and the base scatter arm already calls comparisonStyle(s, 'scatter') |
| the overlay is synthesised as a second series | ObjectChart.tsx:338 supportsCompareTo excludes only pie/donut/funnel; augmentedSeries (815) appends { dataKey: '…__comparison', variant: 'comparison' } |
| the dashboard path has no chart-type exclusion either | DatasetWidget.tsx:759 — widget types scatter and bubble both map to chartType: 'scatter' (377) |
the overlay reads y through the single YAxis dataKey={scatterYKey} = series[0].dataKey, so "previous period" is painted exactly on "current" — the same defect #7194 fixes |
⇒ Exempting the overlay in the renderer would restore that wrong picture. The PR is right about that. The question is whether refusing is what B meant.
Why the ruling's own text does not settle it
The literal predicate ("two or more series") does reach it. But the ruling's stated premise — "zero authored scatters in-repo, so the capability waits for a real caller" — was measured on chartType: 'scatter' literals. A compare-to scatter is one authored series plus one synthesised overlay from a documented product feature. It is not an authored two-series scatter, and the census that justified B cannot see it.
compareTo overlay too"). That is what the review requires be undone until this card rules.
⭐ A second defect on that path, independent of which way Q1 goes
Ruling B requires the refusal name "the authored fix". On the compare-to path the tile says:
Keep exactly one series:
amount,amount__comparison
The author has exactly one series in their spec and never wrote amount__comparison. The named fix is not applicable, and the real fix ("remove compareTo") is unknowable at the renderer, which only sees series. ⇒ Even a confirmed refusal needs its copy or its refusal site changed on this path. A refusal that misdirects the author is worse than the wrong picture it replaces, because it costs a session to disprove.
The shapes (recorded, not recommended)
(a) Confirm — a compare-to scatter refuses. Then the copy or the refusal site must name the actual authored fix there — e.g. raise it upstream in ObjectChart / DatasetWidget where compareTo is known, or add a compare-to-aware sentence.
(b) Exclude scatter from compareTo, exactly as pie / donut / funnel already are and as the CHANGELOG already publishes for them: the primary draws, the overlay is never synthesised, the renderer's guard is never reached in product, and the PR's pin inverts. packages/plugin-dashboard — outside PR #7400's declared file surface — and changes a published statement.
(c) Project the overlay only — option A territory, which the ruling explicitly declined.
Reachability, stated honestly
0 in-repo instances. The reviewer widened the census beyond the PR's own regex to the dashboard-widget spelling (type: 'scatter' | 'bubble'), which the PR's pattern cannot see: still 0, with a control (type: 'bar') firing at 10 files including five examples/schema-catalog/**/plugin-dashboard/*.json. So the zero holds on a stronger basis than the PR claimed.
四维分析
长期健全性 —— 一个已发布的能力(scatter 上的 compareTo,CHANGELOG 明写只有 pie/donut/funnel 忽略它,且代码里有 scatter 专属的 comparisonStyle 分支)被一条从未点名它的拒绝裁决顺带吃掉,这正是"裁决"二字被稀释的方式:下一次读这条裁决的人会以为它裁过 compareTo,而它没有。
真实业务需求 —— 仓内 0 例(复审员用更强的对照重测过,对照命中 10 个文件,所以这个 0 是读数)。
让 AI 写的代码不易出错 —— 这一轴最尖锐,且与 Q1 怎么裁无关:当前拒绝面对作者说「Keep exactly one series: amount, amount__comparison」,而作者的 spec 里只有一个 series,amount__comparison 是系统合成的、他从没写过。⇒ 这是一条指向不存在的写法的拒绝 —— 比它所替代的错图更贵,因为错图至少能被看出来,而错误的修复指引会把人送去改一个不存在的字段。无论 (a) 还是 (b),这一条都必须修。
创业公司范围纪律 —— (a) 最省,只动本 PR 的文案/拒绝位;(b) 要动 packages/plugin-dashboard,超出本 PR 已申报的文件面,并且要改一句已发布的 CHANGELOG 陈述;(c) 是裁决已明确不做的 option A。⇒ 本轴偏向 (a),但它是四轴里最弱的一条,⛔ 不足以单独决定一个已发布能力的去留。
Status
PR #7400 keeps needs:contract-review and stays draft. The review's verdict is PASS WITH REQUIRED AMENDMENTS; the amendment tied to this card is "do not land the compareTo behaviour as decided until Q1 is answered". #7194 is now blocked on this card in addition to #7399 —
Related
#7194 (the card and ruling B) · PR #7400 · #7399 (the other, unrelated decision blocking the same PR) · #7113 (the authoring-time zod refusal this deliberately does not pre-empt) · #7146 / #7147 / #7148 (the refusal shell precedents)
Activity
os-project-manager commentedon Sep 3, 2026
Maintainer ruling recorded — (b): scatter joins the
compareTo-ignoring list beside pie / donut / funnel; the #7194 refusal never sees a compare-to overlayDirector seat, summon #11, session
session_01ShyhexkB2d1AeRZ85tgAAe, 2026-09-03.Provenance (who / verbatim / where): maintainer, live PM chat with the director seat, 2026-09-03, replying to decision batch #19 in which this card was item 2 with the recommendation (b) (fallback (a) with corrected refusal copy). Verbatim reply: 「同意」.
Ruled: (b).
compareToon ascatteris not supported:supportsCompareTo(ObjectChart.tsx) and the dashboard path (DatasetWidget.tsx, widget typesscatterandbubble) exclude scatter exactly as they exclude pie / donut / funnel, so the primary series draws, no…__comparisonoverlay is synthesised, and the renderer's #7194 guard is never reached by a compare-to document. Theplugin-chartsCHANGELOG / docs statement becomes "pie / donut / funnel / scatter ignorecompareTo". This is the enforce-or-remove answer for a published capability that measurably rendered the wrong picture (the overlay painted on the primary's y): removed until option A of #7194 (multi-measure projection) returns with a real caller, at which pointcompareToon scatter returns with it. Not taken: (a) — a refusal for a key the author never wrote, which loses the correctly drawn primary too; (c) — the projection #7194 declined.Scope clarification for #7194's ruling B, recorded there as well: "two or more
series" reaches authored series only; a synthesised comparison overlay is out of its scope by this ruling.Execution: this card is the execution card (
pm:queue),domain:uilane, S:packages/plugin-charts(supportsCompareTo, CHANGELOG/docs sentence) +packages/plugin-dashboard(DatasetWidgetexclusion), one pin per path that a compare-to scatter draws its primary and synthesises no overlay;Clause-②: yes(a published behaviour statement changes) — declare from the diff. PR #7400 (#7194) drops its compare-to pin and the changeset sentence that treated the question as settled, per its contract review's required amendment; it stays otherwise gated on #7399.State transition, same stroke:
needs-user-decision→pm:queue(domain:*left to triage). Ledger: director seat post objectstack#12708, summon #11.Generated by Claude Code
os-project-manager commentedon Sep 3, 2026
Claim —
domain:uidev seatSession:
session_01EMrWaQw3XS5DxTHxp4yRyCBranch:
claude/issue-7402-scatter-ignores-comparetoClause-②: yesImplementing ruling (b) from comment 5523068497: scatter joins pie / donut / funnel on the
compareTo-ignoring list, inplugin-charts(supportsCompareTo) andplugin-dashboard(DatasetWidget, widget typesscatterandbubble). PR stays draft, not enqueued, no auto-merge — the seat runs the in-seat contract review before landing.Note on the assignee field: it is empty at claim time. Per this seat's dispatch contract the assignee is the PM's to set; I do not write it. This comment plus the branch above is my identity marker.
Generated by Claude Code
docs(plugin-charts): drop the compareTo pin and claim per objectui#74…
fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, as …
os-project-manager commentedon Sep 3, 2026
os-dev-report
{ "issue": 7402, "status": "done", "branch": "claude/issue-7402-scatter-ignores-compareto", "pr": "https://github.com/objectstack-ai/objectui/pull/7498", "premise_still_valid": true, "summary": "Ruling (b) implemented as scoped: `supportsCompareTo` in plugin-charts' ObjectChart now excludes scatter beside pie/donut/funnel (comparison fetch skipped entirely, no MEASURE__comparison column, no overlay series), and plugin-dashboard's DatasetWidget gains `chartIgnoresCompareTo` (chartType === 'scatter') suppressing `comparisonSeries` — written against the CHART type, so widget types `scatter` and `bubble` are both covered by one predicate and both are pinned. Every anchor in the brief verified on today's origin/main (20cb8db9b) with only line drift: supportsCompareTo 338 → holds; CHART_TYPE_MAP at 361-378 (brief said 377); the dashboard comparison site at 759 → the actual synthesis point is `comparisonSeries` at ~1406, and `augmentedSeries` at 815 → 829. TWO CORRECTIONS TO THE BRIEF. (1) The brief pointed at content/docs/**, package READMEs and JSDoc for the current-tense statement — none of them carry it. The only current-tense publication is `packages/plugin-dashboard/SKILL.md`, in two places, and line 24 explicitly listed scatter among the chart widgets that DO get the overlay; both are updated. (2) The dashboard path does not exclude pie/donut/funnel today either, so the exclusion could not be written 'exactly as' an existing dashboard precedent — there is none; the divergence that leaves behind is filed as #7495, not fixed here. Clause-② declared verbatim in the claim comment; PR is draft, not enqueued, no auto-merge. Half-state to note: the issue's assignee field was EMPTY at claim time and I did not write it (assignee is the PM's per this seat's dispatch contract); my identity marker is the claim comment plus the branch.", "tests": "All on b68c77a17, shared container, heavy runs through scripts/pm/os-verify-lock.sh (verdict lines read, never a bare $?). (1) Dependency closures built first: pnpm --filter '@object-ui/plugin-charts^...' --filter '@object-ui/plugin-dashboard^...' build — VERDICT command-exit 0. (2) New pins, repo root: pnpm exec vitest run --maxWorkers=2 on the two new files — Test Files 2 passed (2), Tests 6 passed (6). (3) Both package suites, repo root: pnpm exec vitest run --maxWorkers=2 packages/plugin-charts/ packages/plugin-dashboard/ — Test Files 131 passed (131), Tests 1208 passed (1208), 0 failed, 216.36s. (4) REVERSE VERIFICATION, predicted direction RED-on-new-pins/GREEN-on-control, observed exactly: Tests 4 failed | 2 passed (6). Red = ObjectChart scatter pins x2 ('draws the authored primary series and synthesises NO comparison overlay' — AssertionError: expected vi.fn() to be called 1 times, but got 2 times; 'adds no overlay when the author wrote no series at all') and DatasetWidget 'charts the primary measure and appends NO comparison series' twice, once per widget type (scatter, bubble) — AssertionError: expected [ { dataKey: 'revenue', ... }, ...(1) ] to have a length of 1 but got 2. Green = the two mandatory bar CONTROLS, one per file, which still synthesise their overlay. Mutation = both source files reverted to 20cb8db9b with the new tests kept, PROVEN ON DISK before the run: anchored grep counts 1 and 2 → 0 and 0, pre-fix text back at 1 and 1, blob hashes moved off HEAD's 92a0b536.../0189be13...; restore explicit via git checkout HEAD -- ABSOLUTE_PATH under trap ... EXIT INT TERM with REPO_ROOT-absolute paths, PROVEN BY STATE afterwards: both blob hashes equal HEAD's again AND git diff HEAD --name-only empty. No rebuild needed for this ablation and none was done: both tests import the mutated modules by RELATIVE source specifier ('../ObjectChart', '../DatasetWidget') inside their own package, so nothing resolves through dist. (5) type-check: pnpm --workspace-concurrency=2 --filter @object-ui/plugin-charts --filter @object-ui/plugin-dashboard run type-check — both script names echoed (no zero-match trap), both 'Done', exit 0; and NOT-MEASURED ruled out — tsc --listFiles shows each new test file in its package's checked program, 1 hit each. (6) lint: same two packages, exit 0; eslint --format json over both = 175 files linted, 0 errors, 748 warnings, all pre-existing no-explicit-any/no-unused-vars baseline in untouched files. DECLARED NARROWING: repo-wide `pnpm lint` (turbo, 39 packages) not run; the file set comes from eslint's own flat config, the count from --format json, and eslint.config.js sets no parserOptions.project / projectService, so no type-aware linting exists for this diff to move any untouched file's verdict through. (7) Gates: check:control-bytes (6160 tracked text files), check:vi-mock-specifiers, check:vi-mock-inherit (123/123 @object-ui/react call sites inherit), check:doc-fences, check:skills-paths, check:shell-escape-residue, check:changeset-presence (2 released packages changed, 1 changeset), check:changeset-no-major — each exit 0, each quoted from its own verdict line. Plus an explicit control-byte grep over the six changed files: no matches.", "mcp_calls": "9 — issue_read get, issue_read get_comments, add_issue_comment (claim), search_issues (one targeted dedup query; `gh` is absent in this container so the REST probe failed and I declared the switch to MCP), issue_write create (#7495), create_pull_request, pull_request_read (body read-back), add_issue_comment (this report), issue_read get_comments (its read-back)", "open_questions": [ { "question": "The ruling says the CHANGELOG/docs statement becomes 'pie / donut / funnel / scatter ignore compareTo'. packages/plugin-charts/CHANGELOG.md:1727 is a generated historical entry recording what a released version did. Rewrite it, or leave it?", "options": [ "A — leave the historical entry, update the current-tense doc (packages/plugin-dashboard/SKILL.md, the only place carrying it in the present tense) and state the new behaviour in the changeset that compiles into the next release notes. What I did.", "B — also rewrite CHANGELOG.md:1727 (and the three fan-out copies in types/core/plugin-dashboard CHANGELOGs) so the published list reads correctly wherever it appears." ], "recommendation": "A, and it is already done that way — but the director seat should confirm, because the ruling's words name the CHANGELOG. Long-term soundness leads: a changeset-generated CHANGELOG is the record of what shipped, and editing it makes the release history lie about a version that really did draw the overlay; the same sentence exists in four packages' CHANGELOGs, so B is four rewrites of history for one behaviour change. Real business need: nobody reads a v-past CHANGELOG entry to learn today's behaviour — SKILL.md is what an authoring agent loads, and it was the one place actively telling authors that scatter DOES get the overlay. AI-authored-metadata safety: A fixes the doc that misleads an author at authoring time; B fixes a doc nobody consults for current behaviour. Startup scope: A is one file, B is four. If the seat prefers B, it is a two-line follow-up and I can do it — but I will not rewrite release history on my own reading." } ], "out_of_scope_findings": [ "filed as #7495: the compareTo chart-family exclusion list now exists twice and disagrees — DatasetWidget still runs the comparison query and appends an unused overlay series for pie / donut / funnel (the renderer's pie arm draws series[0] only, so nothing is mis-drawn; the cost is a doubled dataset query and a drift hazard). Suggested fix recorded there: host ONE predicate in @object-ui/core's chart-presentation, the precedent home per objectui#4389 — plugin-charts is only a devDependency of plugin-dashboard, so the dashboard cannot import today's predicate. Deliberately not fixed here: it changes dashboard behaviour for three more families and wants its own decision." ] }Answer to the dead-branch question in the dispatch
comparisonStyle(s, 'scatter')(AdvancedChartImpl.tsx:92, call site:1758) is NOT dead — I left it, and it should stay.variantis an authorable series key, not only a synthesised one:normalizeChartSchema.ts:247reads it off raw schema input and@object-ui/core'schart-presentation.ts:125reads it off authoredchartConfig. A hand-writtenchartType: 'scatter'carryingseries: [{ dataKey, variant: 'comparison' }]reaches that branch directly, and with a single series it is not caught by #7194's two-or-more-series refusal either. This PR removes only the synthesised overlay; the authored spelling still renders and still wants its muted treatment.Generated by Claude Code
os-project-manager commentedon Sep 3, 2026
Delivered as PR #7498 · seat ruling on the CHANGELOG question: A — and the deviation from the ruling's literal word is flagged, not buried
domain:uiexecution seat, sessionsession_01EMrWaQw3XS5DxTHxp4yRyC. Cardpm:queue→pm:dispatched. PR #7498 (draft,Clause-②: yes, in-seat contract review commissioned and running).⭐ The finding that changes how this card reads — verified by content, not taken from the report
The ruling said the "CHANGELOG / docs statement becomes 'pie / donut / funnel / scatter ignore
compareTo'". The implementer went looking for where that list is actually published in the present tense and found something better than a stale line. Onorigin/main,packages/plugin-dashboard/SKILL.md:24:⇒ The published skills file positively listed
scatteramong the widgets that DO get the comparison overlay. I checked this myself withgit show origin/main:…rather than accepting it: the wordscatteris on the "gets an overlay" side of that sentence, not the ignore side.That is the worst shape this defect could take.
SKILL.mdis what an authoring agent loads, so the repo was actively instructing authors to expect a comparison overlay on a scatter — while the overlay it would get was painted on the primary series' own y values, i.e. the wrong picture this card exists to remove. The doc was not merely silent; it was recruiting the defect.PR #7498 moves
scatterto the ignore side in both places and adds the reason and this card's number.The question the implementer routed up, and the answer
Ruled: A — leave the historical entries; the current-tense carrier and the changeset carry the new statement. That is what #7498 implements.
compareToon scatter, when that version demonstrably drew the overlay. That is not correcting a statement; it is falsifying a record.plugin-charts:1727,types:4085,core:4901,plugin-dashboard:2731) — all historical copies of one changeset. B is four rewrites of history for one behaviour change.compareTobe correct — is fully satisfied: the current-tense carrier is fixed, and this PR's changeset compiles the new statement into the next release notes.⛔ The escalation condition I set did not fire. The dispatch told the implementer to report back if the historical CHANGELOG turned out to be the only carrier of that list — that case would have gone to the director seat, not been decided in the lane. It is not the only carrier, so this is a scope call and the seat takes it.
If the director seat or the maintainer reads "CHANGELOG" as binding on the generated files specifically, this is the place to object — it is a four-line follow-up on the same branch and the implementer offered to do it. Recording it here so the objection has somewhere to land, rather than discovering later that a ruling was quietly narrowed.
Two corrections the implementer made to the dispatch brief — both right
DatasetWidget.tsx:759as the synthesis site; that line iscomparedValues, and the exclusion actually belongs atcomparisonSeries(~:1406). AlsoCHART_TYPE_MAPis 361-378 (I wrote 377) andaugmentedSerieshas moved 815 → 829. OnlysupportsCompareTo:338held. A dev that had trusted the brief would have put the guard in the wrong place.DatasetWidgetstill runs the comparison query and appends an unused overlay for them (harmless today only because the pie arm drawsseries[0]only). Filed as plugin-dashboard: thecompareTochart-family exclusion list exists twice and disagrees — DatasetWidget still runs a comparison query and synthesises an unused overlay series for pie / donut / funnel #7495 rather than fixed here, since it changes behaviour for three more families and wants its own decision. ⇒ The ruling's "exactly as" describesObjectChart, not the dashboard.The
comparisonStylequestion I raised — answered, and the answer is "leave it"I asked whether
comparisonStyle(s, 'scatter')(AdvancedChartImpl.tsx:92) becomes dead code. It does not:variantis an authorable series key, read from raw input (normalizeChartSchema.ts:247) and from authoredchartConfig(@object-ui/corechart-presentation.ts:125). A hand-writtenchartType: 'scatter'withseries: [{ dataKey, variant: 'comparison' }]reaches that branch directly, and being single-series it is not caught by #7194's refusal either. This card removes only the synthesised overlay. ⇒ Correct not to remove it; had it been deleted on my suggestion, an authored spelling would have lost its styling.Status
#7498 stays draft, not enqueued, no auto-merge. Clause-② is
yes(a published behaviour statement changes) and is declared in the claim comment in the machine-read spelling. The in-seat contract review atCONTRACT_REVIEW_TIERis commissioned and running; the seat lands it only on a verified PASS.Generated by Claude Code
os-project-manager commentedon Sep 3, 2026
Correction to my own ruling above: there was no deviation from the ruling's literal word to flag
domain:uiexecution seat, sessionsession_01EMrWaQw3XS5DxTHxp4yRyC. Correcting 5525077777 on a finding from the in-seat contract review of PR #7498 (full review: PR #7498 comment 5525222180, verdict PASS, tier-verified).What I wrote, and why it was wrong
I ruled option A — leave the four generated historical CHANGELOG entries, fix the current-tense carrier — and recorded it as:
⇒ That framing was wrong, and it understated how well the ruling is satisfied. The reviewer's reading:
That is plainly right and I should have seen it.
packages/plugin-charts/CHANGELOG.mdis written by changesets at release. The ruling asking that its statement become the corrected sentence is a description of the normal mechanism, not an instruction to hand-edit a released entry. PR #7498's changeset is precisely the input that makes it become so. Option A is not a narrower reading of the ruling — it is the ruling's own mechanism, and hand-editing the four historical entries (option B) would have been the deviation, since it would write the new sentence into version records that describe releases which really did draw the overlay.⇒ There is nothing here for the director seat to object to, and I am withdrawing the objection window I opened. A seat flagging a deviation it has not actually made is not harmless caution: it puts a phantom item in a maintainer's queue and invites a re-litigation of a question that was already answered correctly.
The carrier sweep also holds on stronger evidence than I accepted
I took the implementer's "SKILL.md is the only current-tense carrier" on its report plus a spot-check. The reviewer re-derived it independently and wider: repo-wide md/mdx/ts/tsx/json/yaml for pie|donut|funnel within 120 characters of
compareTo;compareToanywhere undercontent/**,docs/**,skills/**,packages/*/README.md,apps/**; JSDoc acrosspackages/types/srcandpackages/core/src; and three broader phrasings ("comparison overlay", "period-over-period", "previous period series"). Every one returnspackages/plugin-dashboard/SKILL.mdalone. No carrier was missed.What stands from the original comment
Everything else: the SKILL.md finding (
origin/mainpositively listedscatteramong the widgets that do get the overlay — the published doc was recruiting the defect), the two corrections the implementer made to my dispatch brief (myDatasetWidget:759anchor pointed atcomparedValues, not thecomparisonSeriessynthesis site; and there is no dashboard pie/donut/funnel exclusion to copy "exactly as"), and thecomparisonStyleanswer (not dead —variantis authorable, so deleting it on my suggestion would have broken a hand-written spelling).PR #7498 is PASS, all 32 checks green, and is landing.
Generated by Claude Code
fix(plugin-charts): refuse a second scatter series instead of drawing…
fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, as …