fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do - #7498

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto
Sep 3, 2026
Merged

fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do#7498
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7402

Clause-②: yes — a published behaviour statement changes, so this PR stays draft, is not enqueued and has no auto-merge: the domain:ui seat runs its in-seat contract review before it lands.

Implements the maintainer ruling recorded on #7402 (comment 5523068497, 2026-09-03, verbatim 「同意」): (b)compareTo on a scatter is not supported, and scatter joins pie / donut / funnel on the ignore list.

What changed

filechange
packages/plugin-charts/src/ObjectChart.tsxsupportsCompareTo excludes scatter as well as pie / donut / funnel. The comparison fetch is skipped entirely, so no MEASURE__comparison column exists and augmentedSeries never appends the overlay.
packages/plugin-dashboard/src/DatasetWidget.tsxthe chart path gains chartIgnoresCompareTo (chartType === 'scatter'), which suppresses comparisonSeries. Written against the CHART type, so it covers both widget-type spellings — scatter and bubble both map to chartType: 'scatter' through CHART_TYPE_MAP.
packages/plugin-dashboard/SKILL.mdthe current-tense published statement of which families ignore compareTo, in both places it appears.
.changeset/7402-scatter-ignores-compareto.mdminor on both packages, stating plainly that a published capability is removed and naming the condition for its return.

A compare-to document therefore never reaches the two-or-more-series scatter refusal of #7194 — the primary draws, and nothing is refused. #7194 remains open and is unaffected by this PR.

Why removal rather than a refusal

A scatter binds ONE measure and the renderer reads y through the single YAxis dataKey={series[0].dataKey}, so the synthesised overlay was plotted on the primary's y: "previous period" painted exactly on top of "current". Drawing it honestly needs the multi-measure projection recorded as option A of #7194, which is not built. Per the ruling the capability is removed until that projection arrives, and returns with it — one payment, no transition. A refusal was rejected because it names a key the author never wrote and loses the correctly drawn primary too.

Verification (all figures observed on b68c77a17, on a SHARED container — heavy runs went through this box's verify lock, so wall-clock absolutes are contended readings)

  • New pins, both pathspnpm exec vitest run from the repo root over the two new files: 2 files passed, 6 tests passed.
  • Both package suitespnpm exec vitest run --maxWorkers=2 packages/plugin-charts/ packages/plugin-dashboard/ from the repo root: 131 files passed, 1208 tests passed, 0 failed (216s).
  • Reverse verification — with ONLY the two source files reverted to origin/main (20cb8db9b) over the new tests: 4 failed | 2 passed. Red: both scatter pins in plugin-charts and the scatter + bubble cases in plugin-dashboard. Green: the two bar CONTROLS — which is the point of having them, since a regression that suppressed every comparison series would otherwise satisfy every new assertion. The mutation was proven on disk before the run (blob hash moved from 92a0b536/0189be13, anchored grep counts 1/20/0, pre-fix text back at 1/1); the restore was proven by state afterwards (blob hashes equal HEAD's again, git diff HEAD empty), under trap … EXIT INT TERM with absolute paths.
  • type-checkpnpm --filter @object-ui/plugin-charts --filter @object-ui/plugin-dashboard run type-check: both Done, exit 0. Both new test files were confirmed to be IN the checked program (tsc --listFiles, 1 hit each) rather than assumed to be.
  • lint — same two packages, exit 0: 175 files linted, 0 errors, 748 warnings, all warnings the repo's pre-existing no-explicit-any / no-unused-vars baseline in files this PR does not touch. Narrowed to the two changed packages deliberately: the file set comes from eslint's own flat config, the count from --format json, and the config enables no type-aware linting (no parserOptions.project / projectService), so this diff cannot move the verdict on any file it does not touch.
  • Gatescheck:control-bytes (6160 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, check:changeset-no-major: all exit 0, each read off the gate's own verdict line.

Two judgment calls, surfaced rather than decided quietly

1. The historical CHANGELOG.md line is left as written.packages/plugin-charts/CHANGELOG.md:1727 is generated by changesets and records what a released version did; editing it rewrites history that was true when it shipped. The same sentence also sits in three other packages' CHANGELOGs (types, core, plugin-dashboard) — all historical fan-out of one changeset, all left alone. The current-tense publication of that list turned out to be packages/plugin-dashboard/SKILL.md, which is updated here in both places, and the new statement is carried into the next release notes by this PR's changeset. Nothing was silently rewritten.

2. comparisonStyle(s, 'scatter') (AdvancedChartImpl.tsx:92, call site :1758) is NOT dead, so it stays.variant is an authorable series key on the chart schema, read from raw input in normalizeChartSchema.ts:247 and from authored chartConfig in @object-ui/core's chart-presentation.ts:125. A hand-authored chartType: 'scatter' with series: [{ dataKey, variant: 'comparison' }] reaches that branch directly, and being a single-series chart it is not caught by #7194's refusal either. What this PR removes is only the SYNTHESISED overlay; the authored spelling is untouched.

Filed, not fixed here

#7495 — the compareTo chart-family exclusion list now exists in two places and disagrees: DatasetWidget still runs the comparison query and appends an unused overlay series for pie / donut / funnel (the renderer draws series[0] only, so nothing is mis-drawn). Out of this card's declared scope; that one wants its own decision because it changes dashboard behaviour for three more families.

Reachability at the time of the change

0 authored scatter/bubble instances in-repo across both spellings, and no compareTo at all in examples/**/*.json; control "type": "bar" fires at 5 example files. Incidence in deployed tenant metadata is not measurable from this repo.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…pie/donut/funnel do
A `chartType: 'scatter'` chart — and the dashboard widget types `scatter` and
`bubble`, which both map onto it — synthesised a muted "previous period"
overlay series when `compareTo` was set. A scatter binds ONE measure and the
renderer reads y through the single `YAxis dataKey={series[0].dataKey}`, so
that overlay was plotted on the PRIMARY series' y: the previous period was
painted exactly on top of the current one (#7194).
Ruling on #7402 (maintainer, 2026-09-03): remove the published capability
rather than keep drawing that picture. `supportsCompareTo` in ObjectChart and
the DatasetWidget chart path now exclude scatter exactly as they exclude
pie / donut / funnel, so the primary draws, no `__comparison` / `__compare`
series is synthesised, and a compare-to document never reaches the
two-or-more-series scatter refusal of #7194. It returns with the multi-measure
projection recorded as option A of #7194.
`packages/plugin-dashboard/SKILL.md` — the current-tense published statement of
which families ignore `compareTo` — is updated. The historical CHANGELOG entry
that first published the list is left as written.
One pin per path plus a mandatory `bar` control that still gets its overlay;
without the control a regression suppressing every comparison series would
pass every new assertion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-DBHpSRoB.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.32KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Clause-② in-seat contract review — PASS

Commissioned by the domain:ui execution seat (PM session session_01EMrWaQw3XS5DxTHxp4yRyC) per the in-seat review route (维护者 2026-08-31 裁定). This seat runs opus and is not at tier, so it commissioned an isolated reviewer rather than self-reviewing. The reviewer was given only the card, the ruling and the PR — ⛔ not the dispatch order and not this seat's opinion.

Tier verification (维护者 2026-08-27 裁定 — a commissioned review is legitimate only if verified): every harness-stamped model field in the transcript was read before adopting — 96 stamps, all claude-fable-5-1, no other value. One string matching a fallback pattern was inspected individually and is the word "overloaded" inside a source comment the reviewer was reading (colors is overloaded kanban-style), not a model fallback. CONTRACT_REVIEW_TIER is claude-fable-5 (objectstack/scripts/pm/dispatch-gates.mjs:8091); claude-fable-5-1 is at or above that floor. ⇒ Verified, so the verdict is adopted verbatim — ⛔ nothing below is rewritten, abridged or polished.


Contract review — objectui PR #7498 (head b68c77a), card #7402, ruling comment 5523068497 option (b)

① Derived judgments (accept set / published surface)

Inline chart path — packages/plugin-charts/src/ObjectChart.tsx

  • supportsCompareTo adds ct !== 'scatter' (PR head :350-351). It is read at exactly two sites and both are gated: :553wantsComparison (the second runAggregate and the <valueKey>__comparison merge at :600) and :811enableComparisonSeries (the overlay append at :829-838). For chartType: 'scatter' no comparison fetch runs, no __comparison column exists, no overlay series is synthesised. Right — matches ruling (b) verbatim.
  • The dataset-bound inline branch (:543-556) never forwards compareTo to queryDataset, and finalSchema (:872-874) takes datasetChart.series, not augmentedSeries. There is no second inline overlay path the predicate misses. Right.
  • Every other reader of the column, verified repo-wide (packages/**, apps/**, non-test): __comparison / COMPARISON_SUFFIX appear only in ObjectChart.tsx (:41 export, :600, :808). variant: 'comparison' readers: AdvancedChartImpl.tsx:92 (comparisonStyle), :1847-1871 (bar-arm colour pairing), normalizeChartSchema.ts:247, packages/core/src/utils/chart-presentation.ts:125, DatasetWidget.tsx:1419. All consume a series that may carry the variant; none requires one to exist for a scatter. The scatter arm (AdvancedChartImpl.tsx:1694-1777) legend uses nameKey={scatterYKey} and the default ChartTooltipContent; no legend, tooltip, export, click payload, or saved-view reader depends on the overlay. Removal is exactly the ruling's width, not wider.

Dashboard path — packages/plugin-dashboard/src/DatasetWidget.tsx

  • chartIgnoresCompareTo = chartType === 'scatter' (:1418) gates comparisonSeries (:1420). chartType comes from CHART_TYPE_MAP[widgetType] ?? 'bar' (:1373); the map has scatter: 'scatter' and bubble: 'scatter' (:377-378). Both spellings verified in source.
  • bubble by any other path: widgetDispatch.ts:40 aliases bubble → scatter; DashboardRenderer.tsx:602-640 builds the non-dataset chart with chartType: resolvedWidgetType (= scatter) and forwards compareTo (:639) — lands on ObjectChart's predicate; DashboardGridLayout.tsx:223-258 passes dispatch.chartType and forwards no compareTo at all; a dataset-bound bubble reaches DatasetWidget whole (DashboardRenderer.tsx:920). No product path delivers a raw 'bubble' to either predicate. The ruling's "widget types scatter and bubble" is satisfied.
  • comparedValues (:759) is untouched, so metric/table/pivot/export paths are unaffected. The chart return (:1502-1512) renders no comparison caption — compareCaption (:1007) sits in the table branch; the overlay label at :1433 was compareLabel's only chart-path carrier. No orphan "vs previous year" caption survives on a scatter. Right.
  • Semantics differ from inline by design: the dashboard still issues the comparison query (executor attaches __compare) and only declines to draw it. That is the same author-visible behaviour pie/donut/funnel have on the dashboard today (no exclusion there at all; renderer draws series[0]). "Exactly as pie / donut / funnel" holds at the visible level; the internal asymmetry is stated in the code comment and filed (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). Right.

Published surface — packages/plugin-dashboard/SKILL.md

  • :24-26: "For chart widgets (line / area / bar / horizontal-bar / combo), a muted second series … Pie, donut, funnel and scatter charts ignore compareTo." True at the author-visible level on both paths; does not overclaim a skipped query on the dashboard. Accurate.
  • :161-164: "Scatter charts, and the bubble widget type that renders as one … compareTo is ignored until scatter can project a second measure (objectui#7194 option A); ruled in objectui#7402." bubble claim verified against both maps; return condition matches the ruling. Accurate.
  • Governance fact, stated for the record: this file is not in GOVERNED_SURFACES (scripts/check-governed-queue-guard.mjs:201 — the governed/published skills tree is root skills/**, i.e. skills/objectui/), and not in plugin-dashboard/package.jsonfiles (dist, README.md, CHANGELOG.md, LICENSE). It is an in-repo authoring guide, not the npm-published or governed skills/** surface. I judged its wording as a contract statement regardless; it holds.

Changeset — .changeset/7402-scatter-ignores-compareto.md

  • "no comparison query is issued on the inline chart path" — true and correctly scoped. "No <measure>__comparison (inline) / <measure>__compare (dashboard) series is appended" — true. "bubble … renders as a scatter" — true.
  • One imprecision, inherited not introduced: "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive, but radar also draws the appended series on both paths (supportsCompareTo('radar') is true; CHART_TYPE_MAP.radar = 'radar'; the radar arm at AdvancedChartImpl.tsx:1675-1687 maps every series, unstyled). Pre-PR SKILL.md:24 carried the same list minus scatter. Not a ruling defect; optional.

Unmentioned changes: none. Six files; four in the PR table, two tests in the verification section; the DatasetWidget comment's #7495 note is mentioned.

Base drift: PR is 2 commits behind main (20cb8db..48c19bd); that delta touches only .changeset/* and a new DatasetWidget.dateMeasure.test.tsx. No overlap. #7400 has not merged (no refusal on this head's AdvancedChartImpl); nothing here depends on it.

② Semver

  • Declared: minor on both packages. scripts/check-changeset-no-major.mjs header: "objectui's own breaking changes ship as minor with the break spelled out in the changeset body"; major is reserved for the objectstack-synchronised bump (OBJECTUI_ALLOW_MAJOR=1). All 39 packages sit in one fixed group, so the level is group-wide anyway.
  • The body spells the break out in bold ("This removes a published capability, deliberately"), states before/after behaviour and the return condition.
  • I would assign minor. Matches.Changeset Bump Policy and Changeset Fixed Group Check both green.

③ Boundary flags

  • (a) CHANGELOG left as history — correct. Verified the four fan-out copies (packages/core/CHANGELOG.md:4901, packages/plugin-charts/CHANGELOG.md:1728, packages/plugin-dashboard/CHANGELOG.md:2731, packages/types/CHANGELOG.md:4085), all versioned history. Independent carrier sweep: md/mdx/ts/tsx/json/yaml repo-wide for pie|donut|funnel within 120 chars of compareTo → only SKILL.md:26; compareTo anywhere in content/**, docs/**, skills/**, packages/*/README.md, apps/** → none; JSDoc in packages/types/src and packages/core/src → no family list; broader phrasing ("comparison overlay", "period-over-period", "previous period series") → only SKILL.md. The implementer's claim holds; no carrier was missed. Answer to the dev report's open question: leave it. The ruling's "the plugin-charts CHANGELOG … statement becomes …" is satisfied by mechanism — this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release, which is exactly how the original sentence arrived there. Recommend the seat record that reading rather than reopen it.
  • (b) comparisonStyle(s, 'scatter') kept — correct. Verified: def AdvancedChartImpl.tsx:91-92, scatter call site :1758 (PR head); normalizeChartSchema.ts:246-247 accepts raw.variant ∈ {comparison, current, primary} from raw series; chart-presentation.ts:125 accepts {primary, comparison} from authored chartConfig. A single authored series with variant: 'comparison' reaches the branch and is not a two-series scatter. Deleting it would widen the removal beyond the ruling.
  • (c) 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 deferred — correct, and this PR stays coherent. The dashboard has no pie/donut/funnel exclusion to copy, so making the two lists agree means changing three more families' dashboard behaviour (query skip) — a separate decision, out of this card's scope. Within this PR, scatter's author-visible behaviour equals pie/donut/funnel on both paths; the one internal asymmetry (dashboard still queries) is shared with those three families today and is stated in both the changeset and the code comment with the issue link.

Pins — not vacuous

  • Ran in my own detached worktree at b68c77a (/home/user/objectui-review-7498, root vitest config aliases every @object-ui/* to src): forward 2 files / 6 tests passed.
  • Reverse verification, both sources reverted to 20cb8db with tests kept (mutation confirmed on disk: anchored grep counts 0/0): 4 failed / 2 passed — red: both ObjectChart scatter pins ("expected vi.fn() to be called 1 times, but got 2 times"), both DatasetWidget cases (scatter, bubble: "to have a length of 1 but got 2"); green: both bar controls. Restore verified (git diff HEAD --name-only empty).
  • Controls genuinely discriminate: ObjectChart's asserts aggregate called twice, series.length === 2, overlay variant: 'comparison', and data amount__comparison: 100; DatasetWidget's asserts series.length === 2 with revenue__comparevariant: 'comparison'. A change suppressing every comparison series fails both.
  • Coverage: both paths (inline aggregate; dashboard dataset) and both widget spellings (describe.each(['scatter','bubble'])). The non-dataset bubble → DashboardRenderer → ObjectChart('scatter') hand-off is covered transitively (data mapping with its own spec-parity test), not by a dedicated pin; acceptable.
  • Pre-existing ObjectChart.compareTo* / DatasetWidget.compareTo* suites on the PR head: 4 files / 36 tests passed.

CI by name (head b68c77a)

  • Green: Skill Example Check, Pre-Install Import Graph Check, Skill Guide Path Check, Skill Eval Token Check, Internal Docs Link Check, Changeset Declaration, Docs Route Eager Closure Check, Shell Escape Residue Scan, Live E2E (informational), Changeset Overwrite Report, Doc Component Type Check, Test (shard 3/4), Test (shard 4/4), Bundle Analysis, README Export Check, Lint, Build & E2E, Changeset Fixed Group Check, Control Byte Scan, Type Check, Governed Surface Queue Guard, Changeset Bump Policy, label, Inert vi.mock Specifier Check, Doc Snippet Type Check.
  • Skipped by design: dependabot, Test (coverage), Test (coverage shard).
  • Still in progress at review time: Test (shard 1/4), Test (shard 2/4) (started 11:29Z; shards 3/4 took ~10 min). Landing condition: both green. My local run of both packages' compareTo suites is the partial substitute, not a replacement.
  • Minor: the PR body cites scripts/pm/os-verify-lock.sh; it is not tracked in this repo (exists untracked in the main checkout and in ../objectstack). Immaterial to the verdict.

Verdict

PASS.

Non-required notes for the seat (not amendments): (1) optionally tighten the changeset's "Charts that keep the overlay:" sentence to "the families the overlay is designed for:" or add radar, since radar draws the appended series unstyled; (2) record on #7402 that the ruling's CHANGELOG sentence is met by the changeset compiling into packages/plugin-charts/CHANGELOG.md at release, closing the dev report's open question; (3) wait for Test (shard 1/4) and Test (shard 2/4) before any landing action. I left /home/user/objectui-review-7498 in place (detached, installed, closure built) — delete if not wanted.


Seat disposition

PASS ⇒ this seat clears the gate and lands it, per 「PASS ⇒ 同席剥标、ready、auto-merge」 — once the reviewer's own landing condition is met: Test (shard 1/4) and Test (shard 2/4) green by name. Holding until then.

The reviewer's note (2) corrects this seat, and I am adopting the correction. On #7402 I ruled option A and recorded it as "deviating from the ruling's literal word", flagging it for the director. The reviewer shows there is no deviation to flag: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I will correct that on #7402 rather than leave a false self-accusation standing.

Its carrier sweep is also stronger than the one I accepted: it checked content/**, docs/**, skills/**, package READMEs, JSDoc in types/core, and three broader phrasings, all independently — the implementer's "SKILL.md is the only current-tense carrier" claim holds on that wider basis.

Note (1) is optional and I am not requiring it: the radar imprecision is inherited from the pre-PR sentence, not introduced here, and widening this PR to fix inherited copy is the scope creep the lane avoids.


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 11:46
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Landing — contract review PASS, 32/32 green, ready + auto-merge

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Provenance: 「PASS ⇒ 同席剥标、ready、auto-merge」 (维护者 2026-08-31 裁定).

The reviewer's own landing condition was wait for Test (shard 1/4) and Test (shard 2/4). Both are now green — shard 1 at 11:44:13, shard 2 at 11:43:53. Full reading on head b68c77a17: 29 success, 3 skipped by design (dependabot, Test (coverage), Test (coverage shard …)), read individually across all 32, not in aggregate. mergeable_state: clean against main at 48c19bd4.

⚠️ A carrier half-state, recorded rather than glossed

Landing precondition ② is "needs:contract-review cleared on both carriers." Here there was nothing to clear — the label was never hung on either carrier:

carrierlabels
PR #7498plugin, plugin: charts, tests
card #7402pm:dispatched

The protocol has the PR creator hang needs:contract-reviewwith the reviewable contract increment, and Clause-②: yes was declared here in the body and in the claim comment. The label was not applied, so condition ② is satisfied only trivially — the gate never existed to be passed.

⛔ I am not treating that as equivalent to a cleared gate. The substance is satisfied: an isolated reviewer at CONTRACT_REVIEW_TIER reviewed this increment and returned PASS, which is what the label exists to force. But this is the second carrier defect this lane has hit today — the first was PR #7491, where the card-side Clause-② declaration was machine-invisible because no comment matched the claim marker. Two different failures, same class: the declaration was made and the mechanism that reads it was not fed.

⚠️ And I could not have caught either mechanically: scripts/pm/check-clause2-carriers.mjs exits 2 in this session (it needs its own GitHub token; this seat reaches GitHub only through MCP, and its own output says that is not a clearance). Both carrier readings here are manual, and a manual reading covers strictly less than the tool. Naming that so the next seat knows which instrument was used.

⭐ The review corrected me, and I have taken the correction

On #7402 I ruled the CHANGELOG question option A and recorded it as "deviating from the ruling's literal word," flagging it for the director to object to. The reviewer showed there is no deviation: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this PR's changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I am correcting that on #7402 rather than leaving a false self-accusation on the record.

Its carrier sweep for that question was also wider than the one I accepted — content/**, docs/**, skills/**, package READMEs, JSDoc in types and core, plus three broader phrasings, each checked independently — and the implementer's "SKILL.md is the only current-tense carrier" claim holds on that stronger basis.

One optional note deliberately NOT required

The reviewer observed the changeset's "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive while radar also draws the appended series. ⛔ Not required as an amendment: that imprecision is inherited from the pre-PR SKILL.md:24 sentence, not introduced here, and widening this PR to correct inherited copy is the scope creep the lane exists to avoid. Recorded so it is a known, chosen gap rather than an unnoticed one.

Housekeeping

The reviewer left /home/user/objectui-review-7498 in place (detached worktree, installed, closure built). It is a session-local scratch tree on a shared container — whoever next needs the disk can remove it; nothing depends on it.


Generated by Claude Code

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

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do - #7498

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto
Sep 3, 2026
Merged

fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do#7498
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7402

Clause-②: yes — a published behaviour statement changes, so this PR stays draft, is not enqueued and has no auto-merge: the domain:ui seat runs its in-seat contract review before it lands.

Implements the maintainer ruling recorded on #7402 (comment 5523068497, 2026-09-03, verbatim 「同意」): (b)compareTo on a scatter is not supported, and scatter joins pie / donut / funnel on the ignore list.

What changed

filechange
packages/plugin-charts/src/ObjectChart.tsxsupportsCompareTo excludes scatter as well as pie / donut / funnel. The comparison fetch is skipped entirely, so no MEASURE__comparison column exists and augmentedSeries never appends the overlay.
packages/plugin-dashboard/src/DatasetWidget.tsxthe chart path gains chartIgnoresCompareTo (chartType === 'scatter'), which suppresses comparisonSeries. Written against the CHART type, so it covers both widget-type spellings — scatter and bubble both map to chartType: 'scatter' through CHART_TYPE_MAP.
packages/plugin-dashboard/SKILL.mdthe current-tense published statement of which families ignore compareTo, in both places it appears.
.changeset/7402-scatter-ignores-compareto.mdminor on both packages, stating plainly that a published capability is removed and naming the condition for its return.

A compare-to document therefore never reaches the two-or-more-series scatter refusal of #7194 — the primary draws, and nothing is refused. #7194 remains open and is unaffected by this PR.

Why removal rather than a refusal

A scatter binds ONE measure and the renderer reads y through the single YAxis dataKey={series[0].dataKey}, so the synthesised overlay was plotted on the primary's y: "previous period" painted exactly on top of "current". Drawing it honestly needs the multi-measure projection recorded as option A of #7194, which is not built. Per the ruling the capability is removed until that projection arrives, and returns with it — one payment, no transition. A refusal was rejected because it names a key the author never wrote and loses the correctly drawn primary too.

Verification (all figures observed on b68c77a17, on a SHARED container — heavy runs went through this box's verify lock, so wall-clock absolutes are contended readings)

  • New pins, both pathspnpm exec vitest run from the repo root over the two new files: 2 files passed, 6 tests passed.
  • Both package suitespnpm exec vitest run --maxWorkers=2 packages/plugin-charts/ packages/plugin-dashboard/ from the repo root: 131 files passed, 1208 tests passed, 0 failed (216s).
  • Reverse verification — with ONLY the two source files reverted to origin/main (20cb8db9b) over the new tests: 4 failed | 2 passed. Red: both scatter pins in plugin-charts and the scatter + bubble cases in plugin-dashboard. Green: the two bar CONTROLS — which is the point of having them, since a regression that suppressed every comparison series would otherwise satisfy every new assertion. The mutation was proven on disk before the run (blob hash moved from 92a0b536/0189be13, anchored grep counts 1/20/0, pre-fix text back at 1/1); the restore was proven by state afterwards (blob hashes equal HEAD's again, git diff HEAD empty), under trap … EXIT INT TERM with absolute paths.
  • type-checkpnpm --filter @object-ui/plugin-charts --filter @object-ui/plugin-dashboard run type-check: both Done, exit 0. Both new test files were confirmed to be IN the checked program (tsc --listFiles, 1 hit each) rather than assumed to be.
  • lint — same two packages, exit 0: 175 files linted, 0 errors, 748 warnings, all warnings the repo's pre-existing no-explicit-any / no-unused-vars baseline in files this PR does not touch. Narrowed to the two changed packages deliberately: the file set comes from eslint's own flat config, the count from --format json, and the config enables no type-aware linting (no parserOptions.project / projectService), so this diff cannot move the verdict on any file it does not touch.
  • Gatescheck:control-bytes (6160 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, check:changeset-no-major: all exit 0, each read off the gate's own verdict line.

Two judgment calls, surfaced rather than decided quietly

1. The historical CHANGELOG.md line is left as written.packages/plugin-charts/CHANGELOG.md:1727 is generated by changesets and records what a released version did; editing it rewrites history that was true when it shipped. The same sentence also sits in three other packages' CHANGELOGs (types, core, plugin-dashboard) — all historical fan-out of one changeset, all left alone. The current-tense publication of that list turned out to be packages/plugin-dashboard/SKILL.md, which is updated here in both places, and the new statement is carried into the next release notes by this PR's changeset. Nothing was silently rewritten.

2. comparisonStyle(s, 'scatter') (AdvancedChartImpl.tsx:92, call site :1758) is NOT dead, so it stays.variant is an authorable series key on the chart schema, read from raw input in normalizeChartSchema.ts:247 and from authored chartConfig in @object-ui/core's chart-presentation.ts:125. A hand-authored chartType: 'scatter' with series: [{ dataKey, variant: 'comparison' }] reaches that branch directly, and being a single-series chart it is not caught by #7194's refusal either. What this PR removes is only the SYNTHESISED overlay; the authored spelling is untouched.

Filed, not fixed here

#7495 — the compareTo chart-family exclusion list now exists in two places and disagrees: DatasetWidget still runs the comparison query and appends an unused overlay series for pie / donut / funnel (the renderer draws series[0] only, so nothing is mis-drawn). Out of this card's declared scope; that one wants its own decision because it changes dashboard behaviour for three more families.

Reachability at the time of the change

0 authored scatter/bubble instances in-repo across both spellings, and no compareTo at all in examples/**/*.json; control "type": "bar" fires at 5 example files. Incidence in deployed tenant metadata is not measurable from this repo.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…pie/donut/funnel do
A `chartType: 'scatter'` chart — and the dashboard widget types `scatter` and
`bubble`, which both map onto it — synthesised a muted "previous period"
overlay series when `compareTo` was set. A scatter binds ONE measure and the
renderer reads y through the single `YAxis dataKey={series[0].dataKey}`, so
that overlay was plotted on the PRIMARY series' y: the previous period was
painted exactly on top of the current one (#7194).
Ruling on #7402 (maintainer, 2026-09-03): remove the published capability
rather than keep drawing that picture. `supportsCompareTo` in ObjectChart and
the DatasetWidget chart path now exclude scatter exactly as they exclude
pie / donut / funnel, so the primary draws, no `__comparison` / `__compare`
series is synthesised, and a compare-to document never reaches the
two-or-more-series scatter refusal of #7194. It returns with the multi-measure
projection recorded as option A of #7194.
`packages/plugin-dashboard/SKILL.md` — the current-tense published statement of
which families ignore `compareTo` — is updated. The historical CHANGELOG entry
that first published the list is left as written.
One pin per path plus a mandatory `bar` control that still gets its overlay;
without the control a regression suppressing every comparison series would
pass every new assertion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-DBHpSRoB.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.32KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Clause-② in-seat contract review — PASS

Commissioned by the domain:ui execution seat (PM session session_01EMrWaQw3XS5DxTHxp4yRyC) per the in-seat review route (维护者 2026-08-31 裁定). This seat runs opus and is not at tier, so it commissioned an isolated reviewer rather than self-reviewing. The reviewer was given only the card, the ruling and the PR — ⛔ not the dispatch order and not this seat's opinion.

Tier verification (维护者 2026-08-27 裁定 — a commissioned review is legitimate only if verified): every harness-stamped model field in the transcript was read before adopting — 96 stamps, all claude-fable-5-1, no other value. One string matching a fallback pattern was inspected individually and is the word "overloaded" inside a source comment the reviewer was reading (colors is overloaded kanban-style), not a model fallback. CONTRACT_REVIEW_TIER is claude-fable-5 (objectstack/scripts/pm/dispatch-gates.mjs:8091); claude-fable-5-1 is at or above that floor. ⇒ Verified, so the verdict is adopted verbatim — ⛔ nothing below is rewritten, abridged or polished.


Contract review — objectui PR #7498 (head b68c77a), card #7402, ruling comment 5523068497 option (b)

① Derived judgments (accept set / published surface)

Inline chart path — packages/plugin-charts/src/ObjectChart.tsx

  • supportsCompareTo adds ct !== 'scatter' (PR head :350-351). It is read at exactly two sites and both are gated: :553wantsComparison (the second runAggregate and the <valueKey>__comparison merge at :600) and :811enableComparisonSeries (the overlay append at :829-838). For chartType: 'scatter' no comparison fetch runs, no __comparison column exists, no overlay series is synthesised. Right — matches ruling (b) verbatim.
  • The dataset-bound inline branch (:543-556) never forwards compareTo to queryDataset, and finalSchema (:872-874) takes datasetChart.series, not augmentedSeries. There is no second inline overlay path the predicate misses. Right.
  • Every other reader of the column, verified repo-wide (packages/**, apps/**, non-test): __comparison / COMPARISON_SUFFIX appear only in ObjectChart.tsx (:41 export, :600, :808). variant: 'comparison' readers: AdvancedChartImpl.tsx:92 (comparisonStyle), :1847-1871 (bar-arm colour pairing), normalizeChartSchema.ts:247, packages/core/src/utils/chart-presentation.ts:125, DatasetWidget.tsx:1419. All consume a series that may carry the variant; none requires one to exist for a scatter. The scatter arm (AdvancedChartImpl.tsx:1694-1777) legend uses nameKey={scatterYKey} and the default ChartTooltipContent; no legend, tooltip, export, click payload, or saved-view reader depends on the overlay. Removal is exactly the ruling's width, not wider.

Dashboard path — packages/plugin-dashboard/src/DatasetWidget.tsx

  • chartIgnoresCompareTo = chartType === 'scatter' (:1418) gates comparisonSeries (:1420). chartType comes from CHART_TYPE_MAP[widgetType] ?? 'bar' (:1373); the map has scatter: 'scatter' and bubble: 'scatter' (:377-378). Both spellings verified in source.
  • bubble by any other path: widgetDispatch.ts:40 aliases bubble → scatter; DashboardRenderer.tsx:602-640 builds the non-dataset chart with chartType: resolvedWidgetType (= scatter) and forwards compareTo (:639) — lands on ObjectChart's predicate; DashboardGridLayout.tsx:223-258 passes dispatch.chartType and forwards no compareTo at all; a dataset-bound bubble reaches DatasetWidget whole (DashboardRenderer.tsx:920). No product path delivers a raw 'bubble' to either predicate. The ruling's "widget types scatter and bubble" is satisfied.
  • comparedValues (:759) is untouched, so metric/table/pivot/export paths are unaffected. The chart return (:1502-1512) renders no comparison caption — compareCaption (:1007) sits in the table branch; the overlay label at :1433 was compareLabel's only chart-path carrier. No orphan "vs previous year" caption survives on a scatter. Right.
  • Semantics differ from inline by design: the dashboard still issues the comparison query (executor attaches __compare) and only declines to draw it. That is the same author-visible behaviour pie/donut/funnel have on the dashboard today (no exclusion there at all; renderer draws series[0]). "Exactly as pie / donut / funnel" holds at the visible level; the internal asymmetry is stated in the code comment and filed (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). Right.

Published surface — packages/plugin-dashboard/SKILL.md

  • :24-26: "For chart widgets (line / area / bar / horizontal-bar / combo), a muted second series … Pie, donut, funnel and scatter charts ignore compareTo." True at the author-visible level on both paths; does not overclaim a skipped query on the dashboard. Accurate.
  • :161-164: "Scatter charts, and the bubble widget type that renders as one … compareTo is ignored until scatter can project a second measure (objectui#7194 option A); ruled in objectui#7402." bubble claim verified against both maps; return condition matches the ruling. Accurate.
  • Governance fact, stated for the record: this file is not in GOVERNED_SURFACES (scripts/check-governed-queue-guard.mjs:201 — the governed/published skills tree is root skills/**, i.e. skills/objectui/), and not in plugin-dashboard/package.jsonfiles (dist, README.md, CHANGELOG.md, LICENSE). It is an in-repo authoring guide, not the npm-published or governed skills/** surface. I judged its wording as a contract statement regardless; it holds.

Changeset — .changeset/7402-scatter-ignores-compareto.md

  • "no comparison query is issued on the inline chart path" — true and correctly scoped. "No <measure>__comparison (inline) / <measure>__compare (dashboard) series is appended" — true. "bubble … renders as a scatter" — true.
  • One imprecision, inherited not introduced: "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive, but radar also draws the appended series on both paths (supportsCompareTo('radar') is true; CHART_TYPE_MAP.radar = 'radar'; the radar arm at AdvancedChartImpl.tsx:1675-1687 maps every series, unstyled). Pre-PR SKILL.md:24 carried the same list minus scatter. Not a ruling defect; optional.

Unmentioned changes: none. Six files; four in the PR table, two tests in the verification section; the DatasetWidget comment's #7495 note is mentioned.

Base drift: PR is 2 commits behind main (20cb8db..48c19bd); that delta touches only .changeset/* and a new DatasetWidget.dateMeasure.test.tsx. No overlap. #7400 has not merged (no refusal on this head's AdvancedChartImpl); nothing here depends on it.

② Semver

  • Declared: minor on both packages. scripts/check-changeset-no-major.mjs header: "objectui's own breaking changes ship as minor with the break spelled out in the changeset body"; major is reserved for the objectstack-synchronised bump (OBJECTUI_ALLOW_MAJOR=1). All 39 packages sit in one fixed group, so the level is group-wide anyway.
  • The body spells the break out in bold ("This removes a published capability, deliberately"), states before/after behaviour and the return condition.
  • I would assign minor. Matches.Changeset Bump Policy and Changeset Fixed Group Check both green.

③ Boundary flags

  • (a) CHANGELOG left as history — correct. Verified the four fan-out copies (packages/core/CHANGELOG.md:4901, packages/plugin-charts/CHANGELOG.md:1728, packages/plugin-dashboard/CHANGELOG.md:2731, packages/types/CHANGELOG.md:4085), all versioned history. Independent carrier sweep: md/mdx/ts/tsx/json/yaml repo-wide for pie|donut|funnel within 120 chars of compareTo → only SKILL.md:26; compareTo anywhere in content/**, docs/**, skills/**, packages/*/README.md, apps/** → none; JSDoc in packages/types/src and packages/core/src → no family list; broader phrasing ("comparison overlay", "period-over-period", "previous period series") → only SKILL.md. The implementer's claim holds; no carrier was missed. Answer to the dev report's open question: leave it. The ruling's "the plugin-charts CHANGELOG … statement becomes …" is satisfied by mechanism — this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release, which is exactly how the original sentence arrived there. Recommend the seat record that reading rather than reopen it.
  • (b) comparisonStyle(s, 'scatter') kept — correct. Verified: def AdvancedChartImpl.tsx:91-92, scatter call site :1758 (PR head); normalizeChartSchema.ts:246-247 accepts raw.variant ∈ {comparison, current, primary} from raw series; chart-presentation.ts:125 accepts {primary, comparison} from authored chartConfig. A single authored series with variant: 'comparison' reaches the branch and is not a two-series scatter. Deleting it would widen the removal beyond the ruling.
  • (c) 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 deferred — correct, and this PR stays coherent. The dashboard has no pie/donut/funnel exclusion to copy, so making the two lists agree means changing three more families' dashboard behaviour (query skip) — a separate decision, out of this card's scope. Within this PR, scatter's author-visible behaviour equals pie/donut/funnel on both paths; the one internal asymmetry (dashboard still queries) is shared with those three families today and is stated in both the changeset and the code comment with the issue link.

Pins — not vacuous

  • Ran in my own detached worktree at b68c77a (/home/user/objectui-review-7498, root vitest config aliases every @object-ui/* to src): forward 2 files / 6 tests passed.
  • Reverse verification, both sources reverted to 20cb8db with tests kept (mutation confirmed on disk: anchored grep counts 0/0): 4 failed / 2 passed — red: both ObjectChart scatter pins ("expected vi.fn() to be called 1 times, but got 2 times"), both DatasetWidget cases (scatter, bubble: "to have a length of 1 but got 2"); green: both bar controls. Restore verified (git diff HEAD --name-only empty).
  • Controls genuinely discriminate: ObjectChart's asserts aggregate called twice, series.length === 2, overlay variant: 'comparison', and data amount__comparison: 100; DatasetWidget's asserts series.length === 2 with revenue__comparevariant: 'comparison'. A change suppressing every comparison series fails both.
  • Coverage: both paths (inline aggregate; dashboard dataset) and both widget spellings (describe.each(['scatter','bubble'])). The non-dataset bubble → DashboardRenderer → ObjectChart('scatter') hand-off is covered transitively (data mapping with its own spec-parity test), not by a dedicated pin; acceptable.
  • Pre-existing ObjectChart.compareTo* / DatasetWidget.compareTo* suites on the PR head: 4 files / 36 tests passed.

CI by name (head b68c77a)

  • Green: Skill Example Check, Pre-Install Import Graph Check, Skill Guide Path Check, Skill Eval Token Check, Internal Docs Link Check, Changeset Declaration, Docs Route Eager Closure Check, Shell Escape Residue Scan, Live E2E (informational), Changeset Overwrite Report, Doc Component Type Check, Test (shard 3/4), Test (shard 4/4), Bundle Analysis, README Export Check, Lint, Build & E2E, Changeset Fixed Group Check, Control Byte Scan, Type Check, Governed Surface Queue Guard, Changeset Bump Policy, label, Inert vi.mock Specifier Check, Doc Snippet Type Check.
  • Skipped by design: dependabot, Test (coverage), Test (coverage shard).
  • Still in progress at review time: Test (shard 1/4), Test (shard 2/4) (started 11:29Z; shards 3/4 took ~10 min). Landing condition: both green. My local run of both packages' compareTo suites is the partial substitute, not a replacement.
  • Minor: the PR body cites scripts/pm/os-verify-lock.sh; it is not tracked in this repo (exists untracked in the main checkout and in ../objectstack). Immaterial to the verdict.

Verdict

PASS.

Non-required notes for the seat (not amendments): (1) optionally tighten the changeset's "Charts that keep the overlay:" sentence to "the families the overlay is designed for:" or add radar, since radar draws the appended series unstyled; (2) record on #7402 that the ruling's CHANGELOG sentence is met by the changeset compiling into packages/plugin-charts/CHANGELOG.md at release, closing the dev report's open question; (3) wait for Test (shard 1/4) and Test (shard 2/4) before any landing action. I left /home/user/objectui-review-7498 in place (detached, installed, closure built) — delete if not wanted.


Seat disposition

PASS ⇒ this seat clears the gate and lands it, per 「PASS ⇒ 同席剥标、ready、auto-merge」 — once the reviewer's own landing condition is met: Test (shard 1/4) and Test (shard 2/4) green by name. Holding until then.

The reviewer's note (2) corrects this seat, and I am adopting the correction. On #7402 I ruled option A and recorded it as "deviating from the ruling's literal word", flagging it for the director. The reviewer shows there is no deviation to flag: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I will correct that on #7402 rather than leave a false self-accusation standing.

Its carrier sweep is also stronger than the one I accepted: it checked content/**, docs/**, skills/**, package READMEs, JSDoc in types/core, and three broader phrasings, all independently — the implementer's "SKILL.md is the only current-tense carrier" claim holds on that wider basis.

Note (1) is optional and I am not requiring it: the radar imprecision is inherited from the pre-PR sentence, not introduced here, and widening this PR to fix inherited copy is the scope creep the lane avoids.


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 11:46
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Landing — contract review PASS, 32/32 green, ready + auto-merge

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Provenance: 「PASS ⇒ 同席剥标、ready、auto-merge」 (维护者 2026-08-31 裁定).

The reviewer's own landing condition was wait for Test (shard 1/4) and Test (shard 2/4). Both are now green — shard 1 at 11:44:13, shard 2 at 11:43:53. Full reading on head b68c77a17: 29 success, 3 skipped by design (dependabot, Test (coverage), Test (coverage shard …)), read individually across all 32, not in aggregate. mergeable_state: clean against main at 48c19bd4.

⚠️ A carrier half-state, recorded rather than glossed

Landing precondition ② is "needs:contract-review cleared on both carriers." Here there was nothing to clear — the label was never hung on either carrier:

carrierlabels
PR #7498plugin, plugin: charts, tests
card #7402pm:dispatched

The protocol has the PR creator hang needs:contract-reviewwith the reviewable contract increment, and Clause-②: yes was declared here in the body and in the claim comment. The label was not applied, so condition ② is satisfied only trivially — the gate never existed to be passed.

⛔ I am not treating that as equivalent to a cleared gate. The substance is satisfied: an isolated reviewer at CONTRACT_REVIEW_TIER reviewed this increment and returned PASS, which is what the label exists to force. But this is the second carrier defect this lane has hit today — the first was PR #7491, where the card-side Clause-② declaration was machine-invisible because no comment matched the claim marker. Two different failures, same class: the declaration was made and the mechanism that reads it was not fed.

⚠️ And I could not have caught either mechanically: scripts/pm/check-clause2-carriers.mjs exits 2 in this session (it needs its own GitHub token; this seat reaches GitHub only through MCP, and its own output says that is not a clearance). Both carrier readings here are manual, and a manual reading covers strictly less than the tool. Naming that so the next seat knows which instrument was used.

⭐ The review corrected me, and I have taken the correction

On #7402 I ruled the CHANGELOG question option A and recorded it as "deviating from the ruling's literal word," flagging it for the director to object to. The reviewer showed there is no deviation: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this PR's changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I am correcting that on #7402 rather than leaving a false self-accusation on the record.

Its carrier sweep for that question was also wider than the one I accepted — content/**, docs/**, skills/**, package READMEs, JSDoc in types and core, plus three broader phrasings, each checked independently — and the implementer's "SKILL.md is the only current-tense carrier" claim holds on that stronger basis.

One optional note deliberately NOT required

The reviewer observed the changeset's "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive while radar also draws the appended series. ⛔ Not required as an amendment: that imprecision is inherited from the pre-PR SKILL.md:24 sentence, not introduced here, and widening this PR to correct inherited copy is the scope creep the lane exists to avoid. Recorded so it is a known, chosen gap rather than an unnoticed one.

Housekeeping

The reviewer left /home/user/objectui-review-7498 in place (detached worktree, installed, closure built). It is a session-local scratch tree on a shared container — whoever next needs the disk can remove it; nothing depends on it.


Generated by Claude Code

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

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do - #7498

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto
Sep 3, 2026
Merged

fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do#7498
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7402

Clause-②: yes — a published behaviour statement changes, so this PR stays draft, is not enqueued and has no auto-merge: the domain:ui seat runs its in-seat contract review before it lands.

Implements the maintainer ruling recorded on #7402 (comment 5523068497, 2026-09-03, verbatim 「同意」): (b)compareTo on a scatter is not supported, and scatter joins pie / donut / funnel on the ignore list.

What changed

filechange
packages/plugin-charts/src/ObjectChart.tsxsupportsCompareTo excludes scatter as well as pie / donut / funnel. The comparison fetch is skipped entirely, so no MEASURE__comparison column exists and augmentedSeries never appends the overlay.
packages/plugin-dashboard/src/DatasetWidget.tsxthe chart path gains chartIgnoresCompareTo (chartType === 'scatter'), which suppresses comparisonSeries. Written against the CHART type, so it covers both widget-type spellings — scatter and bubble both map to chartType: 'scatter' through CHART_TYPE_MAP.
packages/plugin-dashboard/SKILL.mdthe current-tense published statement of which families ignore compareTo, in both places it appears.
.changeset/7402-scatter-ignores-compareto.mdminor on both packages, stating plainly that a published capability is removed and naming the condition for its return.

A compare-to document therefore never reaches the two-or-more-series scatter refusal of #7194 — the primary draws, and nothing is refused. #7194 remains open and is unaffected by this PR.

Why removal rather than a refusal

A scatter binds ONE measure and the renderer reads y through the single YAxis dataKey={series[0].dataKey}, so the synthesised overlay was plotted on the primary's y: "previous period" painted exactly on top of "current". Drawing it honestly needs the multi-measure projection recorded as option A of #7194, which is not built. Per the ruling the capability is removed until that projection arrives, and returns with it — one payment, no transition. A refusal was rejected because it names a key the author never wrote and loses the correctly drawn primary too.

Verification (all figures observed on b68c77a17, on a SHARED container — heavy runs went through this box's verify lock, so wall-clock absolutes are contended readings)

  • New pins, both pathspnpm exec vitest run from the repo root over the two new files: 2 files passed, 6 tests passed.
  • Both package suitespnpm exec vitest run --maxWorkers=2 packages/plugin-charts/ packages/plugin-dashboard/ from the repo root: 131 files passed, 1208 tests passed, 0 failed (216s).
  • Reverse verification — with ONLY the two source files reverted to origin/main (20cb8db9b) over the new tests: 4 failed | 2 passed. Red: both scatter pins in plugin-charts and the scatter + bubble cases in plugin-dashboard. Green: the two bar CONTROLS — which is the point of having them, since a regression that suppressed every comparison series would otherwise satisfy every new assertion. The mutation was proven on disk before the run (blob hash moved from 92a0b536/0189be13, anchored grep counts 1/20/0, pre-fix text back at 1/1); the restore was proven by state afterwards (blob hashes equal HEAD's again, git diff HEAD empty), under trap … EXIT INT TERM with absolute paths.
  • type-checkpnpm --filter @object-ui/plugin-charts --filter @object-ui/plugin-dashboard run type-check: both Done, exit 0. Both new test files were confirmed to be IN the checked program (tsc --listFiles, 1 hit each) rather than assumed to be.
  • lint — same two packages, exit 0: 175 files linted, 0 errors, 748 warnings, all warnings the repo's pre-existing no-explicit-any / no-unused-vars baseline in files this PR does not touch. Narrowed to the two changed packages deliberately: the file set comes from eslint's own flat config, the count from --format json, and the config enables no type-aware linting (no parserOptions.project / projectService), so this diff cannot move the verdict on any file it does not touch.
  • Gatescheck:control-bytes (6160 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, check:changeset-no-major: all exit 0, each read off the gate's own verdict line.

Two judgment calls, surfaced rather than decided quietly

1. The historical CHANGELOG.md line is left as written.packages/plugin-charts/CHANGELOG.md:1727 is generated by changesets and records what a released version did; editing it rewrites history that was true when it shipped. The same sentence also sits in three other packages' CHANGELOGs (types, core, plugin-dashboard) — all historical fan-out of one changeset, all left alone. The current-tense publication of that list turned out to be packages/plugin-dashboard/SKILL.md, which is updated here in both places, and the new statement is carried into the next release notes by this PR's changeset. Nothing was silently rewritten.

2. comparisonStyle(s, 'scatter') (AdvancedChartImpl.tsx:92, call site :1758) is NOT dead, so it stays.variant is an authorable series key on the chart schema, read from raw input in normalizeChartSchema.ts:247 and from authored chartConfig in @object-ui/core's chart-presentation.ts:125. A hand-authored chartType: 'scatter' with series: [{ dataKey, variant: 'comparison' }] reaches that branch directly, and being a single-series chart it is not caught by #7194's refusal either. What this PR removes is only the SYNTHESISED overlay; the authored spelling is untouched.

Filed, not fixed here

#7495 — the compareTo chart-family exclusion list now exists in two places and disagrees: DatasetWidget still runs the comparison query and appends an unused overlay series for pie / donut / funnel (the renderer draws series[0] only, so nothing is mis-drawn). Out of this card's declared scope; that one wants its own decision because it changes dashboard behaviour for three more families.

Reachability at the time of the change

0 authored scatter/bubble instances in-repo across both spellings, and no compareTo at all in examples/**/*.json; control "type": "bar" fires at 5 example files. Incidence in deployed tenant metadata is not measurable from this repo.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…pie/donut/funnel do
A `chartType: 'scatter'` chart — and the dashboard widget types `scatter` and
`bubble`, which both map onto it — synthesised a muted "previous period"
overlay series when `compareTo` was set. A scatter binds ONE measure and the
renderer reads y through the single `YAxis dataKey={series[0].dataKey}`, so
that overlay was plotted on the PRIMARY series' y: the previous period was
painted exactly on top of the current one (#7194).
Ruling on #7402 (maintainer, 2026-09-03): remove the published capability
rather than keep drawing that picture. `supportsCompareTo` in ObjectChart and
the DatasetWidget chart path now exclude scatter exactly as they exclude
pie / donut / funnel, so the primary draws, no `__comparison` / `__compare`
series is synthesised, and a compare-to document never reaches the
two-or-more-series scatter refusal of #7194. It returns with the multi-measure
projection recorded as option A of #7194.
`packages/plugin-dashboard/SKILL.md` — the current-tense published statement of
which families ignore `compareTo` — is updated. The historical CHANGELOG entry
that first published the list is left as written.
One pin per path plus a mandatory `bar` control that still gets its overlay;
without the control a regression suppressing every comparison series would
pass every new assertion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-DBHpSRoB.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.32KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Clause-② in-seat contract review — PASS

Commissioned by the domain:ui execution seat (PM session session_01EMrWaQw3XS5DxTHxp4yRyC) per the in-seat review route (维护者 2026-08-31 裁定). This seat runs opus and is not at tier, so it commissioned an isolated reviewer rather than self-reviewing. The reviewer was given only the card, the ruling and the PR — ⛔ not the dispatch order and not this seat's opinion.

Tier verification (维护者 2026-08-27 裁定 — a commissioned review is legitimate only if verified): every harness-stamped model field in the transcript was read before adopting — 96 stamps, all claude-fable-5-1, no other value. One string matching a fallback pattern was inspected individually and is the word "overloaded" inside a source comment the reviewer was reading (colors is overloaded kanban-style), not a model fallback. CONTRACT_REVIEW_TIER is claude-fable-5 (objectstack/scripts/pm/dispatch-gates.mjs:8091); claude-fable-5-1 is at or above that floor. ⇒ Verified, so the verdict is adopted verbatim — ⛔ nothing below is rewritten, abridged or polished.


Contract review — objectui PR #7498 (head b68c77a), card #7402, ruling comment 5523068497 option (b)

① Derived judgments (accept set / published surface)

Inline chart path — packages/plugin-charts/src/ObjectChart.tsx

  • supportsCompareTo adds ct !== 'scatter' (PR head :350-351). It is read at exactly two sites and both are gated: :553wantsComparison (the second runAggregate and the <valueKey>__comparison merge at :600) and :811enableComparisonSeries (the overlay append at :829-838). For chartType: 'scatter' no comparison fetch runs, no __comparison column exists, no overlay series is synthesised. Right — matches ruling (b) verbatim.
  • The dataset-bound inline branch (:543-556) never forwards compareTo to queryDataset, and finalSchema (:872-874) takes datasetChart.series, not augmentedSeries. There is no second inline overlay path the predicate misses. Right.
  • Every other reader of the column, verified repo-wide (packages/**, apps/**, non-test): __comparison / COMPARISON_SUFFIX appear only in ObjectChart.tsx (:41 export, :600, :808). variant: 'comparison' readers: AdvancedChartImpl.tsx:92 (comparisonStyle), :1847-1871 (bar-arm colour pairing), normalizeChartSchema.ts:247, packages/core/src/utils/chart-presentation.ts:125, DatasetWidget.tsx:1419. All consume a series that may carry the variant; none requires one to exist for a scatter. The scatter arm (AdvancedChartImpl.tsx:1694-1777) legend uses nameKey={scatterYKey} and the default ChartTooltipContent; no legend, tooltip, export, click payload, or saved-view reader depends on the overlay. Removal is exactly the ruling's width, not wider.

Dashboard path — packages/plugin-dashboard/src/DatasetWidget.tsx

  • chartIgnoresCompareTo = chartType === 'scatter' (:1418) gates comparisonSeries (:1420). chartType comes from CHART_TYPE_MAP[widgetType] ?? 'bar' (:1373); the map has scatter: 'scatter' and bubble: 'scatter' (:377-378). Both spellings verified in source.
  • bubble by any other path: widgetDispatch.ts:40 aliases bubble → scatter; DashboardRenderer.tsx:602-640 builds the non-dataset chart with chartType: resolvedWidgetType (= scatter) and forwards compareTo (:639) — lands on ObjectChart's predicate; DashboardGridLayout.tsx:223-258 passes dispatch.chartType and forwards no compareTo at all; a dataset-bound bubble reaches DatasetWidget whole (DashboardRenderer.tsx:920). No product path delivers a raw 'bubble' to either predicate. The ruling's "widget types scatter and bubble" is satisfied.
  • comparedValues (:759) is untouched, so metric/table/pivot/export paths are unaffected. The chart return (:1502-1512) renders no comparison caption — compareCaption (:1007) sits in the table branch; the overlay label at :1433 was compareLabel's only chart-path carrier. No orphan "vs previous year" caption survives on a scatter. Right.
  • Semantics differ from inline by design: the dashboard still issues the comparison query (executor attaches __compare) and only declines to draw it. That is the same author-visible behaviour pie/donut/funnel have on the dashboard today (no exclusion there at all; renderer draws series[0]). "Exactly as pie / donut / funnel" holds at the visible level; the internal asymmetry is stated in the code comment and filed (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). Right.

Published surface — packages/plugin-dashboard/SKILL.md

  • :24-26: "For chart widgets (line / area / bar / horizontal-bar / combo), a muted second series … Pie, donut, funnel and scatter charts ignore compareTo." True at the author-visible level on both paths; does not overclaim a skipped query on the dashboard. Accurate.
  • :161-164: "Scatter charts, and the bubble widget type that renders as one … compareTo is ignored until scatter can project a second measure (objectui#7194 option A); ruled in objectui#7402." bubble claim verified against both maps; return condition matches the ruling. Accurate.
  • Governance fact, stated for the record: this file is not in GOVERNED_SURFACES (scripts/check-governed-queue-guard.mjs:201 — the governed/published skills tree is root skills/**, i.e. skills/objectui/), and not in plugin-dashboard/package.jsonfiles (dist, README.md, CHANGELOG.md, LICENSE). It is an in-repo authoring guide, not the npm-published or governed skills/** surface. I judged its wording as a contract statement regardless; it holds.

Changeset — .changeset/7402-scatter-ignores-compareto.md

  • "no comparison query is issued on the inline chart path" — true and correctly scoped. "No <measure>__comparison (inline) / <measure>__compare (dashboard) series is appended" — true. "bubble … renders as a scatter" — true.
  • One imprecision, inherited not introduced: "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive, but radar also draws the appended series on both paths (supportsCompareTo('radar') is true; CHART_TYPE_MAP.radar = 'radar'; the radar arm at AdvancedChartImpl.tsx:1675-1687 maps every series, unstyled). Pre-PR SKILL.md:24 carried the same list minus scatter. Not a ruling defect; optional.

Unmentioned changes: none. Six files; four in the PR table, two tests in the verification section; the DatasetWidget comment's #7495 note is mentioned.

Base drift: PR is 2 commits behind main (20cb8db..48c19bd); that delta touches only .changeset/* and a new DatasetWidget.dateMeasure.test.tsx. No overlap. #7400 has not merged (no refusal on this head's AdvancedChartImpl); nothing here depends on it.

② Semver

  • Declared: minor on both packages. scripts/check-changeset-no-major.mjs header: "objectui's own breaking changes ship as minor with the break spelled out in the changeset body"; major is reserved for the objectstack-synchronised bump (OBJECTUI_ALLOW_MAJOR=1). All 39 packages sit in one fixed group, so the level is group-wide anyway.
  • The body spells the break out in bold ("This removes a published capability, deliberately"), states before/after behaviour and the return condition.
  • I would assign minor. Matches.Changeset Bump Policy and Changeset Fixed Group Check both green.

③ Boundary flags

  • (a) CHANGELOG left as history — correct. Verified the four fan-out copies (packages/core/CHANGELOG.md:4901, packages/plugin-charts/CHANGELOG.md:1728, packages/plugin-dashboard/CHANGELOG.md:2731, packages/types/CHANGELOG.md:4085), all versioned history. Independent carrier sweep: md/mdx/ts/tsx/json/yaml repo-wide for pie|donut|funnel within 120 chars of compareTo → only SKILL.md:26; compareTo anywhere in content/**, docs/**, skills/**, packages/*/README.md, apps/** → none; JSDoc in packages/types/src and packages/core/src → no family list; broader phrasing ("comparison overlay", "period-over-period", "previous period series") → only SKILL.md. The implementer's claim holds; no carrier was missed. Answer to the dev report's open question: leave it. The ruling's "the plugin-charts CHANGELOG … statement becomes …" is satisfied by mechanism — this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release, which is exactly how the original sentence arrived there. Recommend the seat record that reading rather than reopen it.
  • (b) comparisonStyle(s, 'scatter') kept — correct. Verified: def AdvancedChartImpl.tsx:91-92, scatter call site :1758 (PR head); normalizeChartSchema.ts:246-247 accepts raw.variant ∈ {comparison, current, primary} from raw series; chart-presentation.ts:125 accepts {primary, comparison} from authored chartConfig. A single authored series with variant: 'comparison' reaches the branch and is not a two-series scatter. Deleting it would widen the removal beyond the ruling.
  • (c) 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 deferred — correct, and this PR stays coherent. The dashboard has no pie/donut/funnel exclusion to copy, so making the two lists agree means changing three more families' dashboard behaviour (query skip) — a separate decision, out of this card's scope. Within this PR, scatter's author-visible behaviour equals pie/donut/funnel on both paths; the one internal asymmetry (dashboard still queries) is shared with those three families today and is stated in both the changeset and the code comment with the issue link.

Pins — not vacuous

  • Ran in my own detached worktree at b68c77a (/home/user/objectui-review-7498, root vitest config aliases every @object-ui/* to src): forward 2 files / 6 tests passed.
  • Reverse verification, both sources reverted to 20cb8db with tests kept (mutation confirmed on disk: anchored grep counts 0/0): 4 failed / 2 passed — red: both ObjectChart scatter pins ("expected vi.fn() to be called 1 times, but got 2 times"), both DatasetWidget cases (scatter, bubble: "to have a length of 1 but got 2"); green: both bar controls. Restore verified (git diff HEAD --name-only empty).
  • Controls genuinely discriminate: ObjectChart's asserts aggregate called twice, series.length === 2, overlay variant: 'comparison', and data amount__comparison: 100; DatasetWidget's asserts series.length === 2 with revenue__comparevariant: 'comparison'. A change suppressing every comparison series fails both.
  • Coverage: both paths (inline aggregate; dashboard dataset) and both widget spellings (describe.each(['scatter','bubble'])). The non-dataset bubble → DashboardRenderer → ObjectChart('scatter') hand-off is covered transitively (data mapping with its own spec-parity test), not by a dedicated pin; acceptable.
  • Pre-existing ObjectChart.compareTo* / DatasetWidget.compareTo* suites on the PR head: 4 files / 36 tests passed.

CI by name (head b68c77a)

  • Green: Skill Example Check, Pre-Install Import Graph Check, Skill Guide Path Check, Skill Eval Token Check, Internal Docs Link Check, Changeset Declaration, Docs Route Eager Closure Check, Shell Escape Residue Scan, Live E2E (informational), Changeset Overwrite Report, Doc Component Type Check, Test (shard 3/4), Test (shard 4/4), Bundle Analysis, README Export Check, Lint, Build & E2E, Changeset Fixed Group Check, Control Byte Scan, Type Check, Governed Surface Queue Guard, Changeset Bump Policy, label, Inert vi.mock Specifier Check, Doc Snippet Type Check.
  • Skipped by design: dependabot, Test (coverage), Test (coverage shard).
  • Still in progress at review time: Test (shard 1/4), Test (shard 2/4) (started 11:29Z; shards 3/4 took ~10 min). Landing condition: both green. My local run of both packages' compareTo suites is the partial substitute, not a replacement.
  • Minor: the PR body cites scripts/pm/os-verify-lock.sh; it is not tracked in this repo (exists untracked in the main checkout and in ../objectstack). Immaterial to the verdict.

Verdict

PASS.

Non-required notes for the seat (not amendments): (1) optionally tighten the changeset's "Charts that keep the overlay:" sentence to "the families the overlay is designed for:" or add radar, since radar draws the appended series unstyled; (2) record on #7402 that the ruling's CHANGELOG sentence is met by the changeset compiling into packages/plugin-charts/CHANGELOG.md at release, closing the dev report's open question; (3) wait for Test (shard 1/4) and Test (shard 2/4) before any landing action. I left /home/user/objectui-review-7498 in place (detached, installed, closure built) — delete if not wanted.


Seat disposition

PASS ⇒ this seat clears the gate and lands it, per 「PASS ⇒ 同席剥标、ready、auto-merge」 — once the reviewer's own landing condition is met: Test (shard 1/4) and Test (shard 2/4) green by name. Holding until then.

The reviewer's note (2) corrects this seat, and I am adopting the correction. On #7402 I ruled option A and recorded it as "deviating from the ruling's literal word", flagging it for the director. The reviewer shows there is no deviation to flag: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I will correct that on #7402 rather than leave a false self-accusation standing.

Its carrier sweep is also stronger than the one I accepted: it checked content/**, docs/**, skills/**, package READMEs, JSDoc in types/core, and three broader phrasings, all independently — the implementer's "SKILL.md is the only current-tense carrier" claim holds on that wider basis.

Note (1) is optional and I am not requiring it: the radar imprecision is inherited from the pre-PR sentence, not introduced here, and widening this PR to fix inherited copy is the scope creep the lane avoids.


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 11:46
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Landing — contract review PASS, 32/32 green, ready + auto-merge

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Provenance: 「PASS ⇒ 同席剥标、ready、auto-merge」 (维护者 2026-08-31 裁定).

The reviewer's own landing condition was wait for Test (shard 1/4) and Test (shard 2/4). Both are now green — shard 1 at 11:44:13, shard 2 at 11:43:53. Full reading on head b68c77a17: 29 success, 3 skipped by design (dependabot, Test (coverage), Test (coverage shard …)), read individually across all 32, not in aggregate. mergeable_state: clean against main at 48c19bd4.

⚠️ A carrier half-state, recorded rather than glossed

Landing precondition ② is "needs:contract-review cleared on both carriers." Here there was nothing to clear — the label was never hung on either carrier:

carrierlabels
PR #7498plugin, plugin: charts, tests
card #7402pm:dispatched

The protocol has the PR creator hang needs:contract-reviewwith the reviewable contract increment, and Clause-②: yes was declared here in the body and in the claim comment. The label was not applied, so condition ② is satisfied only trivially — the gate never existed to be passed.

⛔ I am not treating that as equivalent to a cleared gate. The substance is satisfied: an isolated reviewer at CONTRACT_REVIEW_TIER reviewed this increment and returned PASS, which is what the label exists to force. But this is the second carrier defect this lane has hit today — the first was PR #7491, where the card-side Clause-② declaration was machine-invisible because no comment matched the claim marker. Two different failures, same class: the declaration was made and the mechanism that reads it was not fed.

⚠️ And I could not have caught either mechanically: scripts/pm/check-clause2-carriers.mjs exits 2 in this session (it needs its own GitHub token; this seat reaches GitHub only through MCP, and its own output says that is not a clearance). Both carrier readings here are manual, and a manual reading covers strictly less than the tool. Naming that so the next seat knows which instrument was used.

⭐ The review corrected me, and I have taken the correction

On #7402 I ruled the CHANGELOG question option A and recorded it as "deviating from the ruling's literal word," flagging it for the director to object to. The reviewer showed there is no deviation: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this PR's changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I am correcting that on #7402 rather than leaving a false self-accusation on the record.

Its carrier sweep for that question was also wider than the one I accepted — content/**, docs/**, skills/**, package READMEs, JSDoc in types and core, plus three broader phrasings, each checked independently — and the implementer's "SKILL.md is the only current-tense carrier" claim holds on that stronger basis.

One optional note deliberately NOT required

The reviewer observed the changeset's "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive while radar also draws the appended series. ⛔ Not required as an amendment: that imprecision is inherited from the pre-PR SKILL.md:24 sentence, not introduced here, and widening this PR to correct inherited copy is the scope creep the lane exists to avoid. Recorded so it is a known, chosen gap rather than an unnoticed one.

Housekeeping

The reviewer left /home/user/objectui-review-7498 in place (detached worktree, installed, closure built). It is a session-local scratch tree on a shared container — whoever next needs the disk can remove it; nothing depends on it.


Generated by Claude Code

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

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do - #7498

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto
Sep 3, 2026
Merged

fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do#7498
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7402

Clause-②: yes — a published behaviour statement changes, so this PR stays draft, is not enqueued and has no auto-merge: the domain:ui seat runs its in-seat contract review before it lands.

Implements the maintainer ruling recorded on #7402 (comment 5523068497, 2026-09-03, verbatim 「同意」): (b)compareTo on a scatter is not supported, and scatter joins pie / donut / funnel on the ignore list.

What changed

filechange
packages/plugin-charts/src/ObjectChart.tsxsupportsCompareTo excludes scatter as well as pie / donut / funnel. The comparison fetch is skipped entirely, so no MEASURE__comparison column exists and augmentedSeries never appends the overlay.
packages/plugin-dashboard/src/DatasetWidget.tsxthe chart path gains chartIgnoresCompareTo (chartType === 'scatter'), which suppresses comparisonSeries. Written against the CHART type, so it covers both widget-type spellings — scatter and bubble both map to chartType: 'scatter' through CHART_TYPE_MAP.
packages/plugin-dashboard/SKILL.mdthe current-tense published statement of which families ignore compareTo, in both places it appears.
.changeset/7402-scatter-ignores-compareto.mdminor on both packages, stating plainly that a published capability is removed and naming the condition for its return.

A compare-to document therefore never reaches the two-or-more-series scatter refusal of #7194 — the primary draws, and nothing is refused. #7194 remains open and is unaffected by this PR.

Why removal rather than a refusal

A scatter binds ONE measure and the renderer reads y through the single YAxis dataKey={series[0].dataKey}, so the synthesised overlay was plotted on the primary's y: "previous period" painted exactly on top of "current". Drawing it honestly needs the multi-measure projection recorded as option A of #7194, which is not built. Per the ruling the capability is removed until that projection arrives, and returns with it — one payment, no transition. A refusal was rejected because it names a key the author never wrote and loses the correctly drawn primary too.

Verification (all figures observed on b68c77a17, on a SHARED container — heavy runs went through this box's verify lock, so wall-clock absolutes are contended readings)

  • New pins, both pathspnpm exec vitest run from the repo root over the two new files: 2 files passed, 6 tests passed.
  • Both package suitespnpm exec vitest run --maxWorkers=2 packages/plugin-charts/ packages/plugin-dashboard/ from the repo root: 131 files passed, 1208 tests passed, 0 failed (216s).
  • Reverse verification — with ONLY the two source files reverted to origin/main (20cb8db9b) over the new tests: 4 failed | 2 passed. Red: both scatter pins in plugin-charts and the scatter + bubble cases in plugin-dashboard. Green: the two bar CONTROLS — which is the point of having them, since a regression that suppressed every comparison series would otherwise satisfy every new assertion. The mutation was proven on disk before the run (blob hash moved from 92a0b536/0189be13, anchored grep counts 1/20/0, pre-fix text back at 1/1); the restore was proven by state afterwards (blob hashes equal HEAD's again, git diff HEAD empty), under trap … EXIT INT TERM with absolute paths.
  • type-checkpnpm --filter @object-ui/plugin-charts --filter @object-ui/plugin-dashboard run type-check: both Done, exit 0. Both new test files were confirmed to be IN the checked program (tsc --listFiles, 1 hit each) rather than assumed to be.
  • lint — same two packages, exit 0: 175 files linted, 0 errors, 748 warnings, all warnings the repo's pre-existing no-explicit-any / no-unused-vars baseline in files this PR does not touch. Narrowed to the two changed packages deliberately: the file set comes from eslint's own flat config, the count from --format json, and the config enables no type-aware linting (no parserOptions.project / projectService), so this diff cannot move the verdict on any file it does not touch.
  • Gatescheck:control-bytes (6160 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, check:changeset-no-major: all exit 0, each read off the gate's own verdict line.

Two judgment calls, surfaced rather than decided quietly

1. The historical CHANGELOG.md line is left as written.packages/plugin-charts/CHANGELOG.md:1727 is generated by changesets and records what a released version did; editing it rewrites history that was true when it shipped. The same sentence also sits in three other packages' CHANGELOGs (types, core, plugin-dashboard) — all historical fan-out of one changeset, all left alone. The current-tense publication of that list turned out to be packages/plugin-dashboard/SKILL.md, which is updated here in both places, and the new statement is carried into the next release notes by this PR's changeset. Nothing was silently rewritten.

2. comparisonStyle(s, 'scatter') (AdvancedChartImpl.tsx:92, call site :1758) is NOT dead, so it stays.variant is an authorable series key on the chart schema, read from raw input in normalizeChartSchema.ts:247 and from authored chartConfig in @object-ui/core's chart-presentation.ts:125. A hand-authored chartType: 'scatter' with series: [{ dataKey, variant: 'comparison' }] reaches that branch directly, and being a single-series chart it is not caught by #7194's refusal either. What this PR removes is only the SYNTHESISED overlay; the authored spelling is untouched.

Filed, not fixed here

#7495 — the compareTo chart-family exclusion list now exists in two places and disagrees: DatasetWidget still runs the comparison query and appends an unused overlay series for pie / donut / funnel (the renderer draws series[0] only, so nothing is mis-drawn). Out of this card's declared scope; that one wants its own decision because it changes dashboard behaviour for three more families.

Reachability at the time of the change

0 authored scatter/bubble instances in-repo across both spellings, and no compareTo at all in examples/**/*.json; control "type": "bar" fires at 5 example files. Incidence in deployed tenant metadata is not measurable from this repo.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…pie/donut/funnel do
A `chartType: 'scatter'` chart — and the dashboard widget types `scatter` and
`bubble`, which both map onto it — synthesised a muted "previous period"
overlay series when `compareTo` was set. A scatter binds ONE measure and the
renderer reads y through the single `YAxis dataKey={series[0].dataKey}`, so
that overlay was plotted on the PRIMARY series' y: the previous period was
painted exactly on top of the current one (#7194).
Ruling on #7402 (maintainer, 2026-09-03): remove the published capability
rather than keep drawing that picture. `supportsCompareTo` in ObjectChart and
the DatasetWidget chart path now exclude scatter exactly as they exclude
pie / donut / funnel, so the primary draws, no `__comparison` / `__compare`
series is synthesised, and a compare-to document never reaches the
two-or-more-series scatter refusal of #7194. It returns with the multi-measure
projection recorded as option A of #7194.
`packages/plugin-dashboard/SKILL.md` — the current-tense published statement of
which families ignore `compareTo` — is updated. The historical CHANGELOG entry
that first published the list is left as written.
One pin per path plus a mandatory `bar` control that still gets its overlay;
without the control a regression suppressing every comparison series would
pass every new assertion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-DBHpSRoB.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.32KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Clause-② in-seat contract review — PASS

Commissioned by the domain:ui execution seat (PM session session_01EMrWaQw3XS5DxTHxp4yRyC) per the in-seat review route (维护者 2026-08-31 裁定). This seat runs opus and is not at tier, so it commissioned an isolated reviewer rather than self-reviewing. The reviewer was given only the card, the ruling and the PR — ⛔ not the dispatch order and not this seat's opinion.

Tier verification (维护者 2026-08-27 裁定 — a commissioned review is legitimate only if verified): every harness-stamped model field in the transcript was read before adopting — 96 stamps, all claude-fable-5-1, no other value. One string matching a fallback pattern was inspected individually and is the word "overloaded" inside a source comment the reviewer was reading (colors is overloaded kanban-style), not a model fallback. CONTRACT_REVIEW_TIER is claude-fable-5 (objectstack/scripts/pm/dispatch-gates.mjs:8091); claude-fable-5-1 is at or above that floor. ⇒ Verified, so the verdict is adopted verbatim — ⛔ nothing below is rewritten, abridged or polished.


Contract review — objectui PR #7498 (head b68c77a), card #7402, ruling comment 5523068497 option (b)

① Derived judgments (accept set / published surface)

Inline chart path — packages/plugin-charts/src/ObjectChart.tsx

  • supportsCompareTo adds ct !== 'scatter' (PR head :350-351). It is read at exactly two sites and both are gated: :553wantsComparison (the second runAggregate and the <valueKey>__comparison merge at :600) and :811enableComparisonSeries (the overlay append at :829-838). For chartType: 'scatter' no comparison fetch runs, no __comparison column exists, no overlay series is synthesised. Right — matches ruling (b) verbatim.
  • The dataset-bound inline branch (:543-556) never forwards compareTo to queryDataset, and finalSchema (:872-874) takes datasetChart.series, not augmentedSeries. There is no second inline overlay path the predicate misses. Right.
  • Every other reader of the column, verified repo-wide (packages/**, apps/**, non-test): __comparison / COMPARISON_SUFFIX appear only in ObjectChart.tsx (:41 export, :600, :808). variant: 'comparison' readers: AdvancedChartImpl.tsx:92 (comparisonStyle), :1847-1871 (bar-arm colour pairing), normalizeChartSchema.ts:247, packages/core/src/utils/chart-presentation.ts:125, DatasetWidget.tsx:1419. All consume a series that may carry the variant; none requires one to exist for a scatter. The scatter arm (AdvancedChartImpl.tsx:1694-1777) legend uses nameKey={scatterYKey} and the default ChartTooltipContent; no legend, tooltip, export, click payload, or saved-view reader depends on the overlay. Removal is exactly the ruling's width, not wider.

Dashboard path — packages/plugin-dashboard/src/DatasetWidget.tsx

  • chartIgnoresCompareTo = chartType === 'scatter' (:1418) gates comparisonSeries (:1420). chartType comes from CHART_TYPE_MAP[widgetType] ?? 'bar' (:1373); the map has scatter: 'scatter' and bubble: 'scatter' (:377-378). Both spellings verified in source.
  • bubble by any other path: widgetDispatch.ts:40 aliases bubble → scatter; DashboardRenderer.tsx:602-640 builds the non-dataset chart with chartType: resolvedWidgetType (= scatter) and forwards compareTo (:639) — lands on ObjectChart's predicate; DashboardGridLayout.tsx:223-258 passes dispatch.chartType and forwards no compareTo at all; a dataset-bound bubble reaches DatasetWidget whole (DashboardRenderer.tsx:920). No product path delivers a raw 'bubble' to either predicate. The ruling's "widget types scatter and bubble" is satisfied.
  • comparedValues (:759) is untouched, so metric/table/pivot/export paths are unaffected. The chart return (:1502-1512) renders no comparison caption — compareCaption (:1007) sits in the table branch; the overlay label at :1433 was compareLabel's only chart-path carrier. No orphan "vs previous year" caption survives on a scatter. Right.
  • Semantics differ from inline by design: the dashboard still issues the comparison query (executor attaches __compare) and only declines to draw it. That is the same author-visible behaviour pie/donut/funnel have on the dashboard today (no exclusion there at all; renderer draws series[0]). "Exactly as pie / donut / funnel" holds at the visible level; the internal asymmetry is stated in the code comment and filed (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). Right.

Published surface — packages/plugin-dashboard/SKILL.md

  • :24-26: "For chart widgets (line / area / bar / horizontal-bar / combo), a muted second series … Pie, donut, funnel and scatter charts ignore compareTo." True at the author-visible level on both paths; does not overclaim a skipped query on the dashboard. Accurate.
  • :161-164: "Scatter charts, and the bubble widget type that renders as one … compareTo is ignored until scatter can project a second measure (objectui#7194 option A); ruled in objectui#7402." bubble claim verified against both maps; return condition matches the ruling. Accurate.
  • Governance fact, stated for the record: this file is not in GOVERNED_SURFACES (scripts/check-governed-queue-guard.mjs:201 — the governed/published skills tree is root skills/**, i.e. skills/objectui/), and not in plugin-dashboard/package.jsonfiles (dist, README.md, CHANGELOG.md, LICENSE). It is an in-repo authoring guide, not the npm-published or governed skills/** surface. I judged its wording as a contract statement regardless; it holds.

Changeset — .changeset/7402-scatter-ignores-compareto.md

  • "no comparison query is issued on the inline chart path" — true and correctly scoped. "No <measure>__comparison (inline) / <measure>__compare (dashboard) series is appended" — true. "bubble … renders as a scatter" — true.
  • One imprecision, inherited not introduced: "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive, but radar also draws the appended series on both paths (supportsCompareTo('radar') is true; CHART_TYPE_MAP.radar = 'radar'; the radar arm at AdvancedChartImpl.tsx:1675-1687 maps every series, unstyled). Pre-PR SKILL.md:24 carried the same list minus scatter. Not a ruling defect; optional.

Unmentioned changes: none. Six files; four in the PR table, two tests in the verification section; the DatasetWidget comment's #7495 note is mentioned.

Base drift: PR is 2 commits behind main (20cb8db..48c19bd); that delta touches only .changeset/* and a new DatasetWidget.dateMeasure.test.tsx. No overlap. #7400 has not merged (no refusal on this head's AdvancedChartImpl); nothing here depends on it.

② Semver

  • Declared: minor on both packages. scripts/check-changeset-no-major.mjs header: "objectui's own breaking changes ship as minor with the break spelled out in the changeset body"; major is reserved for the objectstack-synchronised bump (OBJECTUI_ALLOW_MAJOR=1). All 39 packages sit in one fixed group, so the level is group-wide anyway.
  • The body spells the break out in bold ("This removes a published capability, deliberately"), states before/after behaviour and the return condition.
  • I would assign minor. Matches.Changeset Bump Policy and Changeset Fixed Group Check both green.

③ Boundary flags

  • (a) CHANGELOG left as history — correct. Verified the four fan-out copies (packages/core/CHANGELOG.md:4901, packages/plugin-charts/CHANGELOG.md:1728, packages/plugin-dashboard/CHANGELOG.md:2731, packages/types/CHANGELOG.md:4085), all versioned history. Independent carrier sweep: md/mdx/ts/tsx/json/yaml repo-wide for pie|donut|funnel within 120 chars of compareTo → only SKILL.md:26; compareTo anywhere in content/**, docs/**, skills/**, packages/*/README.md, apps/** → none; JSDoc in packages/types/src and packages/core/src → no family list; broader phrasing ("comparison overlay", "period-over-period", "previous period series") → only SKILL.md. The implementer's claim holds; no carrier was missed. Answer to the dev report's open question: leave it. The ruling's "the plugin-charts CHANGELOG … statement becomes …" is satisfied by mechanism — this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release, which is exactly how the original sentence arrived there. Recommend the seat record that reading rather than reopen it.
  • (b) comparisonStyle(s, 'scatter') kept — correct. Verified: def AdvancedChartImpl.tsx:91-92, scatter call site :1758 (PR head); normalizeChartSchema.ts:246-247 accepts raw.variant ∈ {comparison, current, primary} from raw series; chart-presentation.ts:125 accepts {primary, comparison} from authored chartConfig. A single authored series with variant: 'comparison' reaches the branch and is not a two-series scatter. Deleting it would widen the removal beyond the ruling.
  • (c) 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 deferred — correct, and this PR stays coherent. The dashboard has no pie/donut/funnel exclusion to copy, so making the two lists agree means changing three more families' dashboard behaviour (query skip) — a separate decision, out of this card's scope. Within this PR, scatter's author-visible behaviour equals pie/donut/funnel on both paths; the one internal asymmetry (dashboard still queries) is shared with those three families today and is stated in both the changeset and the code comment with the issue link.

Pins — not vacuous

  • Ran in my own detached worktree at b68c77a (/home/user/objectui-review-7498, root vitest config aliases every @object-ui/* to src): forward 2 files / 6 tests passed.
  • Reverse verification, both sources reverted to 20cb8db with tests kept (mutation confirmed on disk: anchored grep counts 0/0): 4 failed / 2 passed — red: both ObjectChart scatter pins ("expected vi.fn() to be called 1 times, but got 2 times"), both DatasetWidget cases (scatter, bubble: "to have a length of 1 but got 2"); green: both bar controls. Restore verified (git diff HEAD --name-only empty).
  • Controls genuinely discriminate: ObjectChart's asserts aggregate called twice, series.length === 2, overlay variant: 'comparison', and data amount__comparison: 100; DatasetWidget's asserts series.length === 2 with revenue__comparevariant: 'comparison'. A change suppressing every comparison series fails both.
  • Coverage: both paths (inline aggregate; dashboard dataset) and both widget spellings (describe.each(['scatter','bubble'])). The non-dataset bubble → DashboardRenderer → ObjectChart('scatter') hand-off is covered transitively (data mapping with its own spec-parity test), not by a dedicated pin; acceptable.
  • Pre-existing ObjectChart.compareTo* / DatasetWidget.compareTo* suites on the PR head: 4 files / 36 tests passed.

CI by name (head b68c77a)

  • Green: Skill Example Check, Pre-Install Import Graph Check, Skill Guide Path Check, Skill Eval Token Check, Internal Docs Link Check, Changeset Declaration, Docs Route Eager Closure Check, Shell Escape Residue Scan, Live E2E (informational), Changeset Overwrite Report, Doc Component Type Check, Test (shard 3/4), Test (shard 4/4), Bundle Analysis, README Export Check, Lint, Build & E2E, Changeset Fixed Group Check, Control Byte Scan, Type Check, Governed Surface Queue Guard, Changeset Bump Policy, label, Inert vi.mock Specifier Check, Doc Snippet Type Check.
  • Skipped by design: dependabot, Test (coverage), Test (coverage shard).
  • Still in progress at review time: Test (shard 1/4), Test (shard 2/4) (started 11:29Z; shards 3/4 took ~10 min). Landing condition: both green. My local run of both packages' compareTo suites is the partial substitute, not a replacement.
  • Minor: the PR body cites scripts/pm/os-verify-lock.sh; it is not tracked in this repo (exists untracked in the main checkout and in ../objectstack). Immaterial to the verdict.

Verdict

PASS.

Non-required notes for the seat (not amendments): (1) optionally tighten the changeset's "Charts that keep the overlay:" sentence to "the families the overlay is designed for:" or add radar, since radar draws the appended series unstyled; (2) record on #7402 that the ruling's CHANGELOG sentence is met by the changeset compiling into packages/plugin-charts/CHANGELOG.md at release, closing the dev report's open question; (3) wait for Test (shard 1/4) and Test (shard 2/4) before any landing action. I left /home/user/objectui-review-7498 in place (detached, installed, closure built) — delete if not wanted.


Seat disposition

PASS ⇒ this seat clears the gate and lands it, per 「PASS ⇒ 同席剥标、ready、auto-merge」 — once the reviewer's own landing condition is met: Test (shard 1/4) and Test (shard 2/4) green by name. Holding until then.

The reviewer's note (2) corrects this seat, and I am adopting the correction. On #7402 I ruled option A and recorded it as "deviating from the ruling's literal word", flagging it for the director. The reviewer shows there is no deviation to flag: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I will correct that on #7402 rather than leave a false self-accusation standing.

Its carrier sweep is also stronger than the one I accepted: it checked content/**, docs/**, skills/**, package READMEs, JSDoc in types/core, and three broader phrasings, all independently — the implementer's "SKILL.md is the only current-tense carrier" claim holds on that wider basis.

Note (1) is optional and I am not requiring it: the radar imprecision is inherited from the pre-PR sentence, not introduced here, and widening this PR to fix inherited copy is the scope creep the lane avoids.


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 11:46
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Landing — contract review PASS, 32/32 green, ready + auto-merge

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Provenance: 「PASS ⇒ 同席剥标、ready、auto-merge」 (维护者 2026-08-31 裁定).

The reviewer's own landing condition was wait for Test (shard 1/4) and Test (shard 2/4). Both are now green — shard 1 at 11:44:13, shard 2 at 11:43:53. Full reading on head b68c77a17: 29 success, 3 skipped by design (dependabot, Test (coverage), Test (coverage shard …)), read individually across all 32, not in aggregate. mergeable_state: clean against main at 48c19bd4.

⚠️ A carrier half-state, recorded rather than glossed

Landing precondition ② is "needs:contract-review cleared on both carriers." Here there was nothing to clear — the label was never hung on either carrier:

carrierlabels
PR #7498plugin, plugin: charts, tests
card #7402pm:dispatched

The protocol has the PR creator hang needs:contract-reviewwith the reviewable contract increment, and Clause-②: yes was declared here in the body and in the claim comment. The label was not applied, so condition ② is satisfied only trivially — the gate never existed to be passed.

⛔ I am not treating that as equivalent to a cleared gate. The substance is satisfied: an isolated reviewer at CONTRACT_REVIEW_TIER reviewed this increment and returned PASS, which is what the label exists to force. But this is the second carrier defect this lane has hit today — the first was PR #7491, where the card-side Clause-② declaration was machine-invisible because no comment matched the claim marker. Two different failures, same class: the declaration was made and the mechanism that reads it was not fed.

⚠️ And I could not have caught either mechanically: scripts/pm/check-clause2-carriers.mjs exits 2 in this session (it needs its own GitHub token; this seat reaches GitHub only through MCP, and its own output says that is not a clearance). Both carrier readings here are manual, and a manual reading covers strictly less than the tool. Naming that so the next seat knows which instrument was used.

⭐ The review corrected me, and I have taken the correction

On #7402 I ruled the CHANGELOG question option A and recorded it as "deviating from the ruling's literal word," flagging it for the director to object to. The reviewer showed there is no deviation: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this PR's changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I am correcting that on #7402 rather than leaving a false self-accusation on the record.

Its carrier sweep for that question was also wider than the one I accepted — content/**, docs/**, skills/**, package READMEs, JSDoc in types and core, plus three broader phrasings, each checked independently — and the implementer's "SKILL.md is the only current-tense carrier" claim holds on that stronger basis.

One optional note deliberately NOT required

The reviewer observed the changeset's "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive while radar also draws the appended series. ⛔ Not required as an amendment: that imprecision is inherited from the pre-PR SKILL.md:24 sentence, not introduced here, and widening this PR to correct inherited copy is the scope creep the lane exists to avoid. Recorded so it is a known, chosen gap rather than an unnoticed one.

Housekeeping

The reviewer left /home/user/objectui-review-7498 in place (detached worktree, installed, closure built). It is a session-local scratch tree on a shared container — whoever next needs the disk can remove it; nothing depends on it.


Generated by Claude Code

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

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do - #7498

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto
Sep 3, 2026
Merged

fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do#7498
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7402

Clause-②: yes — a published behaviour statement changes, so this PR stays draft, is not enqueued and has no auto-merge: the domain:ui seat runs its in-seat contract review before it lands.

Implements the maintainer ruling recorded on #7402 (comment 5523068497, 2026-09-03, verbatim 「同意」): (b)compareTo on a scatter is not supported, and scatter joins pie / donut / funnel on the ignore list.

What changed

filechange
packages/plugin-charts/src/ObjectChart.tsxsupportsCompareTo excludes scatter as well as pie / donut / funnel. The comparison fetch is skipped entirely, so no MEASURE__comparison column exists and augmentedSeries never appends the overlay.
packages/plugin-dashboard/src/DatasetWidget.tsxthe chart path gains chartIgnoresCompareTo (chartType === 'scatter'), which suppresses comparisonSeries. Written against the CHART type, so it covers both widget-type spellings — scatter and bubble both map to chartType: 'scatter' through CHART_TYPE_MAP.
packages/plugin-dashboard/SKILL.mdthe current-tense published statement of which families ignore compareTo, in both places it appears.
.changeset/7402-scatter-ignores-compareto.mdminor on both packages, stating plainly that a published capability is removed and naming the condition for its return.

A compare-to document therefore never reaches the two-or-more-series scatter refusal of #7194 — the primary draws, and nothing is refused. #7194 remains open and is unaffected by this PR.

Why removal rather than a refusal

A scatter binds ONE measure and the renderer reads y through the single YAxis dataKey={series[0].dataKey}, so the synthesised overlay was plotted on the primary's y: "previous period" painted exactly on top of "current". Drawing it honestly needs the multi-measure projection recorded as option A of #7194, which is not built. Per the ruling the capability is removed until that projection arrives, and returns with it — one payment, no transition. A refusal was rejected because it names a key the author never wrote and loses the correctly drawn primary too.

Verification (all figures observed on b68c77a17, on a SHARED container — heavy runs went through this box's verify lock, so wall-clock absolutes are contended readings)

  • New pins, both pathspnpm exec vitest run from the repo root over the two new files: 2 files passed, 6 tests passed.
  • Both package suitespnpm exec vitest run --maxWorkers=2 packages/plugin-charts/ packages/plugin-dashboard/ from the repo root: 131 files passed, 1208 tests passed, 0 failed (216s).
  • Reverse verification — with ONLY the two source files reverted to origin/main (20cb8db9b) over the new tests: 4 failed | 2 passed. Red: both scatter pins in plugin-charts and the scatter + bubble cases in plugin-dashboard. Green: the two bar CONTROLS — which is the point of having them, since a regression that suppressed every comparison series would otherwise satisfy every new assertion. The mutation was proven on disk before the run (blob hash moved from 92a0b536/0189be13, anchored grep counts 1/20/0, pre-fix text back at 1/1); the restore was proven by state afterwards (blob hashes equal HEAD's again, git diff HEAD empty), under trap … EXIT INT TERM with absolute paths.
  • type-checkpnpm --filter @object-ui/plugin-charts --filter @object-ui/plugin-dashboard run type-check: both Done, exit 0. Both new test files were confirmed to be IN the checked program (tsc --listFiles, 1 hit each) rather than assumed to be.
  • lint — same two packages, exit 0: 175 files linted, 0 errors, 748 warnings, all warnings the repo's pre-existing no-explicit-any / no-unused-vars baseline in files this PR does not touch. Narrowed to the two changed packages deliberately: the file set comes from eslint's own flat config, the count from --format json, and the config enables no type-aware linting (no parserOptions.project / projectService), so this diff cannot move the verdict on any file it does not touch.
  • Gatescheck:control-bytes (6160 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, check:changeset-no-major: all exit 0, each read off the gate's own verdict line.

Two judgment calls, surfaced rather than decided quietly

1. The historical CHANGELOG.md line is left as written.packages/plugin-charts/CHANGELOG.md:1727 is generated by changesets and records what a released version did; editing it rewrites history that was true when it shipped. The same sentence also sits in three other packages' CHANGELOGs (types, core, plugin-dashboard) — all historical fan-out of one changeset, all left alone. The current-tense publication of that list turned out to be packages/plugin-dashboard/SKILL.md, which is updated here in both places, and the new statement is carried into the next release notes by this PR's changeset. Nothing was silently rewritten.

2. comparisonStyle(s, 'scatter') (AdvancedChartImpl.tsx:92, call site :1758) is NOT dead, so it stays.variant is an authorable series key on the chart schema, read from raw input in normalizeChartSchema.ts:247 and from authored chartConfig in @object-ui/core's chart-presentation.ts:125. A hand-authored chartType: 'scatter' with series: [{ dataKey, variant: 'comparison' }] reaches that branch directly, and being a single-series chart it is not caught by #7194's refusal either. What this PR removes is only the SYNTHESISED overlay; the authored spelling is untouched.

Filed, not fixed here

#7495 — the compareTo chart-family exclusion list now exists in two places and disagrees: DatasetWidget still runs the comparison query and appends an unused overlay series for pie / donut / funnel (the renderer draws series[0] only, so nothing is mis-drawn). Out of this card's declared scope; that one wants its own decision because it changes dashboard behaviour for three more families.

Reachability at the time of the change

0 authored scatter/bubble instances in-repo across both spellings, and no compareTo at all in examples/**/*.json; control "type": "bar" fires at 5 example files. Incidence in deployed tenant metadata is not measurable from this repo.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…pie/donut/funnel do
A `chartType: 'scatter'` chart — and the dashboard widget types `scatter` and
`bubble`, which both map onto it — synthesised a muted "previous period"
overlay series when `compareTo` was set. A scatter binds ONE measure and the
renderer reads y through the single `YAxis dataKey={series[0].dataKey}`, so
that overlay was plotted on the PRIMARY series' y: the previous period was
painted exactly on top of the current one (#7194).
Ruling on #7402 (maintainer, 2026-09-03): remove the published capability
rather than keep drawing that picture. `supportsCompareTo` in ObjectChart and
the DatasetWidget chart path now exclude scatter exactly as they exclude
pie / donut / funnel, so the primary draws, no `__comparison` / `__compare`
series is synthesised, and a compare-to document never reaches the
two-or-more-series scatter refusal of #7194. It returns with the multi-measure
projection recorded as option A of #7194.
`packages/plugin-dashboard/SKILL.md` — the current-tense published statement of
which families ignore `compareTo` — is updated. The historical CHANGELOG entry
that first published the list is left as written.
One pin per path plus a mandatory `bar` control that still gets its overlay;
without the control a regression suppressing every comparison series would
pass every new assertion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-DBHpSRoB.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.32KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Clause-② in-seat contract review — PASS

Commissioned by the domain:ui execution seat (PM session session_01EMrWaQw3XS5DxTHxp4yRyC) per the in-seat review route (维护者 2026-08-31 裁定). This seat runs opus and is not at tier, so it commissioned an isolated reviewer rather than self-reviewing. The reviewer was given only the card, the ruling and the PR — ⛔ not the dispatch order and not this seat's opinion.

Tier verification (维护者 2026-08-27 裁定 — a commissioned review is legitimate only if verified): every harness-stamped model field in the transcript was read before adopting — 96 stamps, all claude-fable-5-1, no other value. One string matching a fallback pattern was inspected individually and is the word "overloaded" inside a source comment the reviewer was reading (colors is overloaded kanban-style), not a model fallback. CONTRACT_REVIEW_TIER is claude-fable-5 (objectstack/scripts/pm/dispatch-gates.mjs:8091); claude-fable-5-1 is at or above that floor. ⇒ Verified, so the verdict is adopted verbatim — ⛔ nothing below is rewritten, abridged or polished.


Contract review — objectui PR #7498 (head b68c77a), card #7402, ruling comment 5523068497 option (b)

① Derived judgments (accept set / published surface)

Inline chart path — packages/plugin-charts/src/ObjectChart.tsx

  • supportsCompareTo adds ct !== 'scatter' (PR head :350-351). It is read at exactly two sites and both are gated: :553wantsComparison (the second runAggregate and the <valueKey>__comparison merge at :600) and :811enableComparisonSeries (the overlay append at :829-838). For chartType: 'scatter' no comparison fetch runs, no __comparison column exists, no overlay series is synthesised. Right — matches ruling (b) verbatim.
  • The dataset-bound inline branch (:543-556) never forwards compareTo to queryDataset, and finalSchema (:872-874) takes datasetChart.series, not augmentedSeries. There is no second inline overlay path the predicate misses. Right.
  • Every other reader of the column, verified repo-wide (packages/**, apps/**, non-test): __comparison / COMPARISON_SUFFIX appear only in ObjectChart.tsx (:41 export, :600, :808). variant: 'comparison' readers: AdvancedChartImpl.tsx:92 (comparisonStyle), :1847-1871 (bar-arm colour pairing), normalizeChartSchema.ts:247, packages/core/src/utils/chart-presentation.ts:125, DatasetWidget.tsx:1419. All consume a series that may carry the variant; none requires one to exist for a scatter. The scatter arm (AdvancedChartImpl.tsx:1694-1777) legend uses nameKey={scatterYKey} and the default ChartTooltipContent; no legend, tooltip, export, click payload, or saved-view reader depends on the overlay. Removal is exactly the ruling's width, not wider.

Dashboard path — packages/plugin-dashboard/src/DatasetWidget.tsx

  • chartIgnoresCompareTo = chartType === 'scatter' (:1418) gates comparisonSeries (:1420). chartType comes from CHART_TYPE_MAP[widgetType] ?? 'bar' (:1373); the map has scatter: 'scatter' and bubble: 'scatter' (:377-378). Both spellings verified in source.
  • bubble by any other path: widgetDispatch.ts:40 aliases bubble → scatter; DashboardRenderer.tsx:602-640 builds the non-dataset chart with chartType: resolvedWidgetType (= scatter) and forwards compareTo (:639) — lands on ObjectChart's predicate; DashboardGridLayout.tsx:223-258 passes dispatch.chartType and forwards no compareTo at all; a dataset-bound bubble reaches DatasetWidget whole (DashboardRenderer.tsx:920). No product path delivers a raw 'bubble' to either predicate. The ruling's "widget types scatter and bubble" is satisfied.
  • comparedValues (:759) is untouched, so metric/table/pivot/export paths are unaffected. The chart return (:1502-1512) renders no comparison caption — compareCaption (:1007) sits in the table branch; the overlay label at :1433 was compareLabel's only chart-path carrier. No orphan "vs previous year" caption survives on a scatter. Right.
  • Semantics differ from inline by design: the dashboard still issues the comparison query (executor attaches __compare) and only declines to draw it. That is the same author-visible behaviour pie/donut/funnel have on the dashboard today (no exclusion there at all; renderer draws series[0]). "Exactly as pie / donut / funnel" holds at the visible level; the internal asymmetry is stated in the code comment and filed (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). Right.

Published surface — packages/plugin-dashboard/SKILL.md

  • :24-26: "For chart widgets (line / area / bar / horizontal-bar / combo), a muted second series … Pie, donut, funnel and scatter charts ignore compareTo." True at the author-visible level on both paths; does not overclaim a skipped query on the dashboard. Accurate.
  • :161-164: "Scatter charts, and the bubble widget type that renders as one … compareTo is ignored until scatter can project a second measure (objectui#7194 option A); ruled in objectui#7402." bubble claim verified against both maps; return condition matches the ruling. Accurate.
  • Governance fact, stated for the record: this file is not in GOVERNED_SURFACES (scripts/check-governed-queue-guard.mjs:201 — the governed/published skills tree is root skills/**, i.e. skills/objectui/), and not in plugin-dashboard/package.jsonfiles (dist, README.md, CHANGELOG.md, LICENSE). It is an in-repo authoring guide, not the npm-published or governed skills/** surface. I judged its wording as a contract statement regardless; it holds.

Changeset — .changeset/7402-scatter-ignores-compareto.md

  • "no comparison query is issued on the inline chart path" — true and correctly scoped. "No <measure>__comparison (inline) / <measure>__compare (dashboard) series is appended" — true. "bubble … renders as a scatter" — true.
  • One imprecision, inherited not introduced: "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive, but radar also draws the appended series on both paths (supportsCompareTo('radar') is true; CHART_TYPE_MAP.radar = 'radar'; the radar arm at AdvancedChartImpl.tsx:1675-1687 maps every series, unstyled). Pre-PR SKILL.md:24 carried the same list minus scatter. Not a ruling defect; optional.

Unmentioned changes: none. Six files; four in the PR table, two tests in the verification section; the DatasetWidget comment's #7495 note is mentioned.

Base drift: PR is 2 commits behind main (20cb8db..48c19bd); that delta touches only .changeset/* and a new DatasetWidget.dateMeasure.test.tsx. No overlap. #7400 has not merged (no refusal on this head's AdvancedChartImpl); nothing here depends on it.

② Semver

  • Declared: minor on both packages. scripts/check-changeset-no-major.mjs header: "objectui's own breaking changes ship as minor with the break spelled out in the changeset body"; major is reserved for the objectstack-synchronised bump (OBJECTUI_ALLOW_MAJOR=1). All 39 packages sit in one fixed group, so the level is group-wide anyway.
  • The body spells the break out in bold ("This removes a published capability, deliberately"), states before/after behaviour and the return condition.
  • I would assign minor. Matches.Changeset Bump Policy and Changeset Fixed Group Check both green.

③ Boundary flags

  • (a) CHANGELOG left as history — correct. Verified the four fan-out copies (packages/core/CHANGELOG.md:4901, packages/plugin-charts/CHANGELOG.md:1728, packages/plugin-dashboard/CHANGELOG.md:2731, packages/types/CHANGELOG.md:4085), all versioned history. Independent carrier sweep: md/mdx/ts/tsx/json/yaml repo-wide for pie|donut|funnel within 120 chars of compareTo → only SKILL.md:26; compareTo anywhere in content/**, docs/**, skills/**, packages/*/README.md, apps/** → none; JSDoc in packages/types/src and packages/core/src → no family list; broader phrasing ("comparison overlay", "period-over-period", "previous period series") → only SKILL.md. The implementer's claim holds; no carrier was missed. Answer to the dev report's open question: leave it. The ruling's "the plugin-charts CHANGELOG … statement becomes …" is satisfied by mechanism — this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release, which is exactly how the original sentence arrived there. Recommend the seat record that reading rather than reopen it.
  • (b) comparisonStyle(s, 'scatter') kept — correct. Verified: def AdvancedChartImpl.tsx:91-92, scatter call site :1758 (PR head); normalizeChartSchema.ts:246-247 accepts raw.variant ∈ {comparison, current, primary} from raw series; chart-presentation.ts:125 accepts {primary, comparison} from authored chartConfig. A single authored series with variant: 'comparison' reaches the branch and is not a two-series scatter. Deleting it would widen the removal beyond the ruling.
  • (c) 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 deferred — correct, and this PR stays coherent. The dashboard has no pie/donut/funnel exclusion to copy, so making the two lists agree means changing three more families' dashboard behaviour (query skip) — a separate decision, out of this card's scope. Within this PR, scatter's author-visible behaviour equals pie/donut/funnel on both paths; the one internal asymmetry (dashboard still queries) is shared with those three families today and is stated in both the changeset and the code comment with the issue link.

Pins — not vacuous

  • Ran in my own detached worktree at b68c77a (/home/user/objectui-review-7498, root vitest config aliases every @object-ui/* to src): forward 2 files / 6 tests passed.
  • Reverse verification, both sources reverted to 20cb8db with tests kept (mutation confirmed on disk: anchored grep counts 0/0): 4 failed / 2 passed — red: both ObjectChart scatter pins ("expected vi.fn() to be called 1 times, but got 2 times"), both DatasetWidget cases (scatter, bubble: "to have a length of 1 but got 2"); green: both bar controls. Restore verified (git diff HEAD --name-only empty).
  • Controls genuinely discriminate: ObjectChart's asserts aggregate called twice, series.length === 2, overlay variant: 'comparison', and data amount__comparison: 100; DatasetWidget's asserts series.length === 2 with revenue__comparevariant: 'comparison'. A change suppressing every comparison series fails both.
  • Coverage: both paths (inline aggregate; dashboard dataset) and both widget spellings (describe.each(['scatter','bubble'])). The non-dataset bubble → DashboardRenderer → ObjectChart('scatter') hand-off is covered transitively (data mapping with its own spec-parity test), not by a dedicated pin; acceptable.
  • Pre-existing ObjectChart.compareTo* / DatasetWidget.compareTo* suites on the PR head: 4 files / 36 tests passed.

CI by name (head b68c77a)

  • Green: Skill Example Check, Pre-Install Import Graph Check, Skill Guide Path Check, Skill Eval Token Check, Internal Docs Link Check, Changeset Declaration, Docs Route Eager Closure Check, Shell Escape Residue Scan, Live E2E (informational), Changeset Overwrite Report, Doc Component Type Check, Test (shard 3/4), Test (shard 4/4), Bundle Analysis, README Export Check, Lint, Build & E2E, Changeset Fixed Group Check, Control Byte Scan, Type Check, Governed Surface Queue Guard, Changeset Bump Policy, label, Inert vi.mock Specifier Check, Doc Snippet Type Check.
  • Skipped by design: dependabot, Test (coverage), Test (coverage shard).
  • Still in progress at review time: Test (shard 1/4), Test (shard 2/4) (started 11:29Z; shards 3/4 took ~10 min). Landing condition: both green. My local run of both packages' compareTo suites is the partial substitute, not a replacement.
  • Minor: the PR body cites scripts/pm/os-verify-lock.sh; it is not tracked in this repo (exists untracked in the main checkout and in ../objectstack). Immaterial to the verdict.

Verdict

PASS.

Non-required notes for the seat (not amendments): (1) optionally tighten the changeset's "Charts that keep the overlay:" sentence to "the families the overlay is designed for:" or add radar, since radar draws the appended series unstyled; (2) record on #7402 that the ruling's CHANGELOG sentence is met by the changeset compiling into packages/plugin-charts/CHANGELOG.md at release, closing the dev report's open question; (3) wait for Test (shard 1/4) and Test (shard 2/4) before any landing action. I left /home/user/objectui-review-7498 in place (detached, installed, closure built) — delete if not wanted.


Seat disposition

PASS ⇒ this seat clears the gate and lands it, per 「PASS ⇒ 同席剥标、ready、auto-merge」 — once the reviewer's own landing condition is met: Test (shard 1/4) and Test (shard 2/4) green by name. Holding until then.

The reviewer's note (2) corrects this seat, and I am adopting the correction. On #7402 I ruled option A and recorded it as "deviating from the ruling's literal word", flagging it for the director. The reviewer shows there is no deviation to flag: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I will correct that on #7402 rather than leave a false self-accusation standing.

Its carrier sweep is also stronger than the one I accepted: it checked content/**, docs/**, skills/**, package READMEs, JSDoc in types/core, and three broader phrasings, all independently — the implementer's "SKILL.md is the only current-tense carrier" claim holds on that wider basis.

Note (1) is optional and I am not requiring it: the radar imprecision is inherited from the pre-PR sentence, not introduced here, and widening this PR to fix inherited copy is the scope creep the lane avoids.


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 11:46
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Landing — contract review PASS, 32/32 green, ready + auto-merge

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Provenance: 「PASS ⇒ 同席剥标、ready、auto-merge」 (维护者 2026-08-31 裁定).

The reviewer's own landing condition was wait for Test (shard 1/4) and Test (shard 2/4). Both are now green — shard 1 at 11:44:13, shard 2 at 11:43:53. Full reading on head b68c77a17: 29 success, 3 skipped by design (dependabot, Test (coverage), Test (coverage shard …)), read individually across all 32, not in aggregate. mergeable_state: clean against main at 48c19bd4.

⚠️ A carrier half-state, recorded rather than glossed

Landing precondition ② is "needs:contract-review cleared on both carriers." Here there was nothing to clear — the label was never hung on either carrier:

carrierlabels
PR #7498plugin, plugin: charts, tests
card #7402pm:dispatched

The protocol has the PR creator hang needs:contract-reviewwith the reviewable contract increment, and Clause-②: yes was declared here in the body and in the claim comment. The label was not applied, so condition ② is satisfied only trivially — the gate never existed to be passed.

⛔ I am not treating that as equivalent to a cleared gate. The substance is satisfied: an isolated reviewer at CONTRACT_REVIEW_TIER reviewed this increment and returned PASS, which is what the label exists to force. But this is the second carrier defect this lane has hit today — the first was PR #7491, where the card-side Clause-② declaration was machine-invisible because no comment matched the claim marker. Two different failures, same class: the declaration was made and the mechanism that reads it was not fed.

⚠️ And I could not have caught either mechanically: scripts/pm/check-clause2-carriers.mjs exits 2 in this session (it needs its own GitHub token; this seat reaches GitHub only through MCP, and its own output says that is not a clearance). Both carrier readings here are manual, and a manual reading covers strictly less than the tool. Naming that so the next seat knows which instrument was used.

⭐ The review corrected me, and I have taken the correction

On #7402 I ruled the CHANGELOG question option A and recorded it as "deviating from the ruling's literal word," flagging it for the director to object to. The reviewer showed there is no deviation: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this PR's changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I am correcting that on #7402 rather than leaving a false self-accusation on the record.

Its carrier sweep for that question was also wider than the one I accepted — content/**, docs/**, skills/**, package READMEs, JSDoc in types and core, plus three broader phrasings, each checked independently — and the implementer's "SKILL.md is the only current-tense carrier" claim holds on that stronger basis.

One optional note deliberately NOT required

The reviewer observed the changeset's "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive while radar also draws the appended series. ⛔ Not required as an amendment: that imprecision is inherited from the pre-PR SKILL.md:24 sentence, not introduced here, and widening this PR to correct inherited copy is the scope creep the lane exists to avoid. Recorded so it is a known, chosen gap rather than an unnoticed one.

Housekeeping

The reviewer left /home/user/objectui-review-7498 in place (detached worktree, installed, closure built). It is a session-local scratch tree on a shared container — whoever next needs the disk can remove it; nothing depends on it.


Generated by Claude Code

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

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do - #7498

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto
Sep 3, 2026
Merged

fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do#7498
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7402

Clause-②: yes — a published behaviour statement changes, so this PR stays draft, is not enqueued and has no auto-merge: the domain:ui seat runs its in-seat contract review before it lands.

Implements the maintainer ruling recorded on #7402 (comment 5523068497, 2026-09-03, verbatim 「同意」): (b)compareTo on a scatter is not supported, and scatter joins pie / donut / funnel on the ignore list.

What changed

filechange
packages/plugin-charts/src/ObjectChart.tsxsupportsCompareTo excludes scatter as well as pie / donut / funnel. The comparison fetch is skipped entirely, so no MEASURE__comparison column exists and augmentedSeries never appends the overlay.
packages/plugin-dashboard/src/DatasetWidget.tsxthe chart path gains chartIgnoresCompareTo (chartType === 'scatter'), which suppresses comparisonSeries. Written against the CHART type, so it covers both widget-type spellings — scatter and bubble both map to chartType: 'scatter' through CHART_TYPE_MAP.
packages/plugin-dashboard/SKILL.mdthe current-tense published statement of which families ignore compareTo, in both places it appears.
.changeset/7402-scatter-ignores-compareto.mdminor on both packages, stating plainly that a published capability is removed and naming the condition for its return.

A compare-to document therefore never reaches the two-or-more-series scatter refusal of #7194 — the primary draws, and nothing is refused. #7194 remains open and is unaffected by this PR.

Why removal rather than a refusal

A scatter binds ONE measure and the renderer reads y through the single YAxis dataKey={series[0].dataKey}, so the synthesised overlay was plotted on the primary's y: "previous period" painted exactly on top of "current". Drawing it honestly needs the multi-measure projection recorded as option A of #7194, which is not built. Per the ruling the capability is removed until that projection arrives, and returns with it — one payment, no transition. A refusal was rejected because it names a key the author never wrote and loses the correctly drawn primary too.

Verification (all figures observed on b68c77a17, on a SHARED container — heavy runs went through this box's verify lock, so wall-clock absolutes are contended readings)

  • New pins, both pathspnpm exec vitest run from the repo root over the two new files: 2 files passed, 6 tests passed.
  • Both package suitespnpm exec vitest run --maxWorkers=2 packages/plugin-charts/ packages/plugin-dashboard/ from the repo root: 131 files passed, 1208 tests passed, 0 failed (216s).
  • Reverse verification — with ONLY the two source files reverted to origin/main (20cb8db9b) over the new tests: 4 failed | 2 passed. Red: both scatter pins in plugin-charts and the scatter + bubble cases in plugin-dashboard. Green: the two bar CONTROLS — which is the point of having them, since a regression that suppressed every comparison series would otherwise satisfy every new assertion. The mutation was proven on disk before the run (blob hash moved from 92a0b536/0189be13, anchored grep counts 1/20/0, pre-fix text back at 1/1); the restore was proven by state afterwards (blob hashes equal HEAD's again, git diff HEAD empty), under trap … EXIT INT TERM with absolute paths.
  • type-checkpnpm --filter @object-ui/plugin-charts --filter @object-ui/plugin-dashboard run type-check: both Done, exit 0. Both new test files were confirmed to be IN the checked program (tsc --listFiles, 1 hit each) rather than assumed to be.
  • lint — same two packages, exit 0: 175 files linted, 0 errors, 748 warnings, all warnings the repo's pre-existing no-explicit-any / no-unused-vars baseline in files this PR does not touch. Narrowed to the two changed packages deliberately: the file set comes from eslint's own flat config, the count from --format json, and the config enables no type-aware linting (no parserOptions.project / projectService), so this diff cannot move the verdict on any file it does not touch.
  • Gatescheck:control-bytes (6160 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, check:changeset-no-major: all exit 0, each read off the gate's own verdict line.

Two judgment calls, surfaced rather than decided quietly

1. The historical CHANGELOG.md line is left as written.packages/plugin-charts/CHANGELOG.md:1727 is generated by changesets and records what a released version did; editing it rewrites history that was true when it shipped. The same sentence also sits in three other packages' CHANGELOGs (types, core, plugin-dashboard) — all historical fan-out of one changeset, all left alone. The current-tense publication of that list turned out to be packages/plugin-dashboard/SKILL.md, which is updated here in both places, and the new statement is carried into the next release notes by this PR's changeset. Nothing was silently rewritten.

2. comparisonStyle(s, 'scatter') (AdvancedChartImpl.tsx:92, call site :1758) is NOT dead, so it stays.variant is an authorable series key on the chart schema, read from raw input in normalizeChartSchema.ts:247 and from authored chartConfig in @object-ui/core's chart-presentation.ts:125. A hand-authored chartType: 'scatter' with series: [{ dataKey, variant: 'comparison' }] reaches that branch directly, and being a single-series chart it is not caught by #7194's refusal either. What this PR removes is only the SYNTHESISED overlay; the authored spelling is untouched.

Filed, not fixed here

#7495 — the compareTo chart-family exclusion list now exists in two places and disagrees: DatasetWidget still runs the comparison query and appends an unused overlay series for pie / donut / funnel (the renderer draws series[0] only, so nothing is mis-drawn). Out of this card's declared scope; that one wants its own decision because it changes dashboard behaviour for three more families.

Reachability at the time of the change

0 authored scatter/bubble instances in-repo across both spellings, and no compareTo at all in examples/**/*.json; control "type": "bar" fires at 5 example files. Incidence in deployed tenant metadata is not measurable from this repo.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…pie/donut/funnel do
A `chartType: 'scatter'` chart — and the dashboard widget types `scatter` and
`bubble`, which both map onto it — synthesised a muted "previous period"
overlay series when `compareTo` was set. A scatter binds ONE measure and the
renderer reads y through the single `YAxis dataKey={series[0].dataKey}`, so
that overlay was plotted on the PRIMARY series' y: the previous period was
painted exactly on top of the current one (#7194).
Ruling on #7402 (maintainer, 2026-09-03): remove the published capability
rather than keep drawing that picture. `supportsCompareTo` in ObjectChart and
the DatasetWidget chart path now exclude scatter exactly as they exclude
pie / donut / funnel, so the primary draws, no `__comparison` / `__compare`
series is synthesised, and a compare-to document never reaches the
two-or-more-series scatter refusal of #7194. It returns with the multi-measure
projection recorded as option A of #7194.
`packages/plugin-dashboard/SKILL.md` — the current-tense published statement of
which families ignore `compareTo` — is updated. The historical CHANGELOG entry
that first published the list is left as written.
One pin per path plus a mandatory `bar` control that still gets its overlay;
without the control a regression suppressing every comparison series would
pass every new assertion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-DBHpSRoB.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.32KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Clause-② in-seat contract review — PASS

Commissioned by the domain:ui execution seat (PM session session_01EMrWaQw3XS5DxTHxp4yRyC) per the in-seat review route (维护者 2026-08-31 裁定). This seat runs opus and is not at tier, so it commissioned an isolated reviewer rather than self-reviewing. The reviewer was given only the card, the ruling and the PR — ⛔ not the dispatch order and not this seat's opinion.

Tier verification (维护者 2026-08-27 裁定 — a commissioned review is legitimate only if verified): every harness-stamped model field in the transcript was read before adopting — 96 stamps, all claude-fable-5-1, no other value. One string matching a fallback pattern was inspected individually and is the word "overloaded" inside a source comment the reviewer was reading (colors is overloaded kanban-style), not a model fallback. CONTRACT_REVIEW_TIER is claude-fable-5 (objectstack/scripts/pm/dispatch-gates.mjs:8091); claude-fable-5-1 is at or above that floor. ⇒ Verified, so the verdict is adopted verbatim — ⛔ nothing below is rewritten, abridged or polished.


Contract review — objectui PR #7498 (head b68c77a), card #7402, ruling comment 5523068497 option (b)

① Derived judgments (accept set / published surface)

Inline chart path — packages/plugin-charts/src/ObjectChart.tsx

  • supportsCompareTo adds ct !== 'scatter' (PR head :350-351). It is read at exactly two sites and both are gated: :553wantsComparison (the second runAggregate and the <valueKey>__comparison merge at :600) and :811enableComparisonSeries (the overlay append at :829-838). For chartType: 'scatter' no comparison fetch runs, no __comparison column exists, no overlay series is synthesised. Right — matches ruling (b) verbatim.
  • The dataset-bound inline branch (:543-556) never forwards compareTo to queryDataset, and finalSchema (:872-874) takes datasetChart.series, not augmentedSeries. There is no second inline overlay path the predicate misses. Right.
  • Every other reader of the column, verified repo-wide (packages/**, apps/**, non-test): __comparison / COMPARISON_SUFFIX appear only in ObjectChart.tsx (:41 export, :600, :808). variant: 'comparison' readers: AdvancedChartImpl.tsx:92 (comparisonStyle), :1847-1871 (bar-arm colour pairing), normalizeChartSchema.ts:247, packages/core/src/utils/chart-presentation.ts:125, DatasetWidget.tsx:1419. All consume a series that may carry the variant; none requires one to exist for a scatter. The scatter arm (AdvancedChartImpl.tsx:1694-1777) legend uses nameKey={scatterYKey} and the default ChartTooltipContent; no legend, tooltip, export, click payload, or saved-view reader depends on the overlay. Removal is exactly the ruling's width, not wider.

Dashboard path — packages/plugin-dashboard/src/DatasetWidget.tsx

  • chartIgnoresCompareTo = chartType === 'scatter' (:1418) gates comparisonSeries (:1420). chartType comes from CHART_TYPE_MAP[widgetType] ?? 'bar' (:1373); the map has scatter: 'scatter' and bubble: 'scatter' (:377-378). Both spellings verified in source.
  • bubble by any other path: widgetDispatch.ts:40 aliases bubble → scatter; DashboardRenderer.tsx:602-640 builds the non-dataset chart with chartType: resolvedWidgetType (= scatter) and forwards compareTo (:639) — lands on ObjectChart's predicate; DashboardGridLayout.tsx:223-258 passes dispatch.chartType and forwards no compareTo at all; a dataset-bound bubble reaches DatasetWidget whole (DashboardRenderer.tsx:920). No product path delivers a raw 'bubble' to either predicate. The ruling's "widget types scatter and bubble" is satisfied.
  • comparedValues (:759) is untouched, so metric/table/pivot/export paths are unaffected. The chart return (:1502-1512) renders no comparison caption — compareCaption (:1007) sits in the table branch; the overlay label at :1433 was compareLabel's only chart-path carrier. No orphan "vs previous year" caption survives on a scatter. Right.
  • Semantics differ from inline by design: the dashboard still issues the comparison query (executor attaches __compare) and only declines to draw it. That is the same author-visible behaviour pie/donut/funnel have on the dashboard today (no exclusion there at all; renderer draws series[0]). "Exactly as pie / donut / funnel" holds at the visible level; the internal asymmetry is stated in the code comment and filed (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). Right.

Published surface — packages/plugin-dashboard/SKILL.md

  • :24-26: "For chart widgets (line / area / bar / horizontal-bar / combo), a muted second series … Pie, donut, funnel and scatter charts ignore compareTo." True at the author-visible level on both paths; does not overclaim a skipped query on the dashboard. Accurate.
  • :161-164: "Scatter charts, and the bubble widget type that renders as one … compareTo is ignored until scatter can project a second measure (objectui#7194 option A); ruled in objectui#7402." bubble claim verified against both maps; return condition matches the ruling. Accurate.
  • Governance fact, stated for the record: this file is not in GOVERNED_SURFACES (scripts/check-governed-queue-guard.mjs:201 — the governed/published skills tree is root skills/**, i.e. skills/objectui/), and not in plugin-dashboard/package.jsonfiles (dist, README.md, CHANGELOG.md, LICENSE). It is an in-repo authoring guide, not the npm-published or governed skills/** surface. I judged its wording as a contract statement regardless; it holds.

Changeset — .changeset/7402-scatter-ignores-compareto.md

  • "no comparison query is issued on the inline chart path" — true and correctly scoped. "No <measure>__comparison (inline) / <measure>__compare (dashboard) series is appended" — true. "bubble … renders as a scatter" — true.
  • One imprecision, inherited not introduced: "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive, but radar also draws the appended series on both paths (supportsCompareTo('radar') is true; CHART_TYPE_MAP.radar = 'radar'; the radar arm at AdvancedChartImpl.tsx:1675-1687 maps every series, unstyled). Pre-PR SKILL.md:24 carried the same list minus scatter. Not a ruling defect; optional.

Unmentioned changes: none. Six files; four in the PR table, two tests in the verification section; the DatasetWidget comment's #7495 note is mentioned.

Base drift: PR is 2 commits behind main (20cb8db..48c19bd); that delta touches only .changeset/* and a new DatasetWidget.dateMeasure.test.tsx. No overlap. #7400 has not merged (no refusal on this head's AdvancedChartImpl); nothing here depends on it.

② Semver

  • Declared: minor on both packages. scripts/check-changeset-no-major.mjs header: "objectui's own breaking changes ship as minor with the break spelled out in the changeset body"; major is reserved for the objectstack-synchronised bump (OBJECTUI_ALLOW_MAJOR=1). All 39 packages sit in one fixed group, so the level is group-wide anyway.
  • The body spells the break out in bold ("This removes a published capability, deliberately"), states before/after behaviour and the return condition.
  • I would assign minor. Matches.Changeset Bump Policy and Changeset Fixed Group Check both green.

③ Boundary flags

  • (a) CHANGELOG left as history — correct. Verified the four fan-out copies (packages/core/CHANGELOG.md:4901, packages/plugin-charts/CHANGELOG.md:1728, packages/plugin-dashboard/CHANGELOG.md:2731, packages/types/CHANGELOG.md:4085), all versioned history. Independent carrier sweep: md/mdx/ts/tsx/json/yaml repo-wide for pie|donut|funnel within 120 chars of compareTo → only SKILL.md:26; compareTo anywhere in content/**, docs/**, skills/**, packages/*/README.md, apps/** → none; JSDoc in packages/types/src and packages/core/src → no family list; broader phrasing ("comparison overlay", "period-over-period", "previous period series") → only SKILL.md. The implementer's claim holds; no carrier was missed. Answer to the dev report's open question: leave it. The ruling's "the plugin-charts CHANGELOG … statement becomes …" is satisfied by mechanism — this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release, which is exactly how the original sentence arrived there. Recommend the seat record that reading rather than reopen it.
  • (b) comparisonStyle(s, 'scatter') kept — correct. Verified: def AdvancedChartImpl.tsx:91-92, scatter call site :1758 (PR head); normalizeChartSchema.ts:246-247 accepts raw.variant ∈ {comparison, current, primary} from raw series; chart-presentation.ts:125 accepts {primary, comparison} from authored chartConfig. A single authored series with variant: 'comparison' reaches the branch and is not a two-series scatter. Deleting it would widen the removal beyond the ruling.
  • (c) 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 deferred — correct, and this PR stays coherent. The dashboard has no pie/donut/funnel exclusion to copy, so making the two lists agree means changing three more families' dashboard behaviour (query skip) — a separate decision, out of this card's scope. Within this PR, scatter's author-visible behaviour equals pie/donut/funnel on both paths; the one internal asymmetry (dashboard still queries) is shared with those three families today and is stated in both the changeset and the code comment with the issue link.

Pins — not vacuous

  • Ran in my own detached worktree at b68c77a (/home/user/objectui-review-7498, root vitest config aliases every @object-ui/* to src): forward 2 files / 6 tests passed.
  • Reverse verification, both sources reverted to 20cb8db with tests kept (mutation confirmed on disk: anchored grep counts 0/0): 4 failed / 2 passed — red: both ObjectChart scatter pins ("expected vi.fn() to be called 1 times, but got 2 times"), both DatasetWidget cases (scatter, bubble: "to have a length of 1 but got 2"); green: both bar controls. Restore verified (git diff HEAD --name-only empty).
  • Controls genuinely discriminate: ObjectChart's asserts aggregate called twice, series.length === 2, overlay variant: 'comparison', and data amount__comparison: 100; DatasetWidget's asserts series.length === 2 with revenue__comparevariant: 'comparison'. A change suppressing every comparison series fails both.
  • Coverage: both paths (inline aggregate; dashboard dataset) and both widget spellings (describe.each(['scatter','bubble'])). The non-dataset bubble → DashboardRenderer → ObjectChart('scatter') hand-off is covered transitively (data mapping with its own spec-parity test), not by a dedicated pin; acceptable.
  • Pre-existing ObjectChart.compareTo* / DatasetWidget.compareTo* suites on the PR head: 4 files / 36 tests passed.

CI by name (head b68c77a)

  • Green: Skill Example Check, Pre-Install Import Graph Check, Skill Guide Path Check, Skill Eval Token Check, Internal Docs Link Check, Changeset Declaration, Docs Route Eager Closure Check, Shell Escape Residue Scan, Live E2E (informational), Changeset Overwrite Report, Doc Component Type Check, Test (shard 3/4), Test (shard 4/4), Bundle Analysis, README Export Check, Lint, Build & E2E, Changeset Fixed Group Check, Control Byte Scan, Type Check, Governed Surface Queue Guard, Changeset Bump Policy, label, Inert vi.mock Specifier Check, Doc Snippet Type Check.
  • Skipped by design: dependabot, Test (coverage), Test (coverage shard).
  • Still in progress at review time: Test (shard 1/4), Test (shard 2/4) (started 11:29Z; shards 3/4 took ~10 min). Landing condition: both green. My local run of both packages' compareTo suites is the partial substitute, not a replacement.
  • Minor: the PR body cites scripts/pm/os-verify-lock.sh; it is not tracked in this repo (exists untracked in the main checkout and in ../objectstack). Immaterial to the verdict.

Verdict

PASS.

Non-required notes for the seat (not amendments): (1) optionally tighten the changeset's "Charts that keep the overlay:" sentence to "the families the overlay is designed for:" or add radar, since radar draws the appended series unstyled; (2) record on #7402 that the ruling's CHANGELOG sentence is met by the changeset compiling into packages/plugin-charts/CHANGELOG.md at release, closing the dev report's open question; (3) wait for Test (shard 1/4) and Test (shard 2/4) before any landing action. I left /home/user/objectui-review-7498 in place (detached, installed, closure built) — delete if not wanted.


Seat disposition

PASS ⇒ this seat clears the gate and lands it, per 「PASS ⇒ 同席剥标、ready、auto-merge」 — once the reviewer's own landing condition is met: Test (shard 1/4) and Test (shard 2/4) green by name. Holding until then.

The reviewer's note (2) corrects this seat, and I am adopting the correction. On #7402 I ruled option A and recorded it as "deviating from the ruling's literal word", flagging it for the director. The reviewer shows there is no deviation to flag: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I will correct that on #7402 rather than leave a false self-accusation standing.

Its carrier sweep is also stronger than the one I accepted: it checked content/**, docs/**, skills/**, package READMEs, JSDoc in types/core, and three broader phrasings, all independently — the implementer's "SKILL.md is the only current-tense carrier" claim holds on that wider basis.

Note (1) is optional and I am not requiring it: the radar imprecision is inherited from the pre-PR sentence, not introduced here, and widening this PR to fix inherited copy is the scope creep the lane avoids.


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 11:46
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Landing — contract review PASS, 32/32 green, ready + auto-merge

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Provenance: 「PASS ⇒ 同席剥标、ready、auto-merge」 (维护者 2026-08-31 裁定).

The reviewer's own landing condition was wait for Test (shard 1/4) and Test (shard 2/4). Both are now green — shard 1 at 11:44:13, shard 2 at 11:43:53. Full reading on head b68c77a17: 29 success, 3 skipped by design (dependabot, Test (coverage), Test (coverage shard …)), read individually across all 32, not in aggregate. mergeable_state: clean against main at 48c19bd4.

⚠️ A carrier half-state, recorded rather than glossed

Landing precondition ② is "needs:contract-review cleared on both carriers." Here there was nothing to clear — the label was never hung on either carrier:

carrierlabels
PR #7498plugin, plugin: charts, tests
card #7402pm:dispatched

The protocol has the PR creator hang needs:contract-reviewwith the reviewable contract increment, and Clause-②: yes was declared here in the body and in the claim comment. The label was not applied, so condition ② is satisfied only trivially — the gate never existed to be passed.

⛔ I am not treating that as equivalent to a cleared gate. The substance is satisfied: an isolated reviewer at CONTRACT_REVIEW_TIER reviewed this increment and returned PASS, which is what the label exists to force. But this is the second carrier defect this lane has hit today — the first was PR #7491, where the card-side Clause-② declaration was machine-invisible because no comment matched the claim marker. Two different failures, same class: the declaration was made and the mechanism that reads it was not fed.

⚠️ And I could not have caught either mechanically: scripts/pm/check-clause2-carriers.mjs exits 2 in this session (it needs its own GitHub token; this seat reaches GitHub only through MCP, and its own output says that is not a clearance). Both carrier readings here are manual, and a manual reading covers strictly less than the tool. Naming that so the next seat knows which instrument was used.

⭐ The review corrected me, and I have taken the correction

On #7402 I ruled the CHANGELOG question option A and recorded it as "deviating from the ruling's literal word," flagging it for the director to object to. The reviewer showed there is no deviation: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this PR's changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I am correcting that on #7402 rather than leaving a false self-accusation on the record.

Its carrier sweep for that question was also wider than the one I accepted — content/**, docs/**, skills/**, package READMEs, JSDoc in types and core, plus three broader phrasings, each checked independently — and the implementer's "SKILL.md is the only current-tense carrier" claim holds on that stronger basis.

One optional note deliberately NOT required

The reviewer observed the changeset's "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive while radar also draws the appended series. ⛔ Not required as an amendment: that imprecision is inherited from the pre-PR SKILL.md:24 sentence, not introduced here, and widening this PR to correct inherited copy is the scope creep the lane exists to avoid. Recorded so it is a known, chosen gap rather than an unnoticed one.

Housekeeping

The reviewer left /home/user/objectui-review-7498 in place (detached worktree, installed, closure built). It is a session-local scratch tree on a shared container — whoever next needs the disk can remove it; nothing depends on it.


Generated by Claude Code

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

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do - #7498

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto
Sep 3, 2026
Merged

fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do#7498
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7402

Clause-②: yes — a published behaviour statement changes, so this PR stays draft, is not enqueued and has no auto-merge: the domain:ui seat runs its in-seat contract review before it lands.

Implements the maintainer ruling recorded on #7402 (comment 5523068497, 2026-09-03, verbatim 「同意」): (b)compareTo on a scatter is not supported, and scatter joins pie / donut / funnel on the ignore list.

What changed

filechange
packages/plugin-charts/src/ObjectChart.tsxsupportsCompareTo excludes scatter as well as pie / donut / funnel. The comparison fetch is skipped entirely, so no MEASURE__comparison column exists and augmentedSeries never appends the overlay.
packages/plugin-dashboard/src/DatasetWidget.tsxthe chart path gains chartIgnoresCompareTo (chartType === 'scatter'), which suppresses comparisonSeries. Written against the CHART type, so it covers both widget-type spellings — scatter and bubble both map to chartType: 'scatter' through CHART_TYPE_MAP.
packages/plugin-dashboard/SKILL.mdthe current-tense published statement of which families ignore compareTo, in both places it appears.
.changeset/7402-scatter-ignores-compareto.mdminor on both packages, stating plainly that a published capability is removed and naming the condition for its return.

A compare-to document therefore never reaches the two-or-more-series scatter refusal of #7194 — the primary draws, and nothing is refused. #7194 remains open and is unaffected by this PR.

Why removal rather than a refusal

A scatter binds ONE measure and the renderer reads y through the single YAxis dataKey={series[0].dataKey}, so the synthesised overlay was plotted on the primary's y: "previous period" painted exactly on top of "current". Drawing it honestly needs the multi-measure projection recorded as option A of #7194, which is not built. Per the ruling the capability is removed until that projection arrives, and returns with it — one payment, no transition. A refusal was rejected because it names a key the author never wrote and loses the correctly drawn primary too.

Verification (all figures observed on b68c77a17, on a SHARED container — heavy runs went through this box's verify lock, so wall-clock absolutes are contended readings)

  • New pins, both pathspnpm exec vitest run from the repo root over the two new files: 2 files passed, 6 tests passed.
  • Both package suitespnpm exec vitest run --maxWorkers=2 packages/plugin-charts/ packages/plugin-dashboard/ from the repo root: 131 files passed, 1208 tests passed, 0 failed (216s).
  • Reverse verification — with ONLY the two source files reverted to origin/main (20cb8db9b) over the new tests: 4 failed | 2 passed. Red: both scatter pins in plugin-charts and the scatter + bubble cases in plugin-dashboard. Green: the two bar CONTROLS — which is the point of having them, since a regression that suppressed every comparison series would otherwise satisfy every new assertion. The mutation was proven on disk before the run (blob hash moved from 92a0b536/0189be13, anchored grep counts 1/20/0, pre-fix text back at 1/1); the restore was proven by state afterwards (blob hashes equal HEAD's again, git diff HEAD empty), under trap … EXIT INT TERM with absolute paths.
  • type-checkpnpm --filter @object-ui/plugin-charts --filter @object-ui/plugin-dashboard run type-check: both Done, exit 0. Both new test files were confirmed to be IN the checked program (tsc --listFiles, 1 hit each) rather than assumed to be.
  • lint — same two packages, exit 0: 175 files linted, 0 errors, 748 warnings, all warnings the repo's pre-existing no-explicit-any / no-unused-vars baseline in files this PR does not touch. Narrowed to the two changed packages deliberately: the file set comes from eslint's own flat config, the count from --format json, and the config enables no type-aware linting (no parserOptions.project / projectService), so this diff cannot move the verdict on any file it does not touch.
  • Gatescheck:control-bytes (6160 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, check:changeset-no-major: all exit 0, each read off the gate's own verdict line.

Two judgment calls, surfaced rather than decided quietly

1. The historical CHANGELOG.md line is left as written.packages/plugin-charts/CHANGELOG.md:1727 is generated by changesets and records what a released version did; editing it rewrites history that was true when it shipped. The same sentence also sits in three other packages' CHANGELOGs (types, core, plugin-dashboard) — all historical fan-out of one changeset, all left alone. The current-tense publication of that list turned out to be packages/plugin-dashboard/SKILL.md, which is updated here in both places, and the new statement is carried into the next release notes by this PR's changeset. Nothing was silently rewritten.

2. comparisonStyle(s, 'scatter') (AdvancedChartImpl.tsx:92, call site :1758) is NOT dead, so it stays.variant is an authorable series key on the chart schema, read from raw input in normalizeChartSchema.ts:247 and from authored chartConfig in @object-ui/core's chart-presentation.ts:125. A hand-authored chartType: 'scatter' with series: [{ dataKey, variant: 'comparison' }] reaches that branch directly, and being a single-series chart it is not caught by #7194's refusal either. What this PR removes is only the SYNTHESISED overlay; the authored spelling is untouched.

Filed, not fixed here

#7495 — the compareTo chart-family exclusion list now exists in two places and disagrees: DatasetWidget still runs the comparison query and appends an unused overlay series for pie / donut / funnel (the renderer draws series[0] only, so nothing is mis-drawn). Out of this card's declared scope; that one wants its own decision because it changes dashboard behaviour for three more families.

Reachability at the time of the change

0 authored scatter/bubble instances in-repo across both spellings, and no compareTo at all in examples/**/*.json; control "type": "bar" fires at 5 example files. Incidence in deployed tenant metadata is not measurable from this repo.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…pie/donut/funnel do
A `chartType: 'scatter'` chart — and the dashboard widget types `scatter` and
`bubble`, which both map onto it — synthesised a muted "previous period"
overlay series when `compareTo` was set. A scatter binds ONE measure and the
renderer reads y through the single `YAxis dataKey={series[0].dataKey}`, so
that overlay was plotted on the PRIMARY series' y: the previous period was
painted exactly on top of the current one (#7194).
Ruling on #7402 (maintainer, 2026-09-03): remove the published capability
rather than keep drawing that picture. `supportsCompareTo` in ObjectChart and
the DatasetWidget chart path now exclude scatter exactly as they exclude
pie / donut / funnel, so the primary draws, no `__comparison` / `__compare`
series is synthesised, and a compare-to document never reaches the
two-or-more-series scatter refusal of #7194. It returns with the multi-measure
projection recorded as option A of #7194.
`packages/plugin-dashboard/SKILL.md` — the current-tense published statement of
which families ignore `compareTo` — is updated. The historical CHANGELOG entry
that first published the list is left as written.
One pin per path plus a mandatory `bar` control that still gets its overlay;
without the control a regression suppressing every comparison series would
pass every new assertion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-DBHpSRoB.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.32KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Clause-② in-seat contract review — PASS

Commissioned by the domain:ui execution seat (PM session session_01EMrWaQw3XS5DxTHxp4yRyC) per the in-seat review route (维护者 2026-08-31 裁定). This seat runs opus and is not at tier, so it commissioned an isolated reviewer rather than self-reviewing. The reviewer was given only the card, the ruling and the PR — ⛔ not the dispatch order and not this seat's opinion.

Tier verification (维护者 2026-08-27 裁定 — a commissioned review is legitimate only if verified): every harness-stamped model field in the transcript was read before adopting — 96 stamps, all claude-fable-5-1, no other value. One string matching a fallback pattern was inspected individually and is the word "overloaded" inside a source comment the reviewer was reading (colors is overloaded kanban-style), not a model fallback. CONTRACT_REVIEW_TIER is claude-fable-5 (objectstack/scripts/pm/dispatch-gates.mjs:8091); claude-fable-5-1 is at or above that floor. ⇒ Verified, so the verdict is adopted verbatim — ⛔ nothing below is rewritten, abridged or polished.


Contract review — objectui PR #7498 (head b68c77a), card #7402, ruling comment 5523068497 option (b)

① Derived judgments (accept set / published surface)

Inline chart path — packages/plugin-charts/src/ObjectChart.tsx

  • supportsCompareTo adds ct !== 'scatter' (PR head :350-351). It is read at exactly two sites and both are gated: :553wantsComparison (the second runAggregate and the <valueKey>__comparison merge at :600) and :811enableComparisonSeries (the overlay append at :829-838). For chartType: 'scatter' no comparison fetch runs, no __comparison column exists, no overlay series is synthesised. Right — matches ruling (b) verbatim.
  • The dataset-bound inline branch (:543-556) never forwards compareTo to queryDataset, and finalSchema (:872-874) takes datasetChart.series, not augmentedSeries. There is no second inline overlay path the predicate misses. Right.
  • Every other reader of the column, verified repo-wide (packages/**, apps/**, non-test): __comparison / COMPARISON_SUFFIX appear only in ObjectChart.tsx (:41 export, :600, :808). variant: 'comparison' readers: AdvancedChartImpl.tsx:92 (comparisonStyle), :1847-1871 (bar-arm colour pairing), normalizeChartSchema.ts:247, packages/core/src/utils/chart-presentation.ts:125, DatasetWidget.tsx:1419. All consume a series that may carry the variant; none requires one to exist for a scatter. The scatter arm (AdvancedChartImpl.tsx:1694-1777) legend uses nameKey={scatterYKey} and the default ChartTooltipContent; no legend, tooltip, export, click payload, or saved-view reader depends on the overlay. Removal is exactly the ruling's width, not wider.

Dashboard path — packages/plugin-dashboard/src/DatasetWidget.tsx

  • chartIgnoresCompareTo = chartType === 'scatter' (:1418) gates comparisonSeries (:1420). chartType comes from CHART_TYPE_MAP[widgetType] ?? 'bar' (:1373); the map has scatter: 'scatter' and bubble: 'scatter' (:377-378). Both spellings verified in source.
  • bubble by any other path: widgetDispatch.ts:40 aliases bubble → scatter; DashboardRenderer.tsx:602-640 builds the non-dataset chart with chartType: resolvedWidgetType (= scatter) and forwards compareTo (:639) — lands on ObjectChart's predicate; DashboardGridLayout.tsx:223-258 passes dispatch.chartType and forwards no compareTo at all; a dataset-bound bubble reaches DatasetWidget whole (DashboardRenderer.tsx:920). No product path delivers a raw 'bubble' to either predicate. The ruling's "widget types scatter and bubble" is satisfied.
  • comparedValues (:759) is untouched, so metric/table/pivot/export paths are unaffected. The chart return (:1502-1512) renders no comparison caption — compareCaption (:1007) sits in the table branch; the overlay label at :1433 was compareLabel's only chart-path carrier. No orphan "vs previous year" caption survives on a scatter. Right.
  • Semantics differ from inline by design: the dashboard still issues the comparison query (executor attaches __compare) and only declines to draw it. That is the same author-visible behaviour pie/donut/funnel have on the dashboard today (no exclusion there at all; renderer draws series[0]). "Exactly as pie / donut / funnel" holds at the visible level; the internal asymmetry is stated in the code comment and filed (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). Right.

Published surface — packages/plugin-dashboard/SKILL.md

  • :24-26: "For chart widgets (line / area / bar / horizontal-bar / combo), a muted second series … Pie, donut, funnel and scatter charts ignore compareTo." True at the author-visible level on both paths; does not overclaim a skipped query on the dashboard. Accurate.
  • :161-164: "Scatter charts, and the bubble widget type that renders as one … compareTo is ignored until scatter can project a second measure (objectui#7194 option A); ruled in objectui#7402." bubble claim verified against both maps; return condition matches the ruling. Accurate.
  • Governance fact, stated for the record: this file is not in GOVERNED_SURFACES (scripts/check-governed-queue-guard.mjs:201 — the governed/published skills tree is root skills/**, i.e. skills/objectui/), and not in plugin-dashboard/package.jsonfiles (dist, README.md, CHANGELOG.md, LICENSE). It is an in-repo authoring guide, not the npm-published or governed skills/** surface. I judged its wording as a contract statement regardless; it holds.

Changeset — .changeset/7402-scatter-ignores-compareto.md

  • "no comparison query is issued on the inline chart path" — true and correctly scoped. "No <measure>__comparison (inline) / <measure>__compare (dashboard) series is appended" — true. "bubble … renders as a scatter" — true.
  • One imprecision, inherited not introduced: "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive, but radar also draws the appended series on both paths (supportsCompareTo('radar') is true; CHART_TYPE_MAP.radar = 'radar'; the radar arm at AdvancedChartImpl.tsx:1675-1687 maps every series, unstyled). Pre-PR SKILL.md:24 carried the same list minus scatter. Not a ruling defect; optional.

Unmentioned changes: none. Six files; four in the PR table, two tests in the verification section; the DatasetWidget comment's #7495 note is mentioned.

Base drift: PR is 2 commits behind main (20cb8db..48c19bd); that delta touches only .changeset/* and a new DatasetWidget.dateMeasure.test.tsx. No overlap. #7400 has not merged (no refusal on this head's AdvancedChartImpl); nothing here depends on it.

② Semver

  • Declared: minor on both packages. scripts/check-changeset-no-major.mjs header: "objectui's own breaking changes ship as minor with the break spelled out in the changeset body"; major is reserved for the objectstack-synchronised bump (OBJECTUI_ALLOW_MAJOR=1). All 39 packages sit in one fixed group, so the level is group-wide anyway.
  • The body spells the break out in bold ("This removes a published capability, deliberately"), states before/after behaviour and the return condition.
  • I would assign minor. Matches.Changeset Bump Policy and Changeset Fixed Group Check both green.

③ Boundary flags

  • (a) CHANGELOG left as history — correct. Verified the four fan-out copies (packages/core/CHANGELOG.md:4901, packages/plugin-charts/CHANGELOG.md:1728, packages/plugin-dashboard/CHANGELOG.md:2731, packages/types/CHANGELOG.md:4085), all versioned history. Independent carrier sweep: md/mdx/ts/tsx/json/yaml repo-wide for pie|donut|funnel within 120 chars of compareTo → only SKILL.md:26; compareTo anywhere in content/**, docs/**, skills/**, packages/*/README.md, apps/** → none; JSDoc in packages/types/src and packages/core/src → no family list; broader phrasing ("comparison overlay", "period-over-period", "previous period series") → only SKILL.md. The implementer's claim holds; no carrier was missed. Answer to the dev report's open question: leave it. The ruling's "the plugin-charts CHANGELOG … statement becomes …" is satisfied by mechanism — this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release, which is exactly how the original sentence arrived there. Recommend the seat record that reading rather than reopen it.
  • (b) comparisonStyle(s, 'scatter') kept — correct. Verified: def AdvancedChartImpl.tsx:91-92, scatter call site :1758 (PR head); normalizeChartSchema.ts:246-247 accepts raw.variant ∈ {comparison, current, primary} from raw series; chart-presentation.ts:125 accepts {primary, comparison} from authored chartConfig. A single authored series with variant: 'comparison' reaches the branch and is not a two-series scatter. Deleting it would widen the removal beyond the ruling.
  • (c) 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 deferred — correct, and this PR stays coherent. The dashboard has no pie/donut/funnel exclusion to copy, so making the two lists agree means changing three more families' dashboard behaviour (query skip) — a separate decision, out of this card's scope. Within this PR, scatter's author-visible behaviour equals pie/donut/funnel on both paths; the one internal asymmetry (dashboard still queries) is shared with those three families today and is stated in both the changeset and the code comment with the issue link.

Pins — not vacuous

  • Ran in my own detached worktree at b68c77a (/home/user/objectui-review-7498, root vitest config aliases every @object-ui/* to src): forward 2 files / 6 tests passed.
  • Reverse verification, both sources reverted to 20cb8db with tests kept (mutation confirmed on disk: anchored grep counts 0/0): 4 failed / 2 passed — red: both ObjectChart scatter pins ("expected vi.fn() to be called 1 times, but got 2 times"), both DatasetWidget cases (scatter, bubble: "to have a length of 1 but got 2"); green: both bar controls. Restore verified (git diff HEAD --name-only empty).
  • Controls genuinely discriminate: ObjectChart's asserts aggregate called twice, series.length === 2, overlay variant: 'comparison', and data amount__comparison: 100; DatasetWidget's asserts series.length === 2 with revenue__comparevariant: 'comparison'. A change suppressing every comparison series fails both.
  • Coverage: both paths (inline aggregate; dashboard dataset) and both widget spellings (describe.each(['scatter','bubble'])). The non-dataset bubble → DashboardRenderer → ObjectChart('scatter') hand-off is covered transitively (data mapping with its own spec-parity test), not by a dedicated pin; acceptable.
  • Pre-existing ObjectChart.compareTo* / DatasetWidget.compareTo* suites on the PR head: 4 files / 36 tests passed.

CI by name (head b68c77a)

  • Green: Skill Example Check, Pre-Install Import Graph Check, Skill Guide Path Check, Skill Eval Token Check, Internal Docs Link Check, Changeset Declaration, Docs Route Eager Closure Check, Shell Escape Residue Scan, Live E2E (informational), Changeset Overwrite Report, Doc Component Type Check, Test (shard 3/4), Test (shard 4/4), Bundle Analysis, README Export Check, Lint, Build & E2E, Changeset Fixed Group Check, Control Byte Scan, Type Check, Governed Surface Queue Guard, Changeset Bump Policy, label, Inert vi.mock Specifier Check, Doc Snippet Type Check.
  • Skipped by design: dependabot, Test (coverage), Test (coverage shard).
  • Still in progress at review time: Test (shard 1/4), Test (shard 2/4) (started 11:29Z; shards 3/4 took ~10 min). Landing condition: both green. My local run of both packages' compareTo suites is the partial substitute, not a replacement.
  • Minor: the PR body cites scripts/pm/os-verify-lock.sh; it is not tracked in this repo (exists untracked in the main checkout and in ../objectstack). Immaterial to the verdict.

Verdict

PASS.

Non-required notes for the seat (not amendments): (1) optionally tighten the changeset's "Charts that keep the overlay:" sentence to "the families the overlay is designed for:" or add radar, since radar draws the appended series unstyled; (2) record on #7402 that the ruling's CHANGELOG sentence is met by the changeset compiling into packages/plugin-charts/CHANGELOG.md at release, closing the dev report's open question; (3) wait for Test (shard 1/4) and Test (shard 2/4) before any landing action. I left /home/user/objectui-review-7498 in place (detached, installed, closure built) — delete if not wanted.


Seat disposition

PASS ⇒ this seat clears the gate and lands it, per 「PASS ⇒ 同席剥标、ready、auto-merge」 — once the reviewer's own landing condition is met: Test (shard 1/4) and Test (shard 2/4) green by name. Holding until then.

The reviewer's note (2) corrects this seat, and I am adopting the correction. On #7402 I ruled option A and recorded it as "deviating from the ruling's literal word", flagging it for the director. The reviewer shows there is no deviation to flag: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I will correct that on #7402 rather than leave a false self-accusation standing.

Its carrier sweep is also stronger than the one I accepted: it checked content/**, docs/**, skills/**, package READMEs, JSDoc in types/core, and three broader phrasings, all independently — the implementer's "SKILL.md is the only current-tense carrier" claim holds on that wider basis.

Note (1) is optional and I am not requiring it: the radar imprecision is inherited from the pre-PR sentence, not introduced here, and widening this PR to fix inherited copy is the scope creep the lane avoids.


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 11:46
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Landing — contract review PASS, 32/32 green, ready + auto-merge

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Provenance: 「PASS ⇒ 同席剥标、ready、auto-merge」 (维护者 2026-08-31 裁定).

The reviewer's own landing condition was wait for Test (shard 1/4) and Test (shard 2/4). Both are now green — shard 1 at 11:44:13, shard 2 at 11:43:53. Full reading on head b68c77a17: 29 success, 3 skipped by design (dependabot, Test (coverage), Test (coverage shard …)), read individually across all 32, not in aggregate. mergeable_state: clean against main at 48c19bd4.

⚠️ A carrier half-state, recorded rather than glossed

Landing precondition ② is "needs:contract-review cleared on both carriers." Here there was nothing to clear — the label was never hung on either carrier:

carrierlabels
PR #7498plugin, plugin: charts, tests
card #7402pm:dispatched

The protocol has the PR creator hang needs:contract-reviewwith the reviewable contract increment, and Clause-②: yes was declared here in the body and in the claim comment. The label was not applied, so condition ② is satisfied only trivially — the gate never existed to be passed.

⛔ I am not treating that as equivalent to a cleared gate. The substance is satisfied: an isolated reviewer at CONTRACT_REVIEW_TIER reviewed this increment and returned PASS, which is what the label exists to force. But this is the second carrier defect this lane has hit today — the first was PR #7491, where the card-side Clause-② declaration was machine-invisible because no comment matched the claim marker. Two different failures, same class: the declaration was made and the mechanism that reads it was not fed.

⚠️ And I could not have caught either mechanically: scripts/pm/check-clause2-carriers.mjs exits 2 in this session (it needs its own GitHub token; this seat reaches GitHub only through MCP, and its own output says that is not a clearance). Both carrier readings here are manual, and a manual reading covers strictly less than the tool. Naming that so the next seat knows which instrument was used.

⭐ The review corrected me, and I have taken the correction

On #7402 I ruled the CHANGELOG question option A and recorded it as "deviating from the ruling's literal word," flagging it for the director to object to. The reviewer showed there is no deviation: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this PR's changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I am correcting that on #7402 rather than leaving a false self-accusation on the record.

Its carrier sweep for that question was also wider than the one I accepted — content/**, docs/**, skills/**, package READMEs, JSDoc in types and core, plus three broader phrasings, each checked independently — and the implementer's "SKILL.md is the only current-tense carrier" claim holds on that stronger basis.

One optional note deliberately NOT required

The reviewer observed the changeset's "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive while radar also draws the appended series. ⛔ Not required as an amendment: that imprecision is inherited from the pre-PR SKILL.md:24 sentence, not introduced here, and widening this PR to correct inherited copy is the scope creep the lane exists to avoid. Recorded so it is a known, chosen gap rather than an unnoticed one.

Housekeeping

The reviewer left /home/user/objectui-review-7498 in place (detached worktree, installed, closure built). It is a session-local scratch tree on a shared container — whoever next needs the disk can remove it; nothing depends on it.


Generated by Claude Code

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

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do - #7498

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto
Sep 3, 2026
Merged

fix(plugin-charts,plugin-dashboard): a scatter ignores compareTo, exactly as pie / donut / funnel do#7498
os-project-manager merged 1 commit into
mainfrom
claude/issue-7402-scatter-ignores-compareto

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7402

Clause-②: yes — a published behaviour statement changes, so this PR stays draft, is not enqueued and has no auto-merge: the domain:ui seat runs its in-seat contract review before it lands.

Implements the maintainer ruling recorded on #7402 (comment 5523068497, 2026-09-03, verbatim 「同意」): (b)compareTo on a scatter is not supported, and scatter joins pie / donut / funnel on the ignore list.

What changed

filechange
packages/plugin-charts/src/ObjectChart.tsxsupportsCompareTo excludes scatter as well as pie / donut / funnel. The comparison fetch is skipped entirely, so no MEASURE__comparison column exists and augmentedSeries never appends the overlay.
packages/plugin-dashboard/src/DatasetWidget.tsxthe chart path gains chartIgnoresCompareTo (chartType === 'scatter'), which suppresses comparisonSeries. Written against the CHART type, so it covers both widget-type spellings — scatter and bubble both map to chartType: 'scatter' through CHART_TYPE_MAP.
packages/plugin-dashboard/SKILL.mdthe current-tense published statement of which families ignore compareTo, in both places it appears.
.changeset/7402-scatter-ignores-compareto.mdminor on both packages, stating plainly that a published capability is removed and naming the condition for its return.

A compare-to document therefore never reaches the two-or-more-series scatter refusal of #7194 — the primary draws, and nothing is refused. #7194 remains open and is unaffected by this PR.

Why removal rather than a refusal

A scatter binds ONE measure and the renderer reads y through the single YAxis dataKey={series[0].dataKey}, so the synthesised overlay was plotted on the primary's y: "previous period" painted exactly on top of "current". Drawing it honestly needs the multi-measure projection recorded as option A of #7194, which is not built. Per the ruling the capability is removed until that projection arrives, and returns with it — one payment, no transition. A refusal was rejected because it names a key the author never wrote and loses the correctly drawn primary too.

Verification (all figures observed on b68c77a17, on a SHARED container — heavy runs went through this box's verify lock, so wall-clock absolutes are contended readings)

  • New pins, both pathspnpm exec vitest run from the repo root over the two new files: 2 files passed, 6 tests passed.
  • Both package suitespnpm exec vitest run --maxWorkers=2 packages/plugin-charts/ packages/plugin-dashboard/ from the repo root: 131 files passed, 1208 tests passed, 0 failed (216s).
  • Reverse verification — with ONLY the two source files reverted to origin/main (20cb8db9b) over the new tests: 4 failed | 2 passed. Red: both scatter pins in plugin-charts and the scatter + bubble cases in plugin-dashboard. Green: the two bar CONTROLS — which is the point of having them, since a regression that suppressed every comparison series would otherwise satisfy every new assertion. The mutation was proven on disk before the run (blob hash moved from 92a0b536/0189be13, anchored grep counts 1/20/0, pre-fix text back at 1/1); the restore was proven by state afterwards (blob hashes equal HEAD's again, git diff HEAD empty), under trap … EXIT INT TERM with absolute paths.
  • type-checkpnpm --filter @object-ui/plugin-charts --filter @object-ui/plugin-dashboard run type-check: both Done, exit 0. Both new test files were confirmed to be IN the checked program (tsc --listFiles, 1 hit each) rather than assumed to be.
  • lint — same two packages, exit 0: 175 files linted, 0 errors, 748 warnings, all warnings the repo's pre-existing no-explicit-any / no-unused-vars baseline in files this PR does not touch. Narrowed to the two changed packages deliberately: the file set comes from eslint's own flat config, the count from --format json, and the config enables no type-aware linting (no parserOptions.project / projectService), so this diff cannot move the verdict on any file it does not touch.
  • Gatescheck:control-bytes (6160 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, check:changeset-no-major: all exit 0, each read off the gate's own verdict line.

Two judgment calls, surfaced rather than decided quietly

1. The historical CHANGELOG.md line is left as written.packages/plugin-charts/CHANGELOG.md:1727 is generated by changesets and records what a released version did; editing it rewrites history that was true when it shipped. The same sentence also sits in three other packages' CHANGELOGs (types, core, plugin-dashboard) — all historical fan-out of one changeset, all left alone. The current-tense publication of that list turned out to be packages/plugin-dashboard/SKILL.md, which is updated here in both places, and the new statement is carried into the next release notes by this PR's changeset. Nothing was silently rewritten.

2. comparisonStyle(s, 'scatter') (AdvancedChartImpl.tsx:92, call site :1758) is NOT dead, so it stays.variant is an authorable series key on the chart schema, read from raw input in normalizeChartSchema.ts:247 and from authored chartConfig in @object-ui/core's chart-presentation.ts:125. A hand-authored chartType: 'scatter' with series: [{ dataKey, variant: 'comparison' }] reaches that branch directly, and being a single-series chart it is not caught by #7194's refusal either. What this PR removes is only the SYNTHESISED overlay; the authored spelling is untouched.

Filed, not fixed here

#7495 — the compareTo chart-family exclusion list now exists in two places and disagrees: DatasetWidget still runs the comparison query and appends an unused overlay series for pie / donut / funnel (the renderer draws series[0] only, so nothing is mis-drawn). Out of this card's declared scope; that one wants its own decision because it changes dashboard behaviour for three more families.

Reachability at the time of the change

0 authored scatter/bubble instances in-repo across both spellings, and no compareTo at all in examples/**/*.json; control "type": "bar" fires at 5 example files. Incidence in deployed tenant metadata is not measurable from this repo.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…pie/donut/funnel do
A `chartType: 'scatter'` chart — and the dashboard widget types `scatter` and
`bubble`, which both map onto it — synthesised a muted "previous period"
overlay series when `compareTo` was set. A scatter binds ONE measure and the
renderer reads y through the single `YAxis dataKey={series[0].dataKey}`, so
that overlay was plotted on the PRIMARY series' y: the previous period was
painted exactly on top of the current one (#7194).
Ruling on #7402 (maintainer, 2026-09-03): remove the published capability
rather than keep drawing that picture. `supportsCompareTo` in ObjectChart and
the DatasetWidget chart path now exclude scatter exactly as they exclude
pie / donut / funnel, so the primary draws, no `__comparison` / `__compare`
series is synthesised, and a compare-to document never reaches the
two-or-more-series scatter refusal of #7194. It returns with the multi-measure
projection recorded as option A of #7194.
`packages/plugin-dashboard/SKILL.md` — the current-tense published statement of
which families ignore `compareTo` — is updated. The historical CHANGELOG entry
that first published the list is left as written.
One pin per path plus a mandatory `bar` control that still gets its overlay;
without the control a regression suppressing every comparison series would
pass every new assertion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-DBHpSRoB.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.32KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Clause-② in-seat contract review — PASS

Commissioned by the domain:ui execution seat (PM session session_01EMrWaQw3XS5DxTHxp4yRyC) per the in-seat review route (维护者 2026-08-31 裁定). This seat runs opus and is not at tier, so it commissioned an isolated reviewer rather than self-reviewing. The reviewer was given only the card, the ruling and the PR — ⛔ not the dispatch order and not this seat's opinion.

Tier verification (维护者 2026-08-27 裁定 — a commissioned review is legitimate only if verified): every harness-stamped model field in the transcript was read before adopting — 96 stamps, all claude-fable-5-1, no other value. One string matching a fallback pattern was inspected individually and is the word "overloaded" inside a source comment the reviewer was reading (colors is overloaded kanban-style), not a model fallback. CONTRACT_REVIEW_TIER is claude-fable-5 (objectstack/scripts/pm/dispatch-gates.mjs:8091); claude-fable-5-1 is at or above that floor. ⇒ Verified, so the verdict is adopted verbatim — ⛔ nothing below is rewritten, abridged or polished.


Contract review — objectui PR #7498 (head b68c77a), card #7402, ruling comment 5523068497 option (b)

① Derived judgments (accept set / published surface)

Inline chart path — packages/plugin-charts/src/ObjectChart.tsx

  • supportsCompareTo adds ct !== 'scatter' (PR head :350-351). It is read at exactly two sites and both are gated: :553wantsComparison (the second runAggregate and the <valueKey>__comparison merge at :600) and :811enableComparisonSeries (the overlay append at :829-838). For chartType: 'scatter' no comparison fetch runs, no __comparison column exists, no overlay series is synthesised. Right — matches ruling (b) verbatim.
  • The dataset-bound inline branch (:543-556) never forwards compareTo to queryDataset, and finalSchema (:872-874) takes datasetChart.series, not augmentedSeries. There is no second inline overlay path the predicate misses. Right.
  • Every other reader of the column, verified repo-wide (packages/**, apps/**, non-test): __comparison / COMPARISON_SUFFIX appear only in ObjectChart.tsx (:41 export, :600, :808). variant: 'comparison' readers: AdvancedChartImpl.tsx:92 (comparisonStyle), :1847-1871 (bar-arm colour pairing), normalizeChartSchema.ts:247, packages/core/src/utils/chart-presentation.ts:125, DatasetWidget.tsx:1419. All consume a series that may carry the variant; none requires one to exist for a scatter. The scatter arm (AdvancedChartImpl.tsx:1694-1777) legend uses nameKey={scatterYKey} and the default ChartTooltipContent; no legend, tooltip, export, click payload, or saved-view reader depends on the overlay. Removal is exactly the ruling's width, not wider.

Dashboard path — packages/plugin-dashboard/src/DatasetWidget.tsx

  • chartIgnoresCompareTo = chartType === 'scatter' (:1418) gates comparisonSeries (:1420). chartType comes from CHART_TYPE_MAP[widgetType] ?? 'bar' (:1373); the map has scatter: 'scatter' and bubble: 'scatter' (:377-378). Both spellings verified in source.
  • bubble by any other path: widgetDispatch.ts:40 aliases bubble → scatter; DashboardRenderer.tsx:602-640 builds the non-dataset chart with chartType: resolvedWidgetType (= scatter) and forwards compareTo (:639) — lands on ObjectChart's predicate; DashboardGridLayout.tsx:223-258 passes dispatch.chartType and forwards no compareTo at all; a dataset-bound bubble reaches DatasetWidget whole (DashboardRenderer.tsx:920). No product path delivers a raw 'bubble' to either predicate. The ruling's "widget types scatter and bubble" is satisfied.
  • comparedValues (:759) is untouched, so metric/table/pivot/export paths are unaffected. The chart return (:1502-1512) renders no comparison caption — compareCaption (:1007) sits in the table branch; the overlay label at :1433 was compareLabel's only chart-path carrier. No orphan "vs previous year" caption survives on a scatter. Right.
  • Semantics differ from inline by design: the dashboard still issues the comparison query (executor attaches __compare) and only declines to draw it. That is the same author-visible behaviour pie/donut/funnel have on the dashboard today (no exclusion there at all; renderer draws series[0]). "Exactly as pie / donut / funnel" holds at the visible level; the internal asymmetry is stated in the code comment and filed (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). Right.

Published surface — packages/plugin-dashboard/SKILL.md

  • :24-26: "For chart widgets (line / area / bar / horizontal-bar / combo), a muted second series … Pie, donut, funnel and scatter charts ignore compareTo." True at the author-visible level on both paths; does not overclaim a skipped query on the dashboard. Accurate.
  • :161-164: "Scatter charts, and the bubble widget type that renders as one … compareTo is ignored until scatter can project a second measure (objectui#7194 option A); ruled in objectui#7402." bubble claim verified against both maps; return condition matches the ruling. Accurate.
  • Governance fact, stated for the record: this file is not in GOVERNED_SURFACES (scripts/check-governed-queue-guard.mjs:201 — the governed/published skills tree is root skills/**, i.e. skills/objectui/), and not in plugin-dashboard/package.jsonfiles (dist, README.md, CHANGELOG.md, LICENSE). It is an in-repo authoring guide, not the npm-published or governed skills/** surface. I judged its wording as a contract statement regardless; it holds.

Changeset — .changeset/7402-scatter-ignores-compareto.md

  • "no comparison query is issued on the inline chart path" — true and correctly scoped. "No <measure>__comparison (inline) / <measure>__compare (dashboard) series is appended" — true. "bubble … renders as a scatter" — true.
  • One imprecision, inherited not introduced: "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive, but radar also draws the appended series on both paths (supportsCompareTo('radar') is true; CHART_TYPE_MAP.radar = 'radar'; the radar arm at AdvancedChartImpl.tsx:1675-1687 maps every series, unstyled). Pre-PR SKILL.md:24 carried the same list minus scatter. Not a ruling defect; optional.

Unmentioned changes: none. Six files; four in the PR table, two tests in the verification section; the DatasetWidget comment's #7495 note is mentioned.

Base drift: PR is 2 commits behind main (20cb8db..48c19bd); that delta touches only .changeset/* and a new DatasetWidget.dateMeasure.test.tsx. No overlap. #7400 has not merged (no refusal on this head's AdvancedChartImpl); nothing here depends on it.

② Semver

  • Declared: minor on both packages. scripts/check-changeset-no-major.mjs header: "objectui's own breaking changes ship as minor with the break spelled out in the changeset body"; major is reserved for the objectstack-synchronised bump (OBJECTUI_ALLOW_MAJOR=1). All 39 packages sit in one fixed group, so the level is group-wide anyway.
  • The body spells the break out in bold ("This removes a published capability, deliberately"), states before/after behaviour and the return condition.
  • I would assign minor. Matches.Changeset Bump Policy and Changeset Fixed Group Check both green.

③ Boundary flags

  • (a) CHANGELOG left as history — correct. Verified the four fan-out copies (packages/core/CHANGELOG.md:4901, packages/plugin-charts/CHANGELOG.md:1728, packages/plugin-dashboard/CHANGELOG.md:2731, packages/types/CHANGELOG.md:4085), all versioned history. Independent carrier sweep: md/mdx/ts/tsx/json/yaml repo-wide for pie|donut|funnel within 120 chars of compareTo → only SKILL.md:26; compareTo anywhere in content/**, docs/**, skills/**, packages/*/README.md, apps/** → none; JSDoc in packages/types/src and packages/core/src → no family list; broader phrasing ("comparison overlay", "period-over-period", "previous period series") → only SKILL.md. The implementer's claim holds; no carrier was missed. Answer to the dev report's open question: leave it. The ruling's "the plugin-charts CHANGELOG … statement becomes …" is satisfied by mechanism — this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release, which is exactly how the original sentence arrived there. Recommend the seat record that reading rather than reopen it.
  • (b) comparisonStyle(s, 'scatter') kept — correct. Verified: def AdvancedChartImpl.tsx:91-92, scatter call site :1758 (PR head); normalizeChartSchema.ts:246-247 accepts raw.variant ∈ {comparison, current, primary} from raw series; chart-presentation.ts:125 accepts {primary, comparison} from authored chartConfig. A single authored series with variant: 'comparison' reaches the branch and is not a two-series scatter. Deleting it would widen the removal beyond the ruling.
  • (c) 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 deferred — correct, and this PR stays coherent. The dashboard has no pie/donut/funnel exclusion to copy, so making the two lists agree means changing three more families' dashboard behaviour (query skip) — a separate decision, out of this card's scope. Within this PR, scatter's author-visible behaviour equals pie/donut/funnel on both paths; the one internal asymmetry (dashboard still queries) is shared with those three families today and is stated in both the changeset and the code comment with the issue link.

Pins — not vacuous

  • Ran in my own detached worktree at b68c77a (/home/user/objectui-review-7498, root vitest config aliases every @object-ui/* to src): forward 2 files / 6 tests passed.
  • Reverse verification, both sources reverted to 20cb8db with tests kept (mutation confirmed on disk: anchored grep counts 0/0): 4 failed / 2 passed — red: both ObjectChart scatter pins ("expected vi.fn() to be called 1 times, but got 2 times"), both DatasetWidget cases (scatter, bubble: "to have a length of 1 but got 2"); green: both bar controls. Restore verified (git diff HEAD --name-only empty).
  • Controls genuinely discriminate: ObjectChart's asserts aggregate called twice, series.length === 2, overlay variant: 'comparison', and data amount__comparison: 100; DatasetWidget's asserts series.length === 2 with revenue__comparevariant: 'comparison'. A change suppressing every comparison series fails both.
  • Coverage: both paths (inline aggregate; dashboard dataset) and both widget spellings (describe.each(['scatter','bubble'])). The non-dataset bubble → DashboardRenderer → ObjectChart('scatter') hand-off is covered transitively (data mapping with its own spec-parity test), not by a dedicated pin; acceptable.
  • Pre-existing ObjectChart.compareTo* / DatasetWidget.compareTo* suites on the PR head: 4 files / 36 tests passed.

CI by name (head b68c77a)

  • Green: Skill Example Check, Pre-Install Import Graph Check, Skill Guide Path Check, Skill Eval Token Check, Internal Docs Link Check, Changeset Declaration, Docs Route Eager Closure Check, Shell Escape Residue Scan, Live E2E (informational), Changeset Overwrite Report, Doc Component Type Check, Test (shard 3/4), Test (shard 4/4), Bundle Analysis, README Export Check, Lint, Build & E2E, Changeset Fixed Group Check, Control Byte Scan, Type Check, Governed Surface Queue Guard, Changeset Bump Policy, label, Inert vi.mock Specifier Check, Doc Snippet Type Check.
  • Skipped by design: dependabot, Test (coverage), Test (coverage shard).
  • Still in progress at review time: Test (shard 1/4), Test (shard 2/4) (started 11:29Z; shards 3/4 took ~10 min). Landing condition: both green. My local run of both packages' compareTo suites is the partial substitute, not a replacement.
  • Minor: the PR body cites scripts/pm/os-verify-lock.sh; it is not tracked in this repo (exists untracked in the main checkout and in ../objectstack). Immaterial to the verdict.

Verdict

PASS.

Non-required notes for the seat (not amendments): (1) optionally tighten the changeset's "Charts that keep the overlay:" sentence to "the families the overlay is designed for:" or add radar, since radar draws the appended series unstyled; (2) record on #7402 that the ruling's CHANGELOG sentence is met by the changeset compiling into packages/plugin-charts/CHANGELOG.md at release, closing the dev report's open question; (3) wait for Test (shard 1/4) and Test (shard 2/4) before any landing action. I left /home/user/objectui-review-7498 in place (detached, installed, closure built) — delete if not wanted.


Seat disposition

PASS ⇒ this seat clears the gate and lands it, per 「PASS ⇒ 同席剥标、ready、auto-merge」 — once the reviewer's own landing condition is met: Test (shard 1/4) and Test (shard 2/4) green by name. Holding until then.

The reviewer's note (2) corrects this seat, and I am adopting the correction. On #7402 I ruled option A and recorded it as "deviating from the ruling's literal word", flagging it for the director. The reviewer shows there is no deviation to flag: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I will correct that on #7402 rather than leave a false self-accusation standing.

Its carrier sweep is also stronger than the one I accepted: it checked content/**, docs/**, skills/**, package READMEs, JSDoc in types/core, and three broader phrasings, all independently — the implementer's "SKILL.md is the only current-tense carrier" claim holds on that wider basis.

Note (1) is optional and I am not requiring it: the radar imprecision is inherited from the pre-PR sentence, not introduced here, and widening this PR to fix inherited copy is the scope creep the lane avoids.


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 11:46
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Landing — contract review PASS, 32/32 green, ready + auto-merge

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Provenance: 「PASS ⇒ 同席剥标、ready、auto-merge」 (维护者 2026-08-31 裁定).

The reviewer's own landing condition was wait for Test (shard 1/4) and Test (shard 2/4). Both are now green — shard 1 at 11:44:13, shard 2 at 11:43:53. Full reading on head b68c77a17: 29 success, 3 skipped by design (dependabot, Test (coverage), Test (coverage shard …)), read individually across all 32, not in aggregate. mergeable_state: clean against main at 48c19bd4.

⚠️ A carrier half-state, recorded rather than glossed

Landing precondition ② is "needs:contract-review cleared on both carriers." Here there was nothing to clear — the label was never hung on either carrier:

carrierlabels
PR #7498plugin, plugin: charts, tests
card #7402pm:dispatched

The protocol has the PR creator hang needs:contract-reviewwith the reviewable contract increment, and Clause-②: yes was declared here in the body and in the claim comment. The label was not applied, so condition ② is satisfied only trivially — the gate never existed to be passed.

⛔ I am not treating that as equivalent to a cleared gate. The substance is satisfied: an isolated reviewer at CONTRACT_REVIEW_TIER reviewed this increment and returned PASS, which is what the label exists to force. But this is the second carrier defect this lane has hit today — the first was PR #7491, where the card-side Clause-② declaration was machine-invisible because no comment matched the claim marker. Two different failures, same class: the declaration was made and the mechanism that reads it was not fed.

⚠️ And I could not have caught either mechanically: scripts/pm/check-clause2-carriers.mjs exits 2 in this session (it needs its own GitHub token; this seat reaches GitHub only through MCP, and its own output says that is not a clearance). Both carrier readings here are manual, and a manual reading covers strictly less than the tool. Naming that so the next seat knows which instrument was used.

⭐ The review corrected me, and I have taken the correction

On #7402 I ruled the CHANGELOG question option A and recorded it as "deviating from the ruling's literal word," flagging it for the director to object to. The reviewer showed there is no deviation: the ruling says the plugin-charts CHANGELOG statement becomes the new sentence, and this PR's changeset compiles into packages/plugin-charts/CHANGELOG.md at the next release — which is exactly how the sentence it replaces got there. The ruling is satisfied by mechanism, not worked around. I am correcting that on #7402 rather than leaving a false self-accusation on the record.

Its carrier sweep for that question was also wider than the one I accepted — content/**, docs/**, skills/**, package READMEs, JSDoc in types and core, plus three broader phrasings, each checked independently — and the implementer's "SKILL.md is the only current-tense carrier" claim holds on that stronger basis.

One optional note deliberately NOT required

The reviewer observed the changeset's "Charts that keep the overlay: line, area, bar, horizontal-bar, combo" reads as exhaustive while radar also draws the appended series. ⛔ Not required as an amendment: that imprecision is inherited from the pre-PR SKILL.md:24 sentence, not introduced here, and widening this PR to correct inherited copy is the scope creep the lane exists to avoid. Recorded so it is a known, chosen gap rather than an unnoticed one.

Housekeeping

The reviewer left /home/user/objectui-review-7498 in place (detached worktree, installed, closure built). It is a session-local scratch tree on a shared container — whoever next needs the disk can remove it; nothing depends on it.


Generated by Claude Code

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

Projects

None yet

2 participants

@os-project-manager@claude