From ae1e90be87b0f7ee455001ae5884af14f5127700 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 01:45:26 +0000 Subject: [PATCH 1/2] test(vi-mock): inherit the real @object-ui/plugin-designer surface in 10 factories All ten `vi.mock('@object-ui/plugin-designer', ...)` factories in the tree hand-listed three of the barrel's 29 exports, so any export a file's import graph reads at module scope resolves to `undefined` and the file dies during COLLECTION with zero failed assertions (objectui#6892). Each factory now obtains the real module through the typed generic and spreads it FIRST, keeping every hand-written stub after it. Both consumers declare the edge (`packages/app-shell` dev + peer, `apps/console` dev), so the typed spelling is licensed. Two of the ten also gain a module-scope `import '@object-ui/plugin-designer'`. `AppContent` reaches `CreateAppPage` only through `React.lazy(() => import('@object-ui/plugin-designer'))`, so the now-awaiting factory first runs inside those files' `findByTestId` budget; under a saturated transform pipeline the barrel's 619-module graph does not fit in it and the file goes red on LOAD rather than on behaviour. Moving the cost into the import phase is the repo's documented repair for that class. No assertion changed. Part of #6892 (slice 11: @object-ui/plugin-designer) Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr --- .../AppContent.systemHubRoutes.test.tsx | 3 ++- .../AppContent.bootRedirectCoverage.test.tsx | 3 ++- .../AppContent.declaredVisibilityKeys.test.tsx | 3 ++- .../AppContent.deniedVsUnpublished.test.tsx | 3 ++- .../AppContent.inaccessibleAppStrand.test.tsx | 3 ++- .../AppContent.missingRecheckKeying.test.tsx | 3 ++- .../AppContent.noAppComponentRoutes.test.tsx | 3 ++- .../__tests__/AppContent.noAppsCta.test.tsx | 16 +++++++++++++++- .../AppContent.pseudoRouteSegments.test.tsx | 16 +++++++++++++++- .../AppContent.unresolvedAdmin-5619.test.tsx | 3 ++- 10 files changed, 46 insertions(+), 10 deletions(-) diff --git a/apps/console/src/__tests__/AppContent.systemHubRoutes.test.tsx b/apps/console/src/__tests__/AppContent.systemHubRoutes.test.tsx index a94d94d150..c7a2e9bbf8 100644 --- a/apps/console/src/__tests__/AppContent.systemHubRoutes.test.tsx +++ b/apps/console/src/__tests__/AppContent.systemHubRoutes.test.tsx @@ -107,7 +107,8 @@ vi.mock('../../../../packages/app-shell/src/console/marketplace/MarketplacePacka MarketplacePackagePage: () =>
, })); -vi.mock('@object-ui/plugin-designer', () => ({ +vi.mock('@object-ui/plugin-designer', async (importOriginal) => ({ + ...(await importOriginal()), CreateAppPage: () =>
create app
, EditAppPage: () =>
, DashboardDesignPage: () =>
, diff --git a/packages/app-shell/src/console/__tests__/AppContent.bootRedirectCoverage.test.tsx b/packages/app-shell/src/console/__tests__/AppContent.bootRedirectCoverage.test.tsx index 9d07165133..3426082ebf 100644 --- a/packages/app-shell/src/console/__tests__/AppContent.bootRedirectCoverage.test.tsx +++ b/packages/app-shell/src/console/__tests__/AppContent.bootRedirectCoverage.test.tsx @@ -34,7 +34,8 @@ import React, { useLayoutEffect } from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import { MemoryRouter, Routes, Route, useLocation } from 'react-router-dom'; -vi.mock('@object-ui/plugin-designer', () => ({ +vi.mock('@object-ui/plugin-designer', async (importOriginal) => ({ + ...(await importOriginal()), CreateAppPage: () =>
create app
, EditAppPage: () =>
, DashboardDesignPage: () =>
, diff --git a/packages/app-shell/src/console/__tests__/AppContent.declaredVisibilityKeys.test.tsx b/packages/app-shell/src/console/__tests__/AppContent.declaredVisibilityKeys.test.tsx index e8ff431911..4d617f58d3 100644 --- a/packages/app-shell/src/console/__tests__/AppContent.declaredVisibilityKeys.test.tsx +++ b/packages/app-shell/src/console/__tests__/AppContent.declaredVisibilityKeys.test.tsx @@ -53,7 +53,8 @@ vi.mock('@object-ui/plugin-form', async (importOriginal) => ({ }, })); -vi.mock('@object-ui/plugin-designer', () => ({ +vi.mock('@object-ui/plugin-designer', async (importOriginal) => ({ + ...(await importOriginal()), CreateAppPage: () =>
, EditAppPage: () =>
, DashboardDesignPage: () =>
, diff --git a/packages/app-shell/src/console/__tests__/AppContent.deniedVsUnpublished.test.tsx b/packages/app-shell/src/console/__tests__/AppContent.deniedVsUnpublished.test.tsx index 37d9b4fd22..05c508ab63 100644 --- a/packages/app-shell/src/console/__tests__/AppContent.deniedVsUnpublished.test.tsx +++ b/packages/app-shell/src/console/__tests__/AppContent.deniedVsUnpublished.test.tsx @@ -66,7 +66,8 @@ import { ObjectStackAdapter } from '@object-ui/data-objectstack'; // Mocks — everything that takes part in the DECISION stays real. // --------------------------------------------------------------------------- -vi.mock('@object-ui/plugin-designer', () => ({ +vi.mock('@object-ui/plugin-designer', async (importOriginal) => ({ + ...(await importOriginal()), CreateAppPage: () =>
, EditAppPage: () =>
, DashboardDesignPage: () =>
, diff --git a/packages/app-shell/src/console/__tests__/AppContent.inaccessibleAppStrand.test.tsx b/packages/app-shell/src/console/__tests__/AppContent.inaccessibleAppStrand.test.tsx index 83f929df99..e03995586e 100644 --- a/packages/app-shell/src/console/__tests__/AppContent.inaccessibleAppStrand.test.tsx +++ b/packages/app-shell/src/console/__tests__/AppContent.inaccessibleAppStrand.test.tsx @@ -69,7 +69,8 @@ import { MemoryRouter, Routes, Route, Navigate, useLocation, useNavigationType } // guards, its nested ) stays real. // --------------------------------------------------------------------------- -vi.mock('@object-ui/plugin-designer', () => ({ +vi.mock('@object-ui/plugin-designer', async (importOriginal) => ({ + ...(await importOriginal()), CreateAppPage: () =>
create app
, EditAppPage: () =>
, DashboardDesignPage: () =>
, diff --git a/packages/app-shell/src/console/__tests__/AppContent.missingRecheckKeying.test.tsx b/packages/app-shell/src/console/__tests__/AppContent.missingRecheckKeying.test.tsx index ab9c135443..c3719b31c2 100644 --- a/packages/app-shell/src/console/__tests__/AppContent.missingRecheckKeying.test.tsx +++ b/packages/app-shell/src/console/__tests__/AppContent.missingRecheckKeying.test.tsx @@ -55,7 +55,8 @@ import { MemoryRouter, Routes, Route, useLocation, useNavigate } from 'react-rou // Mocks — everything that takes part in the re-check DECISION stays real. // --------------------------------------------------------------------------- -vi.mock('@object-ui/plugin-designer', () => ({ +vi.mock('@object-ui/plugin-designer', async (importOriginal) => ({ + ...(await importOriginal()), CreateAppPage: () =>
, EditAppPage: () =>
, DashboardDesignPage: () =>
, diff --git a/packages/app-shell/src/console/__tests__/AppContent.noAppComponentRoutes.test.tsx b/packages/app-shell/src/console/__tests__/AppContent.noAppComponentRoutes.test.tsx index f9713d3deb..4152c0c7af 100644 --- a/packages/app-shell/src/console/__tests__/AppContent.noAppComponentRoutes.test.tsx +++ b/packages/app-shell/src/console/__tests__/AppContent.noAppComponentRoutes.test.tsx @@ -127,7 +127,8 @@ vi.mock('../../views/metadata-admin', () => ({ MetadataDiagnosticsPage: () =>
, })); -vi.mock('@object-ui/plugin-designer', () => ({ +vi.mock('@object-ui/plugin-designer', async (importOriginal) => ({ + ...(await importOriginal()), CreateAppPage: () =>
create app
, EditAppPage: () =>
, DashboardDesignPage: () =>
, diff --git a/packages/app-shell/src/console/__tests__/AppContent.noAppsCta.test.tsx b/packages/app-shell/src/console/__tests__/AppContent.noAppsCta.test.tsx index cd8f74b16e..eafe9a4159 100644 --- a/packages/app-shell/src/console/__tests__/AppContent.noAppsCta.test.tsx +++ b/packages/app-shell/src/console/__tests__/AppContent.noAppsCta.test.tsx @@ -76,12 +76,26 @@ import { MemoryRouter, Routes, Route, Navigate, useLocation } from 'react-router // The CTA's target sits behind `React.lazy(() => import('@object-ui/plugin-designer'))`. // Stubbing it keeps the assertion off the transform pipeline entirely (AGENTS.md // §测试纪律: never let an unbounded module load race a bounded `findBy` window). -vi.mock('@object-ui/plugin-designer', () => ({ +vi.mock('@object-ui/plugin-designer', async (importOriginal) => ({ + ...(await importOriginal()), CreateAppPage: () =>
create app
, EditAppPage: () =>
, DashboardDesignPage: () =>
, })); +// Pay the real designer barrel's load in the IMPORT phase, where no timeout +// applies. `AppContent` reaches `CreateAppPage` only through +// `React.lazy(() => import('@object-ui/plugin-designer'))`, so the factory +// above -- which now awaits the real module -- first runs when that lazy +// boundary resolves, i.e. INSIDE the `findByTestId` budget of the two cases +// below that assert on `create-app-page`. Under a saturated transform pipeline +// the barrel's graph does not fit in that budget and the file goes red on LOAD +// rather than on behaviour (AGENTS.md, the flaky-test discipline: an unbounded +// module load counted against a bounded window). The specifier is byte- +// identical to the component's own, so ESM hands the lazy factory this +// already-resolved module. +import '@object-ui/plugin-designer'; + vi.mock('@object-ui/i18n', async (importOriginal) => ({ ...(await importOriginal>()), useObjectTranslation: () => ({ diff --git a/packages/app-shell/src/console/__tests__/AppContent.pseudoRouteSegments.test.tsx b/packages/app-shell/src/console/__tests__/AppContent.pseudoRouteSegments.test.tsx index 225fcea862..2fc7558b16 100644 --- a/packages/app-shell/src/console/__tests__/AppContent.pseudoRouteSegments.test.tsx +++ b/packages/app-shell/src/console/__tests__/AppContent.pseudoRouteSegments.test.tsx @@ -152,12 +152,26 @@ vi.mock('../marketplace/MarketplacePackagePage', () => ({ MarketplacePackagePage: () =>
, })); -vi.mock('@object-ui/plugin-designer', () => ({ +vi.mock('@object-ui/plugin-designer', async (importOriginal) => ({ + ...(await importOriginal()), CreateAppPage: () =>
create app
, EditAppPage: () =>
, DashboardDesignPage: () =>
, })); +// Pay the real designer barrel's load in the IMPORT phase, where no timeout +// applies. `AppContent` reaches `CreateAppPage` only through +// `React.lazy(() => import('@object-ui/plugin-designer'))`, so the factory +// above -- which now awaits the real module -- first runs when that lazy +// boundary resolves, i.e. INSIDE the `findByTestId` budget of the two cases +// below that assert on `create-app-page`. Under a saturated transform pipeline +// the barrel's graph does not fit in that budget and the file goes red on LOAD +// rather than on behaviour (AGENTS.md, the flaky-test discipline: an unbounded +// module load counted against a bounded window). The specifier is byte- +// identical to the component's own, so ESM hands the lazy factory this +// already-resolved module. +import '@object-ui/plugin-designer'; + /** * The probe that makes the bug visible: it reports WHICH app's shell rendered. * The real `ConsoleLayout` drags in the whole console chrome (sidebar, header, diff --git a/packages/app-shell/src/console/__tests__/AppContent.unresolvedAdmin-5619.test.tsx b/packages/app-shell/src/console/__tests__/AppContent.unresolvedAdmin-5619.test.tsx index 1b45c2f2ed..89491a337f 100644 --- a/packages/app-shell/src/console/__tests__/AppContent.unresolvedAdmin-5619.test.tsx +++ b/packages/app-shell/src/console/__tests__/AppContent.unresolvedAdmin-5619.test.tsx @@ -46,7 +46,8 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import { MemoryRouter, Routes, Route, Navigate, useLocation } from 'react-router-dom'; -vi.mock('@object-ui/plugin-designer', () => ({ +vi.mock('@object-ui/plugin-designer', async (importOriginal) => ({ + ...(await importOriginal()), CreateAppPage: () =>
create app
, EditAppPage: () =>
, DashboardDesignPage: () =>
, From 89fb451e243ea1809803574299ad6d8ee3eec78c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 01:53:10 +0000 Subject: [PATCH 2/2] test(vi-mock): admit @object-ui/plugin-designer to the covered set The sweep in the previous commit takes this specifier to zero frozen, which is the gate's documented precondition for joining COVERED_SPECIFIERS. Fifteen members become sixteen; the header gains the slice-11 per-specifier record in the shape slices 5-10 used, and the remaining-population paragraph is refreshed from this run's census (50 -> 40 frozen, @object-ui/app-shell still PARKED under objectui#8173). Gate on this tree: exit 0, 602 call sites judged over sixteen specifiers, 602 inherit, 0 frozen for @object-ui/plugin-designer. Part of #6892 (slice 11: @object-ui/plugin-designer) Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr --- ...vi-mock-inherit-slice11-plugin-designer.md | 7 ++ scripts/check-vi-mock-inherit.mjs | 87 ++++++++++++++++++- 2 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 .changeset/vi-mock-inherit-slice11-plugin-designer.md diff --git a/.changeset/vi-mock-inherit-slice11-plugin-designer.md b/.changeset/vi-mock-inherit-slice11-plugin-designer.md new file mode 100644 index 0000000000..cce2fcea0d --- /dev/null +++ b/.changeset/vi-mock-inherit-slice11-plugin-designer.md @@ -0,0 +1,7 @@ +--- +--- + +Test-only change: the ten `vi.mock('@object-ui/plugin-designer', ...)` factories in the +`AppContent.*` sibling family now inherit the real module's export surface instead of +freezing three of its 29 exports, and the specifier joins the covered set of +`scripts/check-vi-mock-inherit.mjs`. No published behaviour changes. diff --git a/scripts/check-vi-mock-inherit.mjs b/scripts/check-vi-mock-inherit.mjs index 1839a1868f..637684abf4 100644 --- a/scripts/check-vi-mock-inherit.mjs +++ b/scripts/check-vi-mock-inherit.mjs @@ -82,7 +82,7 @@ * - **Workspace specifiers not in `COVERED_SPECIFIERS`.** See below. * * `COVERED_SPECIFIERS` holds the workspace packages whose frozen sites have - * actually been SWEPT to zero. Today that is fifteen, and each joined by sweep + * actually been SWEPT to zero. Today that is sixteen, and each joined by sweep * rather than by judgement. Running this file's classifier over all 1,499 * `vi.mock` call sites in the tree at `9ce20233f`: * @@ -579,11 +579,89 @@ * ⭐ The free confirmation is present: 5 of the 16 sites already inherited the * real barrel on `main` and passed, all of them in the light `dom` project. * - * The remaining 50 stay on objectui#6892: `@object-ui/app-shell` (23, ALL + * `@object-ui/plugin-designer` joined as objectui#6892's ELEVENTH slice, + * re-derived on `ecf8e726e` by the same `scan()` method, the constant below + * again never widened-and-reverted: + * + * @object-ui/plugin-designer 10 judged, 0 inheriting, 10 frozen -> 0 + * + * with the population moving 50 -> 40 frozen over 659 judged and no site moving + * the other way. The 10 sites are an `AppContent.*` SIBLING FAMILY in one + * syntactic shape -- the zero-parameter object-literal arrow, each hand-listing + * the same three of the barrel's 29 exports -- and this is the first slice on + * this worklist to reach OUTSIDE `packages/`: nine sit under + * `packages/app-shell` and one under `apps/console`, which runs in its own + * merged vitest project rather than in the root config's. + * + * STEP 0 was taken again rather than inherited: 619 modules and 6,825 + * module-scope statements reached from `packages/plugin-designer/src/index.tsx` + * over fourteen workspace packages (`components` 206, `core` 95, `fields` 77, + * `react` 64, `plugin-grid` 31, `plugin-designer` 29, `plugin-form` 28, `i18n` + * 27, `types` 18, `mobile` 16, `permissions` 10, `sdui-parser` 8, + * `data-objectstack` 5, `providers` 5). NOT inert, and the slice proceeded on + * the CLASS of the effect: 130 module-scope `ComponentRegistry.register(...)` + * calls, ZERO of them bare. Every one carries a namespace (`ui` 89, `element` + * 10, `page` 7, `action` 5, `plugin-form` 4, `plugin-designer` 10, `plugin-grid` + * 2, `view` 2, `record` 1); the five rows in the `page.tsx` renderer that READ + * as bare carry `ui` through the spread `pageMeta` constant, so the deprecation + * `console.warn` in `register()` cannot fire. The remaining effects are the + * benign registration class slice 7 already measured in `packages/fields`: five + * `registerFieldRenderer(...)` map writes, one `setCellRendererResolver(...)` + * assignment and `registerAllFields()`. One CSS import, inert (the root config + * declares no `css` option). Empirically, importing the real barrel emits ZERO + * `console.warn` and ZERO `console.error` in BOTH projects and exports 29 names. + * + * ⭐ The import cost was taken in both projects the sites run in, and the two + * differ by an order of magnitude for the same reason slice 10 recorded: under + * the light `dom` project the isolated cold import costs a median 6.89s and + * moves the registry 0 -> 405 keys; under the console project, whose merged + * config uses the HEAVY dom setup, it costs a median 0.54s and moves the + * registry 391 -> 421. Neither number decides anything on its own. The marginal + * cost measured in the files that pay it is uniform and small -- an app-shell + * file 9.19s frozen -> 9.55s inheriting, the console file 18.84s -> 19.22s -- + * and the aggregate over the ten files as ONE invocation is +3.6s on a ~36s + * median (four frozen runs against five inheriting ones, two of them + * interleaved A/B), i.e. inside the noise band of a shared box and nowhere near + * objectui#6580's STOP band. The mechanism is measured, not argued: every one + * of the ten files ALREADY holds 590 of the barrel's 619 modules at module + * scope, so inheriting adds exactly the 29 modules of the designer package + * itself. + * + * ⚠️ This slice met a failure mode the worklist had not met before, and it is + * NOT slice 6's collection death: two of the ten went red on LOAD TIMING. The + * console shell reaches `CreateAppPage` only through + * `React.lazy(() => import('@object-ui/plugin-designer'))`, so an inheriting + * factory first runs when that lazy boundary resolves -- inside the RTL + * `findBy` budget of the two files that assert on the rendered designer page. + * Alone they pass; in the ten-file invocation the transform pipeline is + * saturated and the 29 added modules do not fit in 1000ms, so the assertion + * fails while the shell is still booting. The repair is the one AGENTS.md + * prescribes for an unbounded module load counted against a bounded window -- + * a module-scope `import '@object-ui/plugin-designer'` in those two files, + * which moves the cost into the import phase where no timeout applies. No + * assertion was touched. ⇒ CARRY-FORWARD: a specifier reached only through + * `React.lazy` defers the whole inheritance cost into a test's assertion + * window, so budget the timing check on the AGGREGATE invocation and never on + * the per-file run, which cannot see it. + * + * The neighbour reading found ZERO repairs owed and said so in advance: across + * the ten files every workspace neighbour already inherits (10 `@object-ui/auth` + * sites, 10 `@object-ui/i18n`, 8 `@object-ui/react`, 1 `@object-ui/plugin-form`) + * and there is not one third-party factory in the family at all -- so the frozen + * `lucide-react` neighbour that killed 15 files in slice 6 cannot exist here, + * even though this barrel's graph reads `lucide-react` 106 times. The remaining + * neighbours are 39 local whole-module replacements, out of scope by + * construction. No file among the ten mocks `@object-ui/app-shell`, so the + * parked specifier stays parked. + * + * ⚠️ Like slices 4 and 7, this specifier had ZERO already-inheriting sites, so + * there was no free confirmation -- the suite runs themselves are the evidence. + * + * The remaining 40 stay on objectui#6892: `@object-ui/app-shell` (23, ALL * frozen, PARKED under objectui#8173 -- objectui#6892 and the closed * objectui#6580 point opposite ways on that one specifier and a seat does not - * decide it), `@object-ui/plugin-designer` (10, ALL frozen), - * `@object-ui/plugin-list` (9 of 10) and `@object-ui/fields` (8 of 10). + * decide it), `@object-ui/plugin-list` (9 of 10) and `@object-ui/fields` + * (8 of 10). * * **The precondition for widening is a sweep, not a judgement.** Convert a * specifier's frozen factories to the inheriting form, confirm this gate reads @@ -683,6 +761,7 @@ export const COVERED_SPECIFIERS = Object.freeze([ '@object-ui/permissions', '@object-ui/plugin-detail', '@object-ui/plugin-chatbot', + '@object-ui/plugin-designer', ]); /** Files the walk reads at all. */