fix(charts): retire the Tremor dialect at ChartRenderer, keep the declared categories - #9367
Conversation
…lared `categories`
The card filed four undeclared reads on `ChartRendererProps.schema` as one
group. A cast-aware read census (`schemaReads`, objectui#6576) plus a
TypeScript-checker declaredness reading (`getPropertyOfType`, never a grep --
objectui#8410) measured them apart, and they do not share one verdict:
- `index`, `category`, `value` -- declared on no published face, advertised by
no registry `inputs`, taught by no doc or skill, and written by zero producers
in a structural census over packages/, apps/, examples/, content/docs/ and the
skills corpus. AGENTS.md #0.1 puts the remedy at the producer; there is no
producer. Retired.
- `categories` -- a declared member of the published `ChartSchema` and of its
zod mirror, documented in the schema reference, ruled LIVE by objectui#6896.
`normalizeChartSchema`, the ONE translation point (objectui#2880 S1), already
consumed it, so the branch here was a second, un-normalized read that no
well-formed chart could reach, and that on malformed input threw (`.map` on a
string) or produced a `[{ dataKey: '' }]` series. Branch removed, capability
untouched.
Also deletes the six objectui#8327 bucket-(b) casts the published declarations
had already made unnecessary: `colors` / `categoryColors` / `categoryOrder` on
`ChartRenderer`, and `colors` / `compareTo` / `series` on `ObjectChart`, which
now has no `(schema as any)` read left.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
|
| Step | Outcome |
|---|---|
| Build packages | success |
| Check console performance budget | failure |
Which half objected:
| Eager-closure half | Verdict |
|---|---|
| Aggregate closure ceiling | ✅ pass |
| Per-chunk ceilings | ✅ pass |
| Ceiling sensitivity (headroom) | |
| Ceiling freshness (checkout vs. base branch) | ✅ pass |
⚠️ A broken gauge half is a verdict about the ceiling, not about the bundle: that line has drifted out of range of the regression it exists to catch, or the report behind it cannot be trusted. It does not say anything grew. TheCheck console performance budgetstep log carries the ceiling and the number it was compared against.
Reason: The entry chunk measured 144.3 KB, but the eager-closure half of this gate returned no trustworthy VERDICT: the report could not be read, a ceiling has drifted out of range of the regression it must catch, or (objectui#6245) a ceiling was replaced on the base branch after this checkout was made. The step log says which. This is not a passing budget — and it is not a size regression either.
See the workflow run for details.
📦 Bundle Size Report
| Package | Size | Gzipped |
|---|---|---|
| app-shell (consoleActionDispatch.js) | 0.20KB | 0.19KB |
| app-shell (index.js) | 16.69KB | 6.21KB |
| app-shell (runtime-config.js) | 20.68KB | 7.36KB |
| app-shell (types.js) | 0.01KB | 0.04KB |
| app-shell (urlParams.js) | 10.06KB | 3.86KB |
| auth (ActiveOrganizationStorage.js) | 25.05KB | 9.16KB |
| auth (AuthContext.js) | 0.31KB | 0.24KB |
| auth (AuthGuard.js) | 2.07KB | 1.00KB |
| auth (AuthProvider.js) | 40.18KB | 10.59KB |
| auth (AuthShell.js) | 3.49KB | 1.40KB |
| auth (ForgotPasswordForm.js) | 12.21KB | 3.45KB |
| auth (LoginForm.js) | 18.15KB | 5.39KB |
| auth (PreviewBanner.js) | 0.90KB | 0.50KB |
| auth (RegisterForm.js) | 6.65KB | 2.22KB |
| auth (SocialSignInButtons.js) | 9.61KB | 3.89KB |
| auth (UserMenu.js) | 3.41KB | 1.23KB |
| auth (auth-gate-events.js) | 1.29KB | 0.66KB |
| auth (authStyles.js) | 5.04KB | 1.72KB |
| auth (createAuthClient.js) | 40.21KB | 10.80KB |
| auth (createAuthenticatedFetch.js) | 8.46KB | 3.43KB |
| auth (index.js) | 3.19KB | 1.44KB |
| auth (invitation-status.js) | 1.22KB | 0.70KB |
| auth (org-roles.js) | 6.66KB | 2.78KB |
| auth (phone-identifier.js) | 1.11KB | 0.66KB |
| auth (types.js) | 0.59KB | 0.35KB |
| auth (useAuth.js) | 5.30KB | 1.02KB |
| auth (useWorkspaceAdminStatus.js) | 11.08KB | 4.58KB |
| collaboration (CommentThread.js) | 26.08KB | 7.56KB |
| collaboration (LiveCursors.js) | 3.17KB | 1.27KB |
| collaboration (PresenceAvatars.js) | 6.49KB | 2.64KB |
| collaboration (PresenceProvider.js) | 2.79KB | 1.13KB |
| collaboration (index.js) | 1.68KB | 0.73KB |
| collaboration (useCollaborationTranslation.js) | 6.05KB | 2.52KB |
| collaboration (useCommentSearch.js) | 1.98KB | 0.88KB |
| collaboration (useConflictResolution.js) | 7.75KB | 1.86KB |
| collaboration (useMentionNotifications.js) | 1.81KB | 0.68KB |
| collaboration (usePresence.js) | 6.33KB | 1.84KB |
| collaboration (useRealtimeSubscription.js) | 7.91KB | 2.01KB |
| components (index.js) | 502.02KB | 115.16KB |
| core (index.js) | 8.52KB | 3.41KB |
| create-plugin (index.js) | 27.94KB | 9.51KB |
| data-objectstack (index.js) | 211.58KB | 58.68KB |
| fields (index.js) | 247.89KB | 62.50KB |
| i18n (LocalizationContext.js) | 1.76KB | 0.96KB |
| i18n (builtinAggregateLabels.js) | 0.86KB | 0.49KB |
| i18n (currency.js) | 1.22KB | 0.64KB |
| i18n (fallbackInterpolation.js) | 6.25KB | 2.77KB |
| i18n (i18n.js) | 8.87KB | 3.64KB |
| i18n (index.js) | 5.22KB | 2.26KB |
| i18n (pickLocalized.js) | 9.86KB | 3.95KB |
| i18n (provider.js) | 32.15KB | 10.49KB |
| i18n (useDisplayLocale.js) | 2.85KB | 1.45KB |
| i18n (useObjectLabel.js) | 34.34KB | 9.17KB |
| i18n (useSafeTranslation.js) | 5.60KB | 2.33KB |
| layout (index.js) | 38.83KB | 10.95KB |
| mobile (MobileProvider.js) | 0.92KB | 0.49KB |
| mobile (ResponsiveContainer.js) | 0.94KB | 0.38KB |
| mobile (breakpoints.js) | 1.51KB | 0.70KB |
| mobile (createOfflineDataSource.js) | 5.61KB | 1.75KB |
| mobile (index.js) | 1.99KB | 0.87KB |
| mobile (offlineQueue.js) | 3.91KB | 1.35KB |
| mobile (pwa.js) | 0.97KB | 0.49KB |
| mobile (serviceWorker.js) | 1.48KB | 0.62KB |
| mobile (serviceWorkerSource.js) | 3.41KB | 1.48KB |
| mobile (useBreakpoint.js) | 1.54KB | 0.65KB |
| mobile (useGesture.js) | 6.96KB | 1.98KB |
| mobile (useOfflineSync.js) | 1.99KB | 0.72KB |
| mobile (usePullToRefresh.js) | 2.53KB | 0.85KB |
| mobile (useResponsive.js) | 0.72KB | 0.42KB |
| mobile (useSpecGesture.js) | 4.39KB | 1.66KB |
| mobile (useTouchTarget.js) | 1.01KB | 0.54KB |
| permissions (MePermissionsProvider.js) | 13.52KB | 4.88KB |
| permissions (PermissionContext.js) | 0.31KB | 0.25KB |
| permissions (PermissionGuard.js) | 0.89KB | 0.45KB |
| permissions (PermissionProvider.js) | 6.24KB | 2.16KB |
| permissions (discardProofCache.js) | 1.04KB | 0.55KB |
| permissions (evaluator.js) | 8.39KB | 3.10KB |
| permissions (index.js) | 0.93KB | 0.41KB |
| permissions (store.js) | 0.91KB | 0.42KB |
| permissions (useFieldPermissions.js) | 1.28KB | 0.53KB |
| permissions (usePermissions.js) | 4.83KB | 2.27KB |
| plugin-ai (index.js) | 14.81KB | 3.63KB |
| plugin-calendar (index.js) | 49.25KB | 13.99KB |
| plugin-charts (index.js) | 71.34KB | 19.90KB |
| plugin-chatbot (index.js) | 195.34KB | 46.51KB |
| plugin-dashboard (index.js) | 131.22KB | 34.59KB |
| plugin-designer (index.js) | 215.94KB | 44.33KB |
| plugin-detail (index.js) | 253.46KB | 65.85KB |
| plugin-editor (index.js) | 2.23KB | 1.05KB |
| plugin-form (index.js) | 136.77KB | 34.17KB |
| plugin-gantt (index.js) | 166.95KB | 41.04KB |
| plugin-grid (index.js) | 211.58KB | 57.48KB |
| plugin-kanban (index.js) | 46.01KB | 14.30KB |
| plugin-list (index.js) | 112.58KB | 27.65KB |
| plugin-map (index.js) | 20.64KB | 6.86KB |
| plugin-markdown (index.js) | 13.88KB | 4.80KB |
| plugin-report (index.js) | 43.41KB | 11.93KB |
| plugin-timeline (index.js) | 30.07KB | 8.74KB |
| plugin-tree (index.js) | 9.55KB | 3.32KB |
| plugin-view (index.js) | 84.42KB | 20.79KB |
| providers (DataSourceProvider.js) | 0.75KB | 0.39KB |
| providers (MetadataProvider.js) | 1.37KB | 0.59KB |
| providers (ThemeProvider.js) | 1.90KB | 0.85KB |
| providers (UploadProvider.js) | 11.66KB | 3.50KB |
| providers (index.js) | 0.45KB | 0.23KB |
| providers (types.js) | 0.01KB | 0.04KB |
| react-runtime (index.js) | 5.62KB | 2.34KB |
| react (LazyPluginLoader.js) | 4.47KB | 1.63KB |
| react (SchemaRenderer.js) | 94.03KB | 31.02KB |
| react (data-invalidation.js) | 5.05KB | 2.08KB |
| react (index.js) | 4.63KB | 2.18KB |
| react (schema-input.js) | 4.25KB | 2.04KB |
| react (spec-input.js) | 0.20KB | 0.18KB |
| sdui-parser (codegen.js) | 6.58KB | 2.74KB |
| sdui-parser (dashboard-widget-options.js) | 3.08KB | 1.30KB |
| sdui-parser (index.js) | 5.66KB | 2.50KB |
| sdui-parser (input-type.js) | 2.84KB | 1.40KB |
| sdui-parser (kanban-quick-add.js) | 3.89KB | 1.87KB |
| sdui-parser (parse.js) | 25.28KB | 7.80KB |
| sdui-parser (provenance.js) | 3.66KB | 1.82KB |
| sdui-parser (types.js) | 0.28KB | 0.23KB |
| sdui-parser (validate.js) | 14.82KB | 4.99KB |
| types (ai.js) | 0.20KB | 0.17KB |
| types (api-types.js) | 0.20KB | 0.18KB |
| types (app.js) | 2.87KB | 1.00KB |
| types (base.js) | 0.20KB | 0.18KB |
| types (blocks.js) | 0.20KB | 0.18KB |
| types (complex.js) | 2.93KB | 1.49KB |
| types (crud.js) | 0.20KB | 0.18KB |
| types (dashboard-filter-alias.js) | 6.23KB | 2.74KB |
| types (data-display.js) | 3.75KB | 1.85KB |
| types (data-protocol.js) | 0.20KB | 0.19KB |
| types (data.js) | 0.20KB | 0.18KB |
| types (designer.js) | 1.85KB | 0.85KB |
| types (disclosure.js) | 0.20KB | 0.18KB |
| types (error-code.js) | 1.54KB | 0.88KB |
| types (expression.js) | 0.20KB | 0.18KB |
| types (feedback.js) | 0.20KB | 0.18KB |
| types (field-types.js) | 0.20KB | 0.18KB |
| types (form.js) | 0.20KB | 0.18KB |
| types (http-inflight.js) | 8.87KB | 3.73KB |
| types (http-retry.js) | 4.32KB | 2.02KB |
| types (icon-key-migration.js) | 4.26KB | 1.63KB |
| types (index.js) | 4.74KB | 2.25KB |
| types (layout.js) | 0.20KB | 0.18KB |
| types (managed-by.js) | 0.19KB | 0.18KB |
| types (mobile.js) | 4.73KB | 2.28KB |
| types (navigation.js) | 0.20KB | 0.18KB |
| types (objectql.js) | 0.20KB | 0.18KB |
| types (overlay.js) | 0.20KB | 0.18KB |
| types (permissions.js) | 0.20KB | 0.18KB |
| types (plugin-scope.js) | 0.20KB | 0.18KB |
| types (record-components.js) | 0.20KB | 0.19KB |
| types (record-semantics.js) | 1.28KB | 0.67KB |
| types (registry.js) | 0.20KB | 0.18KB |
| types (reports.js) | 0.20KB | 0.18KB |
| types (select-option.js) | 0.20KB | 0.19KB |
| types (spec-report.js) | 5.05KB | 1.93KB |
| types (spec-ui-namespace.js) | 0.20KB | 0.19KB |
| types (strict-authoring-face.js) | 14.27KB | 5.47KB |
| types (system-fields.js) | 3.33KB | 1.54KB |
| types (theme.js) | 6.28KB | 2.87KB |
| types (ui-action.js) | 8.11KB | 3.32KB |
| types (views.js) | 0.20KB | 0.18KB |
| types (widget.js) | 0.20KB | 0.18KB |
Size Limits
- ✅ Core packages should be < 50KB gzipped
- ✅ Component packages should be < 100KB gzipped
⚠️ Plugin packages should be < 150KB gzipped
Both reds at
|
Contract reviewHead reviewed: Independence pair (machine-readable) ⛔ Not SELF-REVIEW. ⭐ VERDICT: PASS — and the question this review existed to rule is ruledRetiring The reviewer did not take that from the author or from this seat — it re-derived it, and the decisive reading is one neither had: the published prop type actively refused these keys at compile time. ⇒ the author's unanswerable question is answered on governing text rather than on the unmeasurable population: it does not need measuring. Every face such an author could have read refused or omitted the spelling, and the governing rule requires named external evidence to slow a retirement, ⛔ not to permit one. ⭐ And it corrects this seat. I rested part of my answer on «the failure mode is a named on-screen refusal, ⛔ not a silently wrong chart». Measured, that is conditional: rows lacking a Carriers — cleared, ⛔ not strippedPer 「清标即落地:PASS ⇒ 同席同笔剥双载体」, ⛔ Landing — pre-landing check ③ fails, so this PR does NOT enqueue
⛔ Not flipped ready. ⛔ Not enqueued. ⛔ No auto-merge. Blocked only by objectui#9308 (via objectui#9346) and objectui#9204 / objectui#9316 — ⛔ neither this PR's.
|
| # | change | reading | verdict |
|---|---|---|---|
| 1 | index read removed (ChartRenderer.tsx) |
undeclared on ChartRendererProps.schema, ChartSchema, zod mirror, registry inputs; producers: base tree 1 (the specSeries pin, migrated in this PR) |
right |
| 2 | category read removed |
same faces; producers: base tree 1 = packages/types/src/widget.ts:64, a RuntimeWidgetManifest docblock example whose category: 'data-visualization' is a palette category on a manifest, not a chart-node key |
right |
| 3 | value read removed |
same faces; producers 0 | right |
| 4 | categories branch removed |
DECLARED on ChartSchema (string[], checker) and zod mirror; documented (schema-reference.md:508, "alternative series list"); ruled live by #6896 comment 5479473487 item 3; normalizeChartSchema.ts:488-492 consumes it before the old branch could run, so spec.series was populated for every well-formed list. Ablation reproduced: pre-fix renderer in → 4 failed / 3 passed (7), exactly index, category, value and the malformed-categories throw; restored → 7/7; the three "categories still plots" cases pass in both legs (blob 13e49a04 ↔ f2531d0a, git diff HEAD empty). Capability untouched. One behaviour change on malformed input, named: categories: 'revenue' (a string) used to throw in render and now mounts with no series; an all-rejected list used to yield [{ dataKey: '' }] and now yields none (if (series?.length)). No contract promised a throw. |
right |
| 5 | six (schema as any) casts deleted |
colors/categoryColors/categoryOrder DECLARED on ChartRendererProps.schema (string[], Record<string,string>, string[]); colors/compareTo/series DECLARED on ObjectChartSchema = ObjectChartProps.schema (37 members, colors: string[] | Record<string,string>, compareTo: { kind; dimension? }, series: { dataKey; … }[]). Every read is the same expression minus the cast — schema.colors || […], Array.isArray(schema.colors) ? … : undefined, the object/array colors split, Array.isArray(schema.series) ? … : null, useMemo deps [schema.compareTo] / […, schema.series, …] with identical identity. type-check exit 0 is the reading that the declarations and the reads agree. No behaviour moved; nothing declared. |
right |
| 6 | public TS face | no file under packages/types/ in the diff; ChartRendererProps unchanged (23 members before/after); no new export |
right |
| 7 | specSeries test index → xAxisKey |
migrates the one in-repo index writer (a pin) to the canonical spelling |
right |
| 8 | normalizeChartSchema.ts:381-382 comment still says the Tremor-ish categories form is one "ChartRenderer already adapts" |
now false — prose drift, non-contract | wrong, 欠改 |
Instrument re-derived, not trusted: reads via the checker, declaredness via getPropertiesOfType/getPropertyOfType. Index-signature control holds: on ObjectChartSchema and ChartSchema (both string → any) getPropertyOfType('zzqx_no_such_key') is undefined while type is DECLARED; ChartRendererProps.schema has no index signature at all. The (a)/(b) classification is a reading. (Note for re-runners: ObjectChartProps.schema reads as 0 members until @object-ui/types' dist exists — an unresolved import, not a finding.)
Producer census re-derived structurally (object literal / JSON / fenced node whose type is one of the seven spellings; top-level keys extracted at depth 1), controls drawn from the same nodes: base tree 5,514 files / 80 chart nodes — index 1 · category 1 · value 0 · categories 4 (all tests) vs xAxisKey 31 · series 38 · chartType 35 · data 44 · zzqx_no_such_key 0; head tree 87 nodes, the only new index/category/value writers being the PR's own pin. Same shape as the author's 74/41/44/40/52; the zeros are readings.
② Semver grading vs. the changeset
Changeset: '@object-ui/plugin-charts': minor, body "Breaking, deliberately, for three keys" + a Migration paragraph. Gates run here: check-changeset-presence 0 (4 source files of 1 released package, 1 changeset), check-changeset-no-major 0, check-changeset-claims 0. plugin-charts is in the single fixed group.
Absent repo policy: by semver §1 the public API is what is declared, and the declared API of @object-ui/plugin-charts is byte-identical before and after — so the defensible strict-semver grade is minor with the behaviour change called out; only the Hyrum reading ("every observable behaviour is API") makes it a major, and under that reading the honest word is the one the changeset uses. With the policy: AGENTS.md 版本号策略 is explicit — «objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)» — so minor + the break spelled out in the body is the mandated encoding, not a compromise. The policy's existence does not change the verdict; it removes the only alternative. minor is correct and consistent with the changeset.
One owed correction to the body, because a changeset becomes the published CHANGELOG: the Migration sentence «still writes index / category binds no category axis and hits AdvancedChartImpl's existing on-screen refusal rather than drawing a wrong picture» is true only when rows lack a name column; rows carrying name plot silently on it (measured). «one that still writes value plots nothing» is accurate (bar: empty surface; pie: named refusal).
③ Boundary flags and open_questions
- Q1 (retirement arm / external author) — ruled above: A, proceed as landed; not C. Agreeing with the seat's outcome, disagreeing with one of its grounds: the seat rested part of A on «a named on-screen refusal, ⛔ not a silently wrong chart»; measured, that is conditional on the rows, and the ruling does not need it.
- Q2 (labels) — not mine; no label touched by this review.
- The new pin discriminates, but
neverPlotsis a race in the false-green direction. Measured warm time-to-surface for the canonical control in this container: 97–168 ms (cold 578 ms) against a fixed 150 ms wait; the in-file control does not bound it because it waits viawaitFor(≤1 s). It discriminated in the author's run and in mine (the mutatedindexcase failed at 239 ms because the surface arrived inside the window), but on a loaded runner a re-added alias could pass vacuously. 欠改 (non-contract, not a FAIL cause): read the positive signal —waitForon[data-chart-error="missing-category-key"]forindex/category; forvalue, wait for the surface then assert zero marks (bar) or wait forno-positive-magnitude(pie). - Forbidden outcomes — none occurred: no rows in
undeclared-but-consumed-keys-6150.test.ts(untouched; passes in the consumer run), no fold into spec(types)!: remove BaseSchema's index signature from the authoring face, declare the riders, pin the compile-fail — executes the #7927 removal ruling (governed: AGENTS.md #0.1) #8347, no declaration added to any published type (diff = 5 files, none underpackages/types/). - Non-regression — 235 files / 4,211 tests passed, exit 0, across
packages/plugin-charts/,packages/plugin-dashboard/,packages/plugin-report/, app-shellObjectView.chartRelay-7823+chartConfigForward-7891, thepackages/typeschart / anchor (6576, 7946) / 8885 / 6150 pins, andexamples/schema-catalog/.plugin-chartstype-check0,lint0 (0 errors, 338 warnings),check:test-path-roots/vi-mock-override-shape/new-line-citations/spec-symbolsall 0. - Base-side reds (
Doc Snippet Type Check,Bundle Analysis) — name no file this PR touches; not weighed. - Out-of-scope notes the author left unfiled (
ChartRendererProps.schemanot anchored toChartSchema— the finding(types): two widget prop types declare theirschemaas a hand-rolled inline object with noBaseSchemain its ancestry #6576 class;BaseSchemaindex signature — finding(types):BaseSchemacloses with[key: string]: any(packages/types/src/base.ts:467), so NO annotation on any node schema can catch a misspelled metadata key — measured green by a planted probe while the type, optionality and payload-member probes all went red #7927) — correctly not acted on here; the first is the reasoncategoriesis authorable yet refused by the prop type, and belongs to the anchoring family, not this card.
Not reached: the introduction history of the Tremor adapter (shallow clone, 135 commits; the published CHANGELOG carries no entry and the GitHub search is 0 with a firing control); whether pie-chart/donut-chart/radar-chart/scatter-chart are registered to ChartRenderer (only the first 130 lines of index.tsx read — the census included all seven spellings regardless); the author's lint:coverage, entry-guard, upstream-port-parity, bash32-floor, cross-repo-closer-outcome, control-bytes exits (not re-run; none contract-bearing); the external-author population (unmeasurable from here — ruled on governing text, not on a measurement).
Implemented-by: claude/issue-8650-plugin-charts-foreign-dialect
Reviewed-by: session_01L5xpA5q533BgTTNADibEFt
Verdict: PASS
裁定:index / category / value 的退役是未声明容忍的退役,在分诊引用的 enforce-or-remove 机械范围内,⛔ 不是「删已发布能力」——已发布的类型面从未声明过它们、对有类型的调用方在编译期就拒绝它们(TS2353)、无任何文档/README/CHANGELOG/registry 教过它们,已声明接受集在本 PR 前后逐字不变;本卡不因此进决策箱,本 PR 可落地。categories 能力经消融证明未动;六处 cast 删除无行为迁移、无新增声明;minor 与本仓版本策略一致。三项 欠改 随交接记录、不改变契约判词:(a) changeset Migration 句补上「行内含 name 列时静默绑到 name」的限定,(b) neverPlots 改为正向读数,(c) normalizeChartSchema.ts:381-382 陈旧注释。
Generated by Claude Code
…nces Closes the three 欠改 the ceiling-tier contract review recorded. No accept-set change, no declaration, no new key, nothing under packages/types/: the retirement, the `categories` collapse and the six cast deletions are unchanged. 1. changeset Migration sentence. It said a chart still writing `index` / `category` "hits AdvancedChartImpl's existing on-screen refusal rather than drawing a wrong picture". Measured, that holds only for rows lacking a `name` column: `AdvancedChartImpl` defaults `xAxisKey = 'name'` (AdvancedChartImpl.tsx:865), so rows that CARRY one plot silently against `name`. A changeset publishes verbatim into the CHANGELOG, so the overstatement would have shipped. Qualifier added; the `value` sentence, which measured accurate, is byte-identical. 2. `neverPlots` was a race in the false-green direction -- a fixed 150 ms wait then "no surface yet", against a measured warm time-to-surface of 97-168 ms for the canonical control. It discriminated in two runs, but on a loaded runner a re-added alias would plot LATE and the pin would pass vacuously. Replaced with positive readings that fail the other way: `waitFor` on `[data-chart-error="missing-category-key"]` for `index` / `category`, and for `value` the existing `plotted` helper -- wait for the surface to ARRIVE, then count marks. A slow runner now makes these slower, never green. The file docblock carried the same overstatement as the changeset and is corrected with it. 3. normalizeChartSchema.ts:381 said the Tremor-ish `categories` form is one "ChartRenderer already adapts". That branch is gone as of this PR, so the sentence was false about the very removal it sits next to. It now names the reader that does adapt it -- this file, the one translation point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
|
| Step | Outcome |
|---|---|
| Build packages | success |
| Check console performance budget | failure |
Which half objected:
| Eager-closure half | Verdict |
|---|---|
| Aggregate closure ceiling | ✅ pass |
| Per-chunk ceilings | ✅ pass |
| Ceiling sensitivity (headroom) | |
| Ceiling freshness (checkout vs. base branch) | ✅ pass |
⚠️ A broken gauge half is a verdict about the ceiling, not about the bundle: that line has drifted out of range of the regression it exists to catch, or the report behind it cannot be trusted. It does not say anything grew. TheCheck console performance budgetstep log carries the ceiling and the number it was compared against.
Reason: The entry chunk measured 144.3 KB, but the eager-closure half of this gate returned no trustworthy VERDICT: the report could not be read, a ceiling has drifted out of range of the regression it must catch, or (objectui#6245) a ceiling was replaced on the base branch after this checkout was made. The step log says which. This is not a passing budget — and it is not a size regression either.
See the workflow run for details.
📦 Bundle Size Report
| Package | Size | Gzipped |
|---|---|---|
| app-shell (consoleActionDispatch.js) | 0.20KB | 0.19KB |
| app-shell (index.js) | 16.69KB | 6.21KB |
| app-shell (runtime-config.js) | 20.68KB | 7.36KB |
| app-shell (types.js) | 0.01KB | 0.04KB |
| app-shell (urlParams.js) | 10.06KB | 3.86KB |
| auth (ActiveOrganizationStorage.js) | 25.05KB | 9.16KB |
| auth (AuthContext.js) | 0.31KB | 0.24KB |
| auth (AuthGuard.js) | 2.07KB | 1.00KB |
| auth (AuthProvider.js) | 40.18KB | 10.59KB |
| auth (AuthShell.js) | 3.49KB | 1.40KB |
| auth (ForgotPasswordForm.js) | 12.21KB | 3.45KB |
| auth (LoginForm.js) | 18.15KB | 5.39KB |
| auth (PreviewBanner.js) | 0.90KB | 0.50KB |
| auth (RegisterForm.js) | 6.65KB | 2.22KB |
| auth (SocialSignInButtons.js) | 9.61KB | 3.89KB |
| auth (UserMenu.js) | 3.41KB | 1.23KB |
| auth (auth-gate-events.js) | 1.29KB | 0.66KB |
| auth (authStyles.js) | 5.04KB | 1.72KB |
| auth (createAuthClient.js) | 40.21KB | 10.80KB |
| auth (createAuthenticatedFetch.js) | 8.46KB | 3.43KB |
| auth (index.js) | 3.19KB | 1.44KB |
| auth (invitation-status.js) | 1.22KB | 0.70KB |
| auth (org-roles.js) | 6.66KB | 2.78KB |
| auth (phone-identifier.js) | 1.11KB | 0.66KB |
| auth (types.js) | 0.59KB | 0.35KB |
| auth (useAuth.js) | 5.30KB | 1.02KB |
| auth (useWorkspaceAdminStatus.js) | 11.08KB | 4.58KB |
| collaboration (CommentThread.js) | 26.08KB | 7.56KB |
| collaboration (LiveCursors.js) | 3.17KB | 1.27KB |
| collaboration (PresenceAvatars.js) | 6.49KB | 2.64KB |
| collaboration (PresenceProvider.js) | 2.79KB | 1.13KB |
| collaboration (index.js) | 1.68KB | 0.73KB |
| collaboration (useCollaborationTranslation.js) | 6.05KB | 2.52KB |
| collaboration (useCommentSearch.js) | 1.98KB | 0.88KB |
| collaboration (useConflictResolution.js) | 7.75KB | 1.86KB |
| collaboration (useMentionNotifications.js) | 1.81KB | 0.68KB |
| collaboration (usePresence.js) | 6.33KB | 1.84KB |
| collaboration (useRealtimeSubscription.js) | 7.91KB | 2.01KB |
| components (index.js) | 502.02KB | 115.16KB |
| core (index.js) | 8.52KB | 3.41KB |
| create-plugin (index.js) | 27.94KB | 9.51KB |
| data-objectstack (index.js) | 211.58KB | 58.68KB |
| fields (index.js) | 247.89KB | 62.50KB |
| i18n (LocalizationContext.js) | 1.76KB | 0.96KB |
| i18n (builtinAggregateLabels.js) | 0.86KB | 0.49KB |
| i18n (currency.js) | 1.22KB | 0.64KB |
| i18n (fallbackInterpolation.js) | 6.25KB | 2.77KB |
| i18n (i18n.js) | 8.87KB | 3.64KB |
| i18n (index.js) | 5.22KB | 2.26KB |
| i18n (pickLocalized.js) | 9.86KB | 3.95KB |
| i18n (provider.js) | 32.15KB | 10.49KB |
| i18n (useDisplayLocale.js) | 2.85KB | 1.45KB |
| i18n (useObjectLabel.js) | 34.34KB | 9.17KB |
| i18n (useSafeTranslation.js) | 5.60KB | 2.33KB |
| layout (index.js) | 38.83KB | 10.95KB |
| mobile (MobileProvider.js) | 0.92KB | 0.49KB |
| mobile (ResponsiveContainer.js) | 0.94KB | 0.38KB |
| mobile (breakpoints.js) | 1.51KB | 0.70KB |
| mobile (createOfflineDataSource.js) | 5.61KB | 1.75KB |
| mobile (index.js) | 1.99KB | 0.87KB |
| mobile (offlineQueue.js) | 3.91KB | 1.35KB |
| mobile (pwa.js) | 0.97KB | 0.49KB |
| mobile (serviceWorker.js) | 1.48KB | 0.62KB |
| mobile (serviceWorkerSource.js) | 3.41KB | 1.48KB |
| mobile (useBreakpoint.js) | 1.54KB | 0.65KB |
| mobile (useGesture.js) | 6.96KB | 1.98KB |
| mobile (useOfflineSync.js) | 1.99KB | 0.72KB |
| mobile (usePullToRefresh.js) | 2.53KB | 0.85KB |
| mobile (useResponsive.js) | 0.72KB | 0.42KB |
| mobile (useSpecGesture.js) | 4.39KB | 1.66KB |
| mobile (useTouchTarget.js) | 1.01KB | 0.54KB |
| permissions (MePermissionsProvider.js) | 13.52KB | 4.88KB |
| permissions (PermissionContext.js) | 0.31KB | 0.25KB |
| permissions (PermissionGuard.js) | 0.89KB | 0.45KB |
| permissions (PermissionProvider.js) | 6.24KB | 2.16KB |
| permissions (discardProofCache.js) | 1.04KB | 0.55KB |
| permissions (evaluator.js) | 8.39KB | 3.10KB |
| permissions (index.js) | 0.93KB | 0.41KB |
| permissions (store.js) | 0.91KB | 0.42KB |
| permissions (useFieldPermissions.js) | 1.28KB | 0.53KB |
| permissions (usePermissions.js) | 4.83KB | 2.27KB |
| plugin-ai (index.js) | 14.81KB | 3.63KB |
| plugin-calendar (index.js) | 49.25KB | 13.99KB |
| plugin-charts (index.js) | 71.34KB | 19.90KB |
| plugin-chatbot (index.js) | 195.34KB | 46.51KB |
| plugin-dashboard (index.js) | 131.22KB | 34.59KB |
| plugin-designer (index.js) | 215.94KB | 44.33KB |
| plugin-detail (index.js) | 253.46KB | 65.85KB |
| plugin-editor (index.js) | 2.23KB | 1.05KB |
| plugin-form (index.js) | 136.77KB | 34.17KB |
| plugin-gantt (index.js) | 166.95KB | 41.04KB |
| plugin-grid (index.js) | 211.58KB | 57.48KB |
| plugin-kanban (index.js) | 46.01KB | 14.30KB |
| plugin-list (index.js) | 112.58KB | 27.65KB |
| plugin-map (index.js) | 20.64KB | 6.86KB |
| plugin-markdown (index.js) | 13.88KB | 4.80KB |
| plugin-report (index.js) | 43.41KB | 11.93KB |
| plugin-timeline (index.js) | 30.07KB | 8.74KB |
| plugin-tree (index.js) | 9.55KB | 3.32KB |
| plugin-view (index.js) | 84.42KB | 20.79KB |
| providers (DataSourceProvider.js) | 0.75KB | 0.39KB |
| providers (MetadataProvider.js) | 1.37KB | 0.59KB |
| providers (ThemeProvider.js) | 1.90KB | 0.85KB |
| providers (UploadProvider.js) | 11.66KB | 3.50KB |
| providers (index.js) | 0.45KB | 0.23KB |
| providers (types.js) | 0.01KB | 0.04KB |
| react-runtime (index.js) | 5.62KB | 2.34KB |
| react (LazyPluginLoader.js) | 4.47KB | 1.63KB |
| react (SchemaRenderer.js) | 94.03KB | 31.02KB |
| react (data-invalidation.js) | 5.05KB | 2.08KB |
| react (index.js) | 4.63KB | 2.18KB |
| react (schema-input.js) | 4.25KB | 2.04KB |
| react (spec-input.js) | 0.20KB | 0.18KB |
| sdui-parser (codegen.js) | 6.58KB | 2.74KB |
| sdui-parser (dashboard-widget-options.js) | 3.08KB | 1.30KB |
| sdui-parser (index.js) | 5.66KB | 2.50KB |
| sdui-parser (input-type.js) | 2.84KB | 1.40KB |
| sdui-parser (kanban-quick-add.js) | 3.89KB | 1.87KB |
| sdui-parser (parse.js) | 25.28KB | 7.80KB |
| sdui-parser (provenance.js) | 3.66KB | 1.82KB |
| sdui-parser (types.js) | 0.28KB | 0.23KB |
| sdui-parser (validate.js) | 14.82KB | 4.99KB |
| types (ai.js) | 0.20KB | 0.17KB |
| types (api-types.js) | 0.20KB | 0.18KB |
| types (app.js) | 2.87KB | 1.00KB |
| types (base.js) | 0.20KB | 0.18KB |
| types (blocks.js) | 0.20KB | 0.18KB |
| types (complex.js) | 2.93KB | 1.49KB |
| types (crud.js) | 0.20KB | 0.18KB |
| types (dashboard-filter-alias.js) | 6.23KB | 2.74KB |
| types (data-display.js) | 3.75KB | 1.85KB |
| types (data-protocol.js) | 0.20KB | 0.19KB |
| types (data.js) | 0.20KB | 0.18KB |
| types (designer.js) | 1.85KB | 0.85KB |
| types (disclosure.js) | 0.20KB | 0.18KB |
| types (error-code.js) | 1.54KB | 0.88KB |
| types (expression.js) | 0.20KB | 0.18KB |
| types (feedback.js) | 0.20KB | 0.18KB |
| types (field-types.js) | 0.20KB | 0.18KB |
| types (form.js) | 0.20KB | 0.18KB |
| types (http-inflight.js) | 8.87KB | 3.73KB |
| types (http-retry.js) | 4.32KB | 2.02KB |
| types (icon-key-migration.js) | 4.26KB | 1.63KB |
| types (index.js) | 4.74KB | 2.25KB |
| types (layout.js) | 0.20KB | 0.18KB |
| types (managed-by.js) | 0.19KB | 0.18KB |
| types (mobile.js) | 4.73KB | 2.28KB |
| types (navigation.js) | 0.20KB | 0.18KB |
| types (objectql.js) | 0.20KB | 0.18KB |
| types (overlay.js) | 0.20KB | 0.18KB |
| types (permissions.js) | 0.20KB | 0.18KB |
| types (plugin-scope.js) | 0.20KB | 0.18KB |
| types (record-components.js) | 0.20KB | 0.19KB |
| types (record-semantics.js) | 1.28KB | 0.67KB |
| types (registry.js) | 0.20KB | 0.18KB |
| types (reports.js) | 0.20KB | 0.18KB |
| types (select-option.js) | 0.20KB | 0.19KB |
| types (spec-report.js) | 5.05KB | 1.93KB |
| types (spec-ui-namespace.js) | 0.20KB | 0.19KB |
| types (strict-authoring-face.js) | 14.27KB | 5.47KB |
| types (system-fields.js) | 3.33KB | 1.54KB |
| types (theme.js) | 6.28KB | 2.87KB |
| types (ui-action.js) | 8.11KB | 3.32KB |
| types (views.js) | 0.20KB | 0.18KB |
| types (widget.js) | 0.20KB | 0.18KB |
Size Limits
- ✅ Core packages should be < 50KB gzipped
- ✅ Component packages should be < 100KB gzipped
⚠️ Plugin packages should be < 150KB gzipped
Contract reviewHead reviewed: Independence pair (machine-readable) ⛔ Not SELF-REVIEW. ⭐ VERDICT: PASS — all three 欠改 closed, nothing else movedNothing-else-moved, by blob hash rather than diff shape: ⭐ Item 2's ablation is the strongest thing in this round, and it is bidirectional. Re-inserting the retired reads makes the rewritten cases red — and the reviewer proved they redden for the right reason rather than by timing, by running a diagnostic asserting the positive opposite on the same mutated tree: it passed (the alias plotted, 2 bars, no refusal) and then failed on the restored tree. On the restored tree the refusal arrives at 3–10 ms against a 1 s budget — two orders of magnitude inside, so load can only slow or redden it, ⛔ never green it. That is the difference between the old pin and the new one, demonstrated rather than argued. ⭐ Item 3 was proven comment-only by two independent instruments, each with a firing control — The two corrections the author made beyond the asked three — the test docblock and PR body line 86 — are judged right and in scope: the same ruled-false sentence in two further places, one of them in a file already open, and reported rather than done silently. Carriers — cleared, ⛔ not strippedBoth limbs, one stroke each, per 「清标即落地:PASS ⇒ 同席同笔剥双载体」. Record id and judged head are in this comment. ⛔ Landing — ③ still fails, so this PR still does not enqueue① PASS on record at tier ✅ · ② dual carrier cleared, machine-read ✅ · ③ every check green ❌ —
|
| # | item | reading | verdict |
|---|---|---|---|
| 1 | changeset Migration sentence | Re-derived on the unmutated head through ChartRenderer, not from the report. Guard: hasNoCategoryKey (AdvancedChartImpl.tsx:2114-2123) refuses when the chart family plots a category axis, rows are non-empty, and no row carries props.xAxisKey ?? 'name'; the default is xAxisKey = 'name' (:865, :2249). Scratch test, 6 cases, exit 0: index:'month' + rows without name → [data-chart-error="missing-category-key"] present, no .recharts-surface; index:'month' + rows carrying name → surface, 1 bar, no [data-chart-error], axis ticks contain Alpha/Beta and not Jan (the chart plotted against name, silently); category:'month' + name rows → same; control xAxisKey:'month' on the same name-bearing rows → ticks Jan/Feb, not Alpha (the tick reading discriminates); value:'revenue' on bar → surface, 0 bars, no refusal; value on pie → a named refusal, no surface. The new sentence (.changeset/…:38-44) — «falls back to its default category key, name … rows carrying no name column hit its existing on-screen missing-category-key refusal, while rows that DO carry one plot silently against name … One that still writes value plots nothing» — matches every reading. Frontmatter byte-identical (diff of lines 1-3 empty). |
right, closed |
| 2 | neverPlots race |
Residue: head blob df3fae30 has neverPlots 0 occurrences, setTimeout 0, it( lines 7, the refusal selector 1; old blob fa69e57e reads 3 / 2 / 7 / 0 (control fires; occurrences via grep -o | wc -l, ⛔ not grep -c). Ablation in my worktree with an EXIT/INT/TERM trap: the three retired reads (index/category→xAxisKey, value→series) re-inserted at the const series … / const xAxisKey … anchor; proved on disk before any run — blob 997c7691 ≠ HEAD f2531d0a, marker occurrences 0→6 (4 lines), canonical const xAxisKey line 1→0, git diff --stat HEAD 10+/2−. Mutated leg exit 1: pin 3 failed / 4 passed — index (1142 ms) and category (1076 ms) on AssertionError: expected null not to be null at the missing-category-key query, i.e. waitFor exhausted its 1 s budget with no refusal ever rendered; value (64 ms) on { bars: 1 } ≠ { bars: 0 }. Right reason, not timing: a diagnostic file run in the same invocation on the same mutated tree asserted the positive opposite — index/category rows without name → surface arrives with 2 bars and no [data-chart-error] (500 ms / 89 ms), value → exactly 1 bar — and all 3 passed; the refusal is absent because the alias plotted, not because the surface was late. Restore git checkout HEAD -- <path>: blob f2531d0a == HEAD, markers 0, git diff HEAD 0 bytes. Restored leg: pin 7 passed, the refusal arriving at 4 ms / 4 ms (value 43 ms); the diagnostic's 3 cases now fail (expected null to be truthy, expected +0 to be 1) — bidirectional. Three further pin runs on the restored tree: index 4/5/10 ms, category 3/3/4 ms, all exit 0 — two orders of magnitude inside the budget, so load can only slow or redden it, never green it. The 4 cases green in both legs are the canonical control and the three categories cases — the capability this PR does not retire. |
right, closed; pin discriminates |
| 3 | normalizeChartSchema.ts:381-389 comment |
Comment-only, mechanically: ts.transpileModule with removeComments: true (typescript 6.0.3) on blobs 32178744 vs 49c12262 → stripped outputs byte-equal (9263 = 9263, 0 differing lines); control-1 ChartRenderer.tsx 13e49a04 vs f2531d0a → not equal (39 lines, fires); control-2 head blob vs a one-token non-comment mutation (undefined→null) → not equal (1 line, fires). Second instrument: 11 changed lines in the hunk, 11 match ^[+-]\s*//, 0 do not. Truth of the replacement: the categories reader is «below, in this file» — :497-498 Array.isArray(schema.categories); ChartRenderer «carried a second, un-normalized read … until objectui#8650 retired that branch» — base blob 13e49a04 has (schema as any).categories.map(…), head f2531d0a mentions categories only in comments (:172-197); «objectui#2880 S1» is the same citation normalizeChartSchema.test.ts:5 and index.tsx:23 carry; «AGENTS.md #0.1 … second de-facto contract» is that rule's own wording. The pre-existing lead phrase «shorthand for { name }» (the code returns { dataKey: bare }, and normalizeSeries reads raw.dataKey ?? raw.name) is untouched by this round and out of scope. |
right, closed |
| 4 | test docblock rewrite (:34-51) — not in the 欠改 list, reported by the author |
The old paragraph asserted «does not produce a silently wrong picture», the sentence the PASSed ruling measured false; the file was already open for item 2, and leaving it would make the pin contradict the changeset it pins. Its claims hold: DATA lacks name (:83-86); the code-not-wording reading is what sibling suites pin (AdvancedChartImpl.missingCategoryKey.test.tsx, nullCategoryBucket, unprojectedSeriesDimension, ObjectChart.categoryAxisKeyRender-8269 all query data-chart-error="missing-category-key"); objectui#8168 is ObjectChart.absentCategoryAxisRefusal-8168.test.tsx. |
right, in scope |
| 5 | PR body line 86 — same overstatement, corrected and reported | Read back via REST at head c237b09e (body 198 lines): line 86 is the corrected paragraph. A PR-body edit moves no code and no contract; correcting the ruled-false ground where it was repeated is the honest disposition, and reporting it rather than doing it silently is the right form. |
right, in scope |
| 6 | public TS face / accept set | unchanged this round by construction of B: the only source blob that moved is comment-identical after stripping; ChartRendererProps, ChartSchema, zod mirror, registry inputs untouched |
right |
Non-regression on the restored, scratch-free tree (git status --short 0 lines, ChartRenderer.tsx blob f2531d0a): pnpm exec vitest run packages/plugin-charts/ from the worktree root → exit 0, 60 files / 541 tests passed (the author's figure, re-derived). eslint on the two changed files → exit 0, 0 errors (8 pre-existing no-explicit-any warnings). CI on this head: Lint, Type Check (which chains tsconfig.test.json), Changeset Declaration / Bump Policy / Fixed Group / Overwrite Report / Claim Re-read, Build & E2E, Test shard 4/4 all success.
② Semver grading vs. the changeset
Unchanged from the PASSed ruling and unchanged by this round: '@object-ui/plugin-charts': minor, frontmatter byte-identical across the two changeset blobs; the body's break is spelled out, which AGENTS.md 版本号策略 mandates over major. Gates run here: check-changeset-presence 0 (5 source files of 1 released package, 1 changeset), check-changeset-no-major 0, check-changeset-fixed 0, check-changeset-overwrite 0 (0 pre-existing changesets modified), check-changeset-claims 0 (report-only; its findings name ObjectChart.tsx, untouched this round). minor is correct and consistent with the changeset. The Migration paragraph that publishes into the CHANGELOG is now true (① item 1).
③ Boundary flags and open_questions
- One inaccurate claim in the PR body, not in the shipped text: the 欠改 table says the
valuesentence is «byte-identical». It is not —one that still writesbecameOne that\nstill writes(old blob line 40 vs new 43-44) because it now opens a sentence. The published sentence is the same sentence; noted so nobody re-derives «byte-identical» as a measurement. Not a 欠改. - Guard semantics, for precision:
hasNoCategoryKeyrefuses only when no row carriesname; a mixed dataset (some rows carryingname) plots. The changeset's «rows carrying nonamecolumn» / «rows that DO carry one» describes the two homogeneous cases and is not false for the mixed one (it also «plots silently»). No wording change owed. - The silent-
namehalf is pinned nowhere in this PR (the docblock says so: «The cases below pin the refusing half»). It is a pre-existing property of every unbound chart, ⛔ not introduced here, and pinning a wrong picture as expected is not owed by this card. - The docblock's «97–168 ms (cold 578 ms)» is a historical measurement stated as one (past tense), offered as the rationale for the form change, not as a live count. In my runs the in-file cold control took 483–496 ms and warm surfaces 40–89 ms — the same shape; the deleted 150 ms window would have been raced by the cold case. No action.
- Forbidden outcomes — none: no accept-set widening, no declaration, no new key, nothing under
packages/types/, no fold into spec(types)!: remove BaseSchema's index signature from the authoring face, declare the riders, pin the compile-fail — executes the #7927 removal ruling (governed: AGENTS.md #0.1) #8347, no rows inundeclared-but-consumed-keys-6150.test.ts(not in the roster). - Base-side reds (
Doc Snippet Type Check,Bundle Analysis) name no file this PR touches — not weighed.mergeable_state: behindis the base moving, not this diff. - Disagreement with the dispatching seat: none this round; its scoping matched what the diff contains.
Not reached: CI Test shards 1–3 still read in_progress at my last read (shard 4 success); the local package run stands in for plugin-charts and their absence does not change the verdict. pnpm --filter @object-ui/plugin-charts type-check not re-run locally (CI Type Check is success on this head; the only non-test source change is comment-only by two firing instruments). The pie-branch value refusal was observed as a named refusal with no surface but its code string was not asserted (console output is silenced under the repo's test setup); the changeset's «plots nothing» holds either way.
Implemented-by: claude/issue-8650-plugin-charts-foreign-dialect
Reviewed-by: session_01L5xpA5q533BgTTNADibEFt
Verdict: PASS
裁定:本轮 348a4bdf..c237b09e 按 blob hash 核实只动了三个文件,承载退役、categories 收拢与六处 cast 删除的两个源码 blob 与已 PASS 头逐字相同,packages/types/ 零命中(对照 5 命中);三项 欠改 全部正确关闭 —— (1) changeset Migration 句经六例复测为真(含刻度读数对照);(2) neverPlots 已删,消融双向证明新 pin 因「拒绝从未出现、图被画出」而红,恢复后拒绝 3–10 ms 内到达;(3) normalizeChartSchema.ts 改动经 removeComments 转译与逐行分类两套仪器、两个会响的对照证明为纯注释,替换文字属实。附带修正的 docblock 与 PR 正文第 86 行同属被裁定为假的同一句,合规且在范围内。minor 不变、与本仓版本策略一致。契约判词不变,PASS。
Generated by Claude Code
⛔ Correction: a fix for the
|
CI status — both reds are INHERITED, and one of them is provably stale. ⛔ Not pushing yet.
⛔ The re-run was spent on a sibling PR as a probe, and it does not help
The sequencing
⇒ one base merge after objectui#9204 lands and Generated by Claude Code |
Fixes #8650
Implements the triage ruling (comment
5619609278): route to the producer, ⛔ not a declaration. The direction was not re-opened; what this PR settles is the how, and the measurement split the card's one group of four into two different answers.origin/main2e471dc0ObjectCharttakesprops: any» is FALSEObjectChart.tsxdeclaresexport interface ObjectChartPropsandexport const ObjectChart = (props: ObjectChartProps); the checker readsObjectChartProps.schemaasObjectChartSchemawith 37 declared named membersseriesto have moved to bucket (b)colors,compareToandseriesare all declared named members ofObjectChartSchema. thecolorsdrift was settled by objectui#7946 as wellmainthe undeclared population is the card's four. Of the census's seven,colors·categoryColors·categoryOrderare now declared members ofChartRendererProps.schema— they are bucket (b), not undeclared⭐ And a fifth, which neither the card nor the dispatch had:
categoriesis not a foreign key at all. It is a declared member of the publishedChartSchemaand of its zod mirror, is documented incontent/docs/api/schema-reference.mdas "an alternative series list", and was ruled LIVE by objectui#6896 (maintainer ruling 2026-08-31, prose follows machine). The card's headline — "four reads are an explicit FOREIGN DIALECT" — is wrong for one of its four.The instrument, and the index-signature control
Reads:
schemaReadsfrompackages/types/src/__tests__/widget-schema-anchors-6576.test.ts, verbatim — no second scanner. Declaredness: the TypeScript checker (getPropertiesOfType/getPropertyOfType), never a grep (objectui#8410).The confound the dispatch named is real and the instrument survives it:
⇒ an index signature that types any key as
anystill yieldsundefinedfor a nonsense key, while a real member is seen. The classification below is therefore a reading, not an artefact.Two instrument artefacts, both disambiguated with the same instrument rather than by eye:
color/dataKeycame back "undeclared" onChartRenderer.tsxbecauseschemaReadsis FILE-scoped and the file declares two props types — both are declared members ofChartBarRendererProps.schema.chartcame back as a read onObjectChart.tsxfrom a docblock sentence. Re-running the same census on a comment-stripped copy of the same source drops it and keepschartType(non-vacuity).Re-derived read set —
ChartRenderer.tsxChartRendererProps.schemaChartSchemacategoriescategoryindexvaluecolorscategoryColorscategoryOrderQuestion 2 —
ObjectChart's three reads, re-classifiedAll three are bucket (b): declared, then read through a needless cast. ⛔ Nothing was declared; three casts were deleted.
ObjectChartSchemacolorsstring[] OR Record of string to string, optionalcompareTo{ kind: previousPeriod OR previousYear; dimension?: string }, optionalseriesArray of { dataKey: string; … }, optionalObjectChart.tsxnow has zero(schema as any)reads left (one historical mention survives inside a comment). Deleting the casts is not a cosmetic change: with the casts gone,type-checknow actually checks these values against their declarations — it passes, which is itself the reading that the declarations and the reads agree.drillDwonwas met and ⛔ not re-reported: it is the deliberate misspelling inside a string-literal fixture feeding the census's own non-vacuity control.Producer measurement — which arm, and why
A structural producer census over
packages/·apps/·examples/·content/·skills/·.claude/·docs/— 5695 files, every object literal / JSON object / fenced-block node whosetypeis one of ChartRenderer's registry keywords (chart,chart:bar,pie-chart,donut-chart,radar-chart,scatter-chart, plusbar-chart): 74 chart nodes.categoryvalueindexcategoriesChartSchema, not through this branchxAxisKeyserieschartTypedatazzqx_no_such_keyDocumented / taught use, same corpora:
xAxisKeyappears in 8 doc files (positive control fires); the word "Tremor" appears in no doc, guide or skill — only in the in-source comment this PR removes;index/category/valueare taught nowhere as chart-node keys;categoriesIS documented, and it is the one this PR does not retire. None of the three retired keys is advertised by any registryinputs.⇒ Zero live producers, zero taught use, no published declaration, no palette advertisement for
index/category/value. Per the dispatch's own rule that is the "proceed" arm, and the zero is backed by four firing positive controls and a firing negative control.AdvancedChartImplon its default category key,name(AdvancedChartImpl.tsx:865). Only one half of that is a refusal: rows carrying nonamecolumn hit the on-screenmissing-category-keyrefusal (that is the half the first run surfaced, and the half this PR's rows pin), while rows that do carry one plot silently againstname— a wrong picture, not a refusal. The classification never rested on the loudness of the degradation, but the sentence did overstate it, and it was also in the changeset, which publishes verbatim.Reverse verification — commit first, then ablate, with on-disk proof
The fix was committed first, so the restore leg points at a real commit. The mutation restores the pre-fix
ChartRenderer.tsxfrom the pinned base commit2e471dc0aa23ff807aa9960618c419ecccdfdcef, never from a remote-tracking name.schema as anycount 0 → 10;is RETIREDcount 1 → 0; blob13e49a04≠ HEAD blobf2531d0aindex,category,value, and the malformed-categoriesthrowf2531d0a(byte-identical to HEAD),git diff HEADempty⭐ The three "
categoriesstill plots" cases pass in both legs. That is the reading that matters: removingChartRenderer's own branch did not remove the capability, becausenormalizeChartSchemaserved it all along.⭐ The malformed-
categoriescase failing in the mutated leg is what turns "the retired branch called.mapon a string and threw" from a claim into a measurement.The ablation script carries a
trapon EXIT / INT / TERM calling a RESTORE function (the placeholder is spelled as a word on purpose — GitHub's body sanitizer eats angle-bracket-shaped tokens, backticks included) with absolute paths, verifies the mutation reached disk by marker count and by blob hash before running anything, and treats an empty hash as failure. It is a one-shot proof; no test file is left behind by it.Gates
lint:coveragecheck:entry-guardcheck:upstream-port-paritycheck:bash32-floorturbo run lint --filter=@object-ui/plugin-charts --forcecheck:vi-mock-override-shapecheck:test-path-rootscheck:cross-repo-closer-outcomecheck:control-bytescheck:new-line-citationscheck:spec-symbolscheck-changeset-presencecheck-changeset-no-major@object-ui/plugin-charts type-checkvitest packages/plugin-charts/Every exit code captured by redirect before any pipe.
Doc Snippet Type CheckandSkill Example Checkare red onmainitself (chain objectui#9308 → objectui#9346); they name no file this PR touches.欠改 round — the three items the ceiling-tier review logged
Head
c237b09e, one commit on top of the reviewed head348a4bdf. ⛔ Not a re-litigation: the contract gate PASSed and is on record. ⛔ No accept-set change, ⛔ no declaration, ⛔ no new key, ⛔ nothing underpackages/types/. Diff of this round = 3 files, +51 / -21.valuesentence, which measured accurate, is byte-identical.changeset/8650-chart-foreign-dialect-retired.md:39-45neverPlotsis a race in the false-green directionChartRenderer.foreignDialectRetired-8650.test.tsx:103-114, 188, 200, 217categories— this filenormalizeChartSchema.ts:381-389Item 2, what changed and why it now fails the other way. The old form slept a fixed 150 ms and asserted "no surface yet", against a measured warm time-to-surface of 97–168 ms for the canonical control. It discriminated in both runs so far, but a re-added alias that plotted late on a loaded runner would have satisfied it — a pin that cannot fail. The new form waits for the positive signal:
waitForon[data-chart-error="missing-category-key"]forindex/category(plus "no plot surface coexists with the refusal"), and forvaluethe file's existingplottedhelper — wait for the surface to arrive, then count marks. A slow runner now makes these slower, never green. Observable in the ablation timings: the two axis cases redden bywaitFortimeout at ~1087 ms, not by beating a window.Item 3 premise re-checked before complying, not assumed.
git grep -n categories packages/plugin-charts/src/ChartRenderer.tsxreturns only comment lines — the reader-side branch is gone — so the sentence was false about the very removal it sat next to. Item 1 premise likewise:AdvancedChartImpl.tsx:865is literallyxAxisKey = 'name',. Both held; neither was taken on report.Ablation, re-run on the rewritten cases
Committed first (
c237b09e), so the restore leg points at a real commit; mutation and restore are symmetric, the script carriestrap … EXIT INT TERM, and every path in it is absolute.index/category/valuereads put back inChartRenderer.tsx1 → 0; injected marker0 → 2lines; on-disk blob16d9b712≠ HEAD blobf2531d0aTests 3 failed / 4 passed (7): exactlyindex(1087 ms),category(1089 ms),value(69 ms)git checkout HEAD -- …(⛔ never baregit checkout --, which would take the mutation back out of the index)f2531d0a== HEAD blobf2531d0a; canonical line back to1, marker back to0;git diff HEADon the path emptyTests 7 passed (7)The 4 that pass in both legs are the canonical control and the three⚠️ Counting note, so nobody re-derives it wrong: the marker figure is
categoriescases — the capability this PR does not retire, unaffected by the mutation in either direction.grep -c, which counts lines, not occurrences — 3 injected occurrences land on 2 lines.⭐ No build /
distpreflight is owed for this ablation: the pin imports./ChartRendererand./AdvancedChartImplby relative specifier inside the same package, so the mutated source is what runs. There is noexports-resolved hop that a staledistcould keep green.Gates — exit code captured by redirect before any pipe
cmd > log 2>&1; EXIT=$?throughout. ⛔ Not derived fromlint.yml: this repo's doc and skill gates each own a workflow file, and the workflow set was enumerated fromorigin/mainwithgit ls-tree, not from the working tree.check-changeset-presencecheck-changeset-no-major/-fixed/-overwritecheck-changeset-claimsObjectChart.tsxand pre-date this roundcheck-control-bytescheck-new-cross-file-line-citationscheck-test-path-rootscheck-vi-mock-override-shape/-specifiers/-inheritrechartsmockcheck-spec-symbol-derivationcheck-comment-mask-corpuscheck-entry-guard·check-upstream-port-parity·check-bash32-floorcheck-cross-repo-closer-outcome·check-lint-coverage·check-shell-escape-residueturbo type-check --filter=@object-ui/plugin-chartsturbo lint --filter=@object-ui/plugin-charts348a4bdf, so this round adds nonevitest run packages/plugin-charts/(under the shared verify lock)Test counts before → after: pin file 7 → 7 (
it(count on both sides; assertions rewritten, no case added or removed),plugin-chartspackage 541 passed.⛔ Consumer packages are not re-run this round and none is owed: the only non-test source file touched is
normalizeChartSchema.ts, and every changed line in it is a comment — mechanically, changed non-comment lines = 0. The published face did not move.Doc Snippet Type CheckandBundle Analysisremain red from base-side debts (objectui#9308 via objectui#9346; objectui#9204 / objectui#9316). ⛔ Neither is this PR's, and neither was re-run.⛔ Still draft. ⛔ Not enqueued, ⛔ no auto-merge, ⛔ no label added, moved or removed by this round.
Acceptance notes
Out of scope, noted, not filed —
ChartRendererProps.schemais a hand-rolled inline object literal that does not mirror the publishedChartSchema:categoriesis authorable and reachesnormalizeChartSchema, yet the props type refuses it, which is why every in-repo caller writesas any. That is objectui#6576's class (a widget props type anchoring itsschemato no named type), already ruled for two other widgets, and anchoring a third published prop type is a shape decision this card explicitly forbids taking. Carrier: whoever picks up the objectui#6576 anchoring family next; this PR deliberately adds no declaration.Out of scope, noted, not filed —
BaseSchema's[key: string]: anyis the reason none of this is enforceable at authoring time; the standing defect card is objectui#7927 (pm:blocked).⛔ This PR does not put anything in
packages/types/src/__tests__/undeclared-but-consumed-keys-6150.test.ts, does not fold into objectui#8347, and adds no declaration to any published type — the three outcomes the card exists to prevent.🤖 Generated with Claude Code
https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
Generated by Claude Code
Generated by Claude Code