You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
plugin-dashboard: the compareTo chart-family exclusion list exists twice and disagrees — DatasetWidget still runs a comparison query and synthesises an unused overlay series for pie / donut / funnel #7495
pie / donut / funnel still forward compareTo to the executor, still receive MEASURE__compare columns, and still get a variant: 'comparison' series appended
What actually happens on the dashboard path today
A type: 'pie' (or donut / funnel) dataset widget with compareTo:
lowers the comparison window and runs the extra dataset query — real work on the server;
gets MEASURE__compare columns back;
appends a second series { dataKey: 'MEASURE__compare', variant: 'comparison' } to the chart schema;
the renderer drops it — the pie arm (AdvancedChartImpl.tsx, chartType === 'pie' || chartType === 'donut') draws series[0]?.dataKey and nothing else.
So the published statement ("pie / donut / funnel ignore compareTo") holds visually, but only because the renderer happens to look at one series. The cost is a doubled dataset query per compare-to pie widget plus a synthesised series nothing consumes, and the two lists are free to drift further apart.
Suggested direction (not a ruling)
Host ONE predicate both paths read. @object-ui/core's chart-presentation is the precedent home: buildChartSeries / mergeAuthoredPresentation were lifted there for exactly this reason (see that module's header — a second copy of the split beside it is what objectui#4389 filed as a defect), and both packages already depend on @object-ui/core at runtime, while @object-ui/plugin-charts is only a devDependency of plugin-dashboard, so the dashboard cannot import the predicate from where it lives today.
⚠️ Landing that would change dashboard behaviour for pie / donut / funnel (the comparison query stops running) — a behaviour statement change, so it wants its own decision, which is why #7402 deliberately did not do it.
Reachability
compareTo on a pie/donut/funnel dataset widget: 0 authored instances in-repo (grep over examples/**/*.json finds no compareTo at all; control "type": "bar" fires at 5 example files). Incidence in cloud tenant metadata is unmeasurable from here.
Related
#7402 (the scatter exclusion that created the second list) · #7194 · objectui#4389 (the precedent that duplicate chart-presentation logic is a defect)
Landing that would change dashboard behaviour for pie / donut / funnel (the comparison query stops running) — a behaviour statement change, so it wants its own decision, which is why #7402 deliberately did not do it.
Ruling: summon #25 class-1 item 17 · letter A · maintainer 「—」 (class-1 self-adjudication under the charter's 一类自裁门; listed for 追认 in the summon #25 summary on objectstack#12708) 2026-09-20T10:21Z
Director seat, summon #25, session_012GcsUbuqFGBibkEDMRC1eE. Thread re-read to its last comment (5548747753) in the act that wrote this.
Why this card qualifies for class-1
The published statement decides it; the implementation aligns to the statement. The charts package publishes 「pie / donut / funnel / scatter ignore compareTo」 (supportsCompareTo); the dashboard's DatasetWidget carries a second, narrower list (=== 'scatter') and so still runs the comparison query and synthesises an overlay series the renderer drops. Under 「spec 声明 > 实现」 the statement is the contract and the dashboard is the deviating implementation; objectui#4389 already ruled a second copy of chart-presentation logic a defect, and the dependency graph leaves exactly one home both paths can read (@object-ui/core's chart-presentation). ⇒ A. B refused (narrowing a true published statement to fit dead work); C refused (two producers of one rule, the drift already happened once).
Loud, and one revert. The behaviour change is that a query nobody's output consumed stops running; a pin on the dashboard path asserts no comparison fetch for the excluded families, and one PR reverts.
No floor moves. The published statement is unchanged; no security, no capability trade-off (the comparison was never displayed for these families), no gate.
Ruling — A
One predicate in @object-ui/core's chart-presentation, read by both plugin-charts and plugin-dashboard; the dashboard stops running the comparison query and stops appending the unused series for pie / donut / funnel / scatter. Done-when is mechanical: one declaration of 「which chart families ignore compareTo」 in the repo. ⛔ The renderer is not taught to consume the comparison series for pie (a product decision this card does not carry).
Four-facet reading (this seat's own): ① one producer of the rule, at the precedent home; ② in-repo zero authored instances, tenant incidence unmeasurable — the saved work is server-side; ③ an author who writes compareTo on a pie gets one answer instead of two; ④ one predicate moved, one copy deleted.
Prior rulings read: objectui#4389 (duplicate chart-presentation logic is a defect), objectui#7402 (the scatter change that created the second list), thread: 1.
Execution, same stroke
needs-user-decision → pm:queue; priority:p3 · domain:ui stand. The PR states the server-side behaviour change and the in-repo zero as its risk reading; Clause-② declared from the diff (a published statement unchanged, behaviour aligns ⇒ expected no).
The class-1 self-adjudication recorded on 2026-09-20 was listed for the maintainer's 追认 in the summon #25 summary on objectstack#12708. It is now ratified as written: one predicate in @object-ui/core's chart-presentation, read by both plugin-charts and plugin-dashboard, and the dashboard stops running the comparison query for pie, donut, funnel and scatter. Read on objectui origin/main62597c5: packages/plugin-dashboard/src/DatasetWidget.tsx still carries the narrow copy const chartIgnoresCompareTo = chartType === 'scatter', so the work is undone. pm:queue · priority:p3 · domain:ui stand.
Claim: PM loop round 1 — domain:ui execution seat
Session: session_01BA3nKVUwKQJf8DBxrSVtNC
Branch: claude/issue-7495-compareto-one-predicate
Worktree: objectui-issue-7495
Domain: domain:ui
Seat: domain:ui#1
File surface: packages/core/src/utils/chart-presentation.ts (one new predicate: which chart types ignore compareTo), packages/plugin-charts/src/ObjectChart.tsx (its local supportsCompareTo reads the core predicate), packages/plugin-dashboard/src/DatasetWidget.tsx (the narrow chartIgnoresCompareTo copy reads the core predicate, and the comparison query stops running for the ignored types), tests beside them, one .changeset/7495-…md (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: opus (default judgement tier) — priority:p3, a ruled single-producer move.
Clause-②: yes
Thread-read: 5809246443
Serial constraints cleared: open-PR file lists read 2026-09-24T21:37Z. objectui#10169 (seat 2, draft since 2026-09-20) edits chart-presentation.ts at @@ -84, @@ -105 and @@ -217 (label i18n). This claim adds a separate predicate and is expected to be hunk-disjoint; whichever lands second merges main. No open PR touches ObjectChart.tsx's supportsCompareTo or DatasetWidget.tsx. Live pm:dispatched claims were read at the same time: seat 3's objectui#7195 names AdvancedChartImpl.tsx only, which is file-disjoint.
Scope
Ruling 5749199335 (summon #25 class-1 item 17, letter A), ratified by the maintainer as 5809246443: one predicate in @object-ui/core's chart-presentation, read by both plugin-charts and plugin-dashboard. The dashboard stops running the comparison query for pie, donut, funnel and scatter. B and C were refused. Clause-②: yes, because the predicate is a new export of @object-ui/core (its entry re-exports chart-presentation with export *). ⇒ Fixes #7495.
os-dev-report
{
"issue": 7495,
"status": "done",
"branch": "claude/issue-7495-compareto-one-predicate",
"pr": "#10400",
"head_sha": "3861a6a6dbebd377d031a85751345c54c6bcaa70",
"session": "session_01BA3nKVUwKQJf8DBxrSVtNC (subagent: the parent PM session's id; container CLAUDE_CODE_REMOTE_SESSION_ID cse_01BA3nKVUwKQJf8DBxrSVtNC)",
"premise_still_valid": true,
"summary": "Ruling A implemented. @object-ui/core: new export chartTypeIgnoresCompareTo(chartType) in utils/chart-presentation.ts. It answers true for pie/donut/funnel/scatter and false for every other spec family, unknown strings, the empty string, case variants and undefined. It reaches the entry through the existing export * from './utils/chart-presentation.js', so index.ts is untouched. The docblock states the rule and cites objectui#7495. plugin-charts: ObjectChart's local supportsCompareTo is deleted, and both call sites (the comparison fetch and the overlay synthesis) read the core predicate. Answers are identical for every input, so behaviour is unchanged. plugin-dashboard: the chartType === 'scatter' copy is deleted. DatasetWidget now decides compareTo once, near the top: it is undefined when the widget reaches the chart branch (!isMetric && !isTable) and its CHART_TYPE_MAP family ignores compareTo. So for pie/donut/funnel/pyramid/scatter/bubble chart widgets, the queryDataset selection carries no compareTo and no timeDimensions, byte-equal to the no-compareTo selection. The comparison pass no longer runs, and no overlay series is appended. chartType moved up to feed that decision. Metric/table/pivot widgets are unchanged, including a dimensionless pie, which renders as a metric tile and keeps its delta. One visible change, in the ruled direction: the executor's 'compareTo needs a dated window to shift' refusal (objectstack service-analytics dataset-executor.ts) used to replace such a chart when its filter had no dated window. That chart now renders. Zone-2 assumptions: all confirmed. (1) Sites located by content: ObjectChart const supportsCompareTo = (ct?: string) => with 2 call sites; DatasetWidget const chartIgnoresCompareTo = chartType === 'scatter' with 1. The published statement is .changeset/7402-scatter-ignores-compareto.md ('Charts that ignore compareTo: pie, donut, funnel and — as of this change — scatter') and packages/plugin-dashboard/SKILL.md ('Pie, donut, funnel and scatter charts ignore compareTo'); the original is in the plugin-charts CHANGELOG 6.1.0 entry ('Pie / donut / funnel ignore compareTo'). (2) plugin-charts is only in plugin-dashboard's devDependencies; @object-ui/core is a dependency. (3) index.ts has export * from './utils/chart-presentation.js', so no index edit. (4) The comparison 'query' is not a separate call: it is compareTo plus the lowered timeDimensions in the single queryDataset selection. Both are now absent. (5) The chart-presentation hunk is @@ -440,3 +440,48 @@ (appended at EOF). git apply --check of #10169's three chart-presentation hunks on this head exits 0. Worktree cleaned (node_modules removed, git worktree remove without --force, exit 0, directory gone). No server, watcher or monitor started. Harness attribution note: commit trailers carry only Claude-Session (no model Co-Authored-By, per the dispatch). The PR footer is the os-dev session-URL form, not the harness's alternate footer.",
"tests": "At HEAD 3861a6a, from the repo root through os-verify-lock: pnpm exec vitest run packages/core/src/utils/ packages/plugin-charts/ + 9 text-reading pins (residue-namespaces-3546, dashboard-widget-options-census, base-bind-declared, chart-data-model-7113, object-chart-undeclared-keys-8885, widget-schema-anchors-7946, zod-mirror-parity, check-control-bytes, unconsumed-widget-option-claim-6186; found by a repo-wide git grep for tests that read these sources as text) -> 'Test Files 161 passed (161) / Tests 2484 passed (2484)', VERDICT command-exit 0. pnpm exec vitest run packages/plugin-dashboard/ -> 'Test Files 124 passed (124) / Tests 1213 passed (1213)', VERDICT command-exit 0. Type-check: turbo run build --filter='@object-ui/plugin-dashboard^...' --concurrency=2 (12/12 tasks), then pnpm --filter @object-ui/{core,plugin-charts,plugin-dashboard} type-check (tsc --noEmit && tsc -p tsconfig.test.json) all exit 0. --listFilesOnly shows each new test file in its package's test program. The core dist d.ts carries export declare function chartTypeIgnoresCompareTo. New pins: core chart-presentation.compareTo-7495.test.ts (every ChartTypeSchema.options family + undefined/unknown/''/case/bubble/pyramid + package-entry identity); plugin-charts ObjectChart.compareTo.corePredicate-7495.test.tsx (pie/donut/funnel: 1 aggregate call, no overlay; the core predicate spied and forced: true on bar suppresses both call sites, false on pie runs both; truthful bar control); plugin-dashboard DatasetWidget.compareTo.ignoredFamilies-7495.test.tsx (pie/donut/funnel/pyramid/scatter/bubble: no compareTo, no timeDimensions, selection equals the no-compareTo selection, 1 series; bar+line controls forward+lower+overlay; dimensionless pie metric keeps compareTo; undated pie renders vs bar control shows the refusal). Ablation 1 (dispatch-required), committed first, via objectstack scripts/ablation-replace.mjs: the dashboard decision pointed back at chartType === 'scatter'. anchor x1->x0, replacement x0->x1, blob 19d85e1ea29d->aa355d627edc. Result '13 failed | 13 passed (26)': all 12 pie/donut/funnel/pyramid pins + the undated-pie pin red; scatter/bubble and every control green. Restored: blob 19d85e1ea29d == HEAD, git diff HEAD empty. Ablation 2: ObjectChart fetch site pointed back at a local four-family list -> '2 failed | 4 passed (6)' (the two forced-answer pins). Restored blob == HEAD, git diff HEAD empty. No build between mutation and run: the tests load src (relative imports; the root vitest alias @object-ui/core -> packages/core/src). Gates at 3861a6a, all exit 0: check:control-bytes; check:new-line-citations (0 new); check-changeset-presence; check-changeset-no-major; check:changeset-claims (5 pending changesets name DatasetWidget.tsx/ObjectChart.tsx: 7125, 8650, 8885, humanize-label-5444, objectchart-schema-anchor-7946, each paragraph re-read and still true; self-contradiction reading clean); check:vi-mock-specifiers, vi-mock-inherit, vi-mock-override-shape, test-path-roots, pending-changeset-literals, self-import, phantom-deps, spec-symbols, esm-specifiers, unreferenced-sources, i18n-keys; governed-queue-guard --test: NOT GOVERNED. The gate family was hand-derived (objectui has no dispatch-gates deriver). check:readme-exports: NOT MEASURED, reason: no README edited and the gate judges README imports only. ESLint narrowed, as a measurement: (1) population = the root eslint.config.js, used by every package's eslint . (no package-level config); (2) --format json over the 6 touched TS/TSX files: 6 files, 0 errors, 59 warnings; base->head for the 3 edited sources 0/0->0/0, 0/36->0/36, 0/11->0/11; (3) invariance: no type-aware linting (no parserOptions.project/projectService) and no eslint-rules/ rule reads the filesystem. CI: in_progress, not awaited.",
"mcp_calls": "0 — no MCP GitHub tool was called.",
"api_writes": "2 REST writes + git pushes. (1) pr_create through the fleet-write relay: POST /repos/objectstack-ai/objectstack/dispatches (request fw-20260924T221528Z-fc0ac6, run 36066400446, conclusion success), executing POST /repos/objectstack-ai/objectui/pulls (draft) -> #10400. The body was read back byte-identical (10695 bytes, no differing byte). (2) This os-dev-report comment, POST /repos//issues/7495/comments via post-stamped.mjs --repo=objectstack-ai/objectui. git push x3 on the claimed branch (empty-branch probe, fafc817, 3861a6a). Zero label writes. The PR body was written once, never PATCHed. Reads: REST GETs through the container proxy (card 7495 + comments, precedents 4389 and 7402, PR 10169 diff, PR 10400 read-back).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · observation, not class a/b/c: ObjectChartSchema['chartType']'s declared union (packages/types/src/objectql.ts: 'bar' | 'column' | 'horizontal-bar' | 'line' | 'area' | 'pie' | 'donut' | 'scatter') omits funnel/radar/treemap/sankey/combo, yet DashboardRenderer's object-chart branch sets chartType: resolvedWidgetType from widgetDispatch's SERIES_CHART_TYPES, which includes them. No failing behaviour was established, so it went only into PR #10400's Acceptance notes. Dedupe words: ObjectChartSchema chartType union funnel, object-chart resolvedWidgetType, SERIES_CHART_TYPES declared union",
"carrier: 承接者:无 · observation: packages/plugin-dashboard/SKILL.md already states the four families ignore compareTo; it is still accurate and was not edited (outside the claimed file surface). Noted in Acceptance notes only."
]
}
✅ ACCEPT — PR objectui#10400 at 3861a6a6d · contract review PASS · entering the merge queue
domain:ui seat #1, session_01BA3nKVUwKQJf8DBxrSVtNC. The claim says Clause-②: yes: @object-ui/core gains the export chartTypeIgnoresCompareTo. A review-tier contract review ran against the card, the ruling (5749199335, ratified 5809246443) and the PR only, and it PASSED. The seat adopts it after spot-checking two readings at 3861a6a6d: the export reaches the entry through packages/core/src/index.ts's export *, and both ObjectChart call sites read the core predicate.
Maintainer veto window. The one visible change is inside ruling A, but the ruling's risk line (「a query nobody's output consumed stops running」) understated it. A pie, donut, funnel or scatter chart widget with compareTo and no dated window used to be replaced by the executor's refusal 「compareTo needs a dated window to shift」, and now renders. The changeset states this, and pins cover both directions. The seat does not hold landing for it; it is named in the round report.
Inputs read in full: card objectui#7495 (body with 维护者速读 and 四棱 block, triage 5548747753, ruling 5749199335 = summon #25 class-1 item 17 letter A, ratification 5809246443, claim 5822726716, os-dev-report 5823163208); precedents objectui#4389, objectui#7402 and .changeset/7402-scatter-ignores-compareto.md, plugin-charts CHANGELOG 6.1.0 entry (packages/plugin-charts/CHANGELOG.md:1728 「Pie / donut / funnel ignore compareTo」); PR #10400 body, all 7 changed files, git diff origin/main...3861a6a6d (2 commits fafc8172b, 3861a6a6d; 7 files, +589 −43). origin/main has not moved any of the three edited sources since the merge base 00cdaff1f (git diff 00cdaff1f origin/main --stat -- ... is empty; git merge-tree reports 0 conflicts).
① Derived judgments
New public export chartTypeIgnoresCompareTo(chartType) on @object-ui/core.packages/core/src/utils/chart-presentation.ts:444-487: CHART_TYPES_IGNORING_COMPARE_TO = new Set(['pie','donut','funnel','scatter']), return typeof chartType === 'string' && SET.has(chartType). Reachable from the entry: packages/core/src/index.ts:99export * from './utils/chart-presentation.js' (untouched; the core pin asserts entry identity). Answers: true for exactly the four families, false for undefined, '', unknown strings and case variants ('Pie', 'SCATTER') — pinned in chart-presentation.compareTo-7495.test.ts over the run-time ChartTypeSchema.options population. The set equals the published statement byte for byte: CHANGELOG 6.1.0 「Pie / donut / funnel」 plus .changeset/7402 「and — as of this change — scatter」; bubble/pyramid are widget aliases, mapped by callers, not families (7402's own wording: 「the bubble widget type that renders as a scatter」). Case-sensitivity is right: the spec's ChartTypeSchema is a z.enum of lowercase spellings (objectstack packages/spec/src/ui/chart.zod.ts:110-150), normalizeChartSchema and ObjectChart never case-fold (grep toLowerCase packages/plugin-charts/src/ObjectChart.tsx empty), and the retired local predicate was case-sensitive too. Docblock checked claim by claim: pie/funnel arms draw series[0]?.dataKey only (AdvancedChartImpl.tsx:1588, :1654); scatter y reads series[0]?.dataKey (:1957); plugin-charts is only a devDependency of plugin-dashboard (packages/plugin-dashboard/package.json: dependencies has core, devDependencies has plugin-charts); objectui#4389 is the duplicate-glue precedent the card and ruling both cite. Accurate. PASS
ObjectChart: local supportsCompareTo deleted, both call sites read core. Diff packages/plugin-charts/src/ObjectChart.tsx@@ -572,24 +572,6 removes the closure; :805wantsComparison = !!compareTo && !chartTypeIgnoresCompareTo(schema.chartType); :1102enableComparisonSeries likewise. Truth table: old closure body ct !== 'pie' && ct !== 'donut' && ct !== 'funnel' && ct !== 'scatter'; new !(typeof ct === 'string' && SET.has(ct)). For any string these are the same boolean; for undefined both give true (comparison runs); for a non-string value the old returned true and the new returns !false = true. Both sites still read the raw schema.chartType (no normalisation before or after), including the run-time-only values DashboardRenderer's object-chart branch forwards (DashboardRenderer.tsx:672chartType: resolvedWidgetType, resolved through widgetDispatch.tsCHART_TYPE_ALIASES, so funnel arrives as funnel, pyramid as funnel, bubble as scatter — same answers as before). Behaviour identical for every input; the forced-spy pin covers both call sites, and the ablation (local four-family list at the fetch site) went red on exactly the two forced pins. PASS
DatasetWidget: the === 'scatter' copy is deleted; compareTo decided once at the top.DatasetWidget.tsx:439-469: widgetType, isMetric (METRIC_TYPES.has(widgetType) || dimensions.length === 0), isTable, chartType = CHART_TYPE_MAP[widgetType] ?? 'bar' are hoisted (pure values, no hook-order change), then compareTo = !isMetric && !isTable && chartTypeIgnoresCompareTo(chartType) ? undefined : widget?.compareTo. Every downstream reader follows from that one value: compareWindows memo (:582) lowers no window, the selection (:620-621) carries neither timeDimensions nor compareTo, signature (:606) stringifies null, comparedValues (:861) is [], and comparisonSeries (:1574) is empty; the old guard at :1521-1568 is gone (git grep "=== 'scatter'" packages/plugin-dashboard/src excluding tests: empty). Family mapping is right: CHART_TYPE_MAP (:364-386) maps pyramid → 'funnel' and bubble → 'scatter', the same table widgetDispatch.ts:39-40 uses, and the removed guard already operated post-map (bubble was covered by chartType === 'scatter'). Extending to pyramid/bubble is inside the ruling: the ruling names chart FAMILIES and the predicate is asked about the resolved family, exactly as 7402 published bubble as 「renders as a scatter」. Note for the record: the spec (17.4.0, dashboard.zod.ts stageOrder docblock, chart.zod.ts taxonomy note) says 「There is no pyramid widget type — write funnel」 and lists bubble as a fallback-only variant, so both aliases are reachable only through stored legacy metadata; that is the same footing 7402 gave bubble, and harmless here. Metric/table/pivot path unchanged: the gate is !isMetric && !isTable, so a dimensionless pie (isMetric true) keeps compareTo, pinned (selection carries compareTo and timeDimensions; the chart double is never called). Chart branch captions: compareLabel is read only by the metric branch (:994), the table branch (:1040, :1133, :1160-1193) and the overlay series label (:1586), so no chart-branch caption disappears. Combo control untouched (DatasetWidget.comboPresentation.test.tsx still expects the overlay). PASS
The named visible change: undated compare-to pie/donut/funnel/scatter now renders instead of showing the executor refusal. The refusal 「compareTo needs a dated window to shift」 is the executor's answer to the comparison query (objectstack service-analyticsdatasetInvalidError(...), seen in its test doubles). The ruling's operative sentence is 「the dashboard stops running the comparison query ... for pie / donut / funnel / scatter」 and its premise is 「spec 声明 over 实现」 with the published statement 「ignore compareTo」 as the contract; a refusal issued for a compareTo the statement says is ignored was the dashboard deviating from that statement, so removing it is the ruled direction, not a new decision. The ruling's own risk line (「a query nobody's output consumed」) understates this one output, which is why it belongs on the record: the dev disclosed it in the PR body and in the changeset prose that ships to the CHANGELOG, and pinned it both ways (pie renders, role=alert absent; bar control still shows the refusal). No floor moves (nothing an author can write is rejected or renamed). PASS — surfaced to the maintainer under ③, not blocking.
Residual readers of the retired predicates / reliance on the comparison being requested.git grep -nE "IgnoresCompareTo|supportsCompareTo|IGNORING_COMPARE_TO" over packages/*/src and apps/*/src excluding tests lists exactly the one declaration, the exported function, ObjectChart's two call sites and DatasetWidget's one (the PR's done-when, re-run at head: holds). Tests: the existing DatasetWidget.compareTo.scatter.test.tsx and ObjectChart.compareTo.scatter.test.tsx assert overlay absence with doubles that return __compare unconditionally; no test asserts that a pie/donut/funnel dataset widget forwards compareTo. Docs: packages/plugin-dashboard/SKILL.md:25-26 and :159-164 already publish the four families as ignoring (unchanged, still true). Other packages: plugin-report, data-objectstack, ObjectMetricWidget carry no chart-family list (git grep compareTo producers enumerated). One stale mention outside the accept set: .changeset/7194-scatter-one-measure.md:22 names supportsCompareTo as the mechanism — its behaviour claim stays true, only the symbol name is now historical (see ②). PASS
Accept set. No spec key, prop, or renderer arm is added or removed; compareTo on the ignoring families is still accepted and now ignored on both paths, which is the published statement. The renderer is not taught to draw a pie comparison (ruling's ⛔ respected: AdvancedChartImpl.tsx untouched). PASS
② Semver level
Changeset .changeset/7495-compareto-one-predicate.md declares @object-ui/core: minor and @object-ui/plugin-dashboard: minor. Clause-②: yes (PR body line 2) is right — a new export on core's entry widens the published surface — and objectstack AGENTS.md's rule 「yes takes at least minor」 is satisfied. plugin-dashboard minor is consistent with 「objectui 自身的破坏性变更也标 minor」 and the change is not breaking (no FROM→TO migration owed). No major (Changeset Bump Policy: success). Consistent.
@object-ui/plugin-charts undeclared with a source change: legitimate. The gate's rule (scripts/check-changeset-presence.mjs header, AGENTS.md:167 「要的是"声明一次",不是强制发版」) asks for one declaration per change, not one per touched package; Changeset Declaration at head: success (「6 source files of 3 released packages, 1 changeset」). All three packages sit in the one fixed group of .changeset/config.json, so plugin-charts is versioned with core regardless, and its @object-ui/core range is workspace:* (rewritten to the exact release version at publish), so the new import cannot pair with an older core. The 7495 body itself records that ObjectChart now reads the core predicate. Right.
Every sentence of the changeset prose checked against the diff and the code: true for the four core answers, the ObjectChart claim, the six widget types (post-CHART_TYPE_MAP), the 「gated only the overlay series, never the query」 history (old :1568 guard only fed comparisonSeries), the byte-equal selection (pinned with toEqual against the no-compareTo selection), the undated-window visible change, the unchanged list, the metric-tile carve-out, and the reachability (git grep -l compareTo -- 'examples/**' = 0, control "type": "bar" = 5). Self-contradiction reading: none (no declared package is negated in the body).
Pending changesets: .changeset/7402 stays true (it speaks of the inline path's query and of the dashboard's series only); 7125, 8650 (:48 schema-shape mention), 8885 (schema binding of compareTo), humanize-label-single-home-5444, objectchart-schema-anchor-7946 describe no line this PR changes (Changeset Claim Re-read: success). One non-blocking note: .changeset/7194-scatter-one-measure.md:22 cites supportsCompareTo by name; after this PR that local symbol no longer exists, though the sentence's behaviour claim (ObjectChart synthesises no overlay for a compare-to scatter) remains true. A one-line touch (「supportsCompareTo, since objectui#7495 core's chartTypeIgnoresCompareTo」) can ride whichever PR next edits that file; it does not have to be fixed here.
③ Boundary flags
ObjectChartSchema['chartType'] union omits funnel/radar/treemap/sankey/combo while DashboardRenderer.tsx:672 passes them at run time (packages/types/src/objectql.ts:4234). Reading confirmed: widgetDispatch.tsSERIES_CHART_TYPES includes them and the object-chart branch forwards resolvedWidgetType verbatim; the new charts pin casts at one call and says why. Nothing rejects or drops authored metadata (the prop is any-tolerant at the renderer), so under os-dev's three classes this is an observation, not (a)/(b)/(c). Fine — not blocking; the dev's carrier 「承接者:无」 reading is correct. If the maintainer wants the declared union to match the run-time population, that is a separate types card, not this PR's.
packages/plugin-dashboard/SKILL.md not edited.:25-26 「Pie, donut, funnel and scatter charts ignore compareTo」 and :159-164 remain true and are now true of the query as well. One nuance, non-blocking: bullet :156-158 「Filters with no bounded date range — ... the widget reports that instead of rendering a comparison」 is now family-dependent (an ignoring family draws its chart silently, which bullets 2–3 already promise). Not class (a): no example fails. Fine; a precision edit can ride the next SKILL.md change.
For the maintainer, not decided here and not blocking: item ①.4 above — the ruling's risk reading said no consumed output changes; one did (the accidental refusal tile for an undated compare-to pie/donut/funnel/scatter). Direction is the ruled one and it is disclosed in the shipping changeset prose; recorded so the ratification's premise is read with that footnote. Separately, compareTo on an ignoring family stays declared-but-inert metadata (the card's facet ③); the ruling accepted that and refused the renderer change, so an authoring-time refusal would be a new card, not this PR.
PR body lines 1–2:Fixes #7495 and Clause-②: yes. Correct. Every behaviour claim in the body has a pin: pie/donut/funnel one aggregate call + no overlay (charts pin), forced-answer both sites (charts pin), six widget types no compareTo/no timeDimensions/equal selection/one series (dashboard pin), bar+line controls, dimensionless pie keeps it, undated pie renders vs bar refusal (dashboard pin), predicate over the spec enum + entry identity (core pin).
Nothing else should have gone to the maintainer: no spec key, no renderer arm, no gate, no governed surface is touched.
CI at head: 40 success · 3 skipped · 0 failed · 0 in progress
Acceptance notes:.changeset/7194-scatter-one-measure.md:22 names the now-deleted local supportsCompareTo. Its behaviour claim is still true, and the name can be fixed on that file's next edit.
Acceptance notes:packages/plugin-dashboard/SKILL.md stays true, but its 「no bounded date range — the widget reports that」 bullet is now family-dependent. It is a precision edit for the next SKILL.md change.
Acceptance notes: the ObjectChartSchema['chartType'] union omits funnel / radar / treemap / sankey / combo, which the dashboard passes. That is an observation only: nothing rejects or drops metadata.
Activity
os-zhuang commentedon Sep 5, 2026
分诊 —
domain:ui/priority:p3/pm:queue/finding锚定 (anchoring):三个包(
plugin-charts/plugin-dashboard/ 建议的新家@object-ui/core)都在 ui lane ⇒domain:ui,一个 lane。在
origin/maina472b07上复核 —— 两份清单的分歧成立⭐
DatasetWidget.tsx:1428一行就把分歧说尽了:dashboard 侧的排除集是字面上的=== 'scatter',而plugin-charts侧是一个含 pie / donut / funnel / scatter 的谓词。⭐ 卡片给的那条依赖关系我验了 —— 它决定了新家只能是 core
⇒
plugin-dashboard在运行时够不到plugin-charts,所以"直接 import 那个谓词"这条路不存在。卡片提议的@object-ui/core的chart-presentation是唯一两边都能到的地方,而且那里已经有先例(buildChartSeries/mergeAuthoredPresentation就是为同一个理由被抬上去的;#4389 记录了"在它旁边再放一份副本"是缺陷)。⭐ 这不是风格建议,是依赖图给出的唯一解。定级理由
priority:p3:series[0]?.dataKey),所以"这三种忽略 compareTo"这句公开陈述在视觉上成立。compareTo的 pie widget 多跑一次数据集查询(服务端真实开销)+ 合成一个无人消费的序列。compareTo在examples/**/*.json里 0 处,对照"type": "bar"在 5 个示例文件命中 ⇒ 仓内无授权实例。租户元数据里的发生率从这里测不了,卡片诚实标注了。不上 p2:无错误显示、无静默错答;且仓内零实例。
⛔ 定型:这是行为陈述变更,不是纯去重
卡片自己划了这条线,本席确认它成立:
⛔ 给执行席的边界
@object-ui/core的chart-presentation(见上,依赖图上唯一可行)。⛔ 不要在plugin-dashboard里再写一份"和 charts 一样"的清单 —— 那是第三份副本。grep全仓只剩一处"哪些图族忽略 compareTo"的声明。这个判据是机械的,请写进 PR。⛔ 分诊席不认领、不派单、不写码、不裁决行为陈述变更。
Generated by Claude Code
os-project-manager commentedon Sep 20, 2026
Ruling: summon #25 class-1 item 17 · letter A · maintainer 「—」 (class-1 self-adjudication under the charter's 一类自裁门; listed for 追认 in the summon #25 summary on objectstack#12708) 2026-09-20T10:21Z
Director seat, summon #25,
session_012GcsUbuqFGBibkEDMRC1eE. Thread re-read to its last comment (5548747753) in the act that wrote this.Why this card qualifies for class-1
compareTo」 (supportsCompareTo); the dashboard'sDatasetWidgetcarries a second, narrower list (=== 'scatter') and so still runs the comparison query and synthesises an overlay series the renderer drops. Under 「spec 声明 > 实现」 the statement is the contract and the dashboard is the deviating implementation; objectui#4389 already ruled a second copy of chart-presentation logic a defect, and the dependency graph leaves exactly one home both paths can read (@object-ui/core'schart-presentation). ⇒ A. B refused (narrowing a true published statement to fit dead work); C refused (two producers of one rule, the drift already happened once).Ruling — A
One predicate in
@object-ui/core'schart-presentation, read by bothplugin-chartsandplugin-dashboard; the dashboard stops running the comparison query and stops appending the unused series for pie / donut / funnel / scatter. Done-when is mechanical: one declaration of 「which chart families ignorecompareTo」 in the repo. ⛔ The renderer is not taught to consume the comparison series for pie (a product decision this card does not carry).Four-facet reading (this seat's own): ① one producer of the rule, at the precedent home; ② in-repo zero authored instances, tenant incidence unmeasurable — the saved work is server-side; ③ an author who writes
compareToon a pie gets one answer instead of two; ④ one predicate moved, one copy deleted.Prior rulings read:objectui#4389 (duplicate chart-presentation logic is a defect), objectui#7402 (the scatter change that created the second list), thread: 1.Execution, same stroke
needs-user-decision→pm:queue;priority:p3·domain:uistand. The PR states the server-side behaviour change and the in-repo zero as its risk reading;Clause-②declared from the diff (a published statement unchanged, behaviour aligns ⇒ expectedno).Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
Ratified: summon #25 class-1 ruling 5749199335 (letter A) · UI closure batch 2 item 10 · maintainer 「同意」 (chat, director seat summon #29,
session_01EcrTi7s5oDYPHS4Pi7h31d) 2026-09-24T06:50ZThe class-1 self-adjudication recorded on 2026-09-20 was listed for the maintainer's 追认 in the summon #25 summary on objectstack#12708. It is now ratified as written: one predicate in
@object-ui/core'schart-presentation, read by bothplugin-chartsandplugin-dashboard, and the dashboard stops running the comparison query for pie, donut, funnel and scatter. Read on objectuiorigin/main62597c5:packages/plugin-dashboard/src/DatasetWidget.tsxstill carries the narrow copyconst chartIgnoresCompareTo = chartType === 'scatter', so the work is undone.pm:queue·priority:p3·domain:uistand.Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
Claim: PM loop round 1 —
domain:uiexecution seatSession:
session_01BA3nKVUwKQJf8DBxrSVtNCBranch:
claude/issue-7495-compareto-one-predicateWorktree:
objectui-issue-7495Domain:
domain:uiSeat:
domain:ui#1File surface:
packages/core/src/utils/chart-presentation.ts(one new predicate: which chart types ignorecompareTo),packages/plugin-charts/src/ObjectChart.tsx(its localsupportsCompareToreads the core predicate),packages/plugin-dashboard/src/DatasetWidget.tsx(the narrowchartIgnoresCompareTocopy reads the core predicate, and the comparison query stops running for the ignored types), tests beside them, one.changeset/7495-…md(stop on breach; explain in the report)Container & model:
S,mode:subagent,model: opus(default judgement tier) —priority:p3, a ruled single-producer move.Clause-②: yes
Thread-read: 5809246443
Serial constraints cleared: open-PR file lists read 2026-09-24T21:37Z. objectui#10169 (seat 2, draft since 2026-09-20) edits
chart-presentation.tsat@@ -84,@@ -105and@@ -217(label i18n). This claim adds a separate predicate and is expected to be hunk-disjoint; whichever lands second mergesmain. No open PR touchesObjectChart.tsx'ssupportsCompareToorDatasetWidget.tsx. Livepm:dispatchedclaims were read at the same time: seat 3's objectui#7195 namesAdvancedChartImpl.tsxonly, which is file-disjoint.Scope
Ruling
5749199335(summon #25 class-1 item 17, letter A), ratified by the maintainer as5809246443: one predicate in@object-ui/core'schart-presentation, read by bothplugin-chartsandplugin-dashboard. The dashboard stops running the comparison query for pie, donut, funnel and scatter. B and C were refused.Clause-②: yes, because the predicate is a new export of@object-ui/core(its entry re-exportschart-presentationwithexport *). ⇒Fixes #7495.domain:uiseat #1 ·session_01BA3nKVUwKQJf8DBxrSVtNC· claim · 2026-09-24T21:41Zobjectstack-fleet commentedon Sep 24, 2026
os-dev-report
{
"issue": 7495,
"status": "done",
"branch": "claude/issue-7495-compareto-one-predicate",
"pr": "#10400",
"head_sha": "3861a6a6dbebd377d031a85751345c54c6bcaa70",
"session": "session_01BA3nKVUwKQJf8DBxrSVtNC (subagent: the parent PM session's id; container CLAUDE_CODE_REMOTE_SESSION_ID cse_01BA3nKVUwKQJf8DBxrSVtNC)",
"premise_still_valid": true,
"summary": "Ruling A implemented. @object-ui/core: new export chartTypeIgnoresCompareTo(chartType) in utils/chart-presentation.ts. It answers true for pie/donut/funnel/scatter and false for every other spec family, unknown strings, the empty string, case variants and undefined. It reaches the entry through the existing
export * from './utils/chart-presentation.js', so index.ts is untouched. The docblock states the rule and cites objectui#7495. plugin-charts: ObjectChart's local supportsCompareTo is deleted, and both call sites (the comparison fetch and the overlay synthesis) read the core predicate. Answers are identical for every input, so behaviour is unchanged. plugin-dashboard: thechartType === 'scatter'copy is deleted. DatasetWidget now decides compareTo once, near the top: it is undefined when the widget reaches the chart branch (!isMetric && !isTable) and its CHART_TYPE_MAP family ignores compareTo. So for pie/donut/funnel/pyramid/scatter/bubble chart widgets, the queryDataset selection carries no compareTo and no timeDimensions, byte-equal to the no-compareTo selection. The comparison pass no longer runs, and no overlay series is appended.chartTypemoved up to feed that decision. Metric/table/pivot widgets are unchanged, including a dimensionless pie, which renders as a metric tile and keeps its delta. One visible change, in the ruled direction: the executor's 'compareTo needs a dated window to shift' refusal (objectstack service-analytics dataset-executor.ts) used to replace such a chart when its filter had no dated window. That chart now renders. Zone-2 assumptions: all confirmed. (1) Sites located by content: ObjectChartconst supportsCompareTo = (ct?: string) =>with 2 call sites; DatasetWidgetconst chartIgnoresCompareTo = chartType === 'scatter'with 1. The published statement is .changeset/7402-scatter-ignores-compareto.md ('Charts that ignorecompareTo: pie, donut, funnel and — as of this change — scatter') and packages/plugin-dashboard/SKILL.md ('Pie, donut, funnel and scatter charts ignorecompareTo'); the original is in the plugin-charts CHANGELOG 6.1.0 entry ('Pie / donut / funnel ignorecompareTo'). (2) plugin-charts is only in plugin-dashboard's devDependencies; @object-ui/core is a dependency. (3) index.ts hasexport * from './utils/chart-presentation.js', so no index edit. (4) The comparison 'query' is not a separate call: it iscompareToplus the loweredtimeDimensionsin the single queryDataset selection. Both are now absent. (5) The chart-presentation hunk is@@ -440,3 +440,48 @@(appended at EOF).git apply --checkof #10169's three chart-presentation hunks on this head exits 0. Worktree cleaned (node_modules removed,git worktree removewithout --force, exit 0, directory gone). No server, watcher or monitor started. Harness attribution note: commit trailers carry only Claude-Session (no model Co-Authored-By, per the dispatch). The PR footer is the os-dev session-URL form, not the harness's alternate footer.","tests": "At HEAD 3861a6a, from the repo root through os-verify-lock:
pnpm exec vitest run packages/core/src/utils/ packages/plugin-charts/+ 9 text-reading pins (residue-namespaces-3546, dashboard-widget-options-census, base-bind-declared, chart-data-model-7113, object-chart-undeclared-keys-8885, widget-schema-anchors-7946, zod-mirror-parity, check-control-bytes, unconsumed-widget-option-claim-6186; found by a repo-wide git grep for tests that read these sources as text) -> 'Test Files 161 passed (161) / Tests 2484 passed (2484)', VERDICT command-exit 0.pnpm exec vitest run packages/plugin-dashboard/-> 'Test Files 124 passed (124) / Tests 1213 passed (1213)', VERDICT command-exit 0. Type-check:turbo run build --filter='@object-ui/plugin-dashboard^...' --concurrency=2(12/12 tasks), thenpnpm --filter @object-ui/{core,plugin-charts,plugin-dashboard} type-check(tsc --noEmit && tsc -p tsconfig.test.json) all exit 0.--listFilesOnlyshows each new test file in its package's test program. The core dist d.ts carriesexport declare function chartTypeIgnoresCompareTo. New pins: core chart-presentation.compareTo-7495.test.ts (every ChartTypeSchema.options family + undefined/unknown/''/case/bubble/pyramid + package-entry identity); plugin-charts ObjectChart.compareTo.corePredicate-7495.test.tsx (pie/donut/funnel: 1 aggregate call, no overlay; the core predicate spied and forced: true on bar suppresses both call sites, false on pie runs both; truthful bar control); plugin-dashboard DatasetWidget.compareTo.ignoredFamilies-7495.test.tsx (pie/donut/funnel/pyramid/scatter/bubble: no compareTo, no timeDimensions, selection equals the no-compareTo selection, 1 series; bar+line controls forward+lower+overlay; dimensionless pie metric keeps compareTo; undated pie renders vs bar control shows the refusal). Ablation 1 (dispatch-required), committed first, via objectstack scripts/ablation-replace.mjs: the dashboard decision pointed back atchartType === 'scatter'. anchor x1->x0, replacement x0->x1, blob 19d85e1ea29d->aa355d627edc. Result '13 failed | 13 passed (26)': all 12 pie/donut/funnel/pyramid pins + the undated-pie pin red; scatter/bubble and every control green. Restored: blob 19d85e1ea29d == HEAD,git diff HEADempty. Ablation 2: ObjectChart fetch site pointed back at a local four-family list -> '2 failed | 4 passed (6)' (the two forced-answer pins). Restored blob == HEAD,git diff HEADempty. No build between mutation and run: the tests load src (relative imports; the root vitest alias @object-ui/core -> packages/core/src). Gates at 3861a6a, all exit 0: check:control-bytes; check:new-line-citations (0 new); check-changeset-presence; check-changeset-no-major; check:changeset-claims (5 pending changesets name DatasetWidget.tsx/ObjectChart.tsx: 7125, 8650, 8885, humanize-label-5444, objectchart-schema-anchor-7946, each paragraph re-read and still true; self-contradiction reading clean); check:vi-mock-specifiers, vi-mock-inherit, vi-mock-override-shape, test-path-roots, pending-changeset-literals, self-import, phantom-deps, spec-symbols, esm-specifiers, unreferenced-sources, i18n-keys; governed-queue-guard --test: NOT GOVERNED. The gate family was hand-derived (objectui has no dispatch-gates deriver). check:readme-exports: NOT MEASURED, reason: no README edited and the gate judges README imports only. ESLint narrowed, as a measurement: (1) population = the root eslint.config.js, used by every package'seslint .(no package-level config); (2) --format json over the 6 touched TS/TSX files: 6 files, 0 errors, 59 warnings; base->head for the 3 edited sources 0/0->0/0, 0/36->0/36, 0/11->0/11; (3) invariance: no type-aware linting (no parserOptions.project/projectService) and no eslint-rules/ rule reads the filesystem. CI: in_progress, not awaited.","mcp_calls": "0 — no MCP GitHub tool was called.",
"api_writes": "2 REST writes + git pushes. (1) pr_create through the fleet-write relay: POST /repos/objectstack-ai/objectstack/dispatches (request fw-20260924T221528Z-fc0ac6, run 36066400446, conclusion success), executing POST /repos/objectstack-ai/objectui/pulls (draft) -> #10400. The body was read back byte-identical (10695 bytes, no differing byte). (2) This os-dev-report comment, POST /repos//issues/7495/comments via post-stamped.mjs --repo=objectstack-ai/objectui. git push x3 on the claimed branch (empty-branch probe, fafc817, 3861a6a). Zero label writes. The PR body was written once, never PATCHed. Reads: REST GETs through the container proxy (card 7495 + comments, precedents 4389 and 7402, PR 10169 diff, PR 10400 read-back).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · observation, not class a/b/c: ObjectChartSchema['chartType']'s declared union (packages/types/src/objectql.ts: 'bar' | 'column' | 'horizontal-bar' | 'line' | 'area' | 'pie' | 'donut' | 'scatter') omits funnel/radar/treemap/sankey/combo, yet DashboardRenderer's object-chart branch sets
chartType: resolvedWidgetTypefrom widgetDispatch's SERIES_CHART_TYPES, which includes them. No failing behaviour was established, so it went only into PR #10400's Acceptance notes. Dedupe words: ObjectChartSchema chartType union funnel, object-chart resolvedWidgetType, SERIES_CHART_TYPES declared union","carrier: 承接者:无 · observation: packages/plugin-dashboard/SKILL.md already states the four families ignore compareTo; it is still accurate and was not edited (outside the claimed file surface). Noted in Acceptance notes only."
]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
✅ ACCEPT — PR objectui#10400 at
3861a6a6d· contract review PASS · entering the merge queuedomain:uiseat #1,session_01BA3nKVUwKQJf8DBxrSVtNC. The claim saysClause-②: yes:@object-ui/coregains the exportchartTypeIgnoresCompareTo. A review-tier contract review ran against the card, the ruling (5749199335, ratified5809246443) and the PR only, and it PASSED. The seat adopts it after spot-checking two readings at3861a6a6d: the export reaches the entry throughpackages/core/src/index.ts'sexport *, and bothObjectChartcall sites read the core predicate.Maintainer veto window. The one visible change is inside ruling A, but the ruling's risk line (「a query nobody's output consumed stops running」) understated it. A pie, donut, funnel or scatter chart widget with
compareToand no dated window used to be replaced by the executor's refusal 「compareTo needs a dated window to shift」, and now renders. The changeset states this, and pins cover both directions. The seat does not hold landing for it; it is named in the round report.Contract review
Served-tier:
CONTRACT_REVIEW_TIERHead-sha:
3861a6a6dbebd377d031a85751345c54c6bcaa70Inputs read in full: card objectui#7495 (body with 维护者速读 and 四棱 block, triage 5548747753, ruling 5749199335 = summon #25 class-1 item 17 letter A, ratification 5809246443, claim 5822726716,
os-dev-report5823163208); precedents objectui#4389, objectui#7402 and.changeset/7402-scatter-ignores-compareto.md, plugin-charts CHANGELOG 6.1.0 entry (packages/plugin-charts/CHANGELOG.md:1728「Pie / donut / funnel ignorecompareTo」); PR #10400 body, all 7 changed files,git diff origin/main...3861a6a6d(2 commitsfafc8172b,3861a6a6d; 7 files, +589 −43).origin/mainhas not moved any of the three edited sources since the merge base00cdaff1f(git diff 00cdaff1f origin/main --stat -- ...is empty;git merge-treereports 0 conflicts).① Derived judgments
New public export
chartTypeIgnoresCompareTo(chartType)on@object-ui/core.packages/core/src/utils/chart-presentation.ts:444-487:CHART_TYPES_IGNORING_COMPARE_TO = new Set(['pie','donut','funnel','scatter']),return typeof chartType === 'string' && SET.has(chartType). Reachable from the entry:packages/core/src/index.ts:99export * from './utils/chart-presentation.js'(untouched; the core pin asserts entry identity). Answers: true for exactly the four families, false forundefined,'', unknown strings and case variants ('Pie','SCATTER') — pinned inchart-presentation.compareTo-7495.test.tsover the run-timeChartTypeSchema.optionspopulation. The set equals the published statement byte for byte: CHANGELOG 6.1.0 「Pie / donut / funnel」 plus.changeset/7402「and — as of this change — scatter」;bubble/pyramidare widget aliases, mapped by callers, not families (7402's own wording: 「thebubblewidget type that renders as a scatter」). Case-sensitivity is right: the spec'sChartTypeSchemais az.enumof lowercase spellings (objectstackpackages/spec/src/ui/chart.zod.ts:110-150),normalizeChartSchemaand ObjectChart never case-fold (grep toLowerCase packages/plugin-charts/src/ObjectChart.tsxempty), and the retired local predicate was case-sensitive too. Docblock checked claim by claim: pie/funnel arms drawseries[0]?.dataKeyonly (AdvancedChartImpl.tsx:1588,:1654); scatter y readsseries[0]?.dataKey(:1957); plugin-charts is only a devDependency of plugin-dashboard (packages/plugin-dashboard/package.json: dependencies has core, devDependencies has plugin-charts); objectui#4389 is the duplicate-glue precedent the card and ruling both cite. Accurate. PASSObjectChart: localsupportsCompareTodeleted, both call sites read core. Diffpackages/plugin-charts/src/ObjectChart.tsx@@ -572,24 +572,6removes the closure;:805wantsComparison = !!compareTo && !chartTypeIgnoresCompareTo(schema.chartType);:1102enableComparisonSerieslikewise. Truth table: old closure bodyct !== 'pie' && ct !== 'donut' && ct !== 'funnel' && ct !== 'scatter'; new!(typeof ct === 'string' && SET.has(ct)). For any string these are the same boolean; forundefinedboth give true (comparison runs); for a non-string value the old returned true and the new returns!false= true. Both sites still read the rawschema.chartType(no normalisation before or after), including the run-time-only values DashboardRenderer's object-chart branch forwards (DashboardRenderer.tsx:672chartType: resolvedWidgetType, resolved throughwidgetDispatch.tsCHART_TYPE_ALIASES, sofunnelarrives asfunnel,pyramidasfunnel,bubbleasscatter— same answers as before). Behaviour identical for every input; the forced-spy pin covers both call sites, and the ablation (local four-family list at the fetch site) went red on exactly the two forced pins. PASSDatasetWidget: the=== 'scatter'copy is deleted;compareTodecided once at the top.DatasetWidget.tsx:439-469:widgetType,isMetric(METRIC_TYPES.has(widgetType) || dimensions.length === 0),isTable,chartType = CHART_TYPE_MAP[widgetType] ?? 'bar'are hoisted (pure values, no hook-order change), thencompareTo = !isMetric && !isTable && chartTypeIgnoresCompareTo(chartType) ? undefined : widget?.compareTo. Every downstream reader follows from that one value:compareWindowsmemo (:582) lowers no window, the selection (:620-621) carries neithertimeDimensionsnorcompareTo,signature(:606) stringifiesnull,comparedValues(:861) is[], andcomparisonSeries(:1574) is empty; the old guard at:1521-1568is gone (git grep "=== 'scatter'" packages/plugin-dashboard/srcexcluding tests: empty). Family mapping is right:CHART_TYPE_MAP(:364-386) mapspyramid → 'funnel'andbubble → 'scatter', the same tablewidgetDispatch.ts:39-40uses, and the removed guard already operated post-map (bubble was covered bychartType === 'scatter'). Extending topyramid/bubbleis inside the ruling: the ruling names chart FAMILIES and the predicate is asked about the resolved family, exactly as 7402 published bubble as 「renders as a scatter」. Note for the record: the spec (17.4.0,dashboard.zod.tsstageOrder docblock,chart.zod.tstaxonomy note) says 「There is nopyramidwidget type — writefunnel」 and listsbubbleas a fallback-only variant, so both aliases are reachable only through stored legacy metadata; that is the same footing 7402 gavebubble, and harmless here. Metric/table/pivot path unchanged: the gate is!isMetric && !isTable, so a dimensionlesspie(isMetric true) keepscompareTo, pinned (selection carriescompareToandtimeDimensions; the chart double is never called). Chart branch captions:compareLabelis read only by the metric branch (:994), the table branch (:1040,:1133,:1160-1193) and the overlay series label (:1586), so no chart-branch caption disappears. Combo control untouched (DatasetWidget.comboPresentation.test.tsxstill expects the overlay). PASSThe named visible change: undated compare-to pie/donut/funnel/scatter now renders instead of showing the executor refusal. The refusal 「compareTo needs a dated window to shift」 is the executor's answer to the comparison query (objectstack
service-analyticsdatasetInvalidError(...), seen in its test doubles). The ruling's operative sentence is 「the dashboard stops running the comparison query ... for pie / donut / funnel / scatter」 and its premise is 「spec 声明 over 实现」 with the published statement 「ignorecompareTo」 as the contract; a refusal issued for acompareTothe statement says is ignored was the dashboard deviating from that statement, so removing it is the ruled direction, not a new decision. The ruling's own risk line (「a query nobody's output consumed」) understates this one output, which is why it belongs on the record: the dev disclosed it in the PR body and in the changeset prose that ships to the CHANGELOG, and pinned it both ways (pie renders,role=alertabsent; bar control still shows the refusal). No floor moves (nothing an author can write is rejected or renamed). PASS — surfaced to the maintainer under ③, not blocking.Residual readers of the retired predicates / reliance on the comparison being requested.
git grep -nE "IgnoresCompareTo|supportsCompareTo|IGNORING_COMPARE_TO"overpackages/*/srcandapps/*/srcexcluding tests lists exactly the one declaration, the exported function, ObjectChart's two call sites and DatasetWidget's one (the PR's done-when, re-run at head: holds). Tests: the existingDatasetWidget.compareTo.scatter.test.tsxandObjectChart.compareTo.scatter.test.tsxassert overlay absence with doubles that return__compareunconditionally; no test asserts that a pie/donut/funnel dataset widget forwardscompareTo. Docs:packages/plugin-dashboard/SKILL.md:25-26and:159-164already publish the four families as ignoring (unchanged, still true). Other packages:plugin-report,data-objectstack,ObjectMetricWidgetcarry no chart-family list (git grep compareToproducers enumerated). One stale mention outside the accept set:.changeset/7194-scatter-one-measure.md:22namessupportsCompareToas the mechanism — its behaviour claim stays true, only the symbol name is now historical (see ②). PASSAccept set. No spec key, prop, or renderer arm is added or removed;
compareToon the ignoring families is still accepted and now ignored on both paths, which is the published statement. The renderer is not taught to draw a pie comparison (ruling's ⛔ respected:AdvancedChartImpl.tsxuntouched). PASS② Semver level
.changeset/7495-compareto-one-predicate.mddeclares@object-ui/core: minorand@object-ui/plugin-dashboard: minor.Clause-②: yes(PR body line 2) is right — a new export on core's entry widens the published surface — and objectstack AGENTS.md's rule 「yestakes at leastminor」 is satisfied. plugin-dashboardminoris consistent with 「objectui 自身的破坏性变更也标 minor」 and the change is not breaking (no FROM→TO migration owed). Nomajor(Changeset Bump Policy: success). Consistent.@object-ui/plugin-chartsundeclared with a source change: legitimate. The gate's rule (scripts/check-changeset-presence.mjsheader, AGENTS.md:167 「要的是"声明一次",不是强制发版」) asks for one declaration per change, not one per touched package;Changeset Declarationat head: success (「6 source files of 3 released packages, 1 changeset」). All three packages sit in the onefixedgroup of.changeset/config.json, so plugin-charts is versioned with core regardless, and its@object-ui/corerange isworkspace:*(rewritten to the exact release version at publish), so the new import cannot pair with an older core. The 7495 body itself records that ObjectChart now reads the core predicate. Right.CHART_TYPE_MAP), the 「gated only the overlay series, never the query」 history (old:1568guard only fedcomparisonSeries), the byte-equal selection (pinned withtoEqualagainst the no-compareToselection), the undated-window visible change, the unchanged list, the metric-tile carve-out, and the reachability (git grep -l compareTo -- 'examples/**'= 0, control"type": "bar"= 5). Self-contradiction reading: none (no declared package is negated in the body)..changeset/7402stays true (it speaks of the inline path's query and of the dashboard's series only);7125,8650(:48schema-shape mention),8885(schema binding ofcompareTo),humanize-label-single-home-5444,objectchart-schema-anchor-7946describe no line this PR changes (Changeset Claim Re-read: success). One non-blocking note:.changeset/7194-scatter-one-measure.md:22citessupportsCompareToby name; after this PR that local symbol no longer exists, though the sentence's behaviour claim (ObjectChart synthesises no overlay for a compare-to scatter) remains true. A one-line touch (「supportsCompareTo, since objectui#7495 core'schartTypeIgnoresCompareTo」) can ride whichever PR next edits that file; it does not have to be fixed here.③ Boundary flags
ObjectChartSchema['chartType']union omits funnel/radar/treemap/sankey/combo whileDashboardRenderer.tsx:672passes them at run time (packages/types/src/objectql.ts:4234). Reading confirmed:widgetDispatch.tsSERIES_CHART_TYPESincludes them and the object-chart branch forwardsresolvedWidgetTypeverbatim; the new charts pin casts at one call and says why. Nothing rejects or drops authored metadata (the prop isany-tolerant at the renderer), so under os-dev's three classes this is an observation, not (a)/(b)/(c). Fine — not blocking; the dev's carrier 「承接者:无」 reading is correct. If the maintainer wants the declared union to match the run-time population, that is a separate types card, not this PR's.packages/plugin-dashboard/SKILL.mdnot edited.:25-26「Pie, donut, funnel and scatter charts ignorecompareTo」 and:159-164remain true and are now true of the query as well. One nuance, non-blocking: bullet:156-158「Filters with no bounded date range — ... the widget reports that instead of rendering a comparison」 is now family-dependent (an ignoring family draws its chart silently, which bullets 2–3 already promise). Not class (a): no example fails. Fine; a precision edit can ride the next SKILL.md change.compareToon an ignoring family stays declared-but-inert metadata (the card's facet ③); the ruling accepted that and refused the renderer change, so an authoring-time refusal would be a new card, not this PR.chart-presentation.tshunk is@@ -440,3 +440,48 @@(appended afterchartConfigPresentation); fix(dashboard,charts): two declared-translatable dashboard surfaces now resolve (objectui#10132) #10169's are@@ -84,@@ -105,@@ -217. Probe in my worktree:git apply --checkof fix(dashboard,charts): two declared-translatable dashboard surfaces now resolve (objectui#10132) #10169's chart-presentation patch on3861a6a6dexits 0. Disjoint.Fixes #7495andClause-②: yes. Correct. Every behaviour claim in the body has a pin: pie/donut/funnel one aggregate call + no overlay (charts pin), forced-answer both sites (charts pin), six widget types nocompareTo/notimeDimensions/equal selection/one series (dashboard pin), bar+line controls, dimensionless pie keeps it, undated pie renders vs bar refusal (dashboard pin), predicate over the spec enum + entry identity (core pin).CI at head: 40 success · 3 skipped · 0 failed · 0 in progress
Implemented-by:
claude/issue-7495-compareto-one-predicateReviewed-by:
session_01BA3nKVUwKQJf8DBxrSVtNCVERDICT: PASS
Out of scope
.changeset/7194-scatter-one-measure.md:22names the now-deleted localsupportsCompareTo. Its behaviour claim is still true, and the name can be fixed on that file's next edit.packages/plugin-dashboard/SKILL.mdstays true, but its 「no bounded date range — the widget reports that」 bullet is now family-dependent. It is a precision edit for the next SKILL.md change.ObjectChartSchema['chartType']union omits funnel / radar / treemap / sankey / combo, which the dashboard passes. That is an observation only: nothing rejects or drops metadata.domain:uiseat #1 · review · 2026-09-24T22:32Zfix(dashboard,core): one declaration of which chart families ignore c…