Noticed while retiring PluginComponentInput (#5674). Filed rather than fixed: it is out of that card's fence, and it is not the same shape as PluginComponentInput despite sitting on the adjacent line.
Measured
packages/types/src/index.ts publishes ComponentMeta twice, from two different declarations:
| line |
spelling |
declaration |
index.ts:99 |
ComponentMeta |
packages/types/src/base.ts:511 |
index.ts:921 |
ComponentMeta as PluginComponentMeta |
packages/types/src/plugin-scope.ts:149 |
These are structural copies, not an alias pair — which is exactly what makes this different from #5674. PluginComponentInput became a pure alias once #5671 converged its declaration; PluginComponentMeta still names a genuinely separate interface with a different key set.
The divergence is already live
Top-level keys, both declarations read off origin/main:
base.ts (11): label icon category inputs defaultProps examples isContainer resizable resizeConstraints tags description
plugin-scope.ts (9): the same nine, minus tags and description
resizeConstraints' six members are identical in both. So the delta is exactly two keys, and it is the same defect class #4972 recorded for ComponentInput — where the delta was min/max/step/placeholder — one release before it bit.
Why it is worth recording
#4580's ruling was written for this shape, and the wording is general: "a structural copy would reproduce the defect the moment either side moved." Either side has moved: tags and description exist on the published ComponentMeta and not on the plugin-facing twin, so a plugin author typing against the plugin-scope declaration cannot write two keys the main surface advertises.
There is already a test paying for the duplication. packages/types/src/__tests__/default-children-retired-contract-twins.test.ts asserts the same contract twice — once against ComponentMeta from base.ts (line 116) and once against ComponentMeta from plugin-scope.ts (line 131) — and its own header calls the second one "its plugin-facing twin". That file is the running cost of the copy.
Note this is not covered by #4972, which is closed and was scoped to ComponentInput's three copies plus WidgetInput. ComponentMeta is named nowhere in it.
Zero consumers at the published alias name
PluginComponentMeta has one occurrence repo-wide excluding node_modules//dist/: its own export line at index.ts:921. Nothing imports it.
Search scope: every root in the repo (packages/ apps/ content/ docs/ skills/ examples/ e2e/ scripts/ eslint-rules/ public/ .changeset/ and the root *.md), plus the sibling objectstack framework checkout. Control, searched identically so a broken search could not read as a clean one: AppMetadataPlugin, a neighbour in the same export type { ... } block, returns 12 hits including a real cross-package import at packages/core/src/registry/PluginSystem.ts:10; in the framework checkout the control SchemaRenderer returns 30 files and @object-ui/types 23. The apparatus finds names that are there.
The in-repo half is measurable; an importer on npm is not. That distinction is what #5674 was filed about and it applies here unchanged.
Possible directions
Not urgent: no user hits this today, the same way #4972's divergence was dormant until it wasn't. Recording it while the measurement is fresh.
Related: #4580 / #4548 (the ruling and the 19/35 probe), #4972 (the ComponentInput census), #5671 (the convergence PR), #5674 (the alias retirement this was found under).
Filed unassigned.
Generated by Claude Code
Generated by Claude Code
Noticed while retiring
PluginComponentInput(#5674). Filed rather than fixed: it is out of that card's fence, and it is not the same shape asPluginComponentInputdespite sitting on the adjacent line.Measured
packages/types/src/index.tspublishesComponentMetatwice, from two different declarations:index.ts:99ComponentMetapackages/types/src/base.ts:511index.ts:921ComponentMeta as PluginComponentMetapackages/types/src/plugin-scope.ts:149These are structural copies, not an alias pair — which is exactly what makes this different from #5674.
PluginComponentInputbecame a pure alias once #5671 converged its declaration;PluginComponentMetastill names a genuinely separateinterfacewith a different key set.The divergence is already live
Top-level keys, both declarations read off
origin/main:base.ts(11):labeliconcategoryinputsdefaultPropsexamplesisContainerresizableresizeConstraintstagsdescriptionplugin-scope.ts(9): the same nine, minustagsanddescriptionresizeConstraints' six members are identical in both. So the delta is exactly two keys, and it is the same defect class #4972 recorded forComponentInput— where the delta wasmin/max/step/placeholder— one release before it bit.Why it is worth recording
#4580's ruling was written for this shape, and the wording is general: "a structural copy would reproduce the defect the moment either side moved." Either side has moved:
tagsanddescriptionexist on the publishedComponentMetaand not on the plugin-facing twin, so a plugin author typing against the plugin-scope declaration cannot write two keys the main surface advertises.There is already a test paying for the duplication.
packages/types/src/__tests__/default-children-retired-contract-twins.test.tsasserts the same contract twice — once againstComponentMetafrombase.ts(line 116) and once againstComponentMetafromplugin-scope.ts(line 131) — and its own header calls the second one "its plugin-facing twin". That file is the running cost of the copy.Note this is not covered by #4972, which is closed and was scoped to
ComponentInput's three copies plusWidgetInput.ComponentMetais named nowhere in it.Zero consumers at the published alias name
PluginComponentMetahas one occurrence repo-wide excludingnode_modules//dist/: its own export line atindex.ts:921. Nothing imports it.Search scope: every root in the repo (
packages/apps/content/docs/skills/examples/e2e/scripts/eslint-rules/public/.changeset/and the root*.md), plus the siblingobjectstackframework checkout. Control, searched identically so a broken search could not read as a clean one:AppMetadataPlugin, a neighbour in the sameexport type { ... }block, returns 12 hits including a real cross-package import atpackages/core/src/registry/PluginSystem.ts:10; in the framework checkout the controlSchemaRendererreturns 30 files and@object-ui/types23. The apparatus finds names that are there.The in-repo half is measurable; an importer on npm is not. That distinction is what #5674 was filed about and it applies here unchanged.
Possible directions
plugin-scope.ts'sComponentMetabecomesexport type { ComponentMeta } from './base.js', andtags/descriptionreach the plugin surface. Same move ComponentInput: re-export the one declaration instead of restating it three times #5671 made forComponentInput, same package, and its PR measured the emitted.d.tsboth ways.PluginComponentMeta, which after (a) is a second published name for one type — i.e. the state [finding]PluginComponentInputis a published name with zero consumers, and after #4972 it is a pure alias ofComponentInput#5674 is retiring. Sequencing it as one job avoids deprecating a name that is about to change meaning.ComponentInput有三份结构副本(types 两份 + core 一份)外加一个窄孪生WidgetInput,而 #4580 的裁定说的是「re-export 而不是 restate」—— 副本之间今天已经有一处分歧 #4972's option (c), which was not taken there.Not urgent: no user hits this today, the same way #4972's divergence was dormant until it wasn't. Recording it while the measurement is fresh.
Related: #4580 / #4548 (the ruling and the 19/35 probe), #4972 (the
ComponentInputcensus), #5671 (the convergence PR), #5674 (the alias retirement this was found under).Filed unassigned.
Generated by Claude Code
Generated by Claude Code