You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Step zero: the measurement that chose the direction
The card was filed undecided between A (move the package to plugin-report) and B (move the console and the CLI whitelist to report). The charter made A conditional on nothing in the wild authoring the report:* spellings. Measured:
6 hits, all inside the generated CLI whitelist itself - 0 in authored metadata. The same grep over the sibling objectstack checkout (examples, apps, stored fixtures, docs): 0. By contrast the bare spellings (type: 'report', type: 'spec-report', type: 'report-viewer') are authored in 48 places, including plugin-dashboard's ObjectMetricWidget / DrillDownDrawer and plugin-report's own ReportRenderer. Direction A is chartered: retire the spellings nobody writes, keep the ones everybody writes.
The whitelist is generated (scripts/regenerate-known-schema-types.mjs), so lines 345-347 and 468-470 are not two hand-typed lists - they are the honest union of two disagreeing registration sites. That is the defect, stated by the generator.
What changed
packages/plugin-report/src/index.tsx registers report, spec-report and report-viewer under plugin-report instead of report. packages/cli/src/utils/known-schema-types.ts is regenerated, which drops exactly the three unsatisfiable keys:
apps/console/src/register-plugins.ts needs no change - it already declared plugin-report, and that is the half this PR makes true rather than aspirational.
Why the bare keys do NOT take skipFallback
The card asked for the objectui#6353 skipFallback treatment on the bare report key in either direction. Applying the flag here would be wrong, and the repo's own derivation says why. A census of every registration site (via deriveRegistryKeys, the derivation that feeds the whitelist) shows the bare key has exactly two claimants:
That is not two competing components - it is the console's lazy stub and the eager registration it loads, which is the shape of all 27 other console-stub/plugin pairs in this repo (calendar, kanban, gantt, map, markdown, ...). The objectui#6353 case was different: two distinct renderers (view:timeline and plugin-timeline:timeline) fighting over one bare key, where suppressing the loser is exactly right.
Once the namespaces agree, both claimants name the same full type, so register() clears the stub it replaces and the bare key has one owner by construction. Putting skipFallback: true on either claimant would strand bare report - the only spelling anything authors - and would drop it from the generated whitelist. So this PR gets the guarantee#6353 landed (ownership decided by declaration, not by evaluation order or load phase) from namespace unification, and pins it with the equivalent replay rather than with a flag that has no correct target here. The reasoning is written into src/index.tsx beside the registrations so the next reader does not re-litigate it.
The two pins
packages/plugin-report/src/__tests__/report-bare-key-ownership.test.ts - the #6353 equivalent. It reads this package's real declared metadata back out of the registry (no hand-copied mirror), replays it into a fresh Registry alongside a console-shaped lazy stub in both registration orders, and checks the bare key's declared namespace after every step - so a disagreement reddens mid-sequence even though each step on its own succeeds. It also pins consequence 2 directly: after the module registers, hasLazy(short, 'plugin-report') must be false rather than pending forever.
The metadata lookup deliberately does not pass the namespace under test. An earlier revision used getConfig(short, NS), which selects for the very value the per-step assertion then checks - the assertion could never have failed. Fixed in d46f6c9e2 and demonstrated below.
scripts/__tests__/report-namespace-agreement-6416.test.ts - the cross-site half, extractive from deriveRegistryKeys so nothing in it can drift: each short name must have exactly one namespaced spelling, plugin-report:{short} must be claimed by both the console site and the plugin site (before this PR the console claimed it alone - declared renderable, satisfied by nothing), and no report:* key may survive.
Ghost-assertion evidence
Both mutations restore by comparing the HEAD blob hash, not an exit code, and both restore legs are verified with git diff HEAD empty. Mutation scripts carry trap ... EXIT INT TERM. No rebuild step is involved: vitest.config.mts aliases @object-ui/* to packages/*/src, and the pin imports ../index relatively, so the tests read source directly rather than dist/.
Reproduce, on unmodified main @ 7d2a68941 - predicted red, observed red (16/16):
AssertionError: "report" must be registered as "plugin-report:report" and nothing else ...:
expected [ 'report:report' ] to deeply equal [ 'plugin-report:report' ]
AssertionError: nothing is registered as "plugin-report:report": expected undefined to be defined
Mutation A - revert the namespace to report. Predicted red on both pins; observed 23 failed | 5 passed. The load-bearing part is that the replay's per-step assertion fired 6 times (3 short names x 2 orders), not just the top-level spelling check:
MUTATION ON DISK: plugin-report spelling 3 -> 0; report spelling -> 3
AssertionError: after the "eager" step in order [eager, stub], bare "report" declares a
different namespace than the step before it - the bare key is double-claimed again (#6416):
expected 'report' to be 'plugin-report'
AssertionError: "plugin-report:report" is still a pending lazy stub after the module
registered - the registration is landing under a different full type (#6416)
RESTORE VERIFIED: git diff HEAD is empty for packages/plugin-report/src/index.tsx
Mutation C - mutate only apps/console's three stubs. Predicted: cross-site pin red, package pin green (it deliberately does not read the console - that is the seam between the two files). Observed exactly that:
MUTATION ON DISK: console plugin-report stubs 2 -> 0; report spelling -> 2
scripts/__tests__/report-namespace-agreement-6416.test.ts (12 tests | 7 failed)
Test Files 1 failed | 1 passed (2)
RESTORE VERIFIED: git diff HEAD empty for apps/console/src/register-plugins.ts
Verification, on the final commit d46f6c9e2
Green, quoting each gate's own verdict line:
pnpm exec vitest run packages/plugin-report/ scripts/__tests__/report-namespace-agreement-6416.test.ts scripts/__tests__/known-schema-types-derivation-5115.test.ts scripts/__tests__/check-doc-component-types.test.ts - Test Files 18 passed (18) - Tests 248 passed (248)
pnpm check:doc-types - Every documented component type is registered. (656 derived keys; this is the gate a registration change moves, and it confirms no doc snippet authored a retired key)
node scripts/regenerate-known-schema-types.mjs --check - OK ... matches the registry derivation.
pnpm --filter @object-ui/plugin-report --filter @object-ui/cli run type-check - both Done. Both new test files are confirmed in their type-check programs (tsc --listFiles | grep -c = 1 each), so the green actually covers them.
pnpm --filter @object-ui/plugin-report --filter @object-ui/cli run lint - both Done, 0 errors (pre-existing warnings only)
Declared narrowing.pnpm check:readme-exports and pnpm check:doc-snippets were not measured here: both exited with PRECONDITION NOT MET, naming 13 and 23 unbuilt packages respectively - they need a full-repo build (--build-filter resolves to 21 package closures). That is "I could not run", not a verdict, and CI runs both with a full build. The narrowing is safe to state rather than merely convenient: this diff touches no .md/.mdx file and changes no package's exported surface (only a registration's namespace value), and both gates key off documentation text and declared type entries, neither of which reads ComponentMeta.namespace. The gate that does read registration namespaces, check:doc-types, ran and is green. Likewise pnpm lint / pnpm type-check are repo-wide turbo farms; the two affected packages were run individually, and eslint.config.js configures no type-aware linting (no projectService / parserOptions.project), so this diff cannot move the verdict on any file it does not touch.
Scope
Held to this card. objectui#5106 - the doc-types gate never judging the namespace half of a registration row, the gate that would have caught this whole class - is untouched and stays its own card.
`packages/plugin-report` registered `report`, `spec-report` and `report-viewer`
under namespace `report`, while `apps/console/src/register-plugins.ts` declared
the lazy stubs for the same three short names under `plugin-report` and
`packages/cli/src/utils/known-schema-types.ts` shipped the `plugin-report:*`
spellings as renderable.
`Registry.register` clears the lazy stub for the type IT registers, and that
type was `report:report` — so the three `plugin-report:*` stubs were never
cleared, no component was ever stored under them, and a schema authored with a
whitelisted key resolved to nothing. The bare `report` key was also claimed
twice under two different namespaces, so what it DECLARED depended on whether
the plugin chunk had loaded yet (the objectui#6353 shape).
Direction chosen by measurement: no `report:*` spelling is authored anywhere in
this repo or the sibling `objectstack` checkout (0 hits), while the bare
spellings are authored in 48 places. Move the package to `plugin-report`, retire
the `report:*` keys, and regenerate the whitelist. The bare keys stay claimed
deliberately and must not take `skipFallback`: both claimants now name the same
full type, so suppressing either would strand the only spelling anything
authors.
Two pins outlive the fix: a fresh-`Registry` replay of the real declared
metadata plus a console-shaped lazy stub in BOTH registration orders, checking
the bare key's declared namespace after every step; and a derivation-based pin
that fails if the plugin, the console stubs and the generated whitelist ever
disagree again.
Fixes#6416
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q
…ing it
The replay fetched the eager registration with `getConfig(short, NS)`, which
selects for the very namespace the per-step assertion then checks — so that
assertion could never fail. Look the key up by short name and report whatever
namespace was declared, so a namespace regression reddens inside the replay
rather than only at the top-level spelling check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q
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.
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
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#6416
Step zero: the measurement that chose the direction
The card was filed undecided between A (move the package to
plugin-report) and B (move the console and the CLI whitelist toreport). The charter made A conditional on nothing in the wild authoring thereport:*spellings. Measured:6 hits, all inside the generated CLI whitelist itself - 0 in authored metadata. The same grep over the sibling
objectstackcheckout (examples, apps, stored fixtures, docs): 0. By contrast the bare spellings (type: 'report',type: 'spec-report',type: 'report-viewer') are authored in 48 places, includingplugin-dashboard'sObjectMetricWidget/DrillDownDrawerandplugin-report's ownReportRenderer. Direction A is chartered: retire the spellings nobody writes, keep the ones everybody writes.The whitelist is generated (
scripts/regenerate-known-schema-types.mjs), so lines 345-347 and 468-470 are not two hand-typed lists - they are the honest union of two disagreeing registration sites. That is the defect, stated by the generator.What changed
packages/plugin-report/src/index.tsxregistersreport,spec-reportandreport-viewerunderplugin-reportinstead ofreport.packages/cli/src/utils/known-schema-types.tsis regenerated, which drops exactly the three unsatisfiable keys:apps/console/src/register-plugins.tsneeds no change - it already declaredplugin-report, and that is the half this PR makes true rather than aspirational.Why the bare keys do NOT take
skipFallbackThe card asked for the objectui#6353
skipFallbacktreatment on the barereportkey in either direction. Applying the flag here would be wrong, and the repo's own derivation says why. A census of every registration site (viaderiveRegistryKeys, the derivation that feeds the whitelist) shows the bare key has exactly two claimants:That is not two competing components - it is the console's lazy stub and the eager registration it loads, which is the shape of all 27 other console-stub/plugin pairs in this repo (
calendar,kanban,gantt,map,markdown, ...). The objectui#6353 case was different: two distinct renderers (view:timelineandplugin-timeline:timeline) fighting over one bare key, where suppressing the loser is exactly right.Once the namespaces agree, both claimants name the same full type, so
register()clears the stub it replaces and the bare key has one owner by construction. PuttingskipFallback: trueon either claimant would strand barereport- the only spelling anything authors - and would drop it from the generated whitelist. So this PR gets the guarantee#6353 landed (ownership decided by declaration, not by evaluation order or load phase) from namespace unification, and pins it with the equivalent replay rather than with a flag that has no correct target here. The reasoning is written intosrc/index.tsxbeside the registrations so the next reader does not re-litigate it.The two pins
packages/plugin-report/src/__tests__/report-bare-key-ownership.test.ts- the #6353 equivalent. It reads this package's real declared metadata back out of the registry (no hand-copied mirror), replays it into a freshRegistryalongside a console-shaped lazy stub in both registration orders, and checks the bare key's declared namespace after every step - so a disagreement reddens mid-sequence even though each step on its own succeeds. It also pins consequence 2 directly: after the module registers,hasLazy(short, 'plugin-report')must befalserather than pending forever.The metadata lookup deliberately does not pass the namespace under test. An earlier revision used
getConfig(short, NS), which selects for the very value the per-step assertion then checks - the assertion could never have failed. Fixed ind46f6c9e2and demonstrated below.scripts/__tests__/report-namespace-agreement-6416.test.ts- the cross-site half, extractive fromderiveRegistryKeysso nothing in it can drift: each short name must have exactly one namespaced spelling,plugin-report:{short}must be claimed by both the console site and the plugin site (before this PR the console claimed it alone - declared renderable, satisfied by nothing), and noreport:*key may survive.Ghost-assertion evidence
Both mutations restore by comparing the
HEADblob hash, not an exit code, and both restore legs are verified withgit diff HEADempty. Mutation scripts carrytrap ... EXIT INT TERM. No rebuild step is involved:vitest.config.mtsaliases@object-ui/*topackages/*/src, and the pin imports../indexrelatively, so the tests read source directly rather thandist/.Reproduce, on unmodified
main@7d2a68941- predicted red, observed red (16/16):Mutation A - revert the namespace to
report. Predicted red on both pins; observed23 failed | 5 passed. The load-bearing part is that the replay's per-step assertion fired 6 times (3 short names x 2 orders), not just the top-level spelling check:Mutation C - mutate only
apps/console's three stubs. Predicted: cross-site pin red, package pin green (it deliberately does not read the console - that is the seam between the two files). Observed exactly that:Verification, on the final commit
d46f6c9e2Green, quoting each gate's own verdict line:
pnpm exec vitest run packages/plugin-report/ scripts/__tests__/report-namespace-agreement-6416.test.ts scripts/__tests__/known-schema-types-derivation-5115.test.ts scripts/__tests__/check-doc-component-types.test.ts-Test Files 18 passed (18) - Tests 248 passed (248)pnpm check:doc-types-Every documented component type is registered.(656 derived keys; this is the gate a registration change moves, and it confirms no doc snippet authored a retired key)node scripts/regenerate-known-schema-types.mjs --check-OK ... matches the registry derivation.pnpm --filter @object-ui/plugin-report --filter @object-ui/cli run type-check- bothDone. Both new test files are confirmed in their type-check programs (tsc --listFiles | grep -c= 1 each), so the green actually covers them.pnpm --filter @object-ui/plugin-report --filter @object-ui/cli run lint- bothDone, 0 errors (pre-existing warnings only)pnpm type-check:scripts,pnpm check:control-bytes,check:entry-guard,check:self-import,check:phantom-deps,check:esm-specifiers,check:vi-mock-specifiers,check:doc-fences,check:shell-escape-residue- all exit 0Declared narrowing.
pnpm check:readme-exportsandpnpm check:doc-snippetswere not measured here: both exited withPRECONDITION NOT MET, naming 13 and 23 unbuilt packages respectively - they need a full-repo build (--build-filterresolves to 21 package closures). That is "I could not run", not a verdict, and CI runs both with a full build. The narrowing is safe to state rather than merely convenient: this diff touches no.md/.mdxfile and changes no package's exported surface (only a registration'snamespacevalue), and both gates key off documentation text and declared type entries, neither of which readsComponentMeta.namespace. The gate that does read registration namespaces,check:doc-types, ran and is green. Likewisepnpm lint/pnpm type-checkare repo-wide turbo farms; the two affected packages were run individually, andeslint.config.jsconfigures no type-aware linting (noprojectService/parserOptions.project), so this diff cannot move the verdict on any file it does not touch.Scope
Held to this card. objectui#5106 - the doc-types gate never judging the namespace half of a registration row, the gate that would have caught this whole class - is untouched and stays its own card.
Generated by Claude Code
Generated by Claude Code