test(vi-mock): inherit the real @object-ui/plugin-grid surface in 25 vi.mock factories - #8146
Conversation
…vi.mock factories Sweeps objectui#6892's seventh specifier. All 25 judged call sites on `@object-ui/plugin-grid` were frozen -- 21 under packages/plugin-view, 4 under packages/plugin-designer -- and each now obtains the real barrel through `importOriginal` and spreads it FIRST, keeping its hand-written overrides after. The specifier joins COVERED_SPECIFIERS in the same commit, so the guard holds it at zero from here. Two syntactic shapes: 21 object-literal arrows (15 of them byte-identical on one line) and 4 designer sites that delegated the whole factory to a shared manual mock module, which now spread the real barrel first and the manual mock second. STEP 0 walked the 560-module, 1421-statement graph reachable from the barrel and returned PROCEED on the CLASS of the effect: 114 namespaced module-scope ComponentRegistry.register calls plus three registration idioms in packages/fields (registerFieldRenderer, setCellRendererResolver, registerAllFields), all Map.set or a module-level let; zero timers, globals, storage, fetch or connections. Importing the real barrel under happy-dom registers 375 keys and emits zero console.warn and zero console.error. The pin's neighbour fixture named this specifier while it was uncovered, so the sweep turned its frozen factory into a genuine finding. It now names a specifier no package publishes, which keeps the case about the scope resolver rather than about the covered set's current membership. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
✅ 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
|
|
Standing down on
Every other check on Generated by Claude Code |
|
Armed — squash auto-merge enabled and the PR is in the merge queue,
On MERGED: content probe on re-fetched Generated by Claude Code |
|
LANDED — merged at 2026-09-06T20:55:56Z as Content probe on re-fetched
#6892 stays open ( Generated by Claude Code |
Part of #6892 — slice 7 of the per-specifier
vi.mockinheritance sweep.All 25 judged call sites on
@object-ui/plugin-gridwere frozen — every one of them. Each now obtains the real barrel throughimportOriginaland spreads it first, keeping its hand-written overrides after;@object-ui/plugin-gridjoinsCOVERED_SPECIFIERSin the same PR, so the guard holds it at zero from here.The conversion form is the one slices 3–6 used: an
asyncfactory takingimportOriginal, whose first entry spreads the awaited real module (typed through the same generic parameter the other swept factories use), with the overrides after it.STEP 0 — what inheriting actually runs
Verdict: PROCEED, on the CLASS of the effect rather than on inertness.
The static import graph was walked from
packages/plugin-grid/src/index.tsxwith the TypeScript parser and every top-level statement of every reached module classified. 560 modules, 1421 module-scope statements — the largest graph this worklist has measured, because this barrel reaches@object-ui/components,@object-ui/fields,@object-ui/react,@object-ui/core,@object-ui/permissions,@object-ui/mobileand@object-ui/i18n.It is not inert:
ComponentRegistry.register(...)call sites, every one carrying a namespace —ui89,element10,page7,action5,plugin-grid2,view1. The five inrenderers/layout/page.tsxthat read as bare carryuithrough the spreadpageMetaconstant; checked, not assumed. Because all 114 are namespaced, the deprecationconsole.warninsideregister()cannot fire.packages/fields/src/index.tsxand all the same benign class: fiveregisterFieldRenderer(...)calls (aMap.setinto a module-level registry), onesetCellRendererResolver(...)(assigns a module-levellet), andregisterAllFields(), which loops the widget map intoComponentRegistry.registerunder thefieldnamespace.React.forwardRef, 94new Set, 21createContext, 15Object.freeze, 14new Map, 13cva, 10createDiscardProofCache(anew WeakMapplus a closure), and nine module-scopenewsingletons whose constructors were read and assign fields only —UndoManager'slocalStoragepath is a method nothing calls at import time.fetch, or connections at module scope.Empirically, importing the real barrel under happy-dom moves
ComponentRegistry.getAllTypes()from 0 to 375 keys, exports 20 names, costs ~7.0s cold, and emits zeroconsole.warnand zeroconsole.error. Measured with a throwaway probe test that was deleted afterwards;git statuswas verified clean before the first conversion.One grep/AST disagreement, recorded rather than smoothed over
Two column-anchored greps corroborate the walk with exactly one disagreement, and the grep is the one that is wrong: four
self.addEventListenerlines match at column 0 inpackages/mobile/src/serviceWorkerSource.ts, and all four sit inside the template literal that module returns as generated service-worker source. Importing anything never executes them. The AST walk does not report them; a column-anchored grep cannot see template-literal nesting. Prefer the parser — and record the disagreement rather than the quieter number.The CSS import
The graph has exactly one side-effect import and it is CSS (
packages/components/src/sidebar-fixes.css). The rootvitest.config.mtsdeclares nocssoption — read off the config, not from memory (grep -n css vitest.config.mtsexits 1) — so Vitest leaves it unprocessed and the import is inert.The consuming side
Checked for the failure class the worklist names. Of the 25 converted files exactly one names
ComponentRegistryat all, and it reads one entry —get('object-view'), a keypackages/plugin-view's own index registers, not one this barrel claims — rather than asserting emptiness or a count. Not one of the 25 installs aconsole.warnorconsole.errorspy. So no converted assertion can be broken by the 375 new keys.The census
Re-derived on
21d7989fbthrough the guard's exportedscan()withcoveredset to all 22 workspace specifiers anyvi.mockcall site in the tree names.COVERED_SPECIFIERSwas never widened-and-reverted.@object-ui/plugin-gridAll-specifier population over 659 judged sites: 123 frozen before, 98 after. Zero sites moved the other way — a diff of the per-specifier tables with the
plugin-gridrow removed is empty across all 21 remaining rows.Site distribution: 21 under
packages/plugin-view, 4 underpackages/plugin-designer.There are no subpath call sites of this specifier anywhere in the tree, so objectui#8141 has nothing to report here.
Conversion shapes — two
plugin-view). 15 of them were the same single line byte-for-byte, which is why the transform could be applied mechanically and then verified by count rather than by eye.plugin-designer) — the shape slice 5 first met. These delegated the whole factory to./__mocks__/plugin-grid, which inherits nothing. They now spread the real barrel first and the manual mock second, so the hand-writtenObjectGridstill wins and every other export is the real one. That is exactly the form the neighbouring@object-ui/plugin-formfactory in those same four files already used.Neighbouring factories — the repair slice 6 needed was not needed here
Slice 6's main finding was that converting a workspace factory can kill the file at collection on a different, frozen third-party factory in the same file (15 of 27, on
lucide-react). That did not fire here, and the reason is worth carrying forward rather than reading as luck.Every one of the 25 files carries neighbouring factories — 25 on
@object-ui/plugin-form, 21 on@object-ui/react, 1 on@object-ui/permissions— and all of them were already inheriting, swept by slices 3, 5 and 6 or written that way. There is not a single frozen third-party factory among the 25. A specifier whose sites have no frozen neighbours inherits cleanly. Check the neighbours before budgeting for the repair.No product source was touched.
Cost
Measured the way slice 6 measured it — one converted
plugin-viewfile, twice each:The ~1.1s marginal cost is far below slice 6's ~3s, for a structural reason: these files already inherit the real
@object-ui/reactand@object-ui/plugin-formbarrels, whose graphs already pull@object-ui/components. Inheriting a barrel is cheap once its own dependencies are already loaded in that file.No test was lost or gained
The 25 converted files: exit 0, 25 passed / 226 tests. The same 25 paths with their merge-base blobs restored: exit 0, 25 passed / 226 tests — identical, so nothing died during collection and no test appeared or vanished. That base restore was proven on disk (inheriting factory heads 25 to 0, frozen heads 0 to 25) and left
git diff HEADnaming zero paths.The pin test needed one forced edit
scripts/__tests__/check-vi-mock-inherit.test.tshas a case — "correct neighbours in the same file are NOT flagged" — whose fixture used@object-ui/plugin-gridas its deliberately uncovered workspace neighbour. Sweeping this specifier turned that fixture's frozen factory into a genuine finding, so the case failed for a reason with nothing to do with what it asserts.The fixture now names a specifier no package publishes.
COVERED_SPECIFIERSis grow-only, so any real name in that slot is only ever on loan; the scope resolver classifies by string prefix and never resolves the module, so a name that cannot be swept keeps the case about the resolver instead of about the covered set's current membership. A comment in the fixture says so, which should stop slice 8 and after from re-paying this.It is not vacuous: putting a covered specifier back into that slot reddens exactly that case (ablation leg 4 below).
The header record was worded around objectui#8117 — the pin's own pattern, run locally over the edited gate source, returns zero matches.
Ablation — four legs, one per shape plus the pin
Each from the committed tree. Each mutation proven on disk by anchored marker counts before the result is read; each restore proven by
git hash-objectequalling the HEAD blob and bygit diff HEADnaming zero paths — never by a checkout exit code.packages/plugin-view/src/__tests__/ObjectView.sortSink.test.tsxpackages/plugin-designer/src/__tests__/ObjectManager.test.tsxpackages/plugin-view/src/__tests__/ObjectView.canonicalTableKeys.test.tsxscripts/__tests__/check-vi-mock-inherit.test.tsLegs 2 and 3 exercise the gate's obtain-without-spread rule specifically, since
importOriginalis still named on the line above. The gate reads exit 0 after every restore.No dist preflight was needed: the root Vitest config aliases
@object-ui/plugin-gridtopackages/plugin-grid/src, and the gate is a source-text scanner.Gates
All pinned to the final head
fee7826eb, with exit codes captured by redirect-then-capture rather than through a pipe.pnpm check:vi-mock-inheritpnpm exec vitest run scripts/__tests__/check-vi-mock-inherit.test.tspnpm exec vitest run packages/plugin-view/ packages/plugin-designer/pnpm check:vi-mock-specifierspnpm type-check:scriptspnpm check:control-bytesgrep -naPself-scan of all 28 changed paths, no matchnode scripts/check-changeset-presence.mjspnpm changeset:checknode scripts/check-governed-queue-guard.mjs --testplugin-view/plugin-designertype-checkplugin-view/plugin-designerlint--format json, all 25 edited files present, 0 messages on any of this branch's 189 added linesorigin/maindid not move during this slice (still21d7989fb), so the merge was a no-op and the growth warning stayed quiet for a second consecutive slice. The constant is still widened only against a gate run on the tree that ships.Live E2E (informational)is red on every branch today for an upstream reason (#7990 / objectstack#16186) and is not this PR's.What remains on the worklist
98 frozen sites:
@object-ui/permissions(24 of 48),@object-ui/app-shell(23, all frozen),@object-ui/plugin-detail(13 of 17) and@object-ui/plugin-chatbot(11 of 16).🤖 Generated with Claude Code
https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
Generated by Claude Code