Uh oh!
There was an error while loading. Please reload this page.
ComponentMeta: re-export the one declaration, then deprecate PluginComponentMeta at its final meaning - #6068
Merged
Conversation
…ComponentMeta
`plugin-scope.ts`' `ComponentMeta` becomes `export type { ComponentMeta } from
'./base.js'` — objectui#4580's ruling applied to the second member of the family,
following the shape objectui#5671 executed for the sibling type `ComponentInput`
in the same file.
Either side had moved: `base.ts` carried eleven keys, the plugin-scoped copy
nine — the same nine minus `tags` and `description`. `ComponentMetaSchema` (the
zod mirror) already declared all eleven, so two of three authorities agreed and
the plugin-facing one did not.
The published alias `PluginComponentMeta` is then deprecated at its final
meaning, on objectui#5674's two-stage pattern. The ordering is the point:
deprecating first would have warned about a name still about to change meaning.
Pinned by IDENTITY, not by member set — a member-identical structural copy
passes every type-level check (measured: `type-check` exits 0 on one), which is
exactly the state objectui#4580 predicted would drift.Contributor
✅ Console Performance Budget
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
Size Limits
|
yinlianghui
marked this pull request as ready for review
August 24, 2026 15:42
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 24, 2026
yinlianghui pushed a commit
that referenced
this pull request
Aug 24, 2026
Remove `DashboardConfig.aria` (packages/types/src/designer.ts) and turn its Zod
twin `DashboardConfigSchema.aria` (packages/types/src/zod/complex.zod.ts) into an
ADR-0049 retirement tombstone.
The pair declared `{ label?, description? }` — spellings that match neither
@objectstack/spec's `AriaProps` (`ariaLabel`/`ariaDescribedBy`/`role`) nor any
renderer vocabulary, so no read point could have consumed them. Re-measured on
main: zero `.aria` reads in plugin-designer/src, plugin-dashboard/src and
apps/console/src (the same grep family finds the live `schema.aria` reads in
plugin-detail and plugin-list); zero occurrences of either name in objectstack;
and DashboardConfigPanel.tsx — the panel the interface's doc comment claims to
serve — imports neither name.
- TS half: plain deletion + in-place comment tombstone, mirroring the landed
shape of #5855 (#5830) on the sibling `DashboardComponentSchema.aria`. The
interface's `[key: string]: any` catch-all means an authored `aria` still
compiles; what goes is the type-level suggestion and the false contract claim.
- Zod half: `z.never({ error }).optional().describe(...)` rather than a plain
delete. Measured: `DashboardConfigSchema` is a bare `z.object` with no
`.strict()`, so a deletion would have turned an accepted-and-PRESERVED key
into a silently STRIPPED one (the #6068 behaviour). Loud refusal is this
package's ruled outcome for retirements (data-display.zod.ts
StaticTableColumnSchema, the set crud.zod.ts `confirm` established).
- Tests: the aria-only pin in dashboard-config.test.ts is FLIPPED, not deleted —
it now asserts refusal by name and message, with two controls (an undeclared
key still rides through, proving the red is the tombstone and not strictness;
a legal config still parses). The `aria:` line in the full-config TS literal is
replaced by a removal note; kept, it would have green-washed through the index
signature.
Claude-Session: https://claude.ai/code/session_01CSoz9uGhaaSgiq3hshtN7L
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#5893
Direction (b) as ruled: converge, then retire — in that order.
packages/types/src/plugin-scope.ts'ComponentMetabecomesexport type { ComponentMeta } from './base.js', and the published aliasComponentMeta as PluginComponentMetais then deprecated at its final meaning. This inherits #4580's ruling with its reason — "a structural copy would reproduce the defect the moment either side moved" — and follows #5671's executed shape for the sibling typeComponentInput, in the same file. No second convergence shape was invented: the diff is line-for-line the move #5671 made, one type over.The STOP condition was looked for first, and is not present
The ruling's stop was: if the plugin surface deliberately excludes
tags/description, stop and report. Searched before converging, in the four places named:plugin-scope.ts' module header (@module plugin-scope, "Section 3.3: Plugin scope isolation types")Component metadata for registration. No exclusion, no reasonSchemaNode, label vocabulary,visible/disabled.ComponentMetanot mentionedpage-app-dashboard-spec-parity.test.ts' spec-key omissions, anobjectql.tsviewModenote)And the positive evidence runs the other way: the zod mirror never diverged.
ComponentMetaSchema(packages/types/src/zod/base.zod.ts:298) declares all eleven keys includingtagsanddescription. Two of the three authorities carried them and the plugin-facing declaration did not — drift, not design. Converging.The delta, re-derived on this merge-base (
efa70ecb2), key by keypackages/typesmoved five times on 2026-08-23/24, so this is measured here rather than carried from the card.base.ts:511plugin-scope.ts:149ComponentMetaSchemalabeliconcategoryinputsdefaultPropsexamplesisContainerresizableresizeConstraintstagsstring[]z.array(z.string())descriptionstringz.string()The delta is unchanged since the card was filed: exactly two keys, same two, neither grown nor closed.
resizeConstraints' six members verified identical, so the two-key delta is the whole of it.Consumers of
PluginComponentMeta, re-measured with controlsSame method for probe and controls —
grep -rn, excludingnode_modules/,dist/,.git/, over every root present in the tree (packages/ apps/ content/ docs/ skills/ examples/ e2e/ scripts/ eslint-rules/ public/ .changeset/and root docs), plus the siblingobjectstackframework checkout.PluginComponentMeta(probe)packages/types/src/index.ts:942. Nothing imports itAppMetadataPlugin(control — neighbour in the sameexport type { … }block)packages/core/src/registry/PluginSystem.ts:10PluginComponentInput(control — the sibling alias #5674 deprecated)PluginComponentMetainobjectstackcheckout@object-ui/types23 files,SchemaRenderer36 filesA zero with an apparatus that demonstrably finds names that are there.
ComponentInput/ComponentMetare-exports inplugin-scope.tsthat exist only to feed the aliases.Why the ordering is load-bearing. Until this PR the alias named a genuinely different nine-key interface. Deprecating it first would have told consumers to stop using a name that was still about to change meaning. It is tagged now, at its final meaning — and a test asserts those two facts together so the sequence cannot be undone by halves.
Verification: the divergence is pinned by IDENTITY, not by member set
New file:
packages/types/src/__tests__/component-meta-single-declaration.test.ts(13 tests).The load-bearing assertion is that
plugin-scope.tsdeclares noComponentMetaof its own and re-exportsbase.ts'. A member-set assertion cannot do this job, and the file says so rather than assuming it: TypeScript is structurally typed, so a local re-declaration carrying the same eleven keys is mutually assignable with the imported one. A member-identical structural copy is exactly what #4580 predicted would drift, and exactly what this card is the proof of — this copy started identical and acquired its two-key delta later.Ablation — direction predicted before running, and one prediction was wrong
Prediction, written to
PREDICTION.txtbefore any mutation: replacing the re-export with a member-identical localexport interface ComponentMeta(all eleven keys) turns the identity pin red while every member-set / type-level assertion stays green, andtype-checkstays green.Leg 1 — vitest. Matched exactly: 3 failed | 10 passed.
tagsanddescriptionLeg 1's type-check read was CONFOUNDED and its prediction was wrong. I predicted exit 0; it exited 2, with
src/plugin-scope.ts(19,15): error TS2440: Import declaration conflicts with local declaration of 'ComponentMeta'. That red is an artifact of my mutation leaving the file's top-of-fileimport type { ComponentMeta }in place beside the injected local declaration — a name collision, saying nothing about whether the member-set assertions can see a structural copy. Reported rather than quietly re-rolled.Leg 2 — the controlled repeat. Same member-identical interface, and the colliding import removed as well (which is precisely the pre-#5893 file shape, plus the two keys). Read:
pnpm --filter @object-ui/types type-check→ exit 0, green, its own verdict line clean across all threetscinvocations.That is the decisive measurement. Every type-level assertion in the suite — mutual assignability both ways, the published-entry-point check, and the
tags/descriptioncounter-probe — compiles clean against a re-declared copy. Only the source-level identity pin sees it. Which is the contrast the pin exists for.Ablation hygiene, both legs: anchor uniqueness asserted before writing (
count == 1, asserted inside the mutation script so a missed anchor aborts rather than silently no-ops); landing site printed (plugin-scope.ts:177/:176); the mutation proved on disk by grepping the injected text and separately the removed text, never by an editor exit code; restore undertrap … EXIT INT TERMusing absolute paths only, nocd, so it is cwd-independent; and restore verified afterwards rather than assumed — the restored file is sha256-identical to the pre-mutation backup, andgit status --porcelainshows only the five intended files.No package boundary is crossed by this ablation and no
dist/is involved: the pin reads../plugin-scope.tssource, and the type imports resolve tosrc/under vitest. Stated because it is the reason no rebuild-between-mutation-and-read step appears here — not an omission of one.Counter-probe: consumers still type-check
"The duplicate is gone" must not be satisfiable by breaking the type for everyone.
labeliconcategoryinputsisContainerresizabletagsdescriptionagainst the plugin-scoped spelling compiles and its values round-trip. Those two keys were a plain TS error on this spelling before this PR.Gates, each read from its own verdict line, all on the final commit
3640aba1agit status --porcelainwas empty when these ran, so they read the committed tree.pnpm --filter @object-ui/types type-check(script name echoed:type-check, hyphenated)tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json— all three. The third is why the type-level assertions and@ts-expect-errors are real enforcementpnpm exec vitest run packages/types/src --maxWorkers=2(root form)Test Files 56 passed (56) · Tests 619 passed (619)--reporter=verbosepnpm --workspace-concurrency=4 --filter '...@object-ui/types' builderror TS, zeroERR_PNPMpnpm exec eslint .inpackages/types(plain, no--no-inline-config)242 problems (0 errors, 242 warnings)— all pre-existingno-explicit-anycheck:control-bytes✅ OK (scanned 5013 tracked text file(s); skipped 85 binary)check-changeset-presence✅ 4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)check-changeset-fixed✅ All workspace packages are in the changeset fixed groupcheck-changeset-no-major✅ No changeset declares a major bumpcheck:esm-specifiersno un-ledgered package emits an extensionless relative specifiercheck:self-import✅ No package names itself inside its own src/(44 packages, 16798 specifiers)check:phantom-deps✅ Every in-scope import is declared by the package that publishes itcheck:doc-types✅ Every documented component type is registeredcheck:published-dist✅ No published package's build output carries tooling materialExit codes captured before any pipe (
cmd > log 2>&1; EXIT=$?), never from a$?following atail.Notes on two gates.
The downstream sweep direction is downstream —
--filter '...@object-ui/types'is the prefix form, i.e. the 43 packages that depend on@object-ui/types. That is the correct direction for a contract change in this package: the widened type flows to consumers.@object-ui/react-runtimewas built separately first, per the gotcha measured on #5927 — it does not depend on@object-ui/types, so it is never in the prefix closure, and without itapps/siteandpackages/componentsfail for reasons unrelated to any change. Not a finding; just the prerequisite.Nothing is claimed about rejection. Per #6058:
check:zod-mirror-parityis not a gate in this repository — grepped.github/workflows/,scripts/, and rootpackage.json, zero hits — so there is no ratchet here to over-credit for a TS-side-only change. And independently of that:ComponentMetaSchemais a plainz.objectwith no.strict(), so it strips unknown keys rather than refusing them (measured on zod 4.4.3 bydefault-children-retired-contract-twins.test.ts). This convergence buys ACCEPTANCE of two keys on the plugin-facing type. It buys no rejection of anything, anywhere. TheBaseSchemaindex-signature ceiling (#5155) is untouched —ComponentMetadoes not extendBaseSchema, so it was never on that axis.Blast radius
Nothing narrows: no key removed, no key's type changed. Two optional keys become writable on one spelling. That is why 43 downstream packages build green and why the changeset is a
minor, not amajor(this repo's own breaking changes never declaremajor).Also in this diff
default-children-retired-contract-twins.test.ts— the file #5893 named as "the running cost of the copy" — has its header corrected. Its case 3 no longer exercises a second declaration; it exercises a second published spelling of the first. It is deliberately kept, not deleted: while the deprecated alias is published, an author can still reach the type by that name, and the pin costs one@ts-expect-error. It is marked to be dropped together with the alias in stage 2.Out of scope, filed not fixed
#6067 —
@object-ui/core'sRegistry.ts:37is a thirdComponentMeta, and it is missingtags/descriptiontoo: the same two keys, now the only remaining structural copy of the name in the workspace. It is a superset-with-holes rather than a copy (it addstier,namespace,skipFallback,labelling), so it cannot be a bare re-export and needs a shape decision — which is why it is filed for triage rather than folded in here. #5671 convergedComponentInputin that very file and left this one, deliberately, as out of its fence. Searched open issues before filing; #4972 is closed and never namesComponentMeta, and #4631 is the wider three-surfaces card, not this.packages/corewas not touched.objectql.tswas not touched (#5903's PR #6053 is in flight there). No file underapp-shell/src/utils/,plugin-kanban,plugin-detail,data-objectstack,apps/console, ormetadata-admin/previews/was touched.Generated by Claude Code
Generated by Claude Code