test(vi-mock): inherit the real @object-ui/fields surface in 8 vi.mock factories (objectui#6892 slice 13) - #8265
Conversation
…k factories Every one of the eight frozen factories hand-listed one or two of the barrel's 140 exports, so the next export any module in the file's import graph reads at module scope would resolve to `undefined` against the stand-in and the file would die during collection. Each now spreads the real module first; the hand-written doubles stay as the overrides, which is the behaviour those tests assert. No assertion moved. All ten call sites on this specifier (the eight converted plus the two that already inherited) run 58 tests green in both states. Part of objectui#6892 (slice 13). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
The sweep in the previous commit took this specifier to zero frozen, so it joins COVERED_SPECIFIERS -- the grow-only list whose documented widening precondition is a sweep, not a judgement. Seventeen members become eighteen and the gate reads 623 of 623 inheriting. The header gains the slice-13 record in the shape slices 5 to 12 used: the graph walk and its import-time effect classification, the two projects the sites run in and why they answer the cost question in opposite ways, the neighbour reading, and the refreshed remaining population -- which is now one specifier, the parked @object-ui/app-shell. Part of objectui#6892 (slice 13). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
Eight test files under a published package changed, so check-changeset-presence demands a declaration. It is test-only, hence the empty frontmatter. Part of objectui#6892 (slice 13). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
No new vi.mock site on @object-ui/fields in the incoming commit (checked before merging: a new frozen site would land the widened gate red). The incoming diff touches packages/plugin-tree/README.md and scripts/check-doc-snippet-types.mjs, neither of which this branch reads. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
The clause counted the AccessExplainPanel spec twice, so the eight frozen sites read as nine. All eight sit under one directory; the breakdown now says so and adds to eight. Part of objectui#6892 (slice 13). 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
|
|
Armed: PR #8265 flipped ready at 2026-09-07T06:50:43Z on head Generated by Claude Code |
|
LANDED: merged at 2026-09-07T07:07:15Z as Generated by Claude Code |
Part of #6892 (slice 13:
@object-ui/fields)Base
340489334(slice 12, PR #8250).origin/mainmoved tocdbc852e9mid-slice; it was mergedin with a merge commit (never rebased) after checking the incoming diff for new
vi.mocksites onthis specifier, and every reading below is pinned to the final head
c389601b2.The repair
Eight
vi.mockfactories froze this barrel's export surface: each obtained nothing and hand-listedone or two of its 140 exports. Each now takes an
importOriginalcallback, awaits it withtypeof import('@object-ui/fields')as its TYPE ARGUMENT, and spreads the result first; thehand-written doubles stay as the overrides after the spread, because they are the behaviour those
tests assert. Not one assertion line moved — the only removed lines are the eight factory headers.
The two sites that already inherited keep their own spelling untouched.
STEP 0 — PROCEED
Method. A TypeScript-parser walk of the static import graph from
packages/fields/src/index.tsx,with the alias table read out of
vitest.config.mtsitself and TS ESM'./x.js'resolved to'./x.ts'/'.tsx'; every module-scope statement classified; then a second AST pass that stops atevery function-like boundary, so a call inside an arrow body is not counted as an import-time
effect. Corroborated empirically by importing the real barrel in both projects the sites run in.
Graph. 504 modules, 5,455 module-scope statements over nine workspace packages —
components205,core95,fields77,react64,i18n27,types17,sdui-parser8,data-objectstack6,providers5. 98 bare side-effect imports (the@object-ui/componentsrenderer cascade) and one CSSimport, inert because the root config declares no
cssoption. 51 external leaves,react240 andlucide-react76 by count.Not inert, and the class of the effect is what settled it. 112 module-scope
ComponentRegistry.register(...)calls, zero of them bare —ui90,element10,page7,action5, with thepage.tsxrenderer's five rows carryinguithrough the spreadpageMetaconstant (the namespace lives in
register()'s THIRD argument, which is what was read). So thedeprecation
console.warninregister()cannot fire.The barrel's own module-scope effects are the benign
packages/fieldsclass slices 7 and 12recorded — confirmed here rather than inherited: five
registerFieldRenderer(...)map writes, onesetCellRendererResolver(...)assignment, andregisterAllFields()at the last line, which loops thewidget map into
ComponentRegistry.registerunder thefieldnamespace wrapping aReact.lazyloader — so registering a widget does not load it. Beyond registration the graph holds only
allocation: 171
React.forwardRef, 81new Set, 30createContext, 14Object.freeze, 13new Map,13
cva, 11createSafeTranslation, fourSymbol.for, and onesubscribeDataChanges(aSet.addinto a module-level listener set). The second pass reports ZERO timers, globals, storage,
fetchand connections at import time.
Empirically, importing the real barrel exports 140 names and emits zero
console.warnandzero
console.errorin both projects.Import cost
The ten sites span two projects and the two answer the cost question in opposite directions, so
taking one alone would have been useless. Eight run in the light
domproject;AccessExplainPanel.test.tsxandAssignedUsersSection.test.tsxare inheavyDomTestsand run indom-heavy— whose setup,vitest.setup.dom.tsx, already imports this very barrel at modulescope.
dom(6225 / 5712 / 5799 ms)dom-heavy(0 / 0 / 0 ms)The
dom-heavyzero is the mechanism, not a rounding artefact: the module is resident before any testfile in that project is transformed. For two of the ten, inheriting cannot cost anything at all.
Statically, the eight converted files already hold 352 to 422 of the barrel's 504 modules with
their frozen specifiers cut as leaves, so inheriting adds 82 to 152. Measured in the files that pay it:
AssignedUsersSection.test.tsx(dom-heavy, 352/504)PermissionMatrixEditor.basics.test.tsx(dom, 422/504)FlowReferenceField.lookup.test.tsx(dom, 358/504)The aggregate decides, taken as an interleaved A/B against the committed tree (the frozen leg
restored from the base blob, the inheriting leg from
HEAD, each proven on disk before it was timed):About 2% on a ~25.4 s aggregate — inside the noise band and nowhere near objectui#6580's STOP
band, with 58 of 58 tests green in every state and the same 10 files collected in every run.
The
React.lazyclause — slice 11's class is ABSENT hereThe only
import('@object-ui/fields')anywhere in the tree is a type position(
typeof import(...)) inside a factory that already inherits, so no consuming module reaches thisbarrel through
React.lazy. The two module-scopeReact.lazycalls the graph does contain loadpackage-local widget modules (
./MarkdownContent.js,./ImageCropperDialog.js), and the widgetloader map is lazy by construction — all of it inside the package, which the real barrel resolves
itself. The test comment the dispatch flagged (
ActionParamDialog.dialogRecord.test.tsx, naming alazy of
./widgets/RadioField) is accurate about the mechanism —fieldWidgetMap.radiois a dynamicimport('./widgets/RadioField.js')wrapped byReact.lazy(loader)inregisterField— and that filecarries no
vi.mockat all, so it is not one of the ten and touches no converted file'sassertion window. No file went red on LOAD in the aggregate run. No module-scope import repair was
owed, and none was made.
STEP 1 — the census, through the gate's own exported
scan()Re-derived with
coveredwidened in memory to the 22 workspace specifiers anyvi.mockcall sitein the tree names;
COVERED_SPECIFIERSwas never widened-and-reverted on disk.packages/app-shellpackages/plugin-detailMatches the slice-12 forecast (10 / 2 / 8) exactly. Population over all 22 specifiers: 660 judged,
frozen 31 to 23, inherits 629 to 637; a diff of the two per-specifier tables with the
@object-ui/fieldsrow removed is EMPTY — no site moved the other way.Shape count. All eight frozen factories are ONE syntactic shape — the zero-parameter
object-literal arrow — carrying THREE double families: a null-rendering
RecordPickerDialoginthree files; a
CapabilityMultiSelectFieldJSX probe paired with aparseCapabilityNamesfunctiondouble in three; a
LookupFieldJSX probe in two. Each hand-listed one or two of 140 exports.Free confirmation — present, and the strongest form this worklist has had. Two of the ten already
inherited the real barrel on
mainand pass, one of them from genuinely cold (it holds 0 of the 504modules by any other route). On top of that, the
dom-heavysetup imports this barrel at module scopefor every test in that project, so the real module was known to load there before anything was
converted.
Neighbour reading — ZERO repairs, predicted in advance from the walk
Every other factory in all ten files was classified through the gate's own
findCallSitesbeforeany edit, then judged against the walked graph under slice 12's sharpened rule: a frozen neighbour is
dangerous when the newly-real graph reads a missing binding from it at module scope.
@object-ui/reactsubscribeDataChanges, read bycomponents/src/hooks/related-count-store.tsat module scope@object-ui/auth@object-ui/auth./useMetadata(4),./AssignedUsersSection(3),../previews/useObjectFields(2)packages/app-shell⭐ The reading worth carrying: there is not one third-party factory in the ten files, so slice 6's
collection-death class cannot fire here even though this graph reads
lucide-react76 times andreaches
sonner. And the one binding this graph does read at module scope from a mocked workspacespecifier —
subscribeDataChanges— was already provided by every factory that could starve it. Theprediction was made from the walk and then confirmed by the runs: zero neighbouring repairs.
No file among the ten mocks
@object-ui/app-shell— re-derived on this base, so the parkedspecifier (objectui#8173) stays parked and untouched.
Dependency edges
Confirmed on the base exactly as dispatched:
packages/app-shell/package.json:76(workspace:*),packages/plugin-detail/package.json:41(peerworkspace:^) and:52(devworkspace:*),apps/console/package.json:67(workspace:*). All eight converted files live inpackages/app-shell,so app-shell's edge is what licenses the typed generic.
check:phantom-depsexits 0.Reverse verification that the generic is real, not erased: swapping one factory's type argument for
a module that does not exist turns
tsc -p tsconfig.test.jsonred at exactly that line(
error TS2307: Cannot find module ...), and restoring returns the file's blob to theHEADone. Thetype argument is resolved against the declared edge.
Covered set: 17 to 18
'@object-ui/fields'joinsCOVERED_SPECIFIERSin this same PR, exact string match, one line — thelist stays grow-only. The header's count word moves seventeen to eighteen, the slice-13 record is added
in the shape slices 5 to 12 used, and the remaining-population paragraph is refreshed 31 to 23.
Remaining population after this slice: one specifier.
@object-ui/app-shell, 23 of 23 frozen,PARKED under objectui#8173 and re-annotated as such. Every other workspace specifier any
vi.mockcall site in this tree names is covered and reads zero frozen.
The record was written without the shape objectui#8117's pin rejects (a backticked test filename
followed by a comma or a closing bracket); a mechanical
grep -nPfor that pattern over the whole gatesource returns 0 matches. The pin itself needed no edit — it still derives its names from
COVERED_SPECIFIERS— which was confirmed by running it: 67 of 67, file unedited.Positive control — two legs, from the committed tree
Target
packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.basics.test.tsx, HEAD bloba163daa36db3652a6a83319a8c0f0a973d462922. Both legs ran under atrapwith absolute paths. Eachmutation was proven on disk by a count anchored on the fields spread line specifically — this file
carries other spreads, so a file-wide count could never fall to zero — and by a changed blob, before
the gate result was read. Each restore is proven by STATE, never by a checkout exit code.
a163daa3…(= HEAD)f6d45251……basics.test.tsx:39— "the factory obtains the real module but never spreads it"34048933478873e39…Leg 1 is objectui#8183's canonical control shape, not the mention-only one. After each leg
git hash-objectequalled the HEAD blob andgit diff HEADnamed zero paths;git statusisclean. No dist preflight is owed: the gate is a source-text scanner that reads tracked paths off disk,
and both configs alias this specifier to source.
Gates — all pinned to
c389601b2, exit codes captured by redirect-then-capturenode scripts/check-vi-mock-inherit.mjs@object-ui/fields, 0 auto-mockedvitest run scripts/__tests__/check-vi-mock-inherit.test.tsvitest run scripts/__tests__/(whole directory)vitest run packages/app-shell/in 4 shardsvitest run packages/plugin-detail/type-check--listFiles— all 8 edited files in a 4,449-file programtype-checkpnpm type-check:scriptspnpm lint:rootlintlinteslint --no-inline-config --format jsonover the 9 non-changeset pathseslint.config.js)pnpm check:phantom-depspnpm check:control-bytesgrep -naPcontrol-byte self-scan of all 10 changed paths (no match)node scripts/check-changeset-presence.mjsnode scripts/check-changeset-no-major.mjsnode scripts/check-governed-queue-guard.mjs --testpnpm check:entry-guardpnpm check:vi-mock-specifiersDependency closure built first (
pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' --filter '@object-ui/plugin-detail^...' build, exit 0 — the flag before the filters).The last commit (
c389601b2) is a comment-only correction inside the gate's own header:git diff 25a4b7607..c389601b2 --name-onlynamesscripts/check-vi-mock-inherit.mjsand nothing else,so
packages/**is byte-identical between the two and the package suites, type-checks and lints abovemeasure the shipping tree. The ratchet family — the gate itself, its pin, the whole
scripts/__tests__directory,
type-check:scriptsandlint:root— was re-run onc389601b2after that push.Changeset
node scripts/check-changeset-presence.mjsdemanded one (eight test files under a published package),so
.changeset/vi-mock-inherit-slice13-fields.mddeclares an empty frontmatter: test-only, releasesnothing. No
skip-changesetlabel — that mechanism is not this repository's.In-flight overlap
No open PR touches
scripts/check-vi-mock-inherit.mjsor any of the eight converted files, and noneadds a
vi.mockcall site on this specifier — checked across the 12 open pull requests. #8164 edits adifferent file in the same directory, with no textual or semantic overlap.
Generated by Claude Code